diff -Nru bluez-5.66/aclocal.m4 bluez-5.68/aclocal.m4 --- bluez-5.66/aclocal.m4 2022-11-10 20:24:54.000000000 +0000 +++ bluez-5.68/aclocal.m4 2023-06-30 22:15:26.000000000 +0000 @@ -9108,7 +9108,7 @@ m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) -# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# pkg.m4 - Macros to locate and use pkg-config. -*- Autoconf -*- # serial 12 (pkg-config-0.29.2) dnl Copyright © 2004 Scott James Remnant . @@ -9196,7 +9196,7 @@ dnl PKG_CHECK_MODULES(), but does not set variables or print errors. dnl dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) -dnl only at the first occurence in configure.ac, so if the first place +dnl only at the first occurrence in configure.ac, so if the first place dnl it's called might be skipped (such as if it is within an "if", you dnl have to call PKG_CHECK_EXISTS manually AC_DEFUN([PKG_CHECK_EXISTS], @@ -9265,14 +9265,14 @@ AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then - $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else - $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD - m4_default([$4], [AC_MSG_ERROR( + m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS @@ -9284,7 +9284,7 @@ ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) - m4_default([$4], [AC_MSG_FAILURE( + m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. @@ -9294,10 +9294,10 @@ To get pkg-config, see .])[]dnl ]) else - $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS - $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) - $3 + $3 fi[]dnl ])dnl PKG_CHECK_MODULES @@ -9384,6 +9384,74 @@ AS_VAR_IF([$1], [""], [$5], [$4])dnl ])dnl PKG_CHECK_VAR +dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], +dnl [DESCRIPTION], [DEFAULT]) +dnl ------------------------------------------ +dnl +dnl Prepare a "--with-" configure option using the lowercase +dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and +dnl PKG_CHECK_MODULES in a single macro. +AC_DEFUN([PKG_WITH_MODULES], +[ +m4_pushdef([with_arg], m4_tolower([$1])) + +m4_pushdef([description], + [m4_default([$5], [build with ]with_arg[ support])]) + +m4_pushdef([def_arg], [m4_default([$6], [auto])]) +m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) +m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) + +m4_case(def_arg, + [yes],[m4_pushdef([with_without], [--without-]with_arg)], + [m4_pushdef([with_without],[--with-]with_arg)]) + +AC_ARG_WITH(with_arg, + AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, + [AS_TR_SH([with_]with_arg)=def_arg]) + +AS_CASE([$AS_TR_SH([with_]with_arg)], + [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], + [auto],[PKG_CHECK_MODULES([$1],[$2], + [m4_n([def_action_if_found]) $3], + [m4_n([def_action_if_not_found]) $4])]) + +m4_popdef([with_arg]) +m4_popdef([description]) +m4_popdef([def_arg]) + +])dnl PKG_WITH_MODULES + +dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [DESCRIPTION], [DEFAULT]) +dnl ----------------------------------------------- +dnl +dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES +dnl check._[VARIABLE-PREFIX] is exported as make variable. +AC_DEFUN([PKG_HAVE_WITH_MODULES], +[ +PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) + +AM_CONDITIONAL([HAVE_][$1], + [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) +])dnl PKG_HAVE_WITH_MODULES + +dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [DESCRIPTION], [DEFAULT]) +dnl ------------------------------------------------------ +dnl +dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after +dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make +dnl and preprocessor variable. +AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], +[ +PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) + +AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], + [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) +])dnl PKG_HAVE_DEFINE_WITH_MODULES + # Copyright (C) 2002-2021 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation diff -Nru bluez-5.66/android/gatt.c bluez-5.68/android/gatt.c --- bluez-5.66/android/gatt.c 2022-03-16 15:06:19.000000000 +0000 +++ bluez-5.68/android/gatt.c 2023-06-30 08:10:19.000000000 +0000 @@ -3730,7 +3730,11 @@ notification = new0(struct notification_data, 1); +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Warray-bounds\"") +_Pragma("GCC diagnostic ignored \"-Wstringop-overflow\"") memcpy(¬ification->ch, &cmd->char_id, sizeof(notification->ch)); +_Pragma("GCC diagnostic pop") memcpy(¬ification->service, &cmd->srvc_id, sizeof(notification->service)); notification->conn = conn; diff -Nru bluez-5.66/attrib/gattrib.c bluez-5.68/attrib/gattrib.c --- bluez-5.66/attrib/gattrib.c 2022-01-05 21:53:58.000000000 +0000 +++ bluez-5.68/attrib/gattrib.c 2023-06-30 08:10:20.000000000 +0000 @@ -21,17 +21,23 @@ #include #include "lib/bluetooth.h" +#include "lib/uuid.h" #include "btio/btio.h" #include "src/log.h" #include "src/shared/util.h" #include "src/shared/att.h" +#include "src/shared/gatt-helpers.h" #include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "attrib/att.h" #include "attrib/gattrib.h" struct _GAttrib { int ref_count; struct bt_att *att; + struct bt_gatt_client *client; GIOChannel *io; GDestroyNotify destroy; gpointer destroy_user_data; @@ -145,6 +151,7 @@ if (attrib->destroy) attrib->destroy(attrib->destroy_user_data); + bt_gatt_client_unref(attrib->client); bt_att_unref(attrib->att); queue_destroy(attrib->callbacks, attrib_callbacks_destroy); @@ -338,6 +345,20 @@ return TRUE; } +static void client_notify_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + uint8_t *buf = newa(uint8_t, length + 2); + + put_le16(value_handle, buf); + + if (length) + memcpy(buf + 2, value, length); + + attrib_callback_notify(NULL, ATT_OP_HANDLE_NOTIFY, buf, length + 2, + user_data); +} + guint g_attrib_register(GAttrib *attrib, guint8 opcode, guint16 handle, GAttribNotifyFunc func, gpointer user_data, GDestroyNotify notify) @@ -359,6 +380,16 @@ queue_push_head(attrib->callbacks, cb); } + if (opcode == ATT_OP_HANDLE_NOTIFY && attrib->client) { + unsigned int id; + + id = bt_gatt_client_register_notify(attrib->client, handle, + NULL, client_notify_cb, cb, + attrib_callbacks_remove); + if (id) + return id; + } + if (opcode == GATTRIB_ALL_REQS) opcode = BT_ATT_ALL_REQUESTS; @@ -410,6 +441,21 @@ return bt_att_set_mtu(attrib->att, mtu); } +gboolean g_attrib_attach_client(GAttrib *attrib, struct bt_gatt_client *client) +{ + if (!attrib || !client) + return FALSE; + + if (attrib->client) + bt_gatt_client_unref(attrib->client); + + attrib->client = bt_gatt_client_clone(client); + if (!attrib->client) + return FALSE; + + return TRUE; +} + gboolean g_attrib_unregister(GAttrib *attrib, guint id) { if (!attrib) diff -Nru bluez-5.66/attrib/gattrib.h bluez-5.68/attrib/gattrib.h --- bluez-5.66/attrib/gattrib.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/attrib/gattrib.h 2023-06-30 08:10:20.000000000 +0000 @@ -19,6 +19,7 @@ #define GATTRIB_ALL_HANDLES 0x0000 struct bt_att; /* Forward declaration for compatibility */ +struct bt_gatt_client; /* Forward declaration for compatibility */ struct _GAttrib; typedef struct _GAttrib GAttrib; @@ -53,6 +54,7 @@ uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len); gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu); +gboolean g_attrib_attach_client(GAttrib *attrib, struct bt_gatt_client *client); gboolean g_attrib_unregister(GAttrib *attrib, guint id); gboolean g_attrib_unregister_all(GAttrib *attrib); diff -Nru bluez-5.66/attrib/gatttool.c bluez-5.68/attrib/gatttool.c --- bluez-5.66/attrib/gatttool.c 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/attrib/gatttool.c 2023-06-30 08:10:20.000000000 +0000 @@ -456,17 +456,17 @@ static GOptionEntry primary_char_options[] = { { "start", 's' , 0, G_OPTION_ARG_INT, &opt_start, - "Starting handle(optional)", "0x0001" }, + "Starting handle (optional)", "0x0001" }, { "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end, - "Ending handle(optional)", "0xffff" }, + "Ending handle (optional)", "0xffff" }, { "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, - parse_uuid, "UUID16 or UUID128(optional)", "0x1801"}, + parse_uuid, "UUID16 or UUID128 (optional)", "0x1801"}, { NULL }, }; static GOptionEntry char_rw_options[] = { { "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle, - "Read/Write characteristic by handle(required)", "0x0001" }, + "Read/Write characteristic by handle (required)", "0x0001" }, { "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value, "Write characteristic value (required for write operation)", "0x0001" }, diff -Nru bluez-5.66/AUTHORS bluez-5.68/AUTHORS --- bluez-5.66/AUTHORS 2020-02-15 18:33:56.000000000 +0000 +++ bluez-5.68/AUTHORS 2023-06-30 08:10:19.000000000 +0000 @@ -53,7 +53,7 @@ Anderson Briglia Anderson Lizardo Bruna Moreira -Brian Gix +Brian Gix Andre Guedes Sheldon Demario Lucas De Marchi @@ -84,7 +84,7 @@ Scott James Remnant Jakub Tyszkowski Grzegorz Kołodziejczyk -Marcin Krąglak +Marcin Krąglak Łukasz Rymanowski Jerzy Kasenberg Arman Uguray diff -Nru bluez-5.66/btio/btio.c bluez-5.68/btio/btio.c --- bluez-5.66/btio/btio.c 2022-07-24 21:02:14.000000000 +0000 +++ bluez-5.68/btio/btio.c 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2009-2010 Marcel Holtmann * Copyright (C) 2009-2010 Nokia Corporation + * Copyright 2023 NXP * * */ @@ -69,6 +70,7 @@ uint32_t priority; uint16_t voice; struct bt_iso_qos qos; + struct bt_iso_base base; }; struct connect { @@ -857,7 +859,7 @@ return TRUE; } -static gboolean iso_set(int sock, struct bt_iso_qos *qos, GError **err) +static gboolean iso_set_qos(int sock, struct bt_iso_qos *qos, GError **err) { if (setsockopt(sock, SOL_BLUETOOTH, BT_ISO_QOS, qos, sizeof(*qos)) < 0) { @@ -868,6 +870,16 @@ return TRUE; } +static gboolean iso_set_base(int sock, struct bt_iso_base *base, GError **err) +{ + if (setsockopt(sock, SOL_BLUETOOTH, BT_ISO_BASE, base->base, + base->base_len) < 0) { + ERROR_FAILED(err, "setsockopt(BT_ISO_BASE)", errno); + return FALSE; + } + + return TRUE; +} static gboolean parse_set_opts(struct set_opts *opts, GError **err, BtIOOption opt1, va_list args) { @@ -965,6 +977,9 @@ case BT_IO_OPT_QOS: opts->qos = *va_arg(args, struct bt_iso_qos *); break; + case BT_IO_OPT_BASE: + opts->base = *va_arg(args, struct bt_iso_base *); + break; case BT_IO_OPT_INVALID: case BT_IO_OPT_KEY_SIZE: case BT_IO_OPT_SOURCE_CHANNEL: @@ -1289,6 +1304,7 @@ case BT_IO_OPT_MTU: case BT_IO_OPT_VOICE: case BT_IO_OPT_QOS: + case BT_IO_OPT_BASE: default: g_set_error(err, BT_IO_ERROR, EINVAL, "Unknown option %d", opt); @@ -1443,6 +1459,7 @@ case BT_IO_OPT_PRIORITY: case BT_IO_OPT_VOICE: case BT_IO_OPT_QOS: + case BT_IO_OPT_BASE: case BT_IO_OPT_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, @@ -1553,6 +1570,7 @@ case BT_IO_OPT_PRIORITY: case BT_IO_OPT_VOICE: case BT_IO_OPT_QOS: + case BT_IO_OPT_BASE: case BT_IO_OPT_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, @@ -1608,13 +1626,13 @@ *(va_arg(args, uint8_t *)) = dst.iso_bdaddr_type; break; case BT_IO_OPT_MTU: - *(va_arg(args, uint16_t *)) = qos.out.sdu; + *(va_arg(args, uint16_t *)) = qos.ucast.out.sdu; break; case BT_IO_OPT_IMTU: - *(va_arg(args, uint16_t *)) = qos.in.sdu; + *(va_arg(args, uint16_t *)) = qos.ucast.in.sdu; break; case BT_IO_OPT_OMTU: - *(va_arg(args, uint16_t *)) = qos.out.sdu; + *(va_arg(args, uint16_t *)) = qos.ucast.out.sdu; break; case BT_IO_OPT_PHY: if (get_phy(sock, &phy) < 0) { @@ -1626,6 +1644,7 @@ case BT_IO_OPT_QOS: *(va_arg(args, struct bt_iso_qos *)) = qos; break; + case BT_IO_OPT_BASE: case BT_IO_OPT_HANDLE: case BT_IO_OPT_CLASS: case BT_IO_OPT_DEFER_TIMEOUT: @@ -1739,7 +1758,7 @@ case BT_IO_SCO: return sco_set(sock, opts.mtu, opts.voice, err); case BT_IO_ISO: - return iso_set(sock, &opts.qos, err); + return iso_set_qos(sock, &opts.qos, err); case BT_IO_INVALID: default: g_set_error(err, BT_IO_ERROR, EINVAL, @@ -1819,7 +1838,9 @@ } if (iso_bind(sock, &opts->src, opts->src_type, err) < 0) goto failed; - if (!iso_set(sock, &opts->qos, err)) + if (!iso_set_qos(sock, &opts->qos, err)) + goto failed; + if (!iso_set_base(sock, &opts->base, err)) goto failed; break; case BT_IO_INVALID: diff -Nru bluez-5.66/btio/btio.h bluez-5.68/btio/btio.h --- bluez-5.66/btio/btio.h 2022-07-24 21:02:14.000000000 +0000 +++ bluez-5.68/btio/btio.h 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2009-2010 Marcel Holtmann * Copyright (C) 2009-2010 Nokia Corporation + * Copyright 2023 NXP * * */ @@ -45,6 +46,7 @@ BT_IO_OPT_VOICE, BT_IO_OPT_PHY, BT_IO_OPT_QOS, + BT_IO_OPT_BASE } BtIOOption; typedef enum { diff -Nru bluez-5.66/ChangeLog bluez-5.68/ChangeLog --- bluez-5.66/ChangeLog 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/ChangeLog 2023-06-30 22:14:43.000000000 +0000 @@ -1,3 +1,20 @@ +ver 5.68: + Fix issue with A2DP and handling of Transport.Acquire. + +ver 5.67: + Fix issue with BAP and initiating QoS and Enable procedures. + Fix issue with BAP and detaching streams when PAC is removed. + Fix issue with BAP and reading all instances of PAC. + Fix issue with BAP and not being able to reconfigure. + Fix issue with BAP and transport configuration changes. + Fix issue with BAP and handling unexpected disconnect. + Fix issue with GATT and not removing pending services. + Fix issue with GATT and client ready handling. + Fix issue with handling fallback to transient hostname. + Add support for SecureConnections configuration option. + Add support for Mesh Remove Provisioning. + Add support for Mesh Private Beacons. + ver 5.66: Fix issue with A2DP and transport connection collisions. Fix issue with allowing application specific error codes. diff -Nru bluez-5.66/client/advertising.c bluez-5.68/client/advertising.c --- bluez-5.66/client/advertising.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/client/advertising.c 2023-06-30 08:10:20.000000000 +0000 @@ -67,9 +67,11 @@ bool tx_power; bool name; bool appearance; + bool rsi; } ad = { .local_appearance = UINT16_MAX, .discoverable = true, + .rsi = true, }; static void ad_release(DBusConnection *conn) @@ -175,7 +177,8 @@ bt_shell_printf("Appearance: %s\n", ad.appearance ? "on" : "off"); - bt_shell_printf("Discoverable: %s\n", ad.discoverable ? "on": "off"); + bt_shell_printf("Discoverable: %s\n", ad.discoverable ? "on" : "off"); + bt_shell_printf("RSI: %s\n", ad.rsi ? "on" : "off"); if (ad.duration) bt_shell_printf("Duration: %u sec\n", ad.duration); @@ -295,7 +298,7 @@ static gboolean includes_exists(const GDBusPropertyTable *property, void *data) { - return ad.tx_power || ad.name || ad.appearance; + return ad.tx_power || ad.name || ad.appearance || ad.rsi; } static gboolean get_includes(const GDBusPropertyTable *property, @@ -323,6 +326,12 @@ dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str); } + if (ad.rsi) { + const char *str = "rsi"; + + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str); + } + dbus_message_iter_close_container(iter, &array); @@ -1023,3 +1032,20 @@ return bt_shell_noninteractive_quit(EXIT_SUCCESS); } + +void ad_advertise_rsi(DBusConnection *conn, dbus_bool_t *value) +{ + if (!value) { + bt_shell_printf("RSI: %s\n", ad.rsi ? "on" : "off"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.rsi == *value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.rsi = *value; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Includes"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} diff -Nru bluez-5.66/client/advertising.h bluez-5.68/client/advertising.h --- bluez-5.66/client/advertising.h 2021-06-13 19:56:36.000000000 +0000 +++ bluez-5.68/client/advertising.h 2023-06-30 08:10:20.000000000 +0000 @@ -30,3 +30,4 @@ void ad_advertise_discoverable_timeout(DBusConnection *conn, long int *value); void ad_advertise_secondary(DBusConnection *conn, const char *value); void ad_advertise_interval(DBusConnection *conn, uint32_t *min, uint32_t *max); +void ad_advertise_rsi(DBusConnection *conn, dbus_bool_t *value); diff -Nru bluez-5.66/client/gatt.c bluez-5.68/client/gatt.c --- bluez-5.66/client/gatt.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/client/gatt.c 2023-06-30 08:10:20.000000000 +0000 @@ -554,6 +554,241 @@ return select_attribute_by_uuid(NULL, arg); } +static char *find_local_attribute(const char *arg, + struct service **service, + struct chrc **chrc, struct desc **desc) +{ + GList *l; + + for (l = local_services; l; l = g_list_next(l)) { + struct service *s = l->data; + GList *cl; + + if (!strcmp(arg, s->path)) { + if (service) + *service = s; + return s->path; + } + + if (!strcmp(arg, s->uuid)) { + if (service) + *service = s; + return s->path; + } + + for (cl = s->chrcs; cl; cl = g_list_next(cl)) { + struct chrc *c = cl->data; + GList *dl; + + if (!strcmp(arg, c->path)) { + if (chrc) + *chrc = c; + return c->path; + } + + if (!strcmp(arg, c->uuid)) { + if (chrc) + *chrc = c; + return c->path; + } + + for (dl = c->descs; dl; dl = g_list_next(dl)) { + struct desc *d = dl->data; + + if (!strcmp(arg, d->path)) { + if (desc) + *desc = d; + return d->path; + } + + if (!strcmp(arg, d->uuid)) { + if (desc) + *desc = d; + return d->path; + } + } + } + } + + return NULL; +} + +char *gatt_select_local_attribute(const char *arg) +{ + return find_local_attribute(arg, NULL, NULL, NULL); +} + +static int parse_offset(const char *arg) +{ + char *endptr = NULL; + unsigned long offset; + + offset = strtoul(arg, &endptr, 0); + if (!endptr || *endptr != '\0' || offset > UINT16_MAX) { + bt_shell_printf("Invalid offload: %s", arg); + return -EINVAL; + } + + return offset; +} + +void gatt_read_local_attribute(char *data, int argc, char *argv[]) +{ + int offset = 0; + struct service *s = NULL; + struct chrc *c = NULL; + struct desc *d = NULL; + + if (!find_local_attribute(data, &s, &c, &d)) { + bt_shell_printf("Unable to find local attribute %s\n", data); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (argc > 1) { + offset = parse_offset(argv[1]); + if (offset < 0) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (s) { + bt_shell_printf("UUID %s", s->uuid); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (c) { + if ((size_t)offset >= c->value_len) { + bt_shell_printf("Invalid offset: %d >= %zd", offset, + c->value_len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + bt_shell_hexdump(&c->value[offset], c->value_len - offset); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (d) { + if ((size_t)offset >= d->value_len) { + bt_shell_printf("Invalid offset: %d >= %zd", offset, + d->value_len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + bt_shell_hexdump(&d->value[offset], d->value_len - offset); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } +} + +static uint8_t *str2bytearray(char *arg, size_t *val_len) +{ + uint8_t value[MAX_ATTR_VAL_LEN]; + char *entry; + unsigned int i; + + for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++) { + long val; + char *endptr = NULL; + + if (*entry == '\0') + continue; + + if (i >= G_N_ELEMENTS(value)) { + bt_shell_printf("Too much data\n"); + return NULL; + } + + val = strtol(entry, &endptr, 0); + if (!endptr || *endptr != '\0' || val > UINT8_MAX) { + bt_shell_printf("Invalid value at index %d\n", i); + return NULL; + } + + value[i] = val; + } + + *val_len = i; + + return util_memdup(value, i); +} + +static int write_value(size_t *dst_len, uint8_t **dst_value, uint8_t *src_val, + size_t src_len, uint16_t offset, uint16_t max_len) +{ + if ((offset + src_len) > max_len) + return -EOVERFLOW; + + if ((offset + src_len) != *dst_len) { + *dst_len = offset + src_len; + *dst_value = g_realloc(*dst_value, *dst_len); + } + + if (src_val && src_len) + memcpy(*dst_value + offset, src_val, src_len); + + return 0; +} + +void gatt_write_local_attribute(char *data, int argc, char *argv[]) +{ + int offset = 0; + struct service *s = NULL; + struct chrc *c = NULL; + struct desc *d = NULL; + uint8_t *value; + size_t value_len; + + if (!find_local_attribute(data, &s, &c, &d)) { + bt_shell_printf("Unable to find local attribute %s\n", data); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + value = str2bytearray(argv[1], &value_len); + if (!value) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (argc > 2) { + offset = parse_offset(argv[2]); + if (offset < 0) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (s) { + bt_shell_printf("Unable to overwrite local service %s\n", data); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (c) { + if (write_value(&c->value_len, &c->value, + value, value_len, + offset, c->max_val_len)) { + bt_shell_printf("Unable to write local attribute %s\n", + data); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) written\n", + c->path, bt_uuidstr_to_str(c->uuid)); + + g_dbus_emit_property_changed(c->service->conn, c->path, + CHRC_INTERFACE, "Value"); + } + + if (d) { + if (write_value(&d->value_len, &d->value, + value, value_len, + offset, d->max_val_len)) { + bt_shell_printf("Unable to write local attribute %s\n", + data); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) written\n", + d->path, bt_uuidstr_to_str(d->uuid)); + + g_dbus_emit_property_changed(d->chrc->service->conn, d->path, + DESC_INTERFACE, "Value"); + } + + free(value); +} + static char *attribute_generator(const char *text, int state, GList *source) { static int index; @@ -650,20 +885,6 @@ bt_shell_printf("Attempting to read %s\n", g_dbus_proxy_get_path(proxy)); } -static int parse_offset(const char *arg) -{ - char *endptr = NULL; - unsigned long offset; - - offset = strtoul(arg, &endptr, 0); - if (!endptr || *endptr != '\0' || offset > UINT16_MAX) { - bt_shell_printf("Invalid offload: %s", arg); - return -EINVAL; - } - - return offset; -} - void gatt_read_attribute(GDBusProxy *proxy, int argc, char *argv[]) { const char *iface; @@ -782,38 +1003,6 @@ g_dbus_proxy_get_path(proxy)); } -static uint8_t *str2bytearray(char *arg, size_t *val_len) -{ - uint8_t value[MAX_ATTR_VAL_LEN]; - char *entry; - unsigned int i; - - for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++) { - long int val; - char *endptr = NULL; - - if (*entry == '\0') - continue; - - if (i >= G_N_ELEMENTS(value)) { - bt_shell_printf("Too much data\n"); - return NULL; - } - - val = strtol(entry, &endptr, 0); - if (!endptr || *endptr != '\0' || val > UINT8_MAX) { - bt_shell_printf("Invalid value at index %d\n", i); - return NULL; - } - - value[i] = val; - } - - *val_len = i; - - return util_memdup(value, i); -} - void gatt_write_attribute(GDBusProxy *proxy, int argc, char *argv[]) { const char *iface; @@ -2131,23 +2320,6 @@ return 0; } - -static int write_value(size_t *dst_len, uint8_t **dst_value, uint8_t *src_val, - size_t src_len, uint16_t offset, uint16_t max_len) -{ - if ((offset + src_len) > max_len) - return -EOVERFLOW; - - if ((offset + src_len) != *dst_len) { - *dst_len = offset + src_len; - *dst_value = g_realloc(*dst_value, *dst_len); - } - - if (src_val && src_len) - memcpy(*dst_value + offset, src_val, src_len); - - return 0; -} static void authorize_write_response(const char *input, void *user_data) { diff -Nru bluez-5.66/client/gatt.h bluez-5.68/client/gatt.h --- bluez-5.66/client/gatt.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/client/gatt.h 2023-06-30 08:10:20.000000000 +0000 @@ -32,6 +32,10 @@ void gatt_acquire_notify(GDBusProxy *proxy, const char *arg); void gatt_release_notify(GDBusProxy *proxy, const char *arg); +char *gatt_select_local_attribute(const char *arg); +void gatt_read_local_attribute(char *data, int argc, char *argv[]); +void gatt_write_local_attribute(char *data, int argc, char *argv[]); + void gatt_add_manager(GDBusProxy *proxy); void gatt_remove_manager(GDBusProxy *proxy); diff -Nru bluez-5.66/client/main.c bluez-5.68/client/main.c --- bluez-5.66/client/main.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/client/main.c 2023-06-30 08:10:20.000000000 +0000 @@ -24,7 +24,9 @@ #include "src/shared/shell.h" #include "src/shared/util.h" +#include "src/shared/ad.h" #include "gdbus/gdbus.h" +#include "print.h" #include "agent.h" #include "gatt.h" #include "advertising.h" @@ -50,10 +52,12 @@ GDBusProxy *ad_proxy; GDBusProxy *adv_monitor_proxy; GList *devices; + GList *sets; }; static struct adapter *default_ctrl; static GDBusProxy *default_dev; +static char *default_local_attr; static GDBusProxy *default_attr; static GList *ctrl_list; static GList *battery_proxies; @@ -140,10 +144,32 @@ } +#define DISTANCE_VAL_INVALID 0x7FFF + +static struct set_discovery_filter_args { + char *transport; + char *pattern; + dbus_uint16_t rssi; + dbus_int16_t pathloss; + char **uuids; + size_t uuids_len; + dbus_bool_t duplicate; + dbus_bool_t discoverable; + bool set; + bool active; + unsigned int timeout; +} filter = { + .rssi = DISTANCE_VAL_INVALID, + .pathloss = DISTANCE_VAL_INVALID, + .set = true, +}; + static void print_device(GDBusProxy *proxy, const char *description) { DBusMessageIter iter; const char *address, *name; + uint8_t *flags; + int flags_len = 0; if (g_dbus_proxy_get_property(proxy, "Address", &iter) == FALSE) return; @@ -155,176 +181,39 @@ else name = ""; - bt_shell_printf("%s%s%sDevice %s %s\n", - description ? "[" : "", - description ? : "", - description ? "] " : "", - address, name); -} - -static void print_fixed_iter(const char *label, const char *name, - DBusMessageIter *iter) -{ - dbus_bool_t *valbool; - dbus_uint32_t *valu32; - dbus_uint16_t *valu16; - dbus_int16_t *vals16; - unsigned char *byte; - int len; - - switch (dbus_message_iter_get_arg_type(iter)) { - case DBUS_TYPE_BOOLEAN: - dbus_message_iter_get_fixed_array(iter, &valbool, &len); - - if (len <= 0) - return; - - bt_shell_printf("%s%s:\n", label, name); - bt_shell_hexdump((void *)valbool, len * sizeof(*valbool)); - - break; - case DBUS_TYPE_UINT32: - dbus_message_iter_get_fixed_array(iter, &valu32, &len); - - if (len <= 0) - return; + if (g_dbus_proxy_get_property(proxy, "AdvertisingFlags", &iter)) { + DBusMessageIter array; - bt_shell_printf("%s%s:\n", label, name); - bt_shell_hexdump((void *)valu32, len * sizeof(*valu32)); - - break; - case DBUS_TYPE_UINT16: - dbus_message_iter_get_fixed_array(iter, &valu16, &len); - - if (len <= 0) - return; - - bt_shell_printf("%s%s:\n", label, name); - bt_shell_hexdump((void *)valu16, len * sizeof(*valu16)); - - break; - case DBUS_TYPE_INT16: - dbus_message_iter_get_fixed_array(iter, &vals16, &len); - - if (len <= 0) - return; - - bt_shell_printf("%s%s:\n", label, name); - bt_shell_hexdump((void *)vals16, len * sizeof(*vals16)); + dbus_message_iter_recurse(&iter, &array); + dbus_message_iter_get_fixed_array(&array, &flags, &flags_len); + } - break; - case DBUS_TYPE_BYTE: - dbus_message_iter_get_fixed_array(iter, &byte, &len); + if (!flags_len) + goto done; - if (len <= 0) + if (!(flags[0] & (BT_AD_FLAG_LIMITED | BT_AD_FLAG_GENERAL))) { + /* Only print hidden/non-discoverable if filter.discoverable is + * not set. + */ + if (filter.discoverable) return; - bt_shell_printf("%s%s:\n", label, name); - bt_shell_hexdump((void *)byte, len * sizeof(*byte)); + bt_shell_printf("%s%s%s" COLOR_BOLDGRAY "Device %s %s" + COLOR_OFF "\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + address, name); - break; - default: return; - }; -} - -static void print_iter(const char *label, const char *name, - DBusMessageIter *iter) -{ - dbus_bool_t valbool; - dbus_uint32_t valu32; - dbus_uint16_t valu16; - dbus_int16_t vals16; - unsigned char byte; - const char *valstr; - DBusMessageIter subiter; - char *entry; - - if (iter == NULL) { - bt_shell_printf("%s%s is nil\n", label, name); - return; - } - - switch (dbus_message_iter_get_arg_type(iter)) { - case DBUS_TYPE_INVALID: - bt_shell_printf("%s%s is invalid\n", label, name); - break; - case DBUS_TYPE_STRING: - case DBUS_TYPE_OBJECT_PATH: - dbus_message_iter_get_basic(iter, &valstr); - bt_shell_printf("%s%s: %s\n", label, name, valstr); - break; - case DBUS_TYPE_BOOLEAN: - dbus_message_iter_get_basic(iter, &valbool); - bt_shell_printf("%s%s: %s\n", label, name, - valbool == TRUE ? "yes" : "no"); - break; - case DBUS_TYPE_UINT32: - dbus_message_iter_get_basic(iter, &valu32); - bt_shell_printf("%s%s: 0x%08x\n", label, name, valu32); - break; - case DBUS_TYPE_UINT16: - dbus_message_iter_get_basic(iter, &valu16); - bt_shell_printf("%s%s: 0x%04x\n", label, name, valu16); - break; - case DBUS_TYPE_INT16: - dbus_message_iter_get_basic(iter, &vals16); - bt_shell_printf("%s%s: %d\n", label, name, vals16); - break; - case DBUS_TYPE_BYTE: - dbus_message_iter_get_basic(iter, &byte); - bt_shell_printf("%s%s: 0x%02x (%d)\n", label, name, byte, byte); - break; - case DBUS_TYPE_VARIANT: - dbus_message_iter_recurse(iter, &subiter); - print_iter(label, name, &subiter); - break; - case DBUS_TYPE_ARRAY: - dbus_message_iter_recurse(iter, &subiter); - - if (dbus_type_is_fixed( - dbus_message_iter_get_arg_type(&subiter))) { - print_fixed_iter(label, name, &subiter); - break; - } - - while (dbus_message_iter_get_arg_type(&subiter) != - DBUS_TYPE_INVALID) { - print_iter(label, name, &subiter); - dbus_message_iter_next(&subiter); - } - break; - case DBUS_TYPE_DICT_ENTRY: - dbus_message_iter_recurse(iter, &subiter); - entry = g_strconcat(name, " Key", NULL); - print_iter(label, entry, &subiter); - g_free(entry); - - entry = g_strconcat(name, " Value", NULL); - dbus_message_iter_next(&subiter); - print_iter(label, entry, &subiter); - g_free(entry); - break; - default: - bt_shell_printf("%s%s has unsupported type\n", label, name); - break; } -} - -static void print_property_with_label(GDBusProxy *proxy, const char *name, - const char *label) -{ - DBusMessageIter iter; - - if (g_dbus_proxy_get_property(proxy, name, &iter) == FALSE) - return; - - print_iter("\t", label ? label : name, &iter); -} -static void print_property(GDBusProxy *proxy, const char *name) -{ - print_property_with_label(proxy, name, NULL); +done: + bt_shell_printf("%s%s%sDevice %s %s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + address, name); } static void print_uuid(const char *label, const char *uuid) @@ -395,7 +284,7 @@ } } -static gboolean device_is_child(GDBusProxy *device, GDBusProxy *parent) +static gboolean proxy_is_child(GDBusProxy *device, GDBusProxy *parent) { DBusMessageIter iter; const char *adapter, *path; @@ -432,14 +321,14 @@ "org.bluez.Device1") != NULL; } -static struct adapter *find_parent(GDBusProxy *device) +static struct adapter *find_parent(GDBusProxy *proxy) { GList *list; for (list = g_list_first(ctrl_list); list; list = g_list_next(list)) { struct adapter *adapter = list->data; - if (device_is_child(device, adapter->proxy) == TRUE) + if (proxy_is_child(proxy, adapter->proxy) == TRUE) return adapter; } return NULL; @@ -562,6 +451,27 @@ adv_monitor_register_app(dbus_conn); } +static void print_set(GDBusProxy *proxy, const char *description) +{ + bt_shell_printf("%s%s%sDeviceSet %s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + g_dbus_proxy_get_path(proxy)); +} + +static void set_added(GDBusProxy *proxy) +{ + struct adapter *adapter = find_parent(proxy); + + if (!adapter) + return; + + adapter->sets = g_list_append(adapter->sets, proxy); + print_set(proxy, COLORED_NEW); + bt_shell_set_env(g_dbus_proxy_get_path(proxy), proxy); +} + static void proxy_added(GDBusProxy *proxy, void *user_data) { const char *interface; @@ -597,6 +507,8 @@ } else if (!strcmp(interface, "org.bluez.AdvertisementMonitorManager1")) { admon_manager_added(proxy); + } else if (!strcmp(interface, "org.bluez.DeviceSet1")) { + set_added(proxy); } } @@ -604,6 +516,7 @@ { const char *path; + default_local_attr = NULL; default_attr = proxy; path = g_dbus_proxy_get_path(proxy); @@ -646,6 +559,7 @@ ctrl_list = g_list_remove_link(ctrl_list, ll); g_list_free(adapter->devices); + g_list_free(adapter->sets); g_free(adapter); g_list_free(ll); return; @@ -653,6 +567,19 @@ } } +static void set_removed(GDBusProxy *proxy) +{ + struct adapter *adapter = find_parent(proxy); + + if (!adapter) + return; + + adapter->sets = g_list_remove(adapter->sets, proxy); + + print_set(proxy, COLORED_DEL); + bt_shell_set_env(g_dbus_proxy_get_path(proxy), NULL); +} + static void proxy_removed(GDBusProxy *proxy, void *user_data) { const char *interface; @@ -693,6 +620,8 @@ } else if (!strcmp(interface, "org.bluez.AdvertisementMonitorManager1")) { adv_monitor_remove_manager(dbus_conn); + } else if (!strcmp(interface, "org.bluez.DeviceSet1")) { + set_removed(proxy); } } @@ -719,7 +648,7 @@ interface = g_dbus_proxy_get_interface(proxy); if (!strcmp(interface, "org.bluez.Device1")) { - if (default_ctrl && device_is_child(proxy, + if (default_ctrl && proxy_is_child(proxy, default_ctrl->proxy) == TRUE) { DBusMessageIter addr_iter; char *str; @@ -1255,26 +1184,6 @@ agent_default(dbus_conn, agent_manager); } -#define DISTANCE_VAL_INVALID 0x7FFF - -static struct set_discovery_filter_args { - char *transport; - char *pattern; - dbus_uint16_t rssi; - dbus_int16_t pathloss; - char **uuids; - size_t uuids_len; - dbus_bool_t duplicate; - dbus_bool_t discoverable; - bool set; - bool active; - unsigned int timeout; -} filter = { - .rssi = DISTANCE_VAL_INVALID, - .pathloss = DISTANCE_VAL_INVALID, - .set = true, -}; - static void start_discovery_reply(DBusMessage *message, void *user_data) { dbus_bool_t enable = GPOINTER_TO_UINT(user_data); @@ -1721,6 +1630,39 @@ return proxy; } +static struct GDBusProxy *find_set(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (check_default_ctrl() == FALSE) + return NULL; + + proxy = find_proxies_by_path(default_ctrl->sets, argv[1]); + if (!proxy) { + bt_shell_printf("DeviceSet %s not available\n", argv[1]); + return NULL; + } + + return proxy; +} + +static void cmd_set_info(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + proxy = find_set(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + bt_shell_printf("DeviceSet %s\n", g_dbus_proxy_get_path(proxy)); + + print_property(proxy, "AutoConnect"); + print_property(proxy, "Devices"); + print_property(proxy, "Size"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + static void cmd_info(int argc, char *argv[]) { GDBusProxy *proxy; @@ -1730,7 +1672,7 @@ proxy = find_device(argc, argv); if (!proxy) - return bt_shell_noninteractive_quit(EXIT_FAILURE); + return cmd_set_info(argc, argv); if (g_dbus_proxy_get_property(proxy, "Address", &iter) == FALSE) return bt_shell_noninteractive_quit(EXIT_FAILURE); @@ -1767,6 +1709,7 @@ print_property(proxy, "TxPower"); print_property(proxy, "AdvertisingFlags"); print_property(proxy, "AdvertisingData"); + print_property(proxy, "Sets"); battery_proxy = find_proxies_by_path(battery_proxies, g_dbus_proxy_get_path(proxy)); @@ -2146,10 +2089,41 @@ return bt_shell_noninteractive_quit(EXIT_FAILURE); } +static void set_default_local_attribute(char *attr) +{ + char *desc = NULL; + + default_local_attr = attr; + default_attr = NULL; + + desc = g_strdup_printf(COLOR_BLUE "[%s]" COLOR_OFF "# ", attr); + + bt_shell_set_prompt(desc); + free(desc); +} + static void cmd_select_attribute(int argc, char *argv[]) { GDBusProxy *proxy; + if (!strcasecmp("local", argv[1])) { + char *attr; + + if (argc < 2) { + bt_shell_printf("attribute/UUID required\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + attr = gatt_select_local_attribute(argv[2]); + if (!attr) { + bt_shell_printf("Unable to find %s\n", argv[2]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + set_default_local_attribute(attr); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + if (!default_dev) { bt_shell_printf("No device connected\n"); return bt_shell_noninteractive_quit(EXIT_FAILURE); @@ -2234,6 +2208,11 @@ static void cmd_read(int argc, char *argv[]) { + if (default_local_attr) { + gatt_read_local_attribute(default_local_attr, argc, argv); + return; + } + if (!default_attr) { bt_shell_printf("No attribute selected\n"); return bt_shell_noninteractive_quit(EXIT_FAILURE); @@ -2244,6 +2223,11 @@ static void cmd_write(int argc, char *argv[]) { + if (default_local_attr) { + gatt_write_local_attribute(default_local_attr, argc, argv); + return; + } + if (!default_attr) { bt_shell_printf("No attribute selected\n"); return bt_shell_noninteractive_quit(EXIT_FAILURE); @@ -2419,11 +2403,13 @@ index++; - if (g_dbus_proxy_get_property(proxy, property, &iter) == FALSE) + if (!property) + str = g_dbus_proxy_get_path(proxy); + else if (g_dbus_proxy_get_property(proxy, property, &iter)) + dbus_message_iter_get_basic(&iter, &str); + else continue; - dbus_message_iter_get_basic(&iter, &str); - if (!strncasecmp(str, text, len)) return strdup(str); } @@ -2469,6 +2455,23 @@ default_ctrl ? default_ctrl->devices : NULL, "Address"); } +static char *set_generator(const char *text, int state) +{ + return generic_generator(text, state, + default_ctrl ? default_ctrl->sets : NULL, NULL); +} + +static char *dev_set_generator(const char *text, int state) +{ + char *str; + + str = dev_generator(text, state); + if (str) + return str; + + return set_generator(text, state); +} + static char *attribute_generator(const char *text, int state) { return gatt_attribute_generator(text, state); @@ -2730,6 +2733,21 @@ ad_advertise_interval(dbus_conn, &min, &max); } +static void cmd_advertise_rsi(int argc, char *argv[]) +{ + dbus_bool_t value; + + if (argc < 2) { + ad_advertise_rsi(dbus_conn, NULL); + return; + } + + if (!parse_argument(argc, argv, NULL, NULL, &value, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + ad_advertise_rsi(dbus_conn, &value); +} + static void ad_clear_uuids(void) { ad_disable_uuids(dbus_conn); @@ -2928,6 +2946,8 @@ "Set/Get advertise secondary channel" }, { "interval", "[min] [max] ", cmd_advertise_interval, "Set/Get advertise interval range" }, + { "rsi", "[on/off]", cmd_advertise_rsi, + "Show/Enable/Disable RSI to be advertised", NULL }, { "clear", "[uuids/service/manufacturer/config-name...]", cmd_ad_clear, "Clear advertise config" }, { } }, @@ -3001,8 +3021,9 @@ .entries = { { "list-attributes", "[dev/local]", cmd_list_attributes, "List attributes", dev_generator }, - { "select-attribute", "", cmd_select_attribute, - "Select attribute", attribute_generator }, + { "select-attribute", " [attribute/UUID]", + cmd_select_attribute, "Select attribute", + attribute_generator }, { "attribute-info", "[attribute/UUID]", cmd_attribute_info, "Select attribute", attribute_generator }, { "read", "[offset]", cmd_read, "Read attribute value" }, @@ -3085,8 +3106,8 @@ { "set-alias", "", cmd_set_alias, "Set device alias" }, { "scan", "", cmd_scan, "Scan for devices", scan_generator }, - { "info", "[dev]", cmd_info, "Device information", - dev_generator }, + { "info", "[dev/set]", cmd_info, "Device/Set information", + dev_set_generator }, { "pair", "[dev]", cmd_pair, "Pair with device", dev_generator }, { "cancel-pairing", "[dev]", cmd_cancel_pairing, diff -Nru bluez-5.66/client/player.c bluez-5.68/client/player.c --- bluez-5.66/client/player.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/client/player.c 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2020 Intel Corporation. All rights reserved. + * Copyright 2023 NXP * * */ @@ -24,6 +25,8 @@ #include #include #include +#include +#include #include @@ -39,6 +42,7 @@ #include "src/shared/shell.h" #include "src/shared/io.h" #include "src/shared/queue.h" +#include "print.h" #include "player.h" /* String display constants */ @@ -63,13 +67,20 @@ char *path; char *uuid; uint8_t codec; + uint16_t cid; + uint16_t vid; struct iovec *caps; + struct iovec *meta; bool auto_accept; - bool acquiring; - uint8_t cig; - uint8_t cis; - char *transport; + uint8_t max_transports; + uint8_t iso_group; + uint8_t iso_stream; + struct queue *acquiring; + struct queue *transports; DBusMessage *msg; + struct preset *preset; + bool broadcast; + struct iovec *bcode; }; static DBusConnection *dbus_conn; @@ -89,8 +100,26 @@ uint16_t mtu[2]; char *filename; int fd; + struct stat stat; struct io *io; uint32_t seq; + struct io *timer_io; +}; + +static const uint8_t base_lc3_16_2_1[] = { + 0x28, 0x00, 0x00, /* Presentation Delay */ + 0x01, /* Number of Subgroups */ + 0x01, /* Number of BIS */ + 0x06, 0x00, 0x00, 0x00, 0x00, /* Code ID = LC3 (0x06) */ + 0x11, /* Codec Specific Configuration */ + 0x02, 0x01, 0x03, /* 16 KHZ */ + 0x02, 0x02, 0x01, /* 10 ms */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Front Left */ + 0x03, 0x04, 0x28, 0x00, /* Frame Length 40 bytes */ + 0x04, /* Metadata */ + 0x03, 0x02, 0x02, 0x00, /* Audio Context: Convertional */ + 0x01, /* BIS */ + 0x00, /* Codec Specific Configuration */ }; static void endpoint_unregister(void *data) @@ -511,88 +540,6 @@ title, path); } -static void print_iter(const char *label, const char *name, - DBusMessageIter *iter) -{ - dbus_bool_t valbool; - dbus_uint32_t valu32; - dbus_uint16_t valu16; - dbus_int16_t vals16; - unsigned char byte; - const char *valstr; - DBusMessageIter subiter; - - if (iter == NULL) { - bt_shell_printf("%s%s is nil\n", label, name); - return; - } - - switch (dbus_message_iter_get_arg_type(iter)) { - case DBUS_TYPE_INVALID: - bt_shell_printf("%s%s is invalid\n", label, name); - break; - case DBUS_TYPE_STRING: - case DBUS_TYPE_OBJECT_PATH: - dbus_message_iter_get_basic(iter, &valstr); - bt_shell_printf("%s%s: %s\n", label, name, valstr); - break; - case DBUS_TYPE_BOOLEAN: - dbus_message_iter_get_basic(iter, &valbool); - bt_shell_printf("%s%s: %s\n", label, name, - valbool == TRUE ? "yes" : "no"); - break; - case DBUS_TYPE_UINT32: - dbus_message_iter_get_basic(iter, &valu32); - bt_shell_printf("%s%s: 0x%08x (%u)\n", label, name, valu32, - valu32); - break; - case DBUS_TYPE_UINT16: - dbus_message_iter_get_basic(iter, &valu16); - bt_shell_printf("%s%s: 0x%04x (%u)\n", label, name, valu16, - valu16); - break; - case DBUS_TYPE_INT16: - dbus_message_iter_get_basic(iter, &vals16); - bt_shell_printf("%s%s: %d\n", label, name, vals16); - break; - case DBUS_TYPE_BYTE: - dbus_message_iter_get_basic(iter, &byte); - bt_shell_printf("%s%s: 0x%02x (%d)\n", label, name, byte, byte); - break; - case DBUS_TYPE_VARIANT: - dbus_message_iter_recurse(iter, &subiter); - print_iter(label, name, &subiter); - break; - case DBUS_TYPE_ARRAY: - dbus_message_iter_recurse(iter, &subiter); - while (dbus_message_iter_get_arg_type(&subiter) != - DBUS_TYPE_INVALID) { - print_iter(label, name, &subiter); - dbus_message_iter_next(&subiter); - } - break; - case DBUS_TYPE_DICT_ENTRY: - dbus_message_iter_recurse(iter, &subiter); - dbus_message_iter_get_basic(&subiter, &valstr); - dbus_message_iter_next(&subiter); - print_iter(label, valstr, &subiter); - break; - default: - bt_shell_printf("%s%s has unsupported type\n", label, name); - break; - } -} - -static void print_property(GDBusProxy *proxy, const char *name) -{ - DBusMessageIter iter; - - if (g_dbus_proxy_get_property(proxy, name, &iter) == FALSE) - return; - - print_iter("\t", name, &iter); -} - static void print_media(GDBusProxy *proxy, const char *description) { char *str; @@ -1128,8 +1075,19 @@ bt_shell_printf("\tTransport %s\n", path); print_iter("\t", "Properties", &props); - free(ep->transport); - ep->transport = strdup(path); + if (!ep->max_transports) { + bt_shell_printf("Maximum transports reached: rejecting\n"); + return g_dbus_create_error(msg, + "org.bluez.Error.Rejected", + "Maximum transports reached"); + } + + ep->max_transports--; + + if (!ep->transports) + ep->transports = queue_new(); + + queue_push_tail(ep->transports, strdup(path)); if (ep->auto_accept) { bt_shell_printf("Auto Accepting...\n"); @@ -1215,6 +1173,16 @@ CODEC_CAPABILITIES(PAC_SOURCE_UUID, LC3_ID, LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 30, 240)), + /* Broadcast LC3 Source: + * + * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz + * Duration: 7.5 ms 10 ms + * Channel count: 3 + * Frame length: 30-240 + */ + CODEC_CAPABILITIES(BAA_SERVICE_UUID, LC3_ID, + LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, + 3u, 30, 240)), }; struct codec_qos { @@ -1228,11 +1196,10 @@ }; struct codec_preset { - const char *name; + char *name; const struct iovec data; const struct codec_qos qos; - bool is_default; - uint8_t latency; + uint8_t target_latency; }; #define SBC_PRESET(_name, _data) \ @@ -1241,13 +1208,6 @@ .data = _data, \ } -#define SBC_DEFAULT_PRESET(_name, _data) \ - { \ - .name = _name, \ - .data = _data, \ - .is_default = true, \ - } - static struct codec_preset sbc_presets[] = { /* Table 4.7: Recommended sets of SBC parameters in the SRC device * Other settings: Block length = 16, Allocation method = Loudness, @@ -1268,7 +1228,7 @@ CODEC_DATA(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)), SBC_PRESET("HQ_MONO_48", CODEC_DATA(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)), - SBC_DEFAULT_PRESET("HQ_STEREO_44_1", + SBC_PRESET("HQ_STEREO_44_1", CODEC_DATA(0x21, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_44100)), SBC_PRESET("HQ_STEREO_48", CODEC_DATA(0x11, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_48000)), @@ -1368,7 +1328,7 @@ .name = _name, \ .data = _data, \ .qos = _qos, \ - .latency = 0x02, \ + .target_latency = 0x02, \ } #define LC3_PRESET_HR(_name, _data, _qos) \ @@ -1376,16 +1336,7 @@ .name = _name, \ .data = _data, \ .qos = _qos, \ - .latency = 0x03, \ - } - -#define LC3_DEFAULT_PRESET(_name, _data, _qos) \ - { \ - .name = _name, \ - .data = _data, \ - .is_default = true, \ - .qos = _qos, \ - .latency = 0x02, \ + .target_latency = 0x03, \ } static struct codec_preset lc3_presets[] = { @@ -1399,7 +1350,7 @@ LC3_PRESET("16_1_1", LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_7_5, 30u), LC3_7_5_UNFRAMED(30u, 2u, 8u, 40000u)), - LC3_DEFAULT_PRESET("16_2_1", + LC3_PRESET("16_2_1", LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_10, 40u), LC3_10_UNFRAMED(40u, 2u, 10u, 40000u)), LC3_PRESET("24_1_1", @@ -1439,77 +1390,205 @@ LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 155u), LC3_10_UNFRAMED(155u, 5u, 20u, 40000u)), /* QoS Configuration settings for high reliability audio data */ + LC3_PRESET_HR("8_1_2", + LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_7_5, 26u), + LC3_7_5_UNFRAMED(26u, 13u, 75u, 40000u)), + LC3_PRESET_HR("8_2_2", + LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_10, 30u), + LC3_10_UNFRAMED(30u, 13u, 95u, 40000u)), + LC3_PRESET_HR("16_1_2", + LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_7_5, 30u), + LC3_7_5_UNFRAMED(30u, 13u, 75u, 40000u)), + LC3_PRESET_HR("16_2_2", + LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_10, 40u), + LC3_10_UNFRAMED(40u, 13u, 95u, 40000u)), + LC3_PRESET_HR("24_1_2", + LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_7_5, 45u), + LC3_7_5_UNFRAMED(45u, 13u, 75u, 40000u)), + LC3_PRESET_HR("24_2_2", + LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_10, 60u), + LC3_10_UNFRAMED(60u, 13u, 95u, 40000u)), + LC3_PRESET_HR("32_1_2", + LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_7_5, 60u), + LC3_7_5_UNFRAMED(60u, 13u, 75u, 40000u)), + LC3_PRESET_HR("32_2_2", + LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_10, 80u), + LC3_10_UNFRAMED(80u, 13u, 95u, 40000u)), LC3_PRESET_HR("44_1_2", LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_7_5, 98u), - QOS_FRAMED_2M(8163u, 98u, 23u, 54u, 40000u)), + QOS_FRAMED_2M(8163u, 98u, 13u, 80u, 40000u)), LC3_PRESET_HR("44_2_2", LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_10, 130u), - QOS_FRAMED_2M(10884u, 130u, 23u, 71u, 40000u)), + QOS_FRAMED_2M(10884u, 130u, 13u, 85u, 40000u)), LC3_PRESET_HR("48_1_2", LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 75u), - LC3_7_5_UNFRAMED(75u, 23u, 45u, 40000u)), + LC3_7_5_UNFRAMED(75u, 13u, 75u, 40000u)), LC3_PRESET_HR("48_2_2", LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 100u), - LC3_10_UNFRAMED(100u, 23u, 60u, 40000u)), + LC3_10_UNFRAMED(100u, 13u, 95u, 40000u)), LC3_PRESET_HR("48_3_2", LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 90u), - LC3_7_5_UNFRAMED(90u, 23u, 45u, 40000u)), + LC3_7_5_UNFRAMED(90u, 13u, 75u, 40000u)), LC3_PRESET_HR("48_4_2", LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 120u), - LC3_10_UNFRAMED(120u, 23u, 60u, 40000u)), + LC3_10_UNFRAMED(120u, 13u, 100u, 40000u)), LC3_PRESET_HR("48_5_2", LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 117u), - LC3_7_5_UNFRAMED(117u, 23u, 45u, 40000u)), + LC3_7_5_UNFRAMED(117u, 13u, 75u, 40000u)), LC3_PRESET_HR("48_6_2", LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 155u), - LC3_10_UNFRAMED(155u, 23u, 60u, 40000u)), + LC3_10_UNFRAMED(155u, 13u, 100u, 40000u)), }; -#define PRESET(_uuid, _presets) \ +#define PRESET(_uuid, _codec, _presets, _default_index) \ { \ .uuid = _uuid, \ + .codec = _codec, \ + .custom = { .name = "custom" }, \ + .default_preset = &_presets[_default_index], \ .presets = _presets, \ .num_presets = ARRAY_SIZE(_presets), \ } -static const struct preset { +static struct preset { const char *uuid; + uint8_t codec; + uint16_t cid; + uint16_t vid; + struct codec_preset custom; + struct codec_preset *default_preset; struct codec_preset *presets; size_t num_presets; } presets[] = { - PRESET(A2DP_SOURCE_UUID, sbc_presets), - PRESET(A2DP_SINK_UUID, sbc_presets), - PRESET(PAC_SINK_UUID, lc3_presets), - PRESET(PAC_SOURCE_UUID, lc3_presets), + PRESET(A2DP_SOURCE_UUID, A2DP_CODEC_SBC, sbc_presets, 6), + PRESET(A2DP_SINK_UUID, A2DP_CODEC_SBC, sbc_presets, 6), + PRESET(PAC_SINK_UUID, LC3_ID, lc3_presets, 3), + PRESET(PAC_SOURCE_UUID, LC3_ID, lc3_presets, 3), + PRESET(BAA_SERVICE_UUID, LC3_ID, lc3_presets, 3), }; -static struct codec_preset *find_preset(const char *uuid, const char *name) +static void parse_vendor_codec(const char *codec, uint16_t *vid, uint16_t *cid) +{ + char **list; + char *endptr = NULL; + + if (!codec) + return; + + list = g_strsplit(codec, ":", 2); + + if (vid) + *vid = strtol(list[0], &endptr, 0); + + if (cid) + *cid = strtol(list[1], &endptr, 0); + + g_strfreev(list); +} + +static struct preset *find_presets(const char *uuid, uint8_t codec, + uint16_t vid, uint16_t cid) { size_t i; - for (i = 0; i < ARRAY_SIZE(presets); i++) { - const struct preset *preset = &presets[i]; + if (codec == 0xff) { + GList *l; - if (!strcasecmp(preset->uuid, uuid)) { - size_t j; + for (l = local_endpoints; l; l = g_list_next(l)) { + struct endpoint *ep = l->data; + + if (strcasecmp(ep->uuid, uuid) || ep->codec != codec) + continue; - for (j = 0; j < preset->num_presets; j++) { - struct codec_preset *p; + if (ep->codec == 0xff && (ep->vid != vid || + ep->cid != cid)) + continue; - p = &preset->presets[j]; - - if (!name) { - if (p->is_default) - return p; - } else if (!strcmp(p->name, name)) - return p; - } + return ep->preset; } + + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(presets); i++) { + struct preset *preset = &presets[i]; + + if (preset->codec != codec) + continue; + + if (!strcasecmp(preset->uuid, uuid)) + return preset; + } + + return NULL; +} + +static struct preset *find_vendor_presets(const char *uuid, const char *codec) +{ + uint16_t cid; + uint16_t vid; + + if (!uuid || !codec) + return NULL; + + parse_vendor_codec(codec, &vid, &cid); + + return find_presets(uuid, 0xff, vid, cid); +} + +static struct preset *find_presets_name(const char *uuid, const char *codec) +{ + uint8_t id; + char *endptr = NULL; + + if (!uuid || !codec) + return NULL; + + if (strrchr(codec, ':')) + return find_vendor_presets(uuid, codec); + + id = strtol(codec, &endptr, 0); + + return find_presets(uuid, id, 0x0000, 0x0000); +} + +static struct codec_preset *preset_find_name(struct preset *preset, + const char *name) +{ + size_t i; + + if (!preset) + return NULL; + + if (!name) + return preset->default_preset; + else if (!strcmp(name, "custom")) + return &preset->custom; + + for (i = 0; i < preset->num_presets; i++) { + struct codec_preset *p; + + p = &preset->presets[i]; + + if (!strcmp(p->name, name)) + return p; } return NULL; } +static struct codec_preset *find_preset(const char *uuid, const char *codec, + const char *name) +{ + struct preset *preset; + + preset = find_presets_name(uuid, codec); + if (!preset) + return NULL; + + return preset_find_name(preset, name); +} + static DBusMessage *endpoint_select_config_reply(DBusMessage *msg, uint8_t *data, size_t len) { @@ -1574,7 +1653,7 @@ uint8_t *data; size_t len; - p = find_preset(ep->uuid, input); + p = preset_find_name(ep->preset, input); if (p) { data = p->data.iov_base; len = p->data.iov_len; @@ -1615,6 +1694,13 @@ bt_shell_printf("Endpoint: SelectConfiguration\n"); print_iter("\t", "Capabilities", &args); + if (!ep->max_transports) { + bt_shell_printf("Maximum transports reached: rejecting\n"); + return g_dbus_create_error(msg, + "org.bluez.Error.Rejected", + "Maximum transports reached"); + } + if (!ep->auto_accept) { ep->msg = dbus_message_ref(msg); bt_shell_prompt_input("Endpoint", "Enter preset/configuration:", @@ -1622,7 +1708,7 @@ return NULL; } - p = find_preset(ep->uuid, NULL); + p = preset_find_name(ep->preset, NULL); if (!p) { reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected", NULL); @@ -1631,8 +1717,11 @@ reply = endpoint_select_config_reply(msg, p->data.iov_base, p->data.iov_len); - if (!reply) - return NULL; + if (!reply) { + reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected", + NULL); + return reply; + } bt_shell_printf("Auto Accepting using %s...\n", p->name); @@ -1643,16 +1732,40 @@ GDBusProxy *proxy; struct endpoint *ep; struct iovec *caps; + struct iovec *meta; uint8_t target_latency; const struct codec_qos *qos; }; +#define BCODE {0x01, 0x02, 0x68, 0x05, 0x53, 0xf1, 0x41, 0x5a, \ + 0xa2, 0x65, 0xbb, 0xaf, 0xc6, 0xea, 0x03, 0xb8} + +static struct bt_iso_qos bcast_qos = { + .bcast = { + .big = BT_ISO_QOS_BIG_UNSET, + .bis = BT_ISO_QOS_BIS_UNSET, + .sync_interval = 24, + .packing = 0x00, + .framing = 0x00, + .encryption = 0x00, + .bcode = BCODE, + .options = 0x00, + .skip = 0x0000, + .sync_timeout = 0x4000, + .sync_cte_type = 0x00, + .mse = 0x00, + .timeout = 0x4000, + } +}; + static void append_properties(DBusMessageIter *iter, struct endpoint_config *cfg) { DBusMessageIter dict; struct codec_qos *qos = (void *)cfg->qos; const char *key = "Capabilities"; + const char *meta = "Metadata"; + const char *keyBCode = "BroadcastCode"; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); @@ -1663,6 +1776,15 @@ DBUS_TYPE_BYTE, &cfg->caps->iov_base, cfg->caps->iov_len); + if (cfg->meta && cfg->meta->iov_len) { + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &meta, + DBUS_TYPE_BYTE, &cfg->meta->iov_base, + cfg->meta->iov_len); + + bt_shell_printf("Metadata:\n"); + bt_shell_hexdump(cfg->meta->iov_base, cfg->meta->iov_len); + } + if (!qos) goto done; @@ -1672,16 +1794,27 @@ DBUS_TYPE_BYTE, &cfg->target_latency); } - if (cfg->ep->cig != BT_ISO_QOS_CIG_UNSET) { - bt_shell_printf("CIG 0x%2.2x\n", cfg->ep->cig); + if ((!cfg->ep->broadcast) && + (cfg->ep->iso_group != BT_ISO_QOS_GROUP_UNSET)) { + bt_shell_printf("CIG 0x%2.2x\n", cfg->ep->iso_group); g_dbus_dict_append_entry(&dict, "CIG", DBUS_TYPE_BYTE, - &cfg->ep->cig); + &cfg->ep->iso_group); + } else { + bt_shell_printf("BIG 0x%2.2x\n", bcast_qos.bcast.big); + g_dbus_dict_append_entry(&dict, "BIG", DBUS_TYPE_BYTE, + &bcast_qos.bcast.big); } - if (cfg->ep->cis != BT_ISO_QOS_CIS_UNSET) { - bt_shell_printf("CIS 0x%2.2x\n", cfg->ep->cis); + if ((!cfg->ep->broadcast) && + (cfg->ep->iso_stream != BT_ISO_QOS_STREAM_UNSET)) { + bt_shell_printf("CIS 0x%2.2x\n", cfg->ep->iso_stream); g_dbus_dict_append_entry(&dict, "CIS", DBUS_TYPE_BYTE, - &cfg->ep->cis); + &cfg->ep->iso_stream); + + } else { + bt_shell_printf("BIS 0x%2.2x\n", bcast_qos.bcast.bis); + g_dbus_dict_append_entry(&dict, "BIS", DBUS_TYPE_BYTE, + &bcast_qos.bcast.bis); } bt_shell_printf("Interval %u\n", qos->interval); @@ -1689,10 +1822,19 @@ g_dbus_dict_append_entry(&dict, "Interval", DBUS_TYPE_UINT32, &qos->interval); - bt_shell_printf("Framing %s\n", qos->framing ? "true" : "false"); + if (!cfg->ep->broadcast) { + bt_shell_printf("Framing %s\n", + qos->framing ? "true" : "false"); + + g_dbus_dict_append_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN, + &qos->framing); + } else { + bt_shell_printf("Framing %s\n", + bcast_qos.bcast.framing ? "true" : "false"); - g_dbus_dict_append_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN, - &qos->framing); + g_dbus_dict_append_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN, + &bcast_qos.bcast.framing); + } bt_shell_printf("PHY %s\n", qos->phy); @@ -1717,6 +1859,57 @@ g_dbus_dict_append_entry(&dict, "Delay", DBUS_TYPE_UINT32, &qos->delay); + if (!cfg->ep->broadcast) + goto done; + + bt_shell_printf("SyncInterval %u\n", bcast_qos.bcast.sync_interval); + + g_dbus_dict_append_entry(&dict, "SyncInterval", DBUS_TYPE_BYTE, + &bcast_qos.bcast.sync_interval); + + bt_shell_printf("Encryption %u\n", bcast_qos.bcast.encryption); + + g_dbus_dict_append_entry(&dict, "Encryption", DBUS_TYPE_BYTE, + &bcast_qos.bcast.encryption); + + bt_shell_printf("Options %u\n", bcast_qos.bcast.options); + + g_dbus_dict_append_entry(&dict, "Options", DBUS_TYPE_BYTE, + &bcast_qos.bcast.options); + + bt_shell_printf("Skip %u\n", bcast_qos.bcast.skip); + + g_dbus_dict_append_entry(&dict, "Skip", DBUS_TYPE_UINT16, + &bcast_qos.bcast.skip); + + bt_shell_printf("SyncTimeout %u\n", bcast_qos.bcast.sync_timeout); + + g_dbus_dict_append_entry(&dict, "SyncTimeout", DBUS_TYPE_UINT16, + &bcast_qos.bcast.sync_timeout); + + bt_shell_printf("SyncCteType %u\n", bcast_qos.bcast.sync_cte_type); + + g_dbus_dict_append_entry(&dict, "SyncCteType", DBUS_TYPE_BYTE, + &bcast_qos.bcast.sync_cte_type); + + bt_shell_printf("MSE %u\n", bcast_qos.bcast.mse); + + g_dbus_dict_append_entry(&dict, "MSE", DBUS_TYPE_BYTE, + &bcast_qos.bcast.mse); + + bt_shell_printf("Timeout %u\n", bcast_qos.bcast.timeout); + + g_dbus_dict_append_entry(&dict, "Timeout", DBUS_TYPE_UINT16, + &bcast_qos.bcast.timeout); + + bt_shell_printf("BroadcastCode:\n"); + bt_shell_hexdump(cfg->ep->bcode->iov_base, cfg->ep->bcode->iov_len); + + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &keyBCode, + DBUS_TYPE_BYTE, + &cfg->ep->bcode->iov_base, + cfg->ep->bcode->iov_len); + done: dbus_message_iter_close_container(iter, &dict); } @@ -1724,10 +1917,11 @@ static struct iovec *iov_append(struct iovec **iov, const void *data, size_t len) { - if (!*iov) { + if (!*iov) *iov = new0(struct iovec, 1); + + if (!((*iov)->iov_base)) (*iov)->iov_base = new0(uint8_t, UINT8_MAX); - } if (data && len) { memcpy((*iov)->iov_base + (*iov)->iov_len, data, len); @@ -1745,6 +1939,9 @@ DBusMessageIter iter; struct endpoint_config *cfg; + if (!preset) + return NULL; + reply = dbus_message_new_method_return(msg); if (!reply) return NULL; @@ -1754,7 +1951,11 @@ /* Copy capabilities */ iov_append(&cfg->caps, preset->data.iov_base, preset->data.iov_len); - cfg->target_latency = preset->latency; + cfg->target_latency = preset->target_latency; + + /* Copy metadata */ + if (ep->meta) + iov_append(&cfg->meta, ep->meta->iov_base, ep->meta->iov_len); if (preset->qos.phy) /* Set QoS parameters */ @@ -1775,7 +1976,7 @@ struct codec_preset *p; DBusMessage *reply; - p = find_preset(ep->uuid, input); + p = preset_find_name(ep->preset, input); if (p) { reply = endpoint_select_properties_reply(ep, ep->msg, p); goto done; @@ -1803,6 +2004,13 @@ bt_shell_printf("Endpoint: SelectProperties\n"); print_iter("\t", "Properties", &args); + if (!ep->max_transports) { + bt_shell_printf("Maximum transports reached: rejecting\n"); + return g_dbus_create_error(msg, + "org.bluez.Error.Rejected", + "Maximum transports reached"); + } + if (!ep->auto_accept) { ep->msg = dbus_message_ref(msg); bt_shell_prompt_input("Endpoint", "Enter preset/configuration:", @@ -1810,9 +2018,9 @@ return NULL; } - p = find_preset(ep->uuid, NULL); + p = preset_find_name(ep->preset, NULL); if (!p) - NULL; + return NULL; reply = endpoint_select_properties_reply(ep, msg, p); if (!reply) @@ -1823,13 +2031,26 @@ return reply; } +static bool match_str(const void *data, const void *user_data) +{ + return !strcmp(data, user_data); +} + static DBusMessage *endpoint_clear_configuration(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct endpoint *ep = user_data; + DBusMessageIter args; + const char *path; - free(ep->transport); - ep->transport = NULL; + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + + if (ep->max_transports != UINT8_MAX) + ep->max_transports++; + + queue_remove_if(ep->transports, match_str, (void *)path); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } @@ -1899,13 +2120,28 @@ struct endpoint *ep = data; if (ep->caps) { - g_free(ep->caps->iov_base); + if (ep->caps->iov_base) + g_free(ep->caps->iov_base); g_free(ep->caps); } + if (ep->meta) { + if (ep->meta->iov_base) + g_free(ep->meta->iov_base); + g_free(ep->meta); + } + if (ep->msg) dbus_message_unref(ep->msg); + if (ep->codec == 0xff) { + free(ep->preset->custom.name); + free(ep->preset); + } + + queue_destroy(ep->acquiring, NULL); + queue_destroy(ep->transports, free); + g_free(ep->path); g_free(ep->uuid); g_free(ep); @@ -1949,10 +2185,63 @@ return TRUE; } +struct vendor { + uint16_t cid; + uint16_t vid; +} __packed; + +static gboolean endpoint_get_vendor(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct endpoint *ep = data; + struct vendor vendor = { ep->cid, ep->vid }; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &vendor); + + return TRUE; +} + +static gboolean endpoint_vendor_exists(const GDBusPropertyTable *property, + void *data) +{ + struct endpoint *ep = data; + + return ep->cid && ep->vid; +} + +static gboolean endpoint_get_metadata(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct endpoint *ep = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &ep->meta->iov_base, + ep->meta->iov_len); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean endpoint_metadata_exists(const GDBusPropertyTable *property, + void *data) +{ + struct endpoint *ep = data; + + return ep->meta ? TRUE : FALSE; +} + static const GDBusPropertyTable endpoint_properties[] = { { "UUID", "s", endpoint_get_uuid, NULL, NULL }, { "Codec", "y", endpoint_get_codec, NULL, NULL }, { "Capabilities", "ay", endpoint_get_capabilities, NULL, NULL }, + { "Metadata", "ay", endpoint_get_metadata, NULL, + endpoint_metadata_exists }, + { "Vendor", "u", endpoint_get_vendor, NULL, endpoint_vendor_exists }, { } }; @@ -1961,6 +2250,7 @@ struct endpoint *ep = user_data; DBusMessageIter dict; const char *key = "Capabilities"; + const char *meta = "Metadata"; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &ep->path); @@ -1970,12 +2260,30 @@ g_dbus_dict_append_entry(&dict, "Codec", DBUS_TYPE_BYTE, &ep->codec); - g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, + if (ep->cid && ep->vid) { + struct vendor vendor = { ep->cid, ep->vid }; + + g_dbus_dict_append_entry(&dict, "Vendor", DBUS_TYPE_UINT32, + &vendor); + } + + if (ep->caps) { + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, DBUS_TYPE_BYTE, &ep->caps->iov_base, ep->caps->iov_len); - bt_shell_printf("Capabilities:\n"); - bt_shell_hexdump(ep->caps->iov_base, ep->caps->iov_len); + bt_shell_printf("Capabilities:\n"); + bt_shell_hexdump(ep->caps->iov_base, ep->caps->iov_len); + } + + if (ep->meta) { + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &meta, + DBUS_TYPE_BYTE, &ep->meta->iov_base, + ep->meta->iov_len); + + bt_shell_printf("Metadata:\n"); + bt_shell_hexdump(ep->meta->iov_base, ep->meta->iov_len); + } dbus_message_iter_close_container(iter, &dict); } @@ -1991,9 +2299,11 @@ bt_shell_printf("Failed to register endpoint: %s\n", error.name); dbus_error_free(&error); - local_endpoints = g_list_remove(local_endpoints, ep); - g_dbus_unregister_interface(dbus_conn, ep->path, + if (g_list_find(local_endpoints, ep)) { + local_endpoints = g_list_remove(local_endpoints, ep); + g_dbus_unregister_interface(dbus_conn, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE); + } return bt_shell_noninteractive_quit(EXIT_FAILURE); } @@ -2002,9 +2312,32 @@ return bt_shell_noninteractive_quit(EXIT_SUCCESS); } +static bool media_supports_uuid(GDBusProxy *proxy, const char *uuid) +{ + DBusMessageIter iter, array; + + if (!g_dbus_proxy_get_property(proxy, "SupportedUUIDs", &iter)) + return false; + + dbus_message_iter_recurse(&iter, &array); + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { + const char *support_uuid; + + dbus_message_iter_get_basic(&array, &support_uuid); + + if (!strcasecmp(uuid, support_uuid)) + return true; + + dbus_message_iter_next(&array); + } + + return false; +} + static void endpoint_register(struct endpoint *ep) { GList *l; + int registered = 0; if (!g_dbus_register_interface(dbus_conn, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, @@ -2015,6 +2348,9 @@ } for (l = medias; l; l = g_list_next(l)) { + if (!media_supports_uuid(l->data, ep->uuid)) + continue; + if (!g_dbus_proxy_method_call(l->data, "RegisterEndpoint", register_endpoint_setup, register_endpoint_reply, @@ -2023,8 +2359,13 @@ BLUEZ_MEDIA_ENDPOINT_INTERFACE); goto fail; } + + registered++; } + if (!registered) + goto fail; + return; fail: @@ -2034,14 +2375,14 @@ } -static void endpoint_cis(const char *input, void *user_data) +static void endpoint_iso_stream(const char *input, void *user_data) { struct endpoint *ep = user_data; char *endptr = NULL; int value; if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) { - ep->cis = BT_ISO_QOS_CIS_UNSET; + ep->iso_stream = BT_ISO_QOS_STREAM_UNSET; } else { value = strtol(input, &endptr, 0); @@ -2050,20 +2391,20 @@ return bt_shell_noninteractive_quit(EXIT_FAILURE); } - ep->cis = value; + ep->iso_stream = value; } endpoint_register(ep); } -static void endpoint_cig(const char *input, void *user_data) +static void endpoint_iso_group(const char *input, void *user_data) { struct endpoint *ep = user_data; char *endptr = NULL; int value; if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) { - ep->cig = BT_ISO_QOS_CIG_UNSET; + ep->iso_group = BT_ISO_QOS_GROUP_UNSET; } else { value = strtol(input, &endptr, 0); @@ -2072,41 +2413,119 @@ return bt_shell_noninteractive_quit(EXIT_FAILURE); } - ep->cig = value; + ep->iso_group = value; } - bt_shell_prompt_input(ep->path, "CIS (auto/value):", endpoint_cis, ep); + if (!ep->broadcast) + bt_shell_prompt_input(ep->path, "CIS (auto/value):", + endpoint_iso_stream, ep); + else + bt_shell_prompt_input(ep->path, "BIS (auto/value):", + endpoint_iso_stream, ep); } -static void endpoint_auto_accept(const char *input, void *user_data) +static void endpoint_max_transports(const char *input, void *user_data) { struct endpoint *ep = user_data; + char *endptr = NULL; + int value; - if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) { - ep->auto_accept = true; - } else if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) { - ep->auto_accept = false; + if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) { + ep->max_transports = UINT8_MAX; } else { - bt_shell_printf("Invalid input for Auto Accept\n"); - return bt_shell_noninteractive_quit(EXIT_FAILURE); - } + value = strtol(input, &endptr, 0); + + if (!endptr || *endptr != '\0' || value > UINT8_MAX) { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ep->max_transports = value; + } + + if (ep->broadcast) + bt_shell_prompt_input(ep->path, "BIG (auto/value):", + endpoint_iso_group, ep); + else + bt_shell_prompt_input(ep->path, "CIG (auto/value):", + endpoint_iso_group, ep); +} + +static void endpoint_auto_accept(const char *input, void *user_data) +{ + struct endpoint *ep = user_data; + + if (!strcmp(ep->uuid, BAA_SERVICE_UUID)) { + ep->broadcast = true; + } else { + ep->broadcast = false; + } + + if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) { + ep->auto_accept = true; + bt_shell_prompt_input(ep->path, "Max Transports (auto/value):", + endpoint_max_transports, ep); + return; + } else if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) { + ep->auto_accept = false; + } else { + bt_shell_printf("Invalid input for Auto Accept\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (ep->broadcast) + bt_shell_prompt_input(ep->path, "BIG (auto/value):", + endpoint_iso_group, ep); + else + bt_shell_prompt_input(ep->path, "CIG (auto/value):", + endpoint_iso_group, ep); +} + +static void endpoint_set_metadata(const char *input, void *user_data) +{ + struct endpoint *ep = user_data; + + if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) { + free(ep->meta->iov_base); + ep->meta = NULL; + goto done; + } + + if (!ep->meta) + ep->meta = g_new0(struct iovec, 1); + + ep->meta->iov_base = str2bytearray((char *) input, &ep->meta->iov_len); + if (!ep->meta->iov_base) { + free(ep->meta); + ep->meta = NULL; + } - bt_shell_prompt_input(ep->path, "CIG (auto/value):", endpoint_cig, ep); +done: + bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):", + endpoint_auto_accept, ep); } static void endpoint_set_capabilities(const char *input, void *user_data) { struct endpoint *ep = user_data; - if (ep->caps) + if (ep->caps && ep->caps->iov_base) { g_free(ep->caps->iov_base); - else + ep->caps = g_new0(struct iovec, 1); + } else ep->caps = g_new0(struct iovec, 1); ep->caps->iov_base = str2bytearray((char *) input, &ep->caps->iov_len); - bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):", - endpoint_auto_accept, ep); + if (ep->caps->iov_len == 0x01 && + (*(uint8_t *)(ep->caps->iov_base)) == 0x00) { + g_free(ep->caps->iov_base); + ep->caps->iov_base = NULL; + ep->caps->iov_len = 0x00; + } + + bt_shell_prompt_input(ep->path, "Enter Metadata (value/no):", + endpoint_set_metadata, ep); } static char *uuid_generator(const char *text, int state) @@ -2157,10 +2576,22 @@ ep = g_new0(struct endpoint, 1); ep->uuid = g_strdup(argv[1]); ep->codec = strtol(argv[2], &endptr, 0); + ep->cid = 0x0000; + ep->vid = 0x0000; ep->path = g_strdup_printf("%s/ep%u", BLUEZ_MEDIA_ENDPOINT_PATH, g_list_length(local_endpoints)); local_endpoints = g_list_append(local_endpoints, ep); + if (strrchr(argv[2], ':')) { + ep->codec = 0xff; + parse_vendor_codec(argv[2], &ep->cid, &ep->vid); + ep->preset = new0(struct preset, 1); + ep->preset->custom.name = strdup("custom"); + ep->preset->default_preset = &ep->preset->custom; + } else { + ep->preset = find_presets_name(ep->uuid, argv[2]); + } + if (argc > 3) endpoint_set_capabilities(argv[3], ep); else { @@ -2318,15 +2749,23 @@ } if (argc > 3) { - preset = find_preset(cfg->ep->uuid, argv[3]); + preset = preset_find_name(cfg->ep->preset, argv[3]); if (!preset) { bt_shell_printf("Preset %s not found\n", argv[3]); goto fail; } - /* Copy capabilities */ - iov_append(&cfg->caps, preset->data.iov_base, - preset->data.iov_len); + if (cfg->ep->broadcast) { + iov_append(&cfg->ep->bcode, bcast_qos.bcast.bcode, + sizeof(bcast_qos.bcast.bcode)); + /* Copy capabilities for broadcast*/ + iov_append(&cfg->caps, base_lc3_16_2_1, + sizeof(base_lc3_16_2_1)); + } else { + /* Copy capabilities */ + iov_append(&cfg->caps, preset->data.iov_base, + preset->data.iov_len); + } /* Set QoS parameters */ cfg->qos = &preset->qos; @@ -2345,43 +2784,375 @@ return bt_shell_noninteractive_quit(EXIT_FAILURE); } -static void cmd_presets_endpoint(int argc, char *argv[]) +static void custom_delay(const char *input, void *user_data) { - size_t i; - struct codec_preset *default_preset = NULL; + struct codec_preset *p = user_data; + struct codec_qos *qos = (void *)&p->qos; + char *endptr = NULL; - if (argc > 2) { - default_preset = find_preset(argv[1], argv[2]); - if (!default_preset) { - bt_shell_printf("Preset %s not found\n", argv[2]); + qos->delay = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void custom_latency(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct codec_qos *qos = (void *)&p->qos; + char *endptr = NULL; + + qos->latency = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_prompt_input("QoS", "Enter Presentation Delay (us):", + custom_delay, user_data); +} + +static void custom_rtn(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct codec_qos *qos = (void *)&p->qos; + char *endptr = NULL; + + qos->rtn = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_prompt_input("QoS", "Enter Max Transport Latency (ms):", + custom_latency, user_data); +} + +static void custom_sdu(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct codec_qos *qos = (void *)&p->qos; + char *endptr = NULL; + + qos->sdu = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_prompt_input("QoS", "Enter RTN:", custom_rtn, user_data); +} + +static void custom_phy(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct codec_qos *qos = (void *)&p->qos; + + if (!strcmp(input, "1M")) + qos->phy = "1M"; + else if (!strcmp(input, "2M")) + qos->phy = "2M"; + else { + char *endptr = NULL; + uint8_t phy = strtol(input, &endptr, 0); + + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); return bt_shell_noninteractive_quit(EXIT_FAILURE); } - default_preset->is_default = true; + switch (phy) { + case 0x01: + qos->phy = "1M"; + break; + case 0x02: + qos->phy = "2M"; + break; + default: + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } } - for (i = 0; i < ARRAY_SIZE(presets); i++) { - const struct preset *preset = &presets[i]; + bt_shell_prompt_input("QoS", "Enter Max SDU:", custom_sdu, user_data); +} + +static void custom_framing(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct codec_qos *qos = (void *)&p->qos; + + if (!strcasecmp(input, "Unframed")) + qos->framing = 0x00; + else if (!strcasecmp(input, "Framed")) + qos->framing = 0x01; + else { + char *endptr = NULL; + + qos->framing = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + bt_shell_prompt_input("QoS", "Enter PHY (1M, 2M):", custom_phy, + user_data); +} + +static void custom_interval(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct codec_qos *qos = (void *)&p->qos; + char *endptr = NULL; + + qos->interval = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_prompt_input("QoS", "Enter Framing (Unframed, Framed):", + custom_framing, user_data); +} + +static void custom_target_latency(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + + if (!strcasecmp(input, "Low")) + p->target_latency = 0x01; + else if (!strcasecmp(input, "Balance")) + p->target_latency = 0x02; + else if (!strcasecmp(input, "High")) + p->target_latency = 0x02; + else { + char *endptr = NULL; + + p->target_latency = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + bt_shell_prompt_input("QoS", "Enter SDU Interval (us):", + custom_interval, user_data); +} + +static void custom_length(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct iovec *iov = (void *)&p->data; + uint8_t ltv[4] = { 0x03, LC3_CONFIG_FRAME_LEN }; + uint16_t len; + char *endptr = NULL; + + len = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ltv[2] = len; + ltv[3] = len >> 8; + + iov_append(&iov, ltv, sizeof(ltv)); + + bt_shell_prompt_input("QoS", "Enter Target Latency " + "(Low, Balance, High):", + custom_target_latency, user_data); +} + +static void custom_location(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct iovec *iov = (void *)&p->data; + uint32_t location; + char *endptr = NULL; + + location = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + /* Only add Channel Allocation if set */ + if (location) { + uint8_t ltv[6] = { 0x05, LC3_CONFIG_CHAN_ALLOC }; + + location = cpu_to_le32(location); + memcpy(<v[2], &location, sizeof(location)); + iov_append(&iov, ltv, sizeof(ltv)); + } + + bt_shell_prompt_input("Codec", "Enter frame length:", + custom_length, user_data); +} + +static uint8_t val2duration(uint32_t val) +{ + switch (val) { + case 7: + return 0x00; + case 10: + return 0x01; + default: + return 0xff; + } +} + +static void custom_duration(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct iovec *iov = (void *)&p->data; + uint8_t ltv[3] = { 0x02, LC3_CONFIG_DURATION, 0x00 }; + char *endptr = NULL; + uint32_t val; + + val = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (strncmp(input, "0x", 2)) + ltv[2] = val2duration(val); + else + ltv[2] = val; + + if (ltv[2] == 0xff) { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + iov_append(&iov, ltv, sizeof(ltv)); + + bt_shell_prompt_input("Codec", "Enter channel allocation:", + custom_location, user_data); +} + +static uint8_t val2freq(uint32_t val) +{ + switch (val) { + case 8: + return 0x01; + case 11: + return 0x02; + case 16: + return 0x03; + case 22: + return 0x04; + case 24: + return 0x05; + case 32: + return 0x06; + case 44: + return 0x07; + case 48: + return 0x08; + case 88: + return 0x09; + case 96: + return 0x0a; + case 174: + return 0x0b; + case 192: + return 0x0c; + case 384: + return 0x0d; + default: + return 0x00; + } +} + +static void custom_frequency(const char *input, void *user_data) +{ + struct codec_preset *p = user_data; + struct iovec *iov = (void *)&p->data; + uint8_t ltv[3] = { 0x02, LC3_CONFIG_FREQ, 0x00 }; + uint32_t val; + char *endptr = NULL; + + val = strtol(input, &endptr, 0); + if (!endptr || *endptr != '\0') { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (strncmp(input, "0x", 2)) + ltv[2] = val2freq(val); + else + ltv[2] = val; - if (!strcasecmp(preset->uuid, argv[1])) { - size_t j; + if (!ltv[2]) { + bt_shell_printf("Invalid argument: %s\n", input); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } - for (j = 0; j < preset->num_presets; j++) { - struct codec_preset *p; + /* Reset iov to start over the codec configuration */ + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + iov_append(&iov, ltv, sizeof(ltv)); + + bt_shell_prompt_input("Codec", "Enter frame duration (ms):", + custom_duration, user_data); +} - p = &preset->presets[j]; +static void print_presets(struct preset *preset) +{ + size_t i; + struct codec_preset *p; - if (default_preset && p != default_preset) - p->is_default = false; + p = &preset->custom; + + bt_shell_printf("%s%s\n", p == preset->default_preset ? "*" : "", + p->name); + + for (i = 0; i < preset->num_presets; i++) { + p = &preset->presets[i]; + bt_shell_printf("%s%s\n", p == preset->default_preset ? + "*" : "", p->name); + } +} + +static void cmd_presets_endpoint(int argc, char *argv[]) +{ + struct preset *preset; + struct codec_preset *default_preset = NULL; - if (p->is_default) - bt_shell_printf("*%s\n", p->name); - else - bt_shell_printf("%s\n", p->name); - } + if (argc > 3) { + default_preset = find_preset(argv[1], argv[2], argv[3]); + if (!default_preset) { + bt_shell_printf("Preset %s not found\n", argv[3]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); } } + preset = find_presets_name(argv[1], argv[2]); + if (!preset) { + bt_shell_printf("No preset found\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (default_preset) { + preset->default_preset = default_preset; + goto done; + } + + print_presets(preset); + +done: + if (default_preset && !strcmp(default_preset->name, "custom")) { + bt_shell_prompt_input("Codec", "Enter frequency (Khz):", + custom_frequency, default_preset); + return; + } + return bt_shell_noninteractive_quit(EXIT_SUCCESS); } @@ -2394,7 +3165,7 @@ { "show", "", cmd_show_endpoint, "Endpoint information", endpoint_generator }, - { "register", " [capabilities...]", + { "register", " [capabilities...]", cmd_register_endpoint, "Register Endpoint", uuid_generator }, @@ -2405,7 +3176,8 @@ cmd_config_endpoint, "Configure Endpoint", endpoint_generator }, - { "presets", " [default]", cmd_presets_endpoint, + { "presets", " [default]", + cmd_presets_endpoint, "List available presets", uuid_generator }, {} }, @@ -2417,6 +3189,7 @@ ep = new0(struct endpoint, 1); ep->uuid = g_strdup(cap->uuid); + ep->broadcast = strcmp(cap->uuid, BAA_SERVICE_UUID) ? false : true; ep->codec = cap->codec_id; ep->path = g_strdup_printf("%s/ep%u", BLUEZ_MEDIA_ENDPOINT_PATH, g_list_length(local_endpoints)); @@ -2430,32 +3203,22 @@ static void register_endpoints(GDBusProxy *proxy) { struct endpoint *ep; - DBusMessageIter iter, array; - - if (!g_dbus_proxy_get_property(proxy, "SupportedUUIDs", &iter)) - return; - - dbus_message_iter_recurse(&iter, &array); - while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { - const char *uuid; - size_t i; - - dbus_message_iter_get_basic(&array, &uuid); - - for (i = 0; i < ARRAY_SIZE(caps); i++) { - const struct capabilities *cap = &caps[i]; + size_t i; - if (strcasecmp(cap->uuid, uuid)) - continue; + for (i = 0; i < ARRAY_SIZE(caps); i++) { + const struct capabilities *cap = &caps[i]; - ep = endpoint_new(cap); - ep->auto_accept = true; - ep->cig = BT_ISO_QOS_CIG_UNSET; - ep->cis = BT_ISO_QOS_CIS_UNSET; - endpoint_register(ep); - } + if (!media_supports_uuid(proxy, cap->uuid)) + continue; - dbus_message_iter_next(&array); + ep = endpoint_new(cap); + ep->preset = find_presets(ep->uuid, ep->codec, ep->vid, + ep->cid); + ep->max_transports = UINT8_MAX; + ep->auto_accept = true; + ep->iso_group = BT_ISO_QOS_GROUP_UNSET; + ep->iso_stream = BT_ISO_QOS_STREAM_UNSET; + endpoint_register(ep); } } @@ -2682,14 +3445,14 @@ for (l = local_endpoints; l; l = g_list_next(l)) { struct endpoint *ep = l->data; - if (ep->transport && !strcmp(ep->transport, path)) + if (queue_find(ep->transports, match_str, path)) return ep; } return NULL; } -static struct endpoint *find_link_by_proxy(GDBusProxy *proxy) +static GDBusProxy *find_link_by_proxy(GDBusProxy *proxy) { DBusMessageIter iter, array; @@ -2701,13 +3464,13 @@ while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_OBJECT_PATH) { const char *transport; - struct endpoint *link; dbus_message_iter_get_basic(&array, &transport); - link = find_ep_by_transport(transport); - if (link) - return link; + proxy = g_dbus_proxy_lookup(transports, NULL, transport, + BLUEZ_MEDIA_TRANSPORT_INTERFACE); + if (proxy) + return proxy; } return NULL; @@ -2719,6 +3482,8 @@ return; close(transport->fd); + transport->fd = -1; + free(transport->filename); } @@ -2726,6 +3491,7 @@ { struct transport *transport = data; + io_destroy(transport->timer_io); io_destroy(transport->io); free(transport); } @@ -2755,11 +3521,11 @@ return true; } - bt_shell_printf("[seq %d] recv: %u bytes\n", transport->seq, ret); + bt_shell_echo("[seq %d] recv: %u bytes", transport->seq, ret); transport->seq++; - if (transport->fd) { + if (transport->fd >= 0) { len = write(transport->fd, buf, ret); if (len < 0) bt_shell_printf("Unable to write: %s (%d)", @@ -2791,21 +3557,49 @@ queue_push_tail(ios, transport); } +static void ep_set_acquiring(struct endpoint *ep, GDBusProxy *proxy, bool value) +{ + bt_shell_printf("Transport %s %s\n", g_dbus_proxy_get_path(proxy), + value ? "acquiring" : "acquiring complete"); + + if (value && !ep->acquiring) + ep->acquiring = queue_new(); + + if (value) + queue_push_tail(ep->acquiring, proxy); + else + queue_remove(ep->acquiring, proxy); +} + +static void transport_set_acquiring(GDBusProxy *proxy, bool value) +{ + struct endpoint *ep; + GDBusProxy *link; + + ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy)); + if (!ep) + return; + + ep_set_acquiring(ep, proxy, value); + + link = find_link_by_proxy(proxy); + if (link) { + ep = find_ep_by_transport(g_dbus_proxy_get_path(link)); + if (!ep) + return; + + ep_set_acquiring(ep, link, value); + } +} + static void acquire_reply(DBusMessage *message, void *user_data) { GDBusProxy *proxy = user_data; - struct endpoint *ep, *link; DBusError error; int sk; uint16_t mtu[2]; - ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy)); - if (ep) { - ep->acquiring = false; - link = find_link_by_proxy(proxy); - if (link) - link->acquiring = false; - } + transport_set_acquiring(proxy, false); dbus_error_init(&error); @@ -2834,33 +3628,61 @@ return bt_shell_noninteractive_quit(EXIT_FAILURE); } -static void transport_acquire(const char *input, void *user_data) +static void prompt_acquire(const char *input, void *user_data) { GDBusProxy *proxy = user_data; - struct endpoint *ep, *link; if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) { if (g_dbus_proxy_method_call(proxy, "Acquire", NULL, - acquire_reply, proxy, NULL)) + acquire_reply, proxy, NULL)) { + transport_set_acquiring(proxy, true); return; + } bt_shell_printf("Failed acquire transport\n"); } +} + +static void transport_acquire(GDBusProxy *proxy, bool prompt) +{ + struct endpoint *ep; + GDBusProxy *link; - /* Reset acquiring */ + /* only attempt to acquire if transport is configured with a local + * endpoint. + */ ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy)); - if (ep) { - ep->acquiring = false; - link = find_link_by_proxy(proxy); - if (link) - link->acquiring = false; + if (!ep || queue_find(ep->acquiring, NULL, proxy)) + return; + + link = find_link_by_proxy(proxy); + if (link) { + ep = find_ep_by_transport(g_dbus_proxy_get_path(link)); + /* if link already acquiring wait it to be complete */ + if (!ep || queue_find(ep->acquiring, NULL, link)) + return; + } + + if (ep->auto_accept || !prompt) { + if (!prompt) + bt_shell_printf("auto acquiring...\n"); + if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL, + acquire_reply, proxy, NULL)) { + bt_shell_printf("failed acquire transport\n"); + return; + } + + transport_set_acquiring(proxy, true); + return; } + + bt_shell_prompt_input(g_dbus_proxy_get_path(proxy), "acquire (yes/no):", + prompt_acquire, proxy); } static void transport_property_changed(GDBusProxy *proxy, const char *name, DBusMessageIter *iter) { char *str; - struct endpoint *ep, *link; str = proxy_description(proxy, "Transport", COLORED_CHG); print_iter(str, name, iter); @@ -2874,38 +3696,7 @@ if (strcmp(str, "pending")) return; - /* Only attempt to acquire if transport is configured with a local - * endpoint. - */ - ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy)); - if (!ep || ep->acquiring) - return; - - ep->acquiring = true; - - link = find_link_by_proxy(proxy); - if (link) { - bt_shell_printf("Link %s found\n", link->transport); - /* If link already acquiring wait it to be complete */ - if (link->acquiring) - return; - link->acquiring = true; - } - - if (ep->auto_accept) { - bt_shell_printf("Auto Acquiring...\n"); - if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL, - acquire_reply, proxy, NULL)) { - bt_shell_printf("Failed acquire transport\n"); - ep->acquiring = false; - if (link) - link->acquiring = false; - } - return; - } - - bt_shell_prompt_input(g_dbus_proxy_get_path(proxy), "Acquire (yes/no):", - transport_acquire, proxy); + transport_acquire(proxy, true); } static void property_changed(GDBusProxy *proxy, const char *name, @@ -3010,11 +3801,7 @@ return bt_shell_noninteractive_quit(EXIT_FAILURE); } - if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL, - acquire_reply, proxy, NULL)) { - bt_shell_printf("Failed acquire transport\n"); - return bt_shell_noninteractive_quit(EXIT_FAILURE); - } + transport_acquire(proxy, false); } return bt_shell_noninteractive_quit(EXIT_SUCCESS); @@ -3090,145 +3877,229 @@ return fd; } -#define NSEC_USEC(_t) (_t / 1000L) -#define SEC_USEC(_t) (_t * 1000000L) -#define TS_USEC(_ts) (SEC_USEC((_ts)->tv_sec) + NSEC_USEC((_ts)->tv_nsec)) - -static void send_wait(struct timespec *t_start, uint32_t us) +static int elapsed_time(bool reset, int *secs, int *nsecs) { - struct timespec t_now; - struct timespec t_diff; - int64_t delta_us; + static struct timespec start; + struct timespec curr; - /* Skip sleep at start */ - if (!us) - return; + if (reset) { + if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) { + bt_shell_printf("clock_gettime: %s (%d)", + strerror(errno), errno); + return -errno; + } + } - if (clock_gettime(CLOCK_MONOTONIC, &t_now) < 0) { + if (clock_gettime(CLOCK_MONOTONIC, &curr) < 0) { bt_shell_printf("clock_gettime: %s (%d)", strerror(errno), - errno); - return; + errno); + return -errno; } - t_diff.tv_sec = t_now.tv_sec - t_start->tv_sec; - t_diff.tv_nsec = t_now.tv_nsec - t_start->tv_nsec; - - delta_us = us - TS_USEC(&t_diff); - - if (delta_us < 0) { - bt_shell_printf("Send is behind: %" PRId64 " us - skip sleep", - delta_us); - delta_us = 1000; + *secs = curr.tv_sec - start.tv_sec; + *nsecs = curr.tv_nsec - start.tv_nsec; + if (*nsecs < 0) { + (*secs)--; + *nsecs += 1000000000; } - usleep(delta_us); - - if (clock_gettime(CLOCK_MONOTONIC, t_start) < 0) - bt_shell_printf("clock_gettime: %s (%d)", strerror(errno), - errno); + return 0; } -static int transport_send(struct transport *transport, int fd, - struct bt_iso_qos *qos) +static int transport_send_seq(struct transport *transport, int fd, uint32_t num) { - struct timespec t_start; uint8_t *buf; - uint32_t num = 0; + uint32_t i; - if (qos && clock_gettime(CLOCK_MONOTONIC, &t_start) < 0) { - bt_shell_printf("clock_gettime: %s (%d)", strerror(errno), - errno); - return -errno; - } + if (!num) + return 0; buf = malloc(transport->mtu[1]); - if (!buf) { - bt_shell_printf("malloc: %s (%d)", strerror(errno), errno); + if (!buf) return -ENOMEM; - } - /* num of packets = latency (ms) / interval (us) */ - if (qos) - num = (qos->out.latency * 1000 / qos->out.interval); - - for (transport->seq = 0; ; transport->seq++) { + for (i = 0; i < num; i++, transport->seq++) { ssize_t ret; - int queued; + int secs = 0, nsecs = 0; + off_t offset; ret = read(fd, buf, transport->mtu[1]); if (ret <= 0) { if (ret < 0) bt_shell_printf("read failed: %s (%d)", strerror(errno), errno); - close(fd); + free(buf); return ret; } ret = send(transport->sk, buf, ret, 0); if (ret <= 0) { - bt_shell_printf("Send failed: %s (%d)", + bt_shell_printf("send failed: %s (%d)", strerror(errno), errno); + free(buf); return -errno; } - ioctl(transport->sk, TIOCOUTQ, &queued); + elapsed_time(!transport->seq, &secs, &nsecs); - bt_shell_printf("[seq %d] send: %zd bytes " - "(TIOCOUTQ %d bytes)\n", - transport->seq, ret, queued); - - if (qos) { - if (transport->seq && !((transport->seq + 1) % num)) - send_wait(&t_start, num * qos->out.interval); + if (!transport->seq && fstat(fd, &transport->stat) < 0) { + bt_shell_printf("fstat failed: %s (%d)", + strerror(errno), errno); + free(buf); + return -errno; } + + offset = lseek(fd, 0, SEEK_CUR); + + bt_shell_echo("[seq %d %d.%03ds] send: %zd/%zd bytes", + transport->seq, secs, + (nsecs + 500000) / 1000000, + offset, transport->stat.st_size); } free(buf); + + return i; } -static void cmd_send_transport(int argc, char *argv[]) +static bool transport_timer_read(struct io *io, void *user_data) { - GDBusProxy *proxy; - struct transport *transport; - int fd, err; + struct transport *transport = user_data; struct bt_iso_qos qos; socklen_t len; + int ret, fd; + uint32_t num; + uint64_t exp; - proxy = g_dbus_proxy_lookup(transports, NULL, argv[1], - BLUEZ_MEDIA_TRANSPORT_INTERFACE); - if (!proxy) { - bt_shell_printf("Transport %s not found\n", argv[1]); - return bt_shell_noninteractive_quit(EXIT_FAILURE); + if (transport->fd < 0) + return false; + + fd = io_get_fd(io); + ret = read(fd, &exp, sizeof(exp)); + if (ret < 0) { + bt_shell_printf("Failed to read: %s (%d)\n", strerror(errno), + -errno); + return false; } - transport = find_transport(proxy); - if (!transport) { - bt_shell_printf("Transport %s not acquired\n", argv[1]); - return bt_shell_noninteractive_quit(EXIT_FAILURE); + /* Read QoS if available */ + memset(&qos, 0, sizeof(qos)); + len = sizeof(qos); + if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, + &len) < 0) { + bt_shell_printf("Failed to getsockopt(BT_ISO_QOS): %s (%d)\n", + strerror(errno), -errno); + return false; } - if (transport->sk < 0) { - bt_shell_printf("No Transport Socked found\n"); - return bt_shell_noninteractive_quit(EXIT_FAILURE); + /* num of packets = latency (ms) / interval (us) */ + num = (qos.ucast.out.latency * 1000 / qos.ucast.out.interval); + + ret = transport_send_seq(transport, transport->fd, num); + if (ret < 0) { + bt_shell_printf("Unable to send: %s (%d)\n", + strerror(-ret), ret); + return false; } - fd = open_file(argv[2], O_RDONLY); + if (!ret) { + transport_close(transport); + return false; + } - bt_shell_printf("Sending ...\n"); + return true; +} - /* Read QoS if available */ - memset(&qos, 0, sizeof(qos)); - len = sizeof(qos); - if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, - &len) < 0) - err = transport_send(transport, fd, NULL); - else - err = transport_send(transport, fd, &qos); +static int transport_send(struct transport *transport, int fd, + struct bt_iso_qos *qos) +{ + struct itimerspec ts; + int timer_fd; - close(fd); + transport->seq = 0; - if (err < 0) - return bt_shell_noninteractive_quit(EXIT_FAILURE); + if (!qos) + return transport_send_seq(transport, fd, UINT32_MAX); + + if (transport->fd >= 0) + return -EALREADY; + + timer_fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (timer_fd < 0) + return -errno; + + memset(&ts, 0, sizeof(ts)); + ts.it_value.tv_nsec = qos->ucast.out.latency * 1000000; + ts.it_interval.tv_nsec = qos->ucast.out.latency * 1000000; + + if (timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &ts, NULL) < 0) + return -errno; + + transport->fd = fd; + + transport->timer_io = io_new(timer_fd); + + io_set_read_handler(transport->timer_io, transport_timer_read, + transport, NULL); + + return transport_send_seq(transport, fd, 1); +} + +static void cmd_send_transport(int argc, char *argv[]) +{ + GDBusProxy *proxy; + struct transport *transport; + int fd = -1, err; + struct bt_iso_qos qos; + socklen_t len; + int i; + + for (i = 1; i < argc; i++) { + proxy = g_dbus_proxy_lookup(transports, NULL, argv[i], + BLUEZ_MEDIA_TRANSPORT_INTERFACE); + if (!proxy) { + bt_shell_printf("Transport %s not found\n", argv[i]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + transport = find_transport(proxy); + if (!transport) { + bt_shell_printf("Transport %s not acquired\n", argv[i]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (transport->sk < 0) { + bt_shell_printf("No Transport Socked found\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (i + 1 < argc) { + fd = open_file(argv[++i], O_RDONLY); + if (fd < 0) + return bt_shell_noninteractive_quit( + EXIT_FAILURE); + } + + bt_shell_printf("Sending ...\n"); + + /* Read QoS if available */ + memset(&qos, 0, sizeof(qos)); + len = sizeof(qos); + if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, + &len) < 0) { + bt_shell_printf("Unable to getsockopt(BT_ISO_QOS): %s", + strerror(errno)); + err = transport_send(transport, fd, NULL); + } else + err = transport_send(transport, fd, &qos); + + if (err < 0) { + bt_shell_printf("Unable to send: %s (%d)", + strerror(-err), -err); + close(fd); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } return bt_shell_noninteractive_quit(EXIT_SUCCESS); } @@ -3330,10 +4201,13 @@ { "release", " [transport1...]", cmd_release_transport, "Release Transport", transport_generator }, - { "send", " ", cmd_send_transport, - "Send contents of a file" }, + { "send", " [transport1...]", + cmd_send_transport, + "Send contents of a file", + transport_generator }, { "receive", " [filename]", cmd_receive_transport, - "Get/Set file to receive" }, + "Get/Set file to receive", + transport_generator }, { "volume", " [value]", cmd_volume_transport, "Get/Set transport volume", transport_generator }, diff -Nru bluez-5.66/client/print.c bluez-5.68/client/print.c --- bluez-5.66/client/print.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/client/print.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,197 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2020 Intel Corporation. All rights reserved. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gdbus/gdbus.h" + +#include "src/shared/util.h" +#include "src/shared/shell.h" +#include "print.h" + +static void print_fixed_iter(const char *label, const char *name, + DBusMessageIter *iter) +{ + dbus_bool_t *valbool; + dbus_uint32_t *valu32; + dbus_uint16_t *valu16; + dbus_int16_t *vals16; + unsigned char *byte; + int len; + + switch (dbus_message_iter_get_arg_type(iter)) { + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_fixed_array(iter, &valbool, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)valbool, len * sizeof(*valbool)); + + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_fixed_array(iter, &valu32, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)valu32, len * sizeof(*valu32)); + + break; + case DBUS_TYPE_UINT16: + dbus_message_iter_get_fixed_array(iter, &valu16, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)valu16, len * sizeof(*valu16)); + + break; + case DBUS_TYPE_INT16: + dbus_message_iter_get_fixed_array(iter, &vals16, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)vals16, len * sizeof(*vals16)); + + break; + case DBUS_TYPE_BYTE: + dbus_message_iter_get_fixed_array(iter, &byte, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)byte, len * sizeof(*byte)); + + break; + default: + return; + }; +} + +void print_iter(const char *label, const char *name, DBusMessageIter *iter) +{ + dbus_bool_t valbool; + dbus_uint32_t valu32; + dbus_uint16_t valu16; + dbus_int16_t vals16; + unsigned char byte; + const char *valstr; + DBusMessageIter subiter; + char *entry; + + if (iter == NULL) { + bt_shell_printf("%s%s is nil\n", label, name); + return; + } + + switch (dbus_message_iter_get_arg_type(iter)) { + case DBUS_TYPE_INVALID: + bt_shell_printf("%s%s is invalid\n", label, name); + break; + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + dbus_message_iter_get_basic(iter, &valstr); + bt_shell_printf("%s%s: %s\n", label, name, valstr); + break; + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_basic(iter, &valbool); + bt_shell_printf("%s%s: %s\n", label, name, + valbool == TRUE ? "yes" : "no"); + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_basic(iter, &valu32); + bt_shell_printf("%s%s: 0x%08x (%d)\n", label, name, valu32, + valu32); + break; + case DBUS_TYPE_UINT16: + dbus_message_iter_get_basic(iter, &valu16); + bt_shell_printf("%s%s: 0x%04x (%d)\n", label, name, valu16, + valu16); + break; + case DBUS_TYPE_INT16: + dbus_message_iter_get_basic(iter, &vals16); + bt_shell_printf("%s%s: 0x%04x (%d)\n", label, name, vals16, + vals16); + break; + case DBUS_TYPE_BYTE: + dbus_message_iter_get_basic(iter, &byte); + bt_shell_printf("%s%s: 0x%02x (%d)\n", label, name, byte, byte); + break; + case DBUS_TYPE_VARIANT: + dbus_message_iter_recurse(iter, &subiter); + print_iter(label, name, &subiter); + break; + case DBUS_TYPE_ARRAY: + dbus_message_iter_recurse(iter, &subiter); + + if (dbus_type_is_fixed( + dbus_message_iter_get_arg_type(&subiter))) { + print_fixed_iter(label, name, &subiter); + break; + } + + while (dbus_message_iter_get_arg_type(&subiter) != + DBUS_TYPE_INVALID) { + print_iter(label, name, &subiter); + dbus_message_iter_next(&subiter); + } + break; + case DBUS_TYPE_DICT_ENTRY: + dbus_message_iter_recurse(iter, &subiter); + entry = g_strconcat(name, " Key", NULL); + print_iter(label, entry, &subiter); + g_free(entry); + + entry = g_strconcat(name, " Value", NULL); + dbus_message_iter_next(&subiter); + print_iter(label, entry, &subiter); + g_free(entry); + break; + default: + bt_shell_printf("%s%s has unsupported type\n", label, name); + break; + } +} + +void print_property_with_label(GDBusProxy *proxy, const char *name, + const char *label) +{ + DBusMessageIter iter; + + if (g_dbus_proxy_get_property(proxy, name, &iter) == FALSE) + return; + + print_iter("\t", label ? label : name, &iter); +} + +void print_property(GDBusProxy *proxy, const char *name) +{ + print_property_with_label(proxy, name, NULL); +} diff -Nru bluez-5.66/client/print.h bluez-5.68/client/print.h --- bluez-5.66/client/print.h 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/client/print.h 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * + * + */ + +void print_property(GDBusProxy *proxy, const char *name); +void print_property_with_label(GDBusProxy *proxy, const char *name, + const char *label); +void print_iter(const char *label, const char *name, DBusMessageIter *iter); diff -Nru bluez-5.66/configure bluez-5.68/configure --- bluez-5.66/configure 2022-11-10 20:25:08.000000000 +0000 +++ bluez-5.68/configure 2023-06-30 22:15:40.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for bluez 5.66. +# Generated by GNU Autoconf 2.71 for bluez 5.68. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, @@ -618,8 +618,8 @@ # Identity of this package. PACKAGE_NAME='bluez' PACKAGE_TARNAME='bluez' -PACKAGE_VERSION='5.66' -PACKAGE_STRING='bluez 5.66' +PACKAGE_VERSION='5.68' +PACKAGE_STRING='bluez 5.68' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -735,10 +735,14 @@ MONITOR_TRUE TOOLS_FALSE TOOLS_TRUE +CSIP_FALSE +CSIP_TRUE VCP_FALSE VCP_TRUE MCP_FALSE MCP_TRUE +BASS_FALSE +BASS_TRUE BAP_FALSE BAP_TRUE HEALTH_FALSE @@ -948,8 +952,10 @@ enable_hog enable_health enable_bap +enable_bass enable_mcp enable_vcp +enable_csip enable_tools enable_monitor enable_udev @@ -1560,7 +1566,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures bluez 5.66 to adapt to many kinds of systems. +\`configure' configures bluez 5.68 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1631,7 +1637,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of bluez 5.66:";; + short | recursive ) echo "Configuration of bluez 5.68:";; esac cat <<\_ACEOF @@ -1672,8 +1678,10 @@ --disable-hog disable HoG profile --enable-health enable health profiles --disable-bap disable BAP profile + --disable-bass disable BASS service --disable-mcp disable MCP profile --disable-vcp disable VCP profile + --disable-csip disable CSIP profile --disable-tools disable Bluetooth tools --disable-monitor disable Bluetooth monitor --disable-udev disable udev device support @@ -1837,7 +1845,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -bluez configure 5.66 +bluez configure 5.68 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2055,7 +2063,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by bluez $as_me 5.66, which was +It was created by bluez $as_me 5.68, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3325,7 +3333,7 @@ # Define the identity of the package. PACKAGE='bluez' - VERSION='5.66' + VERSION='5.68' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -6053,37 +6061,6 @@ -# Check whether --enable-static was given. -if test ${enable_static+y} -then : - enableval=$enable_static; p=${PACKAGE-default} - case $enableval in - yes) enable_static=yes ;; - no) enable_static=no ;; - *) - enable_static=no - # Look at the argument we got. We use all the common list separators. - lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, - for pkg in $enableval; do - IFS=$lt_save_ifs - if test "X$pkg" = "X$p"; then - enable_static=yes - fi - done - IFS=$lt_save_ifs - ;; - esac -else $as_nop - enable_static=no -fi - - - - - - - - case `pwd` in *\ * | *\ *) @@ -9910,6 +9887,36 @@ # Set options +# Check whether --enable-static was given. +if test ${enable_static+y} +then : + enableval=$enable_static; p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else $as_nop + enable_static=no +fi + + + + + + + @@ -14876,24 +14883,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - GLIB_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "glib-2.0 >= 2.28" 2>&1` + GLIB_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "glib-2.0 >= 2.28" 2>&1` else - GLIB_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "glib-2.0 >= 2.28" 2>&1` + GLIB_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "glib-2.0 >= 2.28" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$GLIB_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$GLIB_PKG_ERRORS" >&5 - as_fn_error $? "GLib >= 2.28 is required" "$LINENO" 5 + as_fn_error $? "GLib >= 2.28 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "GLib >= 2.28 is required" "$LINENO" 5 + as_fn_error $? "GLib >= 2.28 is required" "$LINENO" 5 else - GLIB_CFLAGS=$pkg_cv_GLIB_CFLAGS - GLIB_LIBS=$pkg_cv_GLIB_LIBS + GLIB_CFLAGS=$pkg_cv_GLIB_CFLAGS + GLIB_LIBS=$pkg_cv_GLIB_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -14954,24 +14961,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - GTHREAD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gthread-2.0 >= 2.16" 2>&1` + GTHREAD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gthread-2.0 >= 2.16" 2>&1` else - GTHREAD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gthread-2.0 >= 2.16" 2>&1` + GTHREAD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gthread-2.0 >= 2.16" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$GTHREAD_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$GTHREAD_PKG_ERRORS" >&5 - as_fn_error $? "GThread >= 2.16 is required" "$LINENO" 5 + as_fn_error $? "GThread >= 2.16 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "GThread >= 2.16 is required" "$LINENO" 5 + as_fn_error $? "GThread >= 2.16 is required" "$LINENO" 5 else - GTHREAD_CFLAGS=$pkg_cv_GTHREAD_CFLAGS - GTHREAD_LIBS=$pkg_cv_GTHREAD_LIBS + GTHREAD_CFLAGS=$pkg_cv_GTHREAD_CFLAGS + GTHREAD_LIBS=$pkg_cv_GTHREAD_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi GLIB_CFLAGS="$GLIB_CFLAGS $GTHREAD_CFLAGS" GLIB_LIBS="$GLIB_LIBS $GTHREAD_LIBS" @@ -15029,24 +15036,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - DBUS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dbus-1 >= 1.6" 2>&1` + DBUS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dbus-1 >= 1.6" 2>&1` else - DBUS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dbus-1 >= 1.6" 2>&1` + DBUS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dbus-1 >= 1.6" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$DBUS_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$DBUS_PKG_ERRORS" >&5 - as_fn_error $? "D-Bus >= 1.6 is required" "$LINENO" 5 + as_fn_error $? "D-Bus >= 1.6 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "D-Bus >= 1.6 is required" "$LINENO" 5 + as_fn_error $? "D-Bus >= 1.6 is required" "$LINENO" 5 else - DBUS_CFLAGS=$pkg_cv_DBUS_CFLAGS - DBUS_LIBS=$pkg_cv_DBUS_LIBS + DBUS_CFLAGS=$pkg_cv_DBUS_CFLAGS + DBUS_LIBS=$pkg_cv_DBUS_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -15327,6 +15334,21 @@ fi +# Check whether --enable-bass was given. +if test ${enable_bass+y} +then : + enableval=$enable_bass; enable_bass=${enableval} +fi + + if test "${enable_bass}" != "no"; then + BASS_TRUE= + BASS_FALSE='#' +else + BASS_TRUE='#' + BASS_FALSE= +fi + + # Check whether --enable-mcp was given. if test ${enable_mcp+y} then : @@ -15357,6 +15379,21 @@ fi +# Check whether --enable-csip was given. +if test ${enable_csip+y} +then : + enableval=$enable_csip; enable_csip=${enableval} +fi + + if test "${enable_csip}" != "no"; then + CSIP_TRUE= + CSIP_FALSE='#' +else + CSIP_TRUE='#' + CSIP_FALSE= +fi + + # Check whether --enable-tools was given. if test ${enable_tools+y} then : @@ -15446,24 +15483,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - UDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libudev >= 172" 2>&1` + UDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libudev >= 172" 2>&1` else - UDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libudev >= 172" 2>&1` + UDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libudev >= 172" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$UDEV_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$UDEV_PKG_ERRORS" >&5 - as_fn_error $? "libudev >= 172 is required" "$LINENO" 5 + as_fn_error $? "libudev >= 172 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "libudev >= 172 is required" "$LINENO" 5 + as_fn_error $? "libudev >= 172 is required" "$LINENO" 5 else - UDEV_CFLAGS=$pkg_cv_UDEV_CFLAGS - UDEV_LIBS=$pkg_cv_UDEV_LIBS + UDEV_CFLAGS=$pkg_cv_UDEV_CFLAGS + UDEV_LIBS=$pkg_cv_UDEV_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -15622,24 +15659,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - JSONC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "json-c >= 0.13" 2>&1` + JSONC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "json-c >= 0.13" 2>&1` else - JSONC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "json-c >= 0.13" 2>&1` + JSONC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "json-c >= 0.13" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$JSONC_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$JSONC_PKG_ERRORS" >&5 - as_fn_error $? "json-c >= 0.13 is required" "$LINENO" 5 + as_fn_error $? "json-c >= 0.13 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "json-c >= 0.13 is required" "$LINENO" 5 + as_fn_error $? "json-c >= 0.13 is required" "$LINENO" 5 else - JSONC_CFLAGS=$pkg_cv_JSONC_CFLAGS - JSONC_LIBS=$pkg_cv_JSONC_LIBS + JSONC_CFLAGS=$pkg_cv_JSONC_CFLAGS + JSONC_LIBS=$pkg_cv_JSONC_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -15713,24 +15750,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - ALSA_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "alsa" 2>&1` + ALSA_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "alsa" 2>&1` else - ALSA_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "alsa" 2>&1` + ALSA_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "alsa" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$ALSA_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$ALSA_PKG_ERRORS" >&5 - as_fn_error $? "ALSA lib is required for MIDI support" "$LINENO" 5 + as_fn_error $? "ALSA lib is required for MIDI support" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "ALSA lib is required for MIDI support" "$LINENO" 5 + as_fn_error $? "ALSA lib is required for MIDI support" "$LINENO" 5 else - ALSA_CFLAGS=$pkg_cv_ALSA_CFLAGS - ALSA_LIBS=$pkg_cv_ALSA_LIBS + ALSA_CFLAGS=$pkg_cv_ALSA_CFLAGS + ALSA_LIBS=$pkg_cv_ALSA_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -15795,24 +15832,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - ICAL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libical" 2>&1` + ICAL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libical" 2>&1` else - ICAL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libical" 2>&1` + ICAL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libical" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$ICAL_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$ICAL_PKG_ERRORS" >&5 - as_fn_error $? "libical is required" "$LINENO" 5 + as_fn_error $? "libical is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "libical is required" "$LINENO" 5 + as_fn_error $? "libical is required" "$LINENO" 5 else - ICAL_CFLAGS=$pkg_cv_ICAL_CFLAGS - ICAL_LIBS=$pkg_cv_ICAL_LIBS + ICAL_CFLAGS=$pkg_cv_ICAL_CFLAGS + ICAL_LIBS=$pkg_cv_ICAL_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -15900,28 +15937,79 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - ELL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ell >= 0.39" 2>&1` + ELL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ell >= 0.39" 2>&1` else - ELL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ell >= 0.39" 2>&1` + ELL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ell >= 0.39" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$ELL_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$ELL_PKG_ERRORS" >&5 - as_fn_error $? "Embedded Linux library >= 0.39 is required" "$LINENO" 5 + as_fn_error $? "Embedded Linux library >= 0.39 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "Embedded Linux library >= 0.39 is required" "$LINENO" 5 + as_fn_error $? "Embedded Linux library >= 0.39 is required" "$LINENO" 5 else - ELL_CFLAGS=$pkg_cv_ELL_CFLAGS - ELL_LIBS=$pkg_cv_ELL_LIBS + ELL_CFLAGS=$pkg_cv_ELL_CFLAGS + ELL_LIBS=$pkg_cv_ELL_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi fi +if (test "${enable_external_ell}" != "yes"); then + as_ac_File=`printf "%s\n" "ac_cv_file_${srcdir}/ell/ell.h" | $as_tr_sh` +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${srcdir}/ell/ell.h" >&5 +printf %s "checking for ${srcdir}/ell/ell.h... " >&6; } +if eval test \${$as_ac_File+y} +then : + printf %s "(cached) " >&6 +else $as_nop + test "$cross_compiling" = yes && + as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5 +if test -r "${srcdir}/ell/ell.h"; then + eval "$as_ac_File=yes" +else + eval "$as_ac_File=no" +fi +fi +eval ac_res=\$$as_ac_File + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } +if eval test \"x\$"$as_ac_File"\" = x"yes" +then : + dummy=yes +else $as_nop + as_ac_File=`printf "%s\n" "ac_cv_file_${srcdir}/../ell/ell/ell.h" | $as_tr_sh` +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${srcdir}/../ell/ell/ell.h" >&5 +printf %s "checking for ${srcdir}/../ell/ell/ell.h... " >&6; } +if eval test \${$as_ac_File+y} +then : + printf %s "(cached) " >&6 +else $as_nop + test "$cross_compiling" = yes && + as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5 +if test -r "${srcdir}/../ell/ell/ell.h"; then + eval "$as_ac_File=yes" +else + eval "$as_ac_File=no" +fi +fi +eval ac_res=\$$as_ac_File + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } +if eval test \"x\$"$as_ac_File"\" = x"yes" +then : + dummy=yes +else $as_nop + as_fn_error $? "ELL source is required or use --enable-external-ell" "$LINENO" 5 +fi + +fi + +fi if test "${enable_external_ell}" = "yes" || (test "${enable_btpclient}" != "yes" && test "${enable_mesh}" != "yes"); then @@ -16337,24 +16425,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - SBC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "sbc >= 1.2" 2>&1` + SBC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "sbc >= 1.2" 2>&1` else - SBC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "sbc >= 1.2" 2>&1` + SBC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "sbc >= 1.2" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$SBC_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$SBC_PKG_ERRORS" >&5 - as_fn_error $? "SBC library >= 1.2 is required" "$LINENO" 5 + as_fn_error $? "SBC library >= 1.2 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "SBC library >= 1.2 is required" "$LINENO" 5 + as_fn_error $? "SBC library >= 1.2 is required" "$LINENO" 5 else - SBC_CFLAGS=$pkg_cv_SBC_CFLAGS - SBC_LIBS=$pkg_cv_SBC_LIBS + SBC_CFLAGS=$pkg_cv_SBC_CFLAGS + SBC_LIBS=$pkg_cv_SBC_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -16413,24 +16501,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - SPEEXDSP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "speexdsp >= 1.2" 2>&1` + SPEEXDSP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "speexdsp >= 1.2" 2>&1` else - SPEEXDSP_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "speexdsp >= 1.2" 2>&1` + SPEEXDSP_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "speexdsp >= 1.2" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$SPEEXDSP_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$SPEEXDSP_PKG_ERRORS" >&5 - as_fn_error $? "SPEEXDSP library >= 1.2 is required" "$LINENO" 5 + as_fn_error $? "SPEEXDSP library >= 1.2 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "SPEEXDSP library >= 1.2 is required" "$LINENO" 5 + as_fn_error $? "SPEEXDSP library >= 1.2 is required" "$LINENO" 5 else - SPEEXDSP_CFLAGS=$pkg_cv_SPEEXDSP_CFLAGS - SPEEXDSP_LIBS=$pkg_cv_SPEEXDSP_LIBS + SPEEXDSP_CFLAGS=$pkg_cv_SPEEXDSP_CFLAGS + SPEEXDSP_LIBS=$pkg_cv_SPEEXDSP_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -16504,24 +16592,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - LIBEBOOK_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libebook-1.2 >= 3.3" 2>&1` + LIBEBOOK_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libebook-1.2 >= 3.3" 2>&1` else - LIBEBOOK_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libebook-1.2 >= 3.3" 2>&1` + LIBEBOOK_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libebook-1.2 >= 3.3" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$LIBEBOOK_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$LIBEBOOK_PKG_ERRORS" >&5 - as_fn_error $? "libebook >= 3.3 is required" "$LINENO" 5 + as_fn_error $? "libebook >= 3.3 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "libebook >= 3.3 is required" "$LINENO" 5 + as_fn_error $? "libebook >= 3.3 is required" "$LINENO" 5 else - LIBEBOOK_CFLAGS=$pkg_cv_LIBEBOOK_CFLAGS - LIBEBOOK_LIBS=$pkg_cv_LIBEBOOK_LIBS + LIBEBOOK_CFLAGS=$pkg_cv_LIBEBOOK_CFLAGS + LIBEBOOK_LIBS=$pkg_cv_LIBEBOOK_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -16577,24 +16665,24 @@ _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - LIBEDATESERVER_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libedataserver-1.2 >= 3.3" 2>&1` + LIBEDATESERVER_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libedataserver-1.2 >= 3.3" 2>&1` else - LIBEDATESERVER_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libedataserver-1.2 >= 3.3" 2>&1` + LIBEDATESERVER_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libedataserver-1.2 >= 3.3" 2>&1` fi - # Put the nasty error message in config.log where it belongs - echo "$LIBEDATESERVER_PKG_ERRORS" >&5 + # Put the nasty error message in config.log where it belongs + echo "$LIBEDATESERVER_PKG_ERRORS" >&5 - as_fn_error $? "libedataserver >= 3.3 is required" "$LINENO" 5 + as_fn_error $? "libedataserver >= 3.3 is required" "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } - as_fn_error $? "libedataserver >= 3.3 is required" "$LINENO" 5 + as_fn_error $? "libedataserver >= 3.3 is required" "$LINENO" 5 else - LIBEDATESERVER_CFLAGS=$pkg_cv_LIBEDATESERVER_CFLAGS - LIBEDATESERVER_LIBS=$pkg_cv_LIBEDATESERVER_LIBS + LIBEDATESERVER_CFLAGS=$pkg_cv_LIBEDATESERVER_CFLAGS + LIBEDATESERVER_LIBS=$pkg_cv_LIBEDATESERVER_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } - dummy=yes + dummy=yes fi @@ -16805,6 +16893,10 @@ as_fn_error $? "conditional \"BAP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${BASS_TRUE}" && test -z "${BASS_FALSE}"; then + as_fn_error $? "conditional \"BASS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${MCP_TRUE}" && test -z "${MCP_FALSE}"; then as_fn_error $? "conditional \"MCP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -16813,6 +16905,10 @@ as_fn_error $? "conditional \"VCP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${CSIP_TRUE}" && test -z "${CSIP_FALSE}"; then + as_fn_error $? "conditional \"CSIP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${TOOLS_TRUE}" && test -z "${TOOLS_FALSE}"; then as_fn_error $? "conditional \"TOOLS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -17299,7 +17395,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by bluez $as_me 5.66, which was +This file was extended by bluez $as_me 5.68, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -17367,7 +17463,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -bluez config.status 5.66 +bluez config.status 5.68 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" @@ -17496,9 +17592,9 @@ sed_quote_subst='$sed_quote_subst' double_quote_subst='$double_quote_subst' delay_variable_subst='$delay_variable_subst' -enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' +enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' @@ -18530,13 +18626,13 @@ # ### BEGIN LIBTOOL CONFIG -# Whether or not to build static libraries. -build_old_libs=$enable_static - # Which release of libtool.m4 was used? macro_version=$macro_version macro_revision=$macro_revision +# Whether or not to build static libraries. +build_old_libs=$enable_static + # Whether or not to build shared libraries. build_libtool_libs=$enable_shared diff -Nru bluez-5.66/configure.ac bluez-5.68/configure.ac --- bluez-5.66/configure.ac 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/configure.ac 2023-06-30 22:14:43.000000000 +0000 @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 AC_PREREQ(2.60) -AC_INIT(bluez, 5.66) +AC_INIT(bluez, 5.68) AM_INIT_AUTOMAKE([foreign subdir-objects color-tests silent-rules tar-pax no-dist-gzip dist-xz]) @@ -32,8 +32,8 @@ m4_define([_LT_AC_TAGCONFIG], []) m4_ifdef([AC_LIBTOOL_TAGS], [AC_LIBTOOL_TAGS([])]) -AC_DISABLE_STATIC -AC_PROG_LIBTOOL +LT_PREREQ(2.2) +LT_INIT([disable-static]) if (test "$USE_MAINTAINER_MODE" = "yes"); then AC_CHECK_PROG(enable_coverage, [lcov], [yes], [no]) @@ -199,6 +199,10 @@ [disable BAP profile]), [enable_bap=${enableval}]) AM_CONDITIONAL(BAP, test "${enable_bap}" != "no") +AC_ARG_ENABLE(bass, AS_HELP_STRING([--disable-bass], + [disable BASS service]), [enable_bass=${enableval}]) +AM_CONDITIONAL(BASS, test "${enable_bass}" != "no") + AC_ARG_ENABLE(mcp, AS_HELP_STRING([--disable-mcp], [disable MCP profile]), [enable_mcp=${enableval}]) AM_CONDITIONAL(MCP, test "${enable_mcp}" != "no") @@ -207,6 +211,10 @@ [disable VCP profile]), [enable_vcp=${enableval}]) AM_CONDITIONAL(VCP, test "${enable_vcp}" != "no") +AC_ARG_ENABLE(csip, AS_HELP_STRING([--disable-csip], + [disable CSIP profile]), [enable_csip=${enableval}]) +AM_CONDITIONAL(CSIP, test "${enable_csip}" != "no") + AC_ARG_ENABLE(tools, AS_HELP_STRING([--disable-tools], [disable Bluetooth tools]), [enable_tools=${enableval}]) AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no") @@ -289,6 +297,11 @@ AC_SUBST(ELL_CFLAGS) AC_SUBST(ELL_LIBS) fi +if (test "${enable_external_ell}" != "yes"); then + AC_CHECK_FILE(${srcdir}/ell/ell.h, dummy=yes, + AC_CHECK_FILE(${srcdir}/../ell/ell/ell.h, dummy=yes, + AC_MSG_ERROR(ELL source is required or use --enable-external-ell))) +fi AM_CONDITIONAL(EXTERNAL_ELL, test "${enable_external_ell}" = "yes" || (test "${enable_btpclient}" != "yes" && test "${enable_mesh}" != "yes")) diff -Nru bluez-5.66/debian/changelog bluez-5.68/debian/changelog --- bluez-5.66/debian/changelog 2022-11-30 08:31:09.000000000 +0000 +++ bluez-5.68/debian/changelog 2023-07-06 10:09:23.000000000 +0000 @@ -1,3 +1,31 @@ +bluez (5.68-0ubuntu1) mantic; urgency=medium + + * Move FIRMWARE_DIR change out of raspi-bcm43xx-load-firmware.patch and + into its own patch: use-lib-firmware.patch + * New upstream release 5.68 (LP: #2025599): + - Fix issue with A2DP and handling of Transport.Acquire. + * Includes short-lived upstream release 5.67: + - Fix issue with BAP and initiating QoS and Enable procedures. + - Fix issue with BAP and detaching streams when PAC is removed. + - Fix issue with BAP and reading all instances of PAC. + - Fix issue with BAP and not being able to reconfigure. + - Fix issue with BAP and transport configuration changes. + - Fix issue with BAP and handling unexpected disconnect. + - Fix issue with GATT and not removing pending services. + - Fix issue with GATT and client ready handling. + - Fix issue with handling fallback to transient hostname. + - Add support for SecureConnections configuration option. + - Add support for Mesh Remove Provisioning. + - Add support for Mesh Private Beacons. + * Dropped patches: + - obex-Use-GLib-helper-function-to-manipulate-paths.patch rather + than resolving the conflicts it has with upstream now. It was proposed + in 2013 and raised again in 2017 where it was rejected by upstream. + * Refreshed patches: + - 0002-hostname-handle-chassis-type-handset.patch + + -- Daniel van Vugt Thu, 06 Jul 2023 12:09:23 +0200 + bluez (5.66-0ubuntu1) lunar; urgency=medium * New upstream release 5.66 (LP: #1997201): diff -Nru bluez-5.66/debian/patches/0002-hostname-handle-chassis-type-handset.patch bluez-5.68/debian/patches/0002-hostname-handle-chassis-type-handset.patch --- bluez-5.66/debian/patches/0002-hostname-handle-chassis-type-handset.patch 2022-11-30 08:31:09.000000000 +0000 +++ bluez-5.68/debian/patches/0002-hostname-handle-chassis-type-handset.patch 2023-07-04 09:33:55.000000000 +0000 @@ -13,7 +13,7 @@ =================================================================== --- bluez.orig/plugins/hostname.c +++ bluez/plugins/hostname.c -@@ -27,10 +27,11 @@ +@@ -29,10 +29,11 @@ #include "src/adapter.h" #include "src/log.h" @@ -26,7 +26,7 @@ #define MINOR_CLASS_UNCATEGORIZED 0x00 #define MINOR_CLASS_DESKTOP 0x01 -@@ -41,6 +42,8 @@ +@@ -43,6 +44,8 @@ #define MINOR_CLASS_WEARABLE 0x06 #define MINOR_CLASS_TABLET 0x07 @@ -35,7 +35,7 @@ static uint8_t major_class = MAJOR_CLASS_MISCELLANEOUS; static uint8_t minor_class = MINOR_CLASS_UNCATEGORIZED; -@@ -109,8 +112,14 @@ static const struct { +@@ -117,8 +120,14 @@ static const struct { { "desktop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_DESKTOP }, { "server", MAJOR_CLASS_COMPUTER, MINOR_CLASS_SERVER }, { "laptop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_LAPTOP }, diff -Nru bluez-5.66/debian/patches/obex-Use-GLib-helper-function-to-manipulate-paths.patch bluez-5.68/debian/patches/obex-Use-GLib-helper-function-to-manipulate-paths.patch --- bluez-5.66/debian/patches/obex-Use-GLib-helper-function-to-manipulate-paths.patch 2022-11-30 08:31:09.000000000 +0000 +++ bluez-5.68/debian/patches/obex-Use-GLib-helper-function-to-manipulate-paths.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -From f7861d27fbcbc519f57d8496aa9486f487908821 Mon Sep 17 00:00:00 2001 -From: Bastien Nocera -Date: Sat, 9 Nov 2013 18:13:43 +0100 -Subject: [PATCH 1/5] obex: Use GLib helper function to manipulate paths - -Instead of trying to do it by hand. This also makes sure that -relative paths aren't used by the agent. ---- - obexd/src/manager.c | 10 +++++----- - 1 file changed, 5 insertions(+), 5 deletions(-) - -Index: bluez/obexd/src/manager.c -=================================================================== ---- bluez.orig/obexd/src/manager.c -+++ bluez/obexd/src/manager.c -@@ -640,14 +640,14 @@ static void agent_reply(DBusPendingCall - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID)) { - /* Splits folder and name */ -- const char *slash = strrchr(name, '/'); -+ gboolean is_relative = !g_path_is_absolute(name); - DBG("Agent replied with %s", name); -- if (!slash) { -- agent->new_name = g_strdup(name); -+ if (is_relative) { -+ agent->new_name = g_path_get_basename(name); - agent->new_folder = NULL; - } else { -- agent->new_name = g_strdup(slash + 1); -- agent->new_folder = g_strndup(name, slash - name); -+ agent->new_name = g_path_get_basename(name); -+ agent->new_folder = g_path_get_dirname(name); - } - } - diff -Nru bluez-5.66/debian/patches/raspi-bcm43xx-load-firmware.patch bluez-5.68/debian/patches/raspi-bcm43xx-load-firmware.patch --- bluez-5.66/debian/patches/raspi-bcm43xx-load-firmware.patch 2022-11-30 08:31:09.000000000 +0000 +++ bluez-5.68/debian/patches/raspi-bcm43xx-load-firmware.patch 2023-07-04 09:32:49.000000000 +0000 @@ -1,12 +1,9 @@ Author: Simon Long Forwarded: https://patchwork.kernel.org/project/bluetooth/patch/20201218190609.107898-4-dave.jones@canonical.com/ -Last-Update: 2020-12-18 +Last-Update: 2022-12-06 Description: Patches for loading the bcm43xx firmware - This patch corrects the location of the firmware from /etc/firmware to - /lib/firmware, and disables setting the UART interface speed *before* loading - the firmware (a later call sets the speed of the interface as requested). It - also introduces a short wait after the firmware load before the module is - reset. + Disables setting the UART interface speed *before* loading the firmware + (a later call sets the speed of the interface as requested). Index: bluez/tools/hciattach_bcm43xx.c =================================================================== @@ -22,16 +19,3 @@ if (bcm43xx_load_firmware(fd, fw_path)) return -1; -Index: bluez/tools/hciattach.h -=================================================================== ---- bluez.orig/tools/hciattach.h -+++ bluez/tools/hciattach.h -@@ -41,7 +41,7 @@ - #define HCI_UART_VND_DETECT 5 - - #ifndef FIRMWARE_DIR --#define FIRMWARE_DIR "/etc/firmware" -+#define FIRMWARE_DIR "/lib/firmware" - #endif - - int read_hci_event(int fd, unsigned char *buf, int size); diff -Nru bluez-5.66/debian/patches/series bluez-5.68/debian/patches/series --- bluez-5.66/debian/patches/series 2022-11-30 08:31:09.000000000 +0000 +++ bluez-5.68/debian/patches/series 2023-07-04 09:33:55.000000000 +0000 @@ -1,7 +1,7 @@ +use-lib-firmware.patch work-around-Logitech-diNovo-Edge-keyboard-firmware-i.patch bluetooth.conf.patch allow-using-obexd-without-systemd-in-the-user-sessio.patch -obex-Use-GLib-helper-function-to-manipulate-paths.patch change_path_of_hogsuspend.patch lp1759836.patch diff -Nru bluez-5.66/debian/patches/use-lib-firmware.patch bluez-5.68/debian/patches/use-lib-firmware.patch --- bluez-5.66/debian/patches/use-lib-firmware.patch 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/debian/patches/use-lib-firmware.patch 2023-07-04 09:32:49.000000000 +0000 @@ -0,0 +1,20 @@ +Author: Daniel van Vugt +Last-Update: 2022-12-06 +Description: Use /lib/firmware + This patch corrects the location of the firmware from /etc/firmware to + /lib/firmware which is the standard Ubuntu directory: + https://wiki.ubuntu.com/Kernel/Firmware + +Index: bluez/tools/hciattach.h +=================================================================== +--- bluez.orig/tools/hciattach.h ++++ bluez/tools/hciattach.h +@@ -41,7 +41,7 @@ + #define HCI_UART_VND_DETECT 5 + + #ifndef FIRMWARE_DIR +-#define FIRMWARE_DIR "/etc/firmware" ++#define FIRMWARE_DIR "/lib/firmware" + #endif + + int read_hci_event(int fd, unsigned char *buf, int size); diff -Nru bluez-5.66/doc/advertising-api.txt bluez-5.68/doc/advertising-api.txt --- bluez-5.66/doc/advertising-api.txt 2022-01-05 21:53:58.000000000 +0000 +++ bluez-5.68/doc/advertising-api.txt 2023-06-30 08:10:20.000000000 +0000 @@ -224,6 +224,7 @@ Possible values: "tx-power" "appearance" "local-name" + "rsi" array{string} SupportedSecondaryChannels [Experimental] diff -Nru bluez-5.66/doc/device-api.txt bluez-5.68/doc/device-api.txt --- bluez-5.66/doc/device-api.txt 2022-07-24 21:02:14.000000000 +0000 +++ bluez-5.68/doc/device-api.txt 2023-06-30 08:10:20.000000000 +0000 @@ -281,3 +281,13 @@ Example: 0x26 0x01 0x01... + + array{object, dict} Sets [readonly, experimental] + + The object paths of the sets the device belongs to + followed by a dictionary which can contain the + following: + + byte Rank: + + Rank of the device in the Set. diff -Nru bluez-5.66/doc/media-api.txt bluez-5.68/doc/media-api.txt --- bluez-5.66/doc/media-api.txt 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/doc/media-api.txt 2023-06-30 08:10:20.000000000 +0000 @@ -34,11 +34,25 @@ match the profile specification which is indicated by the UUID. + uint32_t Vendor [Optional]: + + Vendor-specific Company ID, Codec ID + tuple that the endpoint implements. + + It shall be set to appropriate value + when Vendor Specific Codec (0xff) + is used. + array{byte} Capabilities: Capabilities blob, it is used as it is so the size and byte order must match. + array{byte} Metadata [Optional]: + + Metadata blob, it is used as it is + so the size and byte order must match. + Possible Errors: org.bluez.Error.InvalidArguments org.bluez.Error.NotSupported - emitted when interface for the end-point is @@ -585,6 +599,17 @@ uint16 Latency [ISO only] uint32 Delay [ISO only] uint8 TargetLatency [ISO Latency] + byte BIG [ISO broadcast only] + byte BIS [ISO broadcast only] + byte SyncInterval [ISO broadcast only] + byte Encryption [ISO broadcast only] + byte Options [ISO broadcast only] + uint16 Skip [ISO broadcast only] + uint16 SyncTimeout [ISO broadcast only] + byte SyncCteType [ISO broadcast only] + byte MSE [ISO broadcast only] + uint16 Timeout [ISO broadcast only] + array{byte} BroadcastCode [ISO broadcast only] array{byte} SelectConfiguration(array{byte} capabilities) @@ -635,11 +660,24 @@ The values should match the profile specification which is indicated by the UUID. + uint32_t Vendor [readonly, Optional]: + + Vendor-specific Company ID, Codec ID tuple that the + endpoint implements. + + It shall be set to appropriate value when Vendor + Specific Codec (0xff) is used. + array{byte} Capabilities [readonly, optional]: Capabilities blob, it is used as it is so the size and byte order must match. + array{byte} Metadata [readonly, Optional]: + + Metadata blob, it is used as it is so the size and + byte order must match. + object Device [readonly, optional]: Device object which the endpoint is belongs to. @@ -777,3 +815,43 @@ Linked transport objects which the transport is associated with. + + byte CIG [ISO only, optional, experimental] + + Indicates configured QoS CIG. + Only present when QoS is configured. + + byte CIS [ISO only, optional, experimental] + + Indicates configured QoS CIS. + Only present when QoS is configured. + + uint32 Interval [ISO only, optional, experimental] + + Indicates configured QoS interval. + Only present when QoS is configured. + + boolean Framing [ISO only, optional, experimental] + + Indicates configured QoS framing. + Only present when QoS is configured. + + byte PHY [ISO only, optional, experimental] + + Indicates configured QoS PHY. + Only present when QoS is configured. + + uint16 SDU [ISO only, optional, experimental] + + Indicates configured QoS SDU. + Only present when QoS is configured. + + byte Retransmissions [ISO only, optional, experimental] + + Indicates configured QoS retransmissions. + Only present when QoS is configured. + + uint16 Latency [ISO only, optional, experimental] + + Indicates configured QoS latency. + Only present when QoS is configured. diff -Nru bluez-5.66/doc/mgmt-api.txt bluez-5.68/doc/mgmt-api.txt --- bluez-5.66/doc/mgmt-api.txt 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/doc/mgmt-api.txt 2023-06-30 08:10:20.000000000 +0000 @@ -332,7 +332,10 @@ 15 Static Address 16 PHY Configuration 17 Wideband Speech - 18 Quality Report + 18 Connected Isochronous Stream - Central + 19 Connected Isochronous Stream - Peripheral + 20 Isochronous Broadcaster + 21 Synchronized Receiver This command generates a Command Complete event on success or a Command Status event on failure. @@ -2925,7 +2928,8 @@ 15 Static Address 16 PHY Configuration 17 Wideband Speech - 18 Quality Report + 18 Connected Isochronous Stream - Central + 19 Connected Isochronous Stream - Peripheral The EIR_Data field contains information about class of device, local name and other values. Not all of them might be present. For @@ -4395,6 +4399,7 @@ 2 Not Connectable 3 Reserved (not in use) 4 Name Request Failed + 5 Scan Response For the RSSI field a value of 127 indicates that the RSSI is not available. That can happen with Bluetooth 1.1 and earlier diff -Nru bluez-5.66/doc/obex-agent-api.txt bluez-5.68/doc/obex-agent-api.txt --- bluez-5.66/doc/obex-agent-api.txt 2012-12-24 17:46:54.000000000 +0000 +++ bluez-5.68/doc/obex-agent-api.txt 2023-06-30 08:10:20.000000000 +0000 @@ -46,10 +46,11 @@ This method gets called when the service daemon needs to accept/reject a Bluetooth object push request. - Returns the full path (including the filename) where - the object shall be stored. The tranfer object will - contain a Filename property that contains the default - location and name that can be returned. + Returns the full path (including the filename) or the + folder name suffixed with '/' where the object shall + be stored. The transfer object will contain a Filename + property that contains the default location and name + that can be returned. Possible errors: org.bluez.obex.Error.Rejected org.bluez.obex.Error.Canceled diff -Nru bluez-5.66/ell/asn1-private.h bluez-5.68/ell/asn1-private.h --- bluez-5.66/ell/asn1-private.h 2021-03-29 12:19:13.000000000 +0000 +++ bluez-5.68/ell/asn1-private.h 2022-11-18 09:08:38.000000000 +0000 @@ -34,6 +34,8 @@ #define ASN1_ID_UTF8STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x0c) #define ASN1_ID_PRINTABLESTRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x13) #define ASN1_ID_IA5STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x16) +#define ASN1_ID_UTCTIME ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x17) +#define ASN1_ID_GENERALIZEDTIME ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x18) struct asn1_oid { uint8_t asn1_len; diff -Nru bluez-5.66/ell/cert.c bluez-5.68/ell/cert.c --- bluez-5.66/ell/cert.c 2022-09-07 18:21:45.000000000 +0000 +++ bluez-5.68/ell/cert.c 2023-05-24 14:13:48.000000000 +0000 @@ -25,6 +25,7 @@ #include #include #include +#include #include "private.h" #include "useful.h" @@ -33,6 +34,9 @@ #include "asn1-private.h" #include "cipher.h" #include "pem-private.h" +#include "time.h" +#include "time-private.h" +#include "utf8.h" #include "cert.h" #include "cert-private.h" #include "tls.h" @@ -63,7 +67,7 @@ struct l_cert *issuer; struct l_cert *issued; size_t asn1_len; - uint8_t asn1[0]; + uint8_t asn1[]; }; struct l_certchain { @@ -186,6 +190,188 @@ -1); } +static uint64_t cert_parse_asn1_time(const uint8_t *data, size_t len, + uint8_t tag) +{ + struct tm tm = {}; + int tz_hours; + int tz_mins; + int century; + int msecs = 0; + time_t tt; + unsigned int i; + + for (i = 0; i < len && i < 15; i++) + if (unlikely(!l_ascii_isdigit(data[i]))) + break; + + if (tag == ASN1_ID_UTCTIME) { + if (unlikely(!L_IN_SET(i, 10, 12))) + return L_TIME_INVALID; + + century = 19; + } else if (tag == ASN1_ID_GENERALIZEDTIME) { + if (unlikely(!L_IN_SET(i, 10, 12, 14))) + return L_TIME_INVALID; + + century = (data[0] - '0') * 10 + (data[1] - '0'); + if (century < 19) + return L_TIME_INVALID; + + if (len >= i + 4 && data[i] == '.') { + if (unlikely(!l_ascii_isdigit(data[i + 1]) || + !l_ascii_isdigit(data[i + 2]) || + !l_ascii_isdigit(data[i + 3]))) + return L_TIME_INVALID; + + i++; + msecs += (data[i++] - '0') * 100; + msecs += (data[i++] - '0') * 10; + msecs += (data[i++] - '0'); + } + + data += 2; + len -= 2; + i -= 2; + } else + return L_TIME_INVALID; + + if (unlikely((len != i + 1 || data[i] != 'Z') && + (len != i + 5 || (data[i] != '+' && data[i] != '-')))) + return L_TIME_INVALID; + + tm.tm_year = (data[0] - '0') * 10 + (data[1] - '0'); + tm.tm_mon = (data[2] - '0') * 10 + (data[3] - '0'); + tm.tm_mday = (data[4] - '0') * 10 + (data[5] - '0'); + tm.tm_hour = (data[6] - '0') * 10 + (data[7] - '0'); + + if (unlikely(tm.tm_mon < 1 || tm.tm_mon > 12 || + tm.tm_mday < 1 || tm.tm_mday > 31 || + tm.tm_hour > 23)) + return L_TIME_INVALID; + + if (i >= 10) { + tm.tm_min = (data[8] - '0') * 10 + (data[9] - '0'); + if (unlikely(tm.tm_min > 59)) + return L_TIME_INVALID; + } + + if (i >= 12) { + tm.tm_sec = (data[10] - '0') * 10 + (data[11] - '0'); + if (unlikely(tm.tm_sec > 59)) + return L_TIME_INVALID; + } + + /* RFC5280 Section 4.1.2.5.1 */ + if (tag == ASN1_ID_UTCTIME && tm.tm_year < 50) + century = 20; + + tm.tm_year += (century - 19) * 100; + + /* Month number is 1-based in UTCTime and 0-based in struct tm */ + tm.tm_mon -= 1; + + tt = timegm(&tm); + if (unlikely(tt == (time_t) -1)) + return L_TIME_INVALID; + + if (len == i + 5) { + data += i; + + for (i = 1; i < 5; i++) + if (unlikely(!l_ascii_isdigit(data[i]))) + return L_TIME_INVALID; + + tz_hours = (data[1] - '0') * 10 + (data[2] - '0'); + tz_mins = (data[3] - '0') * 10 + (data[4] - '0'); + + if (unlikely(tz_hours > 14 || tz_mins > 59)) + return L_TIME_INVALID; + + /* The sign converts UTC to local so invert it */ + if (data[0] == '+') + tt -= tz_hours * 3600 + tz_mins * 60; + else + tt += tz_hours * 3600 + tz_mins * 60; + } + + return (uint64_t) tt * L_USEC_PER_SEC + msecs * L_USEC_PER_MSEC; +} + +LIB_EXPORT bool l_cert_get_valid_times(struct l_cert *cert, + uint64_t *out_not_before_time, + uint64_t *out_not_after_time) +{ + const uint8_t *validity; + const uint8_t *not_before; + const uint8_t *not_after; + size_t seq_size; + size_t not_before_size; + size_t not_after_size; + uint8_t not_before_tag; + uint8_t not_after_tag; + uint64_t not_before_time = 0; + uint64_t not_after_time = 0; + + if (unlikely(!cert)) + return false; + + validity = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len, + ASN1_ID_SEQUENCE, &seq_size, + X509_CERTIFICATE_POS, + X509_TBSCERTIFICATE_POS, + X509_TBSCERT_VALIDITY_POS, + -1); + if (unlikely(!validity)) + return false; + + not_before = asn1_der_find_elem(validity, seq_size, 0, ¬_before_tag, + ¬_before_size); + if (!not_before) + return false; + + seq_size -= not_before_size + (not_before - validity); + validity = not_before + not_before_size; + not_after = asn1_der_find_elem(validity, seq_size, 0, ¬_after_tag, + ¬_after_size); + if (!not_after) + return false; + + if (out_not_before_time) { + not_before_time = cert_parse_asn1_time(not_before, + not_before_size, + not_before_tag); + if (not_before_time == L_TIME_INVALID) + return false; + } + + if (out_not_after_time) { + /* + * RFC5280 Section 4.1.2.5: "To indicate that a certificate + * has no well-defined expiration date, the notAfter SHOULD + * be assigned the GeneralizedTime value of 99991231235959Z." + */ + if (not_after_size == 15 && + !memcmp(not_after, "99991231235959Z", 15)) + not_after_time = 0; + else { + not_after_time = cert_parse_asn1_time(not_after, + not_after_size, + not_after_tag); + if (not_after_time == L_TIME_INVALID) + return false; + } + } + + if (out_not_before_time) + *out_not_before_time = not_before_time; + + if (out_not_after_time) + *out_not_after_time = not_after_time; + + return true; +} + const uint8_t *cert_get_extension(struct l_cert *cert, const struct asn1_oid *ext_id, bool *out_critical, size_t *out_len) @@ -377,24 +563,25 @@ break; } -static struct l_keyring *cert_set_to_keyring(struct l_queue *certs, char *error) +static struct l_keyring *cert_set_to_keyring(struct l_cert **certs, char *error) { struct l_keyring *ring; - const struct l_queue_entry *entry; int i = 1; + int count; ring = l_keyring_new(); if (!ring) return NULL; - for (entry = l_queue_get_entries(certs); entry; entry = entry->next) { - struct l_cert *cert = entry->data; + for (count = 0; certs[count]; count++); + + for (; *certs; certs++) { + struct l_cert *cert = *certs; struct l_key *key = l_cert_get_pubkey(cert); if (!key) { sprintf(error, "Can't get public key from certificate " - "%i / %i in certificate set", i, - l_queue_length(certs)); + "%i / %i in certificate set", i, count); goto cleanup; } @@ -402,7 +589,7 @@ l_key_free(key); sprintf(error, "Can't link the public key from " "certificate %i / %i to target keyring", - i, l_queue_length(certs)); + i, count); goto cleanup; } @@ -417,12 +604,10 @@ return NULL; } -static bool cert_is_in_set(struct l_cert *cert, struct l_queue *set) +static bool cert_is_in_set(struct l_cert *cert, struct l_cert **set) { - const struct l_queue_entry *entry; - - for (entry = l_queue_get_entries(set); entry; entry = entry->next) { - struct l_cert *cert2 = entry->data; + for (; *set; set++) { + struct l_cert *cert2 = *set; if (cert == cert2) return true; @@ -436,6 +621,35 @@ return false; } +static struct l_cert **cert_set_filter_by_validity(struct l_queue *set, + uint64_t now, + int *out_total, + int *out_valid) +{ + const struct l_queue_entry *entry; + _auto_(l_free) struct l_cert **valid; + + *out_total = l_queue_length(set); + *out_valid = 0; + valid = l_new(struct l_cert *, *out_total + 1); + + for (entry = l_queue_get_entries(set); entry; entry = entry->next) { + struct l_cert *cert = entry->data; + uint64_t not_before; + uint64_t not_after; + + if (!l_cert_get_valid_times(cert, ¬_before, ¬_after)) + return NULL; + + if (now < not_before || (not_after && now > not_after)) + continue; + + valid[(*out_valid)++] = cert; + } + + return l_steal_ptr(valid); +} + static struct l_key *cert_try_link(struct l_cert *cert, struct l_keyring *ring) { struct l_key *key; @@ -470,22 +684,81 @@ struct l_key *prev_key = NULL; int verified = 0; int ca_match = 0; - int i = 0; - static char error_buf[200]; + int i; + static char error_buf[1024]; + int total = 0; + uint64_t now; + _auto_(l_free) struct l_cert **ca_certs_valid = NULL; + int ca_certs_total_count = 0; + int ca_certs_valid_count = 0; if (unlikely(!chain || !chain->leaf)) RETURN_ERROR("Chain empty"); + for (cert = chain->ca; cert; cert = cert->issued, total++); + + now = time_realtime_now(); + + for (cert = chain->ca, i = 0; cert; cert = cert->issued, i++) { + uint64_t not_before; + uint64_t not_after; + char time_str[100]; + + if (unlikely(!l_cert_get_valid_times(cert, ¬_before, + ¬_after))) + RETURN_ERROR("Can't parse validity in certificate " + "%i / %i", i + 1, total); + + if (unlikely(now < not_before)) { + time_t t = not_before / L_USEC_PER_SEC; + struct tm *tm = gmtime(&t); + + if (!tm || !strftime(time_str, sizeof(time_str), + "%a %F %T UTC", tm)) + strcpy(time_str, ""); + + RETURN_ERROR("Certificate %i / %i not valid before %s", + i + 1, total, time_str); + } + + if (unlikely(not_after && now > not_after)) { + time_t t = not_after / L_USEC_PER_SEC; + struct tm *tm = gmtime(&t); + + if (!tm || !strftime(time_str, sizeof(time_str), + "%a %F %T UTC", tm)) + strcpy(time_str, ""); + + RETURN_ERROR("Certificate %i / %i expired on %s", + i + 1, total, time_str); + } + } + + if (ca_certs) { + if (unlikely(l_queue_isempty(ca_certs))) + RETURN_ERROR("No trusted CA certificates"); + + ca_certs_valid = cert_set_filter_by_validity(ca_certs, now, + &ca_certs_total_count, + &ca_certs_valid_count); + if (unlikely(!ca_certs_valid)) + RETURN_ERROR("Can't parse validity in CA cert(s)"); + + if (unlikely(!ca_certs_valid_count)) + RETURN_ERROR("All trusted CA certs are expired or " + "not-yet-valid"); + + for (cert = chain->ca, i = 0; cert; cert = cert->issued, i++) + if (cert_is_in_set(cert, ca_certs_valid)) { + ca_match = i + 1; + break; + } + } + verify_ring = l_keyring_new(); if (!verify_ring) RETURN_ERROR("Can't create verify keyring"); - for (cert = chain->ca; cert; cert = cert->issued, i++) - if (cert_is_in_set(cert, ca_certs)) { - ca_match = i + 1; - break; - } - cert = chain->ca; /* @@ -505,7 +778,7 @@ * all of the trusted certificates into the kernel, link them * to @ca_ring or link @ca_ring to @verify_ring, instead we * load the first certificate into @verify_ring before we set - * the restric mode on it, same as when no trusted CAs are + * the restrict mode on it, same as when no trusted CAs are * provided. * * Note this happens to work around a kernel issue preventing @@ -516,7 +789,7 @@ * the chain. */ if (ca_certs && !ca_match) { - ca_ring = cert_set_to_keyring(ca_certs, error_buf); + ca_ring = cert_set_to_keyring(ca_certs_valid, error_buf); if (!ca_ring) { if (error) *error = error_buf; @@ -569,23 +842,28 @@ } if (!prev_key) { - int total = 0; - char str[100]; - - for (cert = chain->ca; cert; cert = cert->issued, total++); + char str1[100]; + char str2[100] = ""; if (ca_match) - snprintf(str, sizeof(str), "%i / %i matched a trusted " - "certificate, root not verified", + snprintf(str1, sizeof(str1), "%i / %i matched a trusted" + " certificate, root not verified", ca_match, total); else - snprintf(str, sizeof(str), "root %sverified against " + snprintf(str1, sizeof(str1), "root %sverified against " "trusted CA(s)", - ca_certs && !ca_match && verified ? "" : - "not "); + ca_certs && verified ? "" : "not "); + + if (ca_certs && !ca_match && !verified && + ca_certs_valid_count < ca_certs_total_count) + snprintf(str2, sizeof(str2), ", %i out of %i trused " + "CA(s) were expired or not-yet-valid", + ca_certs_total_count - + ca_certs_valid_count, + ca_certs_total_count); - RETURN_ERROR("Linking certificate %i / %i failed, %s", - verified + 1, total, str); + RETURN_ERROR("Linking certificate %i / %i failed, %s%s", + verified + 1, total, str1, str2); } l_key_free(prev_key); diff -Nru bluez-5.66/ell/cert.h bluez-5.68/ell/cert.h --- bluez-5.66/ell/cert.h 2022-09-07 18:21:45.000000000 +0000 +++ bluez-5.68/ell/cert.h 2022-11-18 09:08:38.000000000 +0000 @@ -48,6 +48,8 @@ const uint8_t *l_cert_get_der_data(struct l_cert *cert, size_t *out_len); const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len); +bool l_cert_get_valid_times(struct l_cert *cert, uint64_t *out_not_before_time, + uint64_t *out_not_after_time); enum l_cert_key_type l_cert_get_pubkey_type(struct l_cert *cert); struct l_key *l_cert_get_pubkey(struct l_cert *cert); diff -Nru bluez-5.66/ell/checksum.c bluez-5.68/ell/checksum.c --- bluez-5.66/ell/checksum.c 2021-03-29 12:19:13.000000000 +0000 +++ bluez-5.68/ell/checksum.c 2022-12-18 19:40:29.000000000 +0000 @@ -146,55 +146,22 @@ return sk; } -/** - * l_checksum_new: - * @type: checksum type - * - * Creates new #l_checksum, using the checksum algorithm @type. - * - * Returns: a newly allocated #l_checksum object. - **/ -LIB_EXPORT struct l_checksum *l_checksum_new(enum l_checksum_type type) -{ - struct l_checksum *checksum; - int fd; - - if (!is_valid_index(checksum_algs, type) || !checksum_algs[type].name) - return NULL; - - checksum = l_new(struct l_checksum, 1); - checksum->alg_info = &checksum_algs[type]; - - fd = create_alg(checksum->alg_info->name); - if (fd < 0) - goto error; - - checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC); - close(fd); - - if (checksum->sk < 0) - goto error; - - return checksum; - -error: - l_free(checksum); - return NULL; -} - -LIB_EXPORT struct l_checksum *l_checksum_new_cmac_aes(const void *key, - size_t key_len) +static struct l_checksum *checksum_new_common(const char *alg, int sockopt, + const void *data, size_t len, + struct checksum_info *info) { struct l_checksum *checksum; int fd; - fd = create_alg("cmac(aes)"); + fd = create_alg(alg); if (fd < 0) return NULL; - if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) { - close(fd); - return NULL; + if (data) { + if (setsockopt(fd, SOL_ALG, sockopt, data, len) < 0) { + close(fd); + return NULL; + } } checksum = l_new(struct l_checksum, 1); @@ -206,40 +173,44 @@ return NULL; } - checksum->alg_info = &checksum_cmac_aes_alg; + checksum->alg_info = info; return checksum; } -LIB_EXPORT struct l_checksum *l_checksum_new_hmac(enum l_checksum_type type, - const void *key, size_t key_len) +/** + * l_checksum_new: + * @type: checksum type + * + * Creates new #l_checksum, using the checksum algorithm @type. + * + * Returns: a newly allocated #l_checksum object. + **/ +LIB_EXPORT struct l_checksum *l_checksum_new(enum l_checksum_type type) { - struct l_checksum *checksum; - int fd; - - if (!is_valid_index(checksum_hmac_algs, type) || - !checksum_hmac_algs[type].name) - return NULL; - - fd = create_alg(checksum_hmac_algs[type].name); - if (fd < 0) + if (!is_valid_index(checksum_algs, type) || !checksum_algs[type].name) return NULL; - if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) { - close(fd); - return NULL; - } + return checksum_new_common(checksum_algs[type].name, 0, NULL, 0, + &checksum_algs[type]); +} - checksum = l_new(struct l_checksum, 1); - checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC); - close(fd); +LIB_EXPORT struct l_checksum *l_checksum_new_cmac_aes(const void *key, + size_t key_len) +{ + return checksum_new_common("cmac(aes)", ALG_SET_KEY, key, key_len, + &checksum_cmac_aes_alg); +} - if (checksum->sk < 0) { - l_free(checksum); +LIB_EXPORT struct l_checksum *l_checksum_new_hmac(enum l_checksum_type type, + const void *key, size_t key_len) +{ + if (!is_valid_index(checksum_hmac_algs, type) || + !checksum_hmac_algs[type].name) return NULL; - } - checksum->alg_info = &checksum_hmac_algs[type]; - return checksum; + return checksum_new_common(checksum_hmac_algs[type].name, + ALG_SET_KEY, key, key_len, + &checksum_hmac_algs[type]); } /** diff -Nru bluez-5.66/ell/cipher.c bluez-5.68/ell/cipher.c --- bluez-5.66/ell/cipher.c 2021-09-14 19:48:51.000000000 +0000 +++ bluez-5.68/ell/cipher.c 2023-05-24 14:13:48.000000000 +0000 @@ -338,9 +338,14 @@ iov[0].iov_base = (void *) ad; iov[0].iov_len = ad_len; - iov[1].iov_base = (void *) in; - iov[1].iov_len = in_len; - msg.msg_iovlen = 2; + + msg.msg_iovlen = 1; + + if (in) { + iov[1].iov_base = (void *) in; + iov[1].iov_len = in_len; + msg.msg_iovlen = 2; + } } else { iov[0].iov_base = (void *) in; iov[0].iov_len = in_len; diff -Nru bluez-5.66/ell/settings.c bluez-5.68/ell/settings.c --- bluez-5.66/ell/settings.c 2022-02-24 20:20:21.000000000 +0000 +++ bluez-5.68/ell/settings.c 2023-05-24 14:13:48.000000000 +0000 @@ -61,7 +61,7 @@ char *name; char type[32]; size_t len; - char data[0]; + char data[]; }; struct group_data { diff -Nru bluez-5.66/ell/time.c bluez-5.68/ell/time.c --- bluez-5.66/ell/time.c 2022-07-15 16:22:44.000000000 +0000 +++ bluez-5.68/ell/time.c 2022-11-18 09:08:38.000000000 +0000 @@ -58,6 +58,14 @@ return _time_from_timespec(&now); } +uint64_t time_realtime_now(void) +{ + struct timespec now; + + clock_gettime(CLOCK_REALTIME, &now); + return _time_from_timespec(&now); +} + /** * l_time_after * diff -Nru bluez-5.66/ell/time-private.h bluez-5.68/ell/time-private.h --- bluez-5.66/ell/time-private.h 2022-07-15 16:22:44.000000000 +0000 +++ bluez-5.68/ell/time-private.h 2022-11-18 09:08:38.000000000 +0000 @@ -24,3 +24,4 @@ uint64_t _time_fuzz_msecs(uint64_t ms); uint64_t _time_fuzz_secs(uint32_t secs, uint32_t max_offset); uint64_t _time_realtime_to_boottime(const struct timeval *ts); +uint64_t time_realtime_now(void); diff -Nru bluez-5.66/ell/tls.c bluez-5.68/ell/tls.c --- bluez-5.66/ell/tls.c 2022-09-07 18:21:45.000000000 +0000 +++ bluez-5.68/ell/tls.c 2023-01-23 18:26:15.000000000 +0000 @@ -46,6 +46,9 @@ #include "strv.h" #include "missing.h" #include "string.h" +#include "settings.h" +#include "time.h" +#include "time-private.h" bool tls10_prf(const void *secret, size_t secret_len, const char *label, @@ -195,6 +198,7 @@ tls->peer_cert = NULL; tls->peer_pubkey = NULL; tls->peer_pubkey_size = 0; + tls->peer_authenticated = false; tls->negotiated_curve = NULL; tls->negotiated_ff_group = NULL; @@ -204,6 +208,12 @@ TLS_SET_STATE(TLS_HANDSHAKE_WAIT_START); tls->cert_requested = 0; tls->cert_sent = 0; + + tls->session_id_size = 0; + tls->session_id_size_replaced = 0; + tls->session_id_new = false; + l_free(l_steal_ptr(tls->session_peer_identity)); + tls->session_resumed = false; } static void tls_cleanup_handshake(struct l_tls *tls) @@ -385,7 +395,7 @@ tls_change_cipher_spec(tls, txrx, NULL); } -bool tls_cipher_suite_is_compatible(struct l_tls *tls, +static bool tls_cipher_suite_is_compatible_no_key_xchg(struct l_tls *tls, const struct tls_cipher_suite *suite, const char **error) { @@ -470,19 +480,6 @@ return false; } - if (suite->key_xchg->need_ffdh && - !l_key_is_supported(L_KEY_FEATURE_DH)) { - if (error) { - *error = error_buf; - snprintf(error_buf, sizeof(error_buf), - "Cipher suite %s's key exchange " - "mechanism needs kernel DH support", - suite->name); - } - - return false; - } - /* * If the certificate is compatible with the signature algorithm it * also must be compatible with the key exchange mechanism because @@ -503,6 +500,38 @@ return false; } + return true; +} + +/* + * Assumes that Client Hello extensions have been processed and that we + * will want to start a new session using this cipher suite, including the + * key exchange. This is unlike tls_cipher_suite_is_compatible_no_key_xchg() + * which runs fewer checks and must succeed even for a cipher suite loaded + * during session resumption. + */ +bool tls_cipher_suite_is_compatible(struct l_tls *tls, + const struct tls_cipher_suite *suite, + const char **error) +{ + static char error_buf[200]; + + if (!tls_cipher_suite_is_compatible_no_key_xchg(tls, suite, error)) + return false; + + if (suite->key_xchg->need_ffdh && + !l_key_is_supported(L_KEY_FEATURE_DH)) { + if (error) { + *error = error_buf; + snprintf(error_buf, sizeof(error_buf), + "Cipher suite %s's key exchange " + "mechanism needs kernel DH support", + suite->name); + } + + return false; + } + /* * On the server we know what elliptic curve we'll be using as soon * as we've processed the ClientHello so for EC-based key exchange @@ -820,6 +849,312 @@ return false; } +static const char *tls_get_cache_group_name(struct l_tls *tls, + const uint8_t *session_id, + size_t session_id_size) +{ + _auto_(l_free) char *session_id_str = NULL; + static char group_name[256]; + + if (!tls->server) + return tls->session_prefix; + + session_id_str = l_util_hexstring(session_id, session_id_size); + snprintf(group_name, sizeof(group_name), "%s-%s", + tls->session_prefix, session_id_str); + return group_name; +} + +static void tls_forget_cached_session(struct l_tls *tls, const char *group_name, + const uint8_t *session_id, + size_t session_id_size, bool call_back) +{ + if (!group_name) + group_name = tls_get_cache_group_name(tls, session_id, + session_id_size); + + l_settings_remove_group(tls->session_settings, group_name); + + if (call_back && tls->session_update_cb) { + tls->in_callback = true; + tls->session_update_cb(tls->session_update_user_data); + tls->in_callback = false; + } +} + +static bool tls_load_cached_session(struct l_tls *tls, const char *group_name, + const uint8_t *session_id, + size_t session_id_size, + const char *session_id_str) +{ + _auto_(l_free) uint8_t *master_secret = NULL; + int version; + _auto_(l_free) uint8_t *cipher_suite_id = NULL; + struct tls_cipher_suite *cipher_suite; + unsigned int compression_method_id; + _auto_(l_free) char *peer_identity = NULL; + size_t size; + const char *error; + + if (l_settings_has_key(tls->session_settings, group_name, + "SessionExpiryTime")) { + uint64_t expiry_time; + + if (unlikely(!l_settings_get_uint64(tls->session_settings, + group_name, + "SessionExpiryTime", + &expiry_time))) + goto warn_corrupt; + + if (time_realtime_now() > expiry_time) { + TLS_DEBUG("Cached session %s is expired, removing it, " + "will start a new session", + session_id_str); + goto forget; + } + } + + if (unlikely(!l_settings_get_int(tls->session_settings, + group_name, + "SessionVersion", + &version) || + version < TLS_MIN_VERSION || version > TLS_MAX_VERSION)) + goto warn_corrupt; + + master_secret = l_settings_get_bytes(tls->session_settings, + group_name, + "SessionMasterSecret", + &size); + if (unlikely(!master_secret || size != 48)) + goto warn_corrupt; + + cipher_suite_id = l_settings_get_bytes(tls->session_settings, + group_name, + "SessionCipherSuite", + &size); + if (unlikely(!cipher_suite_id || size != 2 || + !(cipher_suite = + tls_find_cipher_suite(cipher_suite_id)))) + goto warn_corrupt; + + /* + * While we could attempt to resume a session even though we're now + * configured with, say, a different certificate type than what we + * had when we cached that session, that is too questionable of a + * scenario to support it. We don't specifically check that all of + * the authentication data is the same, e.g. we don't save the + * certificate serial number or path, but ensure the cached cipher + * suite is compatible with current authentication data. + * + * We filter the cipher suites in our Client Hello to only offer the + * ones compatible with current configuration so if we also include + * a Session ID from a session who's cipher suite is not one of those + * listed in that same Client Hello, the server is likely to notice + * and either start a new session or send a fatal Alert. + * + * It is up to the user to keep multiple cache instances if it needs + * to save multiple sessions. + */ + if (unlikely(!tls_cipher_suite_is_compatible_no_key_xchg(tls, + cipher_suite, + &error))) { + TLS_DEBUG("Cached session %s cipher suite not compatible: %s", + session_id_str, error); + goto forget; + } + + if (unlikely(!l_settings_get_uint(tls->session_settings, group_name, + "SessionCompressionMethod", + &compression_method_id) || + !tls_find_compression_method(compression_method_id))) + goto warn_corrupt; + + if (l_settings_has_key(tls->session_settings, group_name, + "SessionPeerIdentity")) { + peer_identity = l_settings_get_string(tls->session_settings, + group_name, + "SessionPeerIdentity"); + if (unlikely(!peer_identity || !cipher_suite->signature)) + goto warn_corrupt; + } + + tls->session_id_size = session_id_size; + memcpy(tls->session_id, session_id, session_id_size); + tls->session_id_new = false; + tls->client_version = version; + memcpy(tls->pending.master_secret, master_secret, 48); + memcpy(tls->session_cipher_suite_id, cipher_suite_id, 2); + tls->session_compression_method_id = compression_method_id; + l_free(tls->session_peer_identity); + tls->session_peer_identity = l_steal_ptr(peer_identity); + return true; + +warn_corrupt: + TLS_DEBUG("Cached session %s data is corrupt or has unsupported " + "parameters, removing it, will start a new session", + session_id_str); + +forget: + tls_forget_cached_session(tls, group_name, session_id, session_id_size, + true); + return false; +} + +static bool tls_load_cached_client_session(struct l_tls *tls) +{ + /* + * The following settings are required: + * SessionID, + * SessionMasterSecret, + * SessionVersion, + * SessionCipherSuite, + * SessionCompressionMethod, + * and these two are optional: + * SessionExpiryTime, + * SessionPeerIdentity. + */ + _auto_(l_free) uint8_t *session_id = NULL; + size_t session_id_size; + _auto_(l_free) char *session_id_str = NULL; + const char *group_name = tls_get_cache_group_name(tls, NULL, 0); + + tls->session_id_size = 0; + tls->session_id_new = false; + + if (!tls->session_settings || + !l_settings_has_key(tls->session_settings, group_name, + "SessionID")) + /* No session cached, no error */ + return false; + + session_id = l_settings_get_bytes(tls->session_settings, group_name, + "SessionID", &session_id_size); + if (unlikely(!session_id || + session_id_size < 1 || session_id_size > 32)) { + TLS_DEBUG("Bad cached session ID format"); + tls_forget_cached_session(tls, group_name, NULL, 0, true); + return false; + } + + session_id_str = l_util_hexstring(session_id, session_id_size); + + return tls_load_cached_session(tls, group_name, session_id, + session_id_size, session_id_str); +} + +static bool tls_load_cached_server_session(struct l_tls *tls, + const uint8_t *session_id, + size_t session_id_size) +{ + _auto_(l_free) char *session_id_str = + l_util_hexstring(session_id, session_id_size); + const char *target_group_name = + tls_get_cache_group_name(tls, session_id, session_id_size); + _auto_(l_strv_free) char **groups = + l_settings_get_groups(tls->session_settings); + char **group; + unsigned int cnt = 0; + size_t prefix_len = strlen(tls->session_prefix); + uint64_t now = time_realtime_now(); + char *oldest_session_group = NULL; + uint64_t oldest_session_expiry = UINT64_MAX; + bool found = false; + bool changed = false; + bool loaded = false; + + tls->session_id_size = 0; + tls->session_id_new = false; + + /* Clean up expired entries and enforce session count limit */ + for (group = groups; *group; group++) { + uint64_t expiry_time; + + if (memcmp(*group, tls->session_prefix, prefix_len) || + (*group)[prefix_len] != '-') + continue; + + /* Group seems to be a session cache entry */ + + if (unlikely(!l_settings_get_uint64(tls->session_settings, + *group, + "SessionExpiryTime", + &expiry_time)) || + expiry_time <= now) { + TLS_DEBUG("Cached session %s is expired or invalid, " + "purging entry", + *group + prefix_len + 1); + l_settings_remove_group(tls->session_settings, *group); + changed = true; + continue; + } + + cnt++; + + if (!strcmp(*group + prefix_len + 1, + target_group_name + prefix_len + 1)) { + found = true; + continue; /* Don't purge this entry */ + } + + if (expiry_time < oldest_session_expiry) { + oldest_session_group = *group; + oldest_session_expiry = expiry_time; + } + } + + /* + * Enforce tls->session_count_max by dropping the entry for the + * oldest session (more specifically the one closest to its expiry + * time) in the cache, that is not the session we're trying to + * load. If the target session was not found, do this as soon as + * tls->session_count_max is reached rather than when it's exceeded + * so as to make room for a new entry. If we end up not saving + * a new session due to an error before the handshake finish, we'll + * be left with tls->session_count_max - 1 entries and will have + * purged that entry unnecessarily but the cache will have room for + * a future session. Same when we did find the target session but + * later had to forget it because of a fatal alert during or after + * the handshake. + * + * If we did find the target session but we later find that we + * can't resume it, we will forget it so the new session can take + * the old session's place and the limit is not exceeded, see + * discussion in tls_server_resume_error. + */ + if (tls->session_count_max && oldest_session_group && + cnt >= tls->session_count_max + (found ? 1 : 0)) { + l_settings_remove_group(tls->session_settings, + oldest_session_group); + changed = true; + } + + if (!found) { + TLS_DEBUG("Requested session %s not found in cache, will " + "start a new session", session_id_str); + goto call_back; + } + + loaded = tls_load_cached_session(tls, target_group_name, + session_id, session_id_size, + session_id_str); + + /* + * If tls_load_cached_session() returned false it will have called + * session_update_cb for us. + */ + if (!loaded) + changed = false; + +call_back: + if (changed && tls->session_update_cb) { + tls->in_callback = true; + tls->session_update_cb(tls->session_update_user_data); + tls->in_callback = false; + } + + return loaded; +} + #define SWITCH_ENUM_TO_STR(val) \ case (val): \ return L_STRINGIFY(val); @@ -868,6 +1203,29 @@ void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc, enum l_tls_alert_desc local_desc) { + bool forget_session = false; + /* Save session_id_size before tls_reset_handshake() */ + size_t session_id_size = tls->session_id_size; + + if ((desc || local_desc) && tls->session_settings && + session_id_size && !tls->session_id_new) + /* + * RFC5246 Section 7.2: "Alert messages with a level of fatal + * result in the immediate termination of the connection. In + * this case, other connections corresponding to the session + * may continue, but the session identifier MUST be + * invalidated, preventing the failed session from being used + * to establish new connections." + * + * and 7.2.1: "Note that as of TLS 1.1, failure to properly + * close a connection no longer requires that a session not + * be resumed." + * + * I.e. we need to remove the session from the cache here but + * not on l_tls_close(). + */ + forget_session = true; + tls_send_alert(tls, true, desc); tls_reset_handshake(tls); @@ -878,6 +1236,15 @@ tls->negotiated_version = 0; tls->ready = false; + tls->renegotiation_info.secure_renegotiation = false; + + if (forget_session) { + tls_forget_cached_session(tls, NULL, tls->session_id, + session_id_size, true); + + if (tls->pending_destroy) + return; + } tls->disconnected(local_desc ?: desc, local_desc && !desc, tls->user_data); @@ -1003,14 +1370,19 @@ /* Fill in the Client Hello body */ - *ptr++ = (uint8_t) (tls->max_version >> 8); - *ptr++ = (uint8_t) (tls->max_version >> 0); + *ptr++ = (uint8_t) (tls->client_version >> 8); + *ptr++ = (uint8_t) (tls->client_version >> 0); tls_write_random(tls->pending.client_random); memcpy(ptr, tls->pending.client_random, 32); ptr += 32; - *ptr++ = 0; /* No SessionID */ + if (tls->session_id_size) { + *ptr++ = tls->session_id_size; + memcpy(ptr, tls->session_id, tls->session_id_size); + ptr += tls->session_id_size; + } else + *ptr++ = 0; len_ptr = ptr; ptr += 2; @@ -1065,7 +1437,12 @@ memcpy(ptr, tls->pending.server_random, 32); ptr += 32; - *ptr++ = 0; /* Sessions are not cached */ + if (tls->session_id_size) { + *ptr++ = tls->session_id_size; + memcpy(ptr, tls->session_id, tls->session_id_size); + ptr += tls->session_id_size; + } else + *ptr++ = 0; *ptr++ = tls->pending.cipher_suite->id[0]; *ptr++ = tls->pending.cipher_suite->id[1]; @@ -1257,22 +1634,10 @@ TLS_HANDSHAKE_HEADER_SIZE); } -void tls_generate_master_secret(struct l_tls *tls, - const uint8_t *pre_master_secret, - int pre_master_secret_len) +static void tls_update_key_block(struct l_tls *tls) { uint8_t seed[64]; - int key_block_size; - - memcpy(seed + 0, tls->pending.client_random, 32); - memcpy(seed + 32, tls->pending.server_random, 32); - - tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len, - "master secret", seed, 64, - tls->pending.master_secret, 48); - - /* Directly generate the key block while we're at it */ - key_block_size = 0; + int key_block_size = 0; if (tls->pending.cipher_suite->encryption) key_block_size += 2 * @@ -1300,8 +1665,25 @@ tls_prf_get_bytes(tls, tls->pending.master_secret, 48, "key expansion", seed, 64, tls->pending.key_block, key_block_size); + explicit_bzero(seed, 64); +} + +void tls_generate_master_secret(struct l_tls *tls, + const uint8_t *pre_master_secret, + int pre_master_secret_len) +{ + uint8_t seed[64]; + memcpy(seed + 0, tls->pending.client_random, 32); + memcpy(seed + 32, tls->pending.server_random, 32); + + tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len, + "master secret", seed, 64, + tls->pending.master_secret, 48); explicit_bzero(seed, 64); + + /* Directly generate the key block while we're at it */ + tls_update_key_block(tls); } static void tls_get_handshake_hash(struct l_tls *tls, @@ -1370,7 +1752,7 @@ tls_tx_record(tls, TLS_CT_CHANGE_CIPHER_SPEC, &buf, 1); } -static size_t tls_verify_data_length(struct l_tls *tls, unsigned int index) +size_t tls_verify_data_length(struct l_tls *tls, unsigned int index) { /* * RFC 5246, Section 7.4.9: @@ -1383,7 +1765,34 @@ return maxsize(tls->cipher_suite[index]->verify_data_length, 12); } -static void tls_send_finished(struct l_tls *tls) +static bool tls_save_verify_data(struct l_tls *tls, bool txrx, + const uint8_t *vd, size_t vdl) +{ + uint8_t *buf; + + if (tls->server == txrx) { + if (vdl > sizeof(tls->renegotiation_info.server_verify_data)) + goto error; + + buf = tls->renegotiation_info.server_verify_data; + } else { + if (vdl > sizeof(tls->renegotiation_info.client_verify_data)) + goto error; + + buf = tls->renegotiation_info.client_verify_data; + } + + memcpy(buf, vd, vdl); + return true; + +error: + TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, + "tls->renegotiation_info.*verify too small for %s, " + "report an ell bug", tls->cipher_suite[txrx]->name); + return false; +} + +static bool tls_send_finished(struct l_tls *tls) { uint8_t buf[512]; uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; @@ -1406,9 +1815,14 @@ "client finished", seed, seed_len, ptr, vdl); + + if (!tls_save_verify_data(tls, 1, ptr, vdl)) + return false; + ptr += vdl; tls_tx_handshake(tls, TLS_FINISHED, buf, ptr - buf); + return true; } static bool tls_verify_finished(struct l_tls *tls, const uint8_t *received, @@ -1451,6 +1865,9 @@ return false; } + if (!tls_save_verify_data(tls, 0, received, vdl)) + return false; + return true; } @@ -1586,6 +2003,37 @@ return false; } +static void tls_server_resume_error(struct l_tls *tls) +{ + /* + * When Client Hello parameters don't match the parameters of the + * cached session that was requested by the client, we'll probably + * start and cache a new session. Even though RFC 5246 doesn't + * specifically mandate that the requested session be forgotten + * (there's no fatal Alert in that case), we overwrite the old + * session's entry in the cache with the new session's data to + * avoid keeping many sessions related to one client in the cache. + * In theory this allows an attacker to connect as a client and + * invalidate a legitimate client's session entry in our cache, + * DoSing the session resumption mechanism so that clients have + * to go through the full handshake. In practice there are many + * ways for an attacker to do that even without this. + * + * Our client mode only caches one last session anyway, other + * implementations may work that way too. + */ + memcpy(tls->session_id_replaced, tls->session_id, tls->session_id_size); + tls->session_id_size_replaced = tls->session_id_size; + + tls->session_id_size = 0; + tls->session_id_new = false; + l_free(l_steal_ptr(tls->session_peer_identity)); +} + +/* RFC 5746 */ +static const uint8_t tls_empty_renegotiation_info_scsv[2] = { 0x00, 0xff }; +static const uint16_t tls_renegotiation_info_id = 0xff01; + static void tls_handle_client_hello(struct l_tls *tls, const uint8_t *buf, size_t len) { @@ -1596,6 +2044,10 @@ int i; struct l_queue *extensions_offered = NULL; enum l_tls_alert_desc alert_desc = TLS_ALERT_HANDSHAKE_FAIL; + bool resuming = false; + _auto_(l_free) char *session_id_str = NULL; + struct tls_cipher_suite *backup_suite = NULL; + struct tls_compression_method *backup_cm = NULL; /* Do we have enough for ProtocolVersion + Random + SessionID size? */ if (len < 2 + 32 + 1) @@ -1605,6 +2057,9 @@ session_id_size = buf[34]; len -= 35; + if (unlikely(session_id_size > 32)) + goto decode_error; + /* * Do we have enough to hold the actual session ID + 2 byte field for * cipher_suite len + minimum of a single cipher suite identifier @@ -1637,6 +2092,21 @@ len -= compression_methods_size; + if (session_id_size && tls->session_settings && + tls_load_cached_server_session(tls, buf + 35, + session_id_size)) { + /* + * Attempt a session resumption but note later checks may + * spoil this. + */ + resuming = true; + session_id_str = l_util_hexstring(tls->session_id, + tls->session_id_size); + } + + if (tls->pending_destroy) + return; + extensions_offered = l_queue_new(); if (!tls_handle_hello_extensions(tls, compression_methods + @@ -1644,13 +2114,6 @@ len, extensions_offered)) goto cleanup; - /* - * Note: if the client is supplying a SessionID we know it is false - * because our server implementation never generates any SessionIDs - * yet so either the client is attempting something strange or was - * trying to connect somewhere else. We might want to throw an error. - */ - /* Save client_version for Premaster Secret verification */ tls->client_version = l_get_be16(buf); @@ -1679,6 +2142,30 @@ goto cleanup; } + if (!tls->renegotiation_info.secure_renegotiation || tls->ready) { + for (i = 0; i < cipher_suites_size; i += 2) + if (l_get_be16(cipher_suites + i) == l_get_be16( + tls_empty_renegotiation_info_scsv)) + break; + + if (i < cipher_suites_size) { + if (unlikely(tls->ready)) { + TLS_DISCONNECT(TLS_ALERT_ILLEGAL_PARAM, 0, + "Empty renegotiation_info in " + "renegotiation Client Hello"); + goto cleanup; + } + + /* + * RFC 5746 Section 3.6, act as if we had received an + * empty renegotiation_info extension. + */ + tls->renegotiation_info.secure_renegotiation = true; + l_queue_push_tail(extensions_offered, L_UINT_TO_PTR( + tls_renegotiation_info_id)); + } + } + /* Select a cipher suite according to client's preference list */ while (cipher_suites_size) { struct tls_cipher_suite *suite = @@ -1705,6 +2192,16 @@ alert_desc = TLS_ALERT_INSUFFICIENT_SECURITY; TLS_DEBUG("non-fatal: Cipher suite %s disallowed " "by config", suite->name); + } else if (resuming && memcmp(tls->session_cipher_suite_id, + suite->id, 2)) { + /* + * For now skip this cipher suite because we're trying + * to find the one from the cached session state. But + * keep it as a backup in case we end up starting a new + * session. + */ + if (!backup_suite) + backup_suite = suite; } else { tls->pending.cipher_suite = suite; break; @@ -1714,7 +2211,15 @@ cipher_suites_size -= 2; } - if (!cipher_suites_size) { + if (unlikely(!cipher_suites_size && backup_suite)) { + TLS_DEBUG("Cached session %s's cipher suite %04x " + "unavailable, will start a new session", + session_id_str, + l_get_be16(tls->session_cipher_suite_id)); + tls->pending.cipher_suite = backup_suite; + resuming = false; + tls_server_resume_error(tls); + } else if (unlikely(!cipher_suites_size)) { TLS_DISCONNECT(alert_desc, 0, "No common cipher suites matching negotiated " "TLS version and our certificate's key type"); @@ -1727,35 +2232,90 @@ goto cleanup; } - TLS_DEBUG("Negotiated %s", tls->pending.cipher_suite->name); - /* Select a compression method */ /* CompressionMethod.null must be present in the vector */ while (compression_methods_size) { - tls->pending.compression_method = + struct tls_compression_method *cm = tls_find_compression_method(*compression_methods); - if (tls->pending.compression_method) + if (!cm) + TLS_DEBUG("non-fatal: Compression %02x unknown", + *compression_methods); + else if (resuming && *compression_methods != + tls->session_compression_method_id) { + /* + * For now skip this compression method because we're + * trying to find the one from the cached session state. + * But keep it as a backup in case we end up starting + * a new * session. + */ + if (!backup_cm) + backup_cm = cm; + } else { + tls->pending.compression_method = cm; break; + } compression_methods++; compression_methods_size--; } - if (!compression_methods_size) { + if (unlikely(!compression_methods_size && backup_cm)) { + TLS_DEBUG("Cached session %s's compression method %02x " + "unavailable, will start a new session", + session_id_str, + tls->session_compression_method_id); + tls->pending.compression_method = backup_cm; + + if (backup_suite) + tls->pending.cipher_suite = backup_suite; + + resuming = false; + tls_server_resume_error(tls); + } else if (unlikely(!compression_methods_size)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, "No common compression methods"); goto cleanup; } + if (resuming) + TLS_DEBUG("Negotiated resumption of cached session %s", + session_id_str); + + TLS_DEBUG("Negotiated %s", tls->pending.cipher_suite->name); TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name); + if (!resuming && tls->session_settings) { + tls->session_id_new = true; + tls->session_id_size = 32; + l_getrandom(tls->session_id, 32); + } + if (!tls_send_server_hello(tls, extensions_offered)) goto cleanup; l_queue_destroy(extensions_offered, NULL); + if (resuming) { + const char *error; + + tls_update_key_block(tls); + tls_send_change_cipher_spec(tls); + + if (!tls_change_cipher_spec(tls, 1, &error)) { + TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, + "change_cipher_spec: %s", error); + return; + } + + if (!tls_send_finished(tls)) + return; + + TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); + return; + } + if (tls->pending.cipher_suite->signature && tls->cert) if (!tls_send_certificate(tls)) return; @@ -1796,11 +2356,14 @@ int i; struct l_queue *extensions_seen; bool result; + uint16_t version; + bool resuming = false; /* Do we have enough for ProtocolVersion + Random + SessionID len ? */ if (len < 2 + 32 + 1) goto decode_error; + version = l_get_be16(buf); memcpy(tls->pending.server_random, buf + 2, 32); session_id_size = buf[34]; len -= 35; @@ -1814,6 +2377,34 @@ compression_method_id = buf[35 + session_id_size + 2]; len -= session_id_size + 2 + 1; + if (session_id_size > 32) + goto decode_error; + + if (tls->session_id_size) { + _auto_(l_free) char *session_id_str = + l_util_hexstring(tls->session_id, tls->session_id_size); + + if (session_id_size == tls->session_id_size && + !memcmp(buf + 35, tls->session_id, + session_id_size)) { + TLS_DEBUG("Negotiated resumption of cached session %s", + session_id_str); + resuming = true; + } else { + TLS_DEBUG("Server decided not to resume cached session " + "%s, sent %s session ID", + session_id_str, + session_id_size ? "a new" : "no"); + tls->session_id_size = 0; + } + } + + if (session_id_size && !resuming && tls->session_settings) { + tls->session_id_new = true; + tls->session_id_size = session_id_size; + memcpy(tls->session_id, buf + 35, session_id_size); + } + extensions_seen = l_queue_new(); result = tls_handle_hello_extensions(tls, buf + 38 + session_id_size, len, extensions_seen); @@ -1822,18 +2413,16 @@ if (!result) return; - tls->negotiated_version = l_get_be16(buf); - - if (tls->negotiated_version < tls->min_version || - tls->negotiated_version > tls->max_version) { - TLS_DISCONNECT(tls->negotiated_version < tls->min_version ? + if (version < tls->min_version || version > tls->max_version) { + TLS_DISCONNECT(version < tls->min_version ? TLS_ALERT_PROTOCOL_VERSION : TLS_ALERT_ILLEGAL_PARAM, 0, - "Unsupported version %02x", - tls->negotiated_version); + "Unsupported version %02x", version); return; } + tls->negotiated_version = version; + /* Stop maintaining handshake message hashes other than MD1 and SHA. */ if (tls->negotiated_version < L_TLS_V12) for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) @@ -1889,7 +2478,30 @@ TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name); - if (tls->pending.cipher_suite->signature) + if (resuming) { + /* + * Now that we've validated the Server Hello parameters and + * know that they're supported by this version of ell and + * consistent with the current configuration, ensure that + * they're identical with the ones in the cached session + * being resumed. This serves as a sanity check for + * rare situations like a corrupt session cache file or + * a file written by a newer ell version. + */ + if (tls->negotiated_version != tls->client_version || + memcmp(cipher_suite_id, + tls->session_cipher_suite_id, 2) || + compression_method_id != + tls->session_compression_method_id) { + TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, + "Session parameters don't match"); + return; + } + + tls_update_key_block(tls); + + TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); + } else if (tls->pending.cipher_suite->signature) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE); else TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); @@ -2177,7 +2789,8 @@ return; } - tls_send_finished(tls); + if (!tls_send_finished(tls)) + return; TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); } @@ -2350,15 +2963,95 @@ static void tls_finished(struct l_tls *tls) { + _auto_(l_free) char *peer_cert_identity = NULL; char *peer_identity = NULL; - - if (tls->peer_authenticated) { - peer_identity = tls_get_peer_identity_str(tls->peer_cert); - if (!peer_identity) { + uint64_t peer_cert_expiry; + bool resuming = tls->session_id_size && !tls->session_id_new; + bool session_update = false; + bool renegotiation = tls->ready; + + if (tls->peer_authenticated && !resuming) { + peer_cert_identity = tls_get_peer_identity_str(tls->peer_cert); + if (!peer_cert_identity) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "tls_get_peer_identity_str failed"); return; } + + peer_identity = peer_cert_identity; + + if (tls->session_id_new && + !l_cert_get_valid_times(tls->peer_cert, NULL, + &peer_cert_expiry)) { + TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, + "l_cert_get_valid_times failed"); + return; + } + } else if (tls->peer_authenticated && resuming) + peer_identity = tls->session_peer_identity; + + if (tls->session_settings && tls->session_id_new) { + _auto_(l_free) char *session_id_str = + l_util_hexstring(tls->session_id, tls->session_id_size); + uint64_t expiry = tls->session_lifetime ? + time_realtime_now() + tls->session_lifetime : 0; + const char *group_name = + tls_get_cache_group_name(tls, tls->session_id, + tls->session_id_size); + + if (tls->peer_authenticated && + (!expiry || peer_cert_expiry < expiry)) + expiry = peer_cert_expiry; + + if (!tls->server) + l_settings_set_bytes(tls->session_settings, group_name, + "SessionID", tls->session_id, + tls->session_id_size); + + l_settings_set_bytes(tls->session_settings, group_name, + "SessionMasterSecret", + tls->pending.master_secret, 48); + l_settings_set_int(tls->session_settings, group_name, + "SessionVersion", + tls->negotiated_version); + l_settings_set_bytes(tls->session_settings, group_name, + "SessionCipherSuite", + tls->pending.cipher_suite->id, 2); + l_settings_set_uint(tls->session_settings, group_name, + "SessionCompressionMethod", + tls->pending.compression_method->id); + + if (expiry) + l_settings_set_uint64(tls->session_settings, + group_name, + "SessionExpiryTime", expiry); + else + /* We may be overwriting an older session's data */ + l_settings_remove_key(tls->session_settings, + group_name, + "SessionExpiryTime"); + + if (tls->peer_authenticated) + l_settings_set_string(tls->session_settings, + group_name, + "SessionPeerIdentity", + peer_identity); + else + /* We may be overwriting an older session's data */ + l_settings_remove_key(tls->session_settings, + group_name, + "SessionPeerIdentity"); + + TLS_DEBUG("Saving new session %s to cache", session_id_str); + session_update = true; + + if (tls->session_id_size_replaced) { + tls_forget_cached_session(tls, NULL, + tls->session_id_replaced, + tls->session_id_size_replaced, + false); + tls->session_id_size_replaced = 0; + } } /* Free up the resources used in the handshake */ @@ -2366,11 +3059,22 @@ TLS_SET_STATE(TLS_HANDSHAKE_DONE); tls->ready = true; + tls->session_resumed = resuming; + + if (session_update && tls->session_update_cb) { + tls->in_callback = true; + tls->session_update_cb(tls->session_update_user_data); + tls->in_callback = false; - tls->in_callback = true; - tls->ready_handle(peer_identity, tls->user_data); - tls->in_callback = false; - l_free(peer_identity); + if (tls->pending_destroy) + return; + } + + if (!renegotiation) { + tls->in_callback = true; + tls->ready_handle(peer_identity, tls->user_data); + tls->in_callback = false; + } tls_cleanup_handshake(tls); } @@ -2378,6 +3082,8 @@ static void tls_handle_handshake(struct l_tls *tls, int type, const uint8_t *buf, size_t len) { + bool resuming; + TLS_DEBUG("Handling a %s of %zi bytes", tls_handshake_type_to_str(type), len); @@ -2401,7 +3107,16 @@ * and "MAY be ignored by the client if it does not wish to * renegotiate a session". */ + if (tls->state != TLS_HANDSHAKE_DONE) { + TLS_DEBUG("Message invalid in state %s", + tls_handshake_state_to_str(tls->state)); + break; + } + if (!tls_send_client_hello(tls)) + break; + + TLS_SET_STATE(TLS_HANDSHAKE_WAIT_HELLO); break; case TLS_CLIENT_HELLO: @@ -2565,7 +3280,9 @@ if (!tls_verify_finished(tls, buf, len)) break; - if (tls->server) { + resuming = tls->session_id_size && !tls->session_id_new; + + if ((tls->server && !resuming) || (!tls->server && resuming)) { const char *error; tls_send_change_cipher_spec(tls); @@ -2575,13 +3292,15 @@ error); break; } - tls_send_finished(tls); + + if (!tls_send_finished(tls)) + break; } /* - * On the client, the server's certificate is now verified - * regardless of the key exchange method, based on the - * following logic: + * When starting a new session on the client, the server's + * certificate is now verified regardless of the key exchange + * method, based on the following logic: * * - tls->ca_certs is non-NULL so tls_handle_certificate * (always called on the client) must have veritifed the @@ -2606,9 +3325,14 @@ * able to sign the client random together with the * ServerKeyExchange parameters using its certified key * pair. + * + * If we're resuming a cached session, we have authenticated + * this peer before and the successful decryption of this + * message confirms their identity hasn't changed. */ - if (!tls->server && tls->cipher_suite[0]->signature && - tls->ca_certs) + if (tls->cipher_suite[0]->signature && + ((!tls->server && !resuming && tls->ca_certs) || + (resuming && tls->session_peer_identity))) tls->peer_authenticated = true; tls_finished(tls); @@ -2643,6 +3367,7 @@ tls->cipher_suite_pref_list = tls_cipher_suite_pref; tls->min_version = TLS_MIN_VERSION; tls->max_version = TLS_MAX_VERSION; + tls->session_lifetime = 24 * 3600 * L_USEC_PER_SEC; /* If we're the server wait for the Client Hello already */ if (tls->server) @@ -2669,6 +3394,7 @@ l_tls_set_auth_data(tls, NULL, NULL); l_tls_set_domain_mask(tls, NULL); l_tls_set_cert_dump_path(tls, NULL); + l_tls_set_session_cache(tls, NULL, NULL, 0, 0, NULL, NULL); tls_reset_handshake(tls); tls_cleanup_handshake(tls); @@ -2870,6 +3596,27 @@ if (!tls_init_handshake_hash(tls)) return false; + /* + * If we're going to try resuming a cached session, send the Client + * Hello with the version we think is supported. + * + * RFC5246 Appendix E.1: + * "Whenever a client already knows the highest protocol version known + * to a server (for example, when resuming a session), it SHOULD + * initiate the connection in that native protocol." + * + * Don't directly set tls->{min,max}_version as that would make the + * handshake fail if the server decides to start a new session with + * a new version instead of resuming, which it is allowed to do. + */ + tls->client_version = tls->max_version; + tls_load_cached_client_session(tls); + + if (tls->pending_destroy) { + l_tls_free(tls); + return false; + } + if (!tls_send_client_hello(tls)) return false; @@ -3042,6 +3789,68 @@ tls->subject_mask = l_strv_copy(mask); } +/** + * l_tls_set_session_cache: + * @tls: TLS object being configured + * @settings: l_settings object to read and write session data from/to or + * NULL to disable caching session states. The object must remain valid + * until this method is called with a different value. + * @group_prefix: prefix to build group names inside @settings. Note: + * existing settings in groups starting with the prefix may be + * overwritten or removed. + * @lifetime: a CLOCK_REALTIME-based microsecond resolution lifetime for + * cached sessions. The RFC recommends 24 hours. + * @max_sessions: limit on the number of sessions in the cache, or 0 for + * unlimited. Ignored in client mode. + * @update_cb: a callback to be invoked whenever the settings in @settings + * have been updated and may need to be written to persistent storage if + * desired, or NULL. + * @user_data: user data pointer to pass to @update_cb. + * + * Enables caching and resuming session states as described in RFC 5246 for + * faster setup. l_tls will maintain the required session state data in + * @settings including removing expired or erroneous sessions. + * + * A client's cache contains at most one session state since the client + * must request one specific Session ID from the server when resuming a + * session. The resumption will only work while the state is cached by + * both the server and the client so clients should keep separate @settings + * objects or use separate groups inside one object for every discrete + * server they may want to connect to. + * + * Multiple l_tls clients connecting to the same server can share one cache + * to allow reusing an established session that is still active (actual + * concurrency is not supported as there's no locking.) + */ +LIB_EXPORT void l_tls_set_session_cache(struct l_tls *tls, + struct l_settings *settings, + const char *group_prefix, + uint64_t lifetime, + unsigned int max_sessions, + l_tls_session_update_cb_t update_cb, + void *user_data) +{ + if (unlikely(!tls)) + return; + + tls->session_settings = settings; + tls->session_lifetime = lifetime; + tls->session_count_max = max_sessions; + tls->session_update_cb = update_cb; + tls->session_update_user_data = user_data; + + l_free(tls->session_prefix); + tls->session_prefix = l_strdup(group_prefix); +} + +LIB_EXPORT bool l_tls_get_session_resumed(struct l_tls *tls) +{ + if (unlikely(!tls || !tls->ready)) + return false; + + return tls->session_resumed; +} + LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc) { switch (desc) { diff -Nru bluez-5.66/ell/tls-extensions.c bluez-5.68/ell/tls-extensions.c --- bluez-5.66/ell/tls-extensions.c 2020-09-05 07:26:19.000000000 +0000 +++ bluez-5.68/ell/tls-extensions.c 2022-11-18 09:08:38.000000000 +0000 @@ -31,6 +31,13 @@ #include "cert.h" #include "tls-private.h" +/* Most extensions are not used when resuming a cached session */ +#define SKIP_ON_RESUMPTION() \ + do { \ + if (tls->session_id_size && !tls->session_id_new) \ + return -ENOMSG; \ + } while (0); + /* RFC 7919, Section A.1 */ static const uint8_t tls_ffdhe2048_prime[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xad, 0xf8, 0x54, 0x58, @@ -560,6 +567,8 @@ static ssize_t tls_ec_point_formats_server_write(struct l_tls *tls, uint8_t *buf, size_t len) { + SKIP_ON_RESUMPTION(); /* RFC 4492 Section 4 */ + if (len < 2) return -ENOMEM; @@ -785,7 +794,7 @@ return ptr + len - buf; } -/* RFC 5462, Section 7.4.1.4.1 */ +/* RFC 5246, Section 7.4.1.4.1 */ static ssize_t tls_signature_algorithms_client_write(struct l_tls *tls, uint8_t *buf, size_t len) { @@ -841,6 +850,130 @@ return true; } +/* RFC 5746, Section 3.2 */ +static ssize_t tls_renegotiation_info_client_write(struct l_tls *tls, + uint8_t *buf, size_t len) +{ + /* + * Section 4.2 implies we should send the client_verify_data on + * renegotiation even if .secure_renegotiation is false, if we want + * to go through with the renegotiation in the first place. + */ + if (tls->ready) { + size_t vdl = tls_verify_data_length(tls, 1); + + if (len < vdl + 1) + return -ENOMEM; + + buf[0] = vdl; + memcpy(buf + 1, tls->renegotiation_info.client_verify_data, + vdl); + return 1 + vdl; + } else { + if (len < 1) + return -ENOMEM; + + buf[0] = 0x00; /* Empty "renegotiated_connection" */ + return 1; + } +} + +static ssize_t tls_renegotiation_info_server_write(struct l_tls *tls, + uint8_t *buf, size_t len) +{ + if (tls->ready) { + size_t rx_vdl = tls_verify_data_length(tls, 0); + size_t tx_vdl = tls_verify_data_length(tls, 1); + + if (len < rx_vdl + tx_vdl + 1) + return -ENOMEM; + + buf[0] = rx_vdl + tx_vdl; + memcpy(buf + 1, + tls->renegotiation_info.client_verify_data, rx_vdl); + memcpy(buf + 1 + rx_vdl, + tls->renegotiation_info.server_verify_data, tx_vdl); + return 1 + rx_vdl + tx_vdl; + } else { + if (len < 1) + return -ENOMEM; + + buf[0] = 0x00; /* Empty "renegotiated_connection" */ + return 1; + } +} + +static bool tls_renegotiation_info_client_handle(struct l_tls *tls, + const uint8_t *buf, size_t len) +{ + if (tls->ready) { + size_t vdl = tls_verify_data_length(tls, 0); + + return len >= 1 + vdl && + tls->renegotiation_info.secure_renegotiation && + !memcmp(tls->renegotiation_info.client_verify_data, + buf + 1, vdl); + } + + /* + * RFC 5746 Section 3.6: "The server MUST then verify that the length + * of the "renegotiated_connection" field is zero, ..." + */ + if (len < 1 || buf[0] != 0x00) + return false; + + tls->renegotiation_info.secure_renegotiation = true; + return true; +} + +static bool tls_renegotiation_info_server_handle(struct l_tls *tls, + const uint8_t *buf, size_t len) +{ + if (tls->ready) { + size_t rx_vdl = tls_verify_data_length(tls, 0); + size_t tx_vdl = tls_verify_data_length(tls, 1); + + return len >= 1 + rx_vdl + tx_vdl && + tls->renegotiation_info.secure_renegotiation && + !memcmp(tls->renegotiation_info.client_verify_data, + buf + 1, tx_vdl) && + !memcmp(tls->renegotiation_info.server_verify_data, + buf + 1 + tx_vdl, rx_vdl); + } + + /* + * RFC 5746 Section 3.4: "The client MUST then verify that the length + * of the "renegotiated_connection" field is zero, ..." + */ + if (len < 1 || buf[0] != 0x00) + return false; + + tls->renegotiation_info.secure_renegotiation = true; + return true; +} + +static bool tls_renegotiation_info_absent(struct l_tls *tls) +{ + /* + * RFC 5746 Section 4.2: "It is possible that un-upgraded servers + * will request that the client renegotiate. It is RECOMMENDED + * that clients refuse this renegotiation request." and Section 4.4: + * "It is RECOMMENDED that servers not permit legacy renegotiation." + * + * This may need to be made configurable, for now follow the + * recommendation and don't renegotiate. + */ + if (tls->ready) + return false; + + /* + * The normal policy otherwise is that the extension must be + * present in renegotation if the previous Client or Server Hello + * did include this extension, or the SCSV in the Client Hello case. + */ + return !tls->ready || !tls->renegotiation_info.secure_renegotiation; +} + const struct tls_hello_extension tls_extensions[] = { { "Supported Groups", "elliptic_curves", 10, @@ -864,6 +997,15 @@ tls_signature_algorithms_client_absent, NULL, NULL, NULL, }, + { + "Secure Renegotiation", "renegotiation_info", 0xff01, + tls_renegotiation_info_client_write, + tls_renegotiation_info_client_handle, + tls_renegotiation_info_absent, + tls_renegotiation_info_server_write, + tls_renegotiation_info_server_handle, + tls_renegotiation_info_absent, + }, {} }; diff -Nru bluez-5.66/ell/tls.h bluez-5.68/ell/tls.h --- bluez-5.66/ell/tls.h 2022-06-04 19:58:23.000000000 +0000 +++ bluez-5.68/ell/tls.h 2023-01-23 18:26:15.000000000 +0000 @@ -36,6 +36,7 @@ struct l_key; struct l_certchain; struct l_queue; +struct l_settings; enum l_tls_alert_desc { TLS_ALERT_CLOSE_NOTIFY = 0, @@ -72,6 +73,7 @@ bool remote, void *user_data); typedef void (*l_tls_debug_cb_t)(const char *str, void *user_data); typedef void (*l_tls_destroy_cb_t)(void *user_data); +typedef void (*l_tls_session_update_cb_t)(void *user_data); /* * app_data_handler gets called with newly received decrypted data. @@ -127,6 +129,13 @@ void l_tls_set_domain_mask(struct l_tls *tls, char **mask); +void l_tls_set_session_cache(struct l_tls *tls, struct l_settings *settings, + const char *group_prefix, uint64_t lifetime, + unsigned int max_sessions, + l_tls_session_update_cb_t update_cb, + void *user_data); +bool l_tls_get_session_resumed(struct l_tls *tls); + const char *l_tls_alert_to_str(enum l_tls_alert_desc desc); enum l_checksum_type; diff -Nru bluez-5.66/ell/tls-private.h bluez-5.68/ell/tls-private.h --- bluez-5.66/ell/tls-private.h 2022-09-07 18:21:45.000000000 +0000 +++ bluez-5.68/ell/tls-private.h 2023-01-23 18:26:15.000000000 +0000 @@ -218,6 +218,13 @@ struct tls_cipher_suite **cipher_suite_pref_list; + struct l_settings *session_settings; + char *session_prefix; + uint64_t session_lifetime; + unsigned int session_count_max; + l_tls_session_update_cb_t session_update_cb; + void *session_update_user_data; + bool in_callback; bool pending_destroy; @@ -251,6 +258,23 @@ const struct tls_named_group *negotiated_curve; const struct tls_named_group *negotiated_ff_group; + uint8_t session_id[32]; + size_t session_id_size; + uint8_t session_id_replaced[32]; + size_t session_id_size_replaced; + bool session_id_new; + uint8_t session_cipher_suite_id[2]; + uint8_t session_compression_method_id; + char *session_peer_identity; + bool session_resumed; + + struct { + bool secure_renegotiation; + /* Max .verify_data_length over supported cipher suites */ + uint8_t client_verify_data[12]; + uint8_t server_verify_data[12]; + } renegotiation_info; + /* SecurityParameters current and pending */ struct { @@ -325,6 +349,8 @@ const uint8_t *pre_master_secret, int pre_master_secret_len); +size_t tls_verify_data_length(struct l_tls *tls, unsigned int index); + const struct tls_named_group *tls_find_group(uint16_t id); const struct tls_named_group *tls_find_ff_group(const uint8_t *prime, size_t prime_len, diff -Nru bluez-5.66/ell/tls-suites.c bluez-5.68/ell/tls-suites.c --- bluez-5.66/ell/tls-suites.c 2022-09-07 18:21:45.000000000 +0000 +++ bluez-5.68/ell/tls-suites.c 2022-11-18 09:08:38.000000000 +0000 @@ -352,8 +352,8 @@ } /* Must match the version in tls_send_client_hello */ - pre_master_secret[0] = (uint8_t) (tls->max_version >> 8); - pre_master_secret[1] = (uint8_t) (tls->max_version >> 0); + pre_master_secret[0] = (uint8_t) (tls->client_version >> 8); + pre_master_secret[1] = (uint8_t) (tls->client_version >> 0); l_getrandom(pre_master_secret + 2, 46); diff -Nru bluez-5.66/ell/util.c bluez-5.68/ell/util.c --- bluez-5.66/ell/util.c 2021-08-01 19:49:37.000000000 +0000 +++ bluez-5.68/ell/util.c 2023-01-23 18:26:15.000000000 +0000 @@ -376,6 +376,36 @@ return str; } +static char *hexstringv_common(const struct iovec *iov, size_t n_iov, + const char hexdigits[static 16]) +{ + char *str; + size_t i, j, c; + size_t len; + + if (unlikely(!iov || !n_iov)) + return NULL; + + for (i = 0, len = 0; i < n_iov; i++) + len += iov[i].iov_len; + + str = l_malloc(len * 2 + 1); + c = 0; + + for (i = 0; i < n_iov; i++) { + const uint8_t *buf = iov[i].iov_base; + + for (j = 0; j < iov[i].iov_len; j++) { + str[c++] = hexdigits[buf[j] >> 4]; + str[c++] = hexdigits[buf[j] & 0xf]; + } + } + + str[len * 2] = '\0'; + + return str; +} + /** * l_util_hexstring: * @buf: buffer pointer @@ -407,6 +437,36 @@ } /** + * l_util_hexstringv: + * @iov: iovec + * @n_iov: length of the iovec + * + * Returns: a newly allocated hex string. Note that the string will contain + * lower case hex digits a-f. If you require upper case hex digits, use + * @l_util_hexstringv_upper + **/ +LIB_EXPORT char *l_util_hexstringv(const struct iovec *iov, size_t n_iov) +{ + static const char hexdigits[] = "0123456789abcdef"; + return hexstringv_common(iov, n_iov, hexdigits); +} + +/** + * l_util_hexstringv_upper: + * @iov: iovec + * @n_iov: length of the iovec + * + * Returns: a newly allocated hex string. Note that the string will contain + * upper case hex digits a-f. If you require lower case hex digits, use + * @l_util_hexstringv + **/ +LIB_EXPORT char *l_util_hexstringv_upper(const struct iovec *iov, size_t n_iov) +{ + static const char hexdigits[] = "0123456789ABCDEF"; + return hexstringv_common(iov, n_iov, hexdigits); +} + +/** * l_util_from_hexstring: * @str: Null-terminated string containing the hex-encoded bytes * @out_len: Number of bytes decoded diff -Nru bluez-5.66/ell/util.h bluez-5.68/ell/util.h --- bluez-5.66/ell/util.h 2022-07-15 16:22:44.000000000 +0000 +++ bluez-5.68/ell/util.h 2023-01-23 18:26:15.000000000 +0000 @@ -282,6 +282,8 @@ char *l_util_hexstring(const void *buf, size_t len); char *l_util_hexstring_upper(const void *buf, size_t len); +char *l_util_hexstringv(const struct iovec *iov, size_t n_iov); +char *l_util_hexstringv_upper(const struct iovec *iov, size_t n_iov); unsigned char *l_util_from_hexstring(const char *str, size_t *out_len); typedef void (*l_util_hexdump_func_t) (const char *str, void *user_data); diff -Nru bluez-5.66/emulator/btdev.c bluez-5.68/emulator/btdev.c --- bluez-5.66/emulator/btdev.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/emulator/btdev.c 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -41,6 +42,7 @@ #define RL_SIZE 16 #define CIS_SIZE 3 #define BIS_SIZE 3 +#define CIG_SIZE 3 #define has_bredr(btdev) (!((btdev)->features[4] & 0x20)) #define has_le(btdev) (!!((btdev)->features[4] & 0x40)) @@ -100,6 +102,12 @@ unsigned int id; }; +struct le_cig { + struct bt_hci_cmd_le_set_cig_params params; + struct bt_hci_cis_params cis[CIS_SIZE]; + bool activated; +} __attribute__ ((packed)); + struct btdev { enum btdev_type type; uint16_t id; @@ -203,10 +211,7 @@ uint16_t le_pa_sync_handle; uint8_t big_handle; uint8_t le_ltk[16]; - struct { - struct bt_hci_cmd_le_set_cig_params params; - struct bt_hci_cis_params cis[CIS_SIZE]; - } __attribute__ ((packed)) le_cig; + struct le_cig le_cig[CIG_SIZE]; uint8_t le_iso_path[2]; /* Real time length of AL array */ @@ -745,7 +750,9 @@ return 0; } - disconnect_complete(dev, conn->handle, BT_HCI_ERR_SUCCESS, cmd->reason); + /* Local host has different reason (Core v5.3 Vol 4 Part E Sec 7.1.6) */ + disconnect_complete(dev, conn->handle, BT_HCI_ERR_SUCCESS, + BT_HCI_ERR_LOCAL_HOST_TERM); if (conn->link) disconnect_complete(conn->link->dev, conn->link->handle, @@ -5756,6 +5763,36 @@ return -ENOTSUP; } +static int find_cig(struct btdev *dev, uint8_t cig_id) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dev->le_cig); ++i) + if (dev->le_cig[i].params.cig_id == cig_id) + return i; + return -1; +} + +static uint16_t make_cis_handle(uint8_t cig_idx, uint8_t cis_idx) +{ + /* Put CIG+CIS idxs to handle so don't need to track separately */ + return ISO_HANDLE + cig_idx*CIS_SIZE + cis_idx; +} + +static int parse_cis_handle(uint16_t handle, int *cis_idx) +{ + int cig_idx; + + if (handle < ISO_HANDLE || handle >= ISO_HANDLE + CIS_SIZE*CIG_SIZE) + return -1; + + cig_idx = (handle - ISO_HANDLE) / CIS_SIZE; + if (cis_idx) + *cis_idx = (handle - ISO_HANDLE) % CIS_SIZE; + + return cig_idx; +} + static int cmd_set_cig_params(struct btdev *dev, const void *data, uint8_t len) { @@ -5765,12 +5802,13 @@ uint16_t handle[CIS_SIZE]; } __attribute__ ((packed)) rsp; int i = 0; + int cig_idx; uint32_t interval; uint16_t latency; memset(&rsp, 0, sizeof(rsp)); - if (cmd->num_cis > ARRAY_SIZE(dev->le_cig.cis)) { + if (cmd->num_cis > ARRAY_SIZE(dev->le_cig[0].cis)) { rsp.params.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } @@ -5819,39 +5857,36 @@ goto done; } - if (dev->le_cig.params.cig_id != 0xff && - dev->le_cig.params.cig_id != cmd->cig_id) { - rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; + cig_idx = find_cig(dev, cmd->cig_id); + if (cig_idx < 0) + cig_idx = find_cig(dev, 0xff); + if (cig_idx < 0) { + rsp.params.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } - memcpy(&dev->le_cig, data, len); + /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 4, Part E + * page 2553 + * + * If the Host issues this command when the CIG is not in the + * configurable state, the Controller shall return the error + * code Command Disallowed (0x0C). + */ + if (dev->le_cig[cig_idx].activated) { + rsp.params.status = BT_HCI_ERR_COMMAND_DISALLOWED; + goto done; + } rsp.params.status = BT_HCI_ERR_SUCCESS; rsp.params.cig_id = cmd->cig_id; for (i = 0; i < cmd->num_cis; i++) { - struct btdev_conn *iso; - rsp.params.num_handles++; - rsp.handle[i] = cpu_to_le16(ISO_HANDLE + i); - - /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 4, Part E - * page 2553 - * - * If the Host issues this command when the CIG is not in the - * configurable state, the Controller shall return the error - * code Command Disallowed (0x0C). - */ - iso = queue_find(dev->conns, match_handle, - UINT_TO_PTR(cpu_to_le16(rsp.handle[i]))); - if (iso) { - rsp.params.status = BT_HCI_ERR_INVALID_PARAMETERS; - i = 0; - goto done; - } + rsp.handle[i] = cpu_to_le16(make_cis_handle(cig_idx, i)); } + memcpy(&dev->le_cig[cig_idx], data, len); + done: cmd_complete(dev, BT_HCI_CMD_LE_SET_CIG_PARAMS, &rsp, sizeof(rsp.params) + (i * sizeof(uint16_t))); @@ -5868,43 +5903,106 @@ static int cmd_create_cis(struct btdev *dev, const void *data, uint8_t len) { + const struct bt_hci_cmd_le_create_cis *cmd = data; + int i, j; + + for (i = 0; i < cmd->num_cis; i++) { + const struct bt_hci_cis *cis = &cmd->cis[i]; + struct btdev_conn *acl; + struct btdev_conn *iso; + int cig_idx, cis_idx; + + /* Check for errors (Core v5.3 Vol 4 Part E Sec. 7.8.99) */ + for (j = 0; j < i; j++) + if (cis->cis_handle == cmd->cis[j].cis_handle) + return -EINVAL; + + cig_idx = parse_cis_handle(le16_to_cpu(cis->cis_handle), + &cis_idx); + if (cig_idx < 0) + return -ENOENT; + if (cis_idx >= dev->le_cig[cig_idx].params.num_cis) + return -ENOENT; + + acl = queue_find(dev->conns, match_handle, + UINT_TO_PTR(le16_to_cpu(cis->acl_handle))); + if (!acl) + return -ENOENT; + + iso = queue_find(dev->conns, match_handle, + UINT_TO_PTR(le16_to_cpu(cis->cis_handle))); + if (iso) + return -EEXIST; + } + cmd_status(dev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_CREATE_CIS); return 0; } +static uint8_t le_cis_interval(uint8_t sdu_interval[3]) +{ + /* ISO Interval (slots of 1.25 ms) = SDU_Interval (us) */ + return get_le24(sdu_interval) / 1250; +} + +static uint32_t le_cis_latecy(uint8_t ft, uint8_t iso_interval, + uint8_t sdu_interval[3]) +{ + uint32_t latency; + uint32_t interval = get_le24(sdu_interval); + + /* Transport_Latency = FT × ISO_Interval - SDU_Interval */ + latency = ft * (iso_interval * 1250) - interval; + + return latency >= interval ? latency : interval; +} + static void le_cis_estabilished(struct btdev *dev, struct btdev_conn *conn, uint8_t status) { struct bt_hci_evt_le_cis_established evt; + int cig_idx, cis_idx = -1; memset(&evt, 0, sizeof(evt)); evt.status = status; evt.conn_handle = conn ? cpu_to_le16(conn->handle) : 0x0000; + cig_idx = conn ? parse_cis_handle(conn->link->handle, &cis_idx) : -1; + if (cig_idx < 0 && !evt.status) + evt.status = BT_HCI_ERR_UNSPECIFIED_ERROR; + if (!evt.status) { struct btdev *remote = conn->link->dev; + struct le_cig *le_cig = &remote->le_cig[cig_idx]; + + memset(evt.cig_sync_delay, 0, sizeof(evt.cig_sync_delay)); + memset(evt.cis_sync_delay, 0, sizeof(evt.cis_sync_delay)); - /* TODO: Figure out if these values makes sense */ - memcpy(evt.cig_sync_delay, remote->le_cig.params.c_interval, - sizeof(remote->le_cig.params.c_interval)); - memcpy(evt.cis_sync_delay, remote->le_cig.params.p_interval, - sizeof(remote->le_cig.params.p_interval)); - memcpy(evt.c_latency, &remote->le_cig.params.c_interval, - sizeof(remote->le_cig.params.c_interval)); - memcpy(evt.p_latency, &remote->le_cig.params.p_interval, - sizeof(remote->le_cig.params.p_interval)); - evt.c_phy = remote->le_cig.cis[0].c_phy; - evt.p_phy = remote->le_cig.cis[0].p_phy; + evt.c_phy = le_cig->cis[cis_idx].c_phy; + evt.p_phy = le_cig->cis[cis_idx].p_phy; evt.nse = 0x01; evt.c_bn = 0x01; evt.p_bn = 0x01; - evt.c_ft = 0x01; - evt.p_ft = 0x01; - evt.c_mtu = remote->le_cig.cis[0].c_sdu; - evt.p_mtu = remote->le_cig.cis[0].p_sdu; - evt.interval = remote->le_cig.params.c_latency; + evt.c_ft = 0x02; + evt.p_ft = 0x02; + evt.c_mtu = le_cig->cis[cis_idx].c_sdu; + evt.p_mtu = le_cig->cis[cis_idx].p_sdu; + evt.interval = le_cis_interval(le_cig->params.c_interval); + + /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 6, Part G + * page 3050: + * + * Transport_Latency_C_To_P = CIG_Sync_Delay + FT_C_To_P × + * ISO_Interval - SDU_Interval_C_To_P + * Transport_Latency_P_To_C = CIG_Sync_Delay + FT_P_To_C × + * ISO_Interval - SDU_Interval_P_To_C + */ + put_le24(le_cis_latecy(evt.c_ft, evt.interval, + le_cig->params.c_interval), evt.c_latency); + put_le24(le_cis_latecy(evt.p_ft, evt.interval, + le_cig->params.p_interval), evt.p_latency); } le_meta_event(dev, BT_HCI_EVT_LE_CIS_ESTABLISHED, &evt, sizeof(evt)); @@ -5925,9 +6023,20 @@ struct btdev_conn *acl; struct btdev_conn *iso; struct bt_hci_evt_le_cis_req evt; + struct le_cig *le_cig; + int cig_idx, cis_idx; + + cig_idx = parse_cis_handle(le16_to_cpu(cis->cis_handle), + &cis_idx); + if (cig_idx < 0) { + le_cis_estabilished(dev, NULL, + BT_HCI_ERR_UNKNOWN_CONN_ID); + break; + } + le_cig = &dev->le_cig[cig_idx]; acl = queue_find(dev->conns, match_handle, - UINT_TO_PTR(cpu_to_le16(cis->acl_handle))); + UINT_TO_PTR(le16_to_cpu(cis->acl_handle))); if (!acl) { le_cis_estabilished(dev, NULL, BT_HCI_ERR_UNKNOWN_CONN_ID); @@ -5935,9 +6044,9 @@ } iso = queue_find(dev->conns, match_handle, - UINT_TO_PTR(cpu_to_le16(cis->cis_handle))); + UINT_TO_PTR(le16_to_cpu(cis->cis_handle))); if (!iso) { - iso = conn_add_cis(acl, cpu_to_le16(cis->cis_handle)); + iso = conn_add_cis(acl, le16_to_cpu(cis->cis_handle)); if (!iso) { le_cis_estabilished(dev, NULL, BT_HCI_ERR_UNKNOWN_CONN_ID); @@ -5947,8 +6056,10 @@ evt.acl_handle = cpu_to_le16(acl->handle); evt.cis_handle = cpu_to_le16(iso->handle); - evt.cig_id = iso->dev->le_cig.params.cig_id; - evt.cis_id = iso->dev->le_cig.cis[0].cis_id; + evt.cig_id = le_cig->params.cig_id; + evt.cis_id = le_cig->cis[cis_idx].cis_id; + + le_cig->activated = true; le_meta_event(iso->link->dev, BT_HCI_EVT_LE_CIS_REQ, &evt, sizeof(evt)); @@ -5957,20 +6068,37 @@ return 0; } +static bool match_handle_cig_idx(const void *data, const void *match_data) +{ + const struct btdev_conn *conn = data; + int cig_idx = PTR_TO_INT(match_data); + + return cig_idx >= 0 && parse_cis_handle(conn->handle, NULL) == cig_idx; +} + static int cmd_remove_cig(struct btdev *dev, const void *data, uint8_t len) { const struct bt_hci_cmd_le_remove_cig *cmd = data; struct bt_hci_rsp_le_remove_cig rsp; + struct btdev_conn *conn = NULL; + int idx; - memset(&dev->le_cig, 0, sizeof(dev->le_cig)); memset(&rsp, 0, sizeof(rsp)); rsp.cig_id = cmd->cig_id; - if (dev->le_cig.params.cig_id == cmd->cig_id) + idx = find_cig(dev, cmd->cig_id); + conn = queue_find(dev->conns, match_handle_cig_idx, INT_TO_PTR(idx)); + + if (idx >= 0 && !conn) { + memset(&dev->le_cig[idx], 0, sizeof(struct le_cig)); + dev->le_cig[idx].params.cig_id = 0xff; rsp.status = BT_HCI_ERR_SUCCESS; - else + } else if (conn) { + rsp.status = BT_HCI_ERR_COMMAND_DISALLOWED; + } else { rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + } cmd_complete(dev, BT_HCI_CMD_LE_REMOVE_CIG, &rsp, sizeof(rsp)); @@ -6028,35 +6156,49 @@ const struct bt_hci_cmd_le_create_big *cmd = data; const struct bt_hci_bis *bis = &cmd->bis; int i; + struct bt_hci_evt_le_big_complete evt; + uint16_t *bis_handle; + uint8_t *pdu; + uint8_t pdu_len; + + pdu_len = sizeof(evt) + cmd->num_bis * sizeof(*bis_handle); + + pdu = malloc(pdu_len); + if (!pdu) + return -ENOMEM; + + bis_handle = (uint16_t *)(pdu + sizeof(evt)); + + memset(&evt, 0, sizeof(evt)); for (i = 0; i < cmd->num_bis; i++) { struct btdev_conn *conn; - struct { - struct bt_hci_evt_le_big_complete evt; - uint16_t handle; - } pdu; - - memset(&pdu, 0, sizeof(pdu)); - conn = conn_add_bis(dev, ISO_HANDLE, bis); + conn = conn_add_bis(dev, ISO_HANDLE + i, bis); if (!conn) { - pdu.evt.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; + evt.status = BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; goto done; } - pdu.evt.handle = cmd->handle; - pdu.evt.num_bis++; - pdu.evt.phy = bis->phy; - pdu.evt.max_pdu = bis->sdu; - memcpy(pdu.evt.sync_delay, bis->sdu_interval, 3); - memcpy(pdu.evt.latency, bis->sdu_interval, 3); - pdu.evt.interval = bis->latency / 1.25; - pdu.handle = cpu_to_le16(conn->handle); + *bis_handle = cpu_to_le16(conn->handle); + bis_handle++; + } + + evt.handle = cmd->handle; + evt.phy = bis->phy; + evt.max_pdu = bis->sdu; + memcpy(evt.sync_delay, bis->sdu_interval, 3); + memcpy(evt.latency, bis->sdu_interval, 3); + evt.interval = bis->latency / 1.25; + evt.num_bis = cmd->num_bis; done: - le_meta_event(dev, BT_HCI_EVT_LE_BIG_COMPLETE, &pdu, - sizeof(pdu)); - } + memcpy(pdu, &evt, sizeof(evt)); + + le_meta_event(dev, BT_HCI_EVT_LE_BIG_COMPLETE, pdu, + pdu_len); + + free(pdu); return 0; } @@ -6161,6 +6303,13 @@ dev->big_handle = cmd->handle; bis = conn->data; + if (bis->encryption != cmd->encryption) { + pdu.ev.status = BT_HCI_ERR_ENC_MODE_NOT_ACCEPTABLE; + le_meta_event(dev, BT_HCI_EVT_LE_BIG_SYNC_ESTABILISHED, &pdu, + sizeof(pdu.ev)); + return 0; + } + pdu.ev.handle = cmd->handle; memcpy(pdu.ev.latency, bis->sdu_interval, sizeof(pdu.ev.interval)); pdu.ev.nse = 0x01; @@ -6833,6 +6982,7 @@ { struct btdev *btdev; int index; + unsigned int i; btdev = malloc(sizeof(*btdev)); if (!btdev) @@ -6900,9 +7050,11 @@ btdev->iso_mtu = 251; btdev->iso_max_pkt = 1; - btdev->le_cig.params.cig_id = 0xff; btdev->big_handle = 0xff; + for (i = 0; i < ARRAY_SIZE(btdev->le_cig); ++i) + btdev->le_cig[i].params.cig_id = 0xff; + btdev->country_code = 0x00; index = add_btdev(btdev); @@ -7061,6 +7213,12 @@ case -EPERM: status = BT_HCI_ERR_COMMAND_DISALLOWED; break; + case -EEXIST: + status = BT_HCI_ERR_CONN_ALREADY_EXISTS; + break; + case -ENOENT: + status = BT_HCI_ERR_UNKNOWN_CONN_ID; + break; default: status = BT_HCI_ERR_UNSPECIFIED_ERROR; break; diff -Nru bluez-5.66/emulator/bthost.c bluez-5.68/emulator/bthost.c --- bluez-5.66/emulator/bthost.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/emulator/bthost.c 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -38,6 +39,8 @@ #define acl_flags(h) (h >> 12) #define iso_flags_pb(f) (f & 0x0003) +#define iso_flags_ts(f) ((f >> 2) & 0x0001) +#define iso_flags_pack(pb, ts) (((pb) & 0x03) | (((ts) & 0x01) << 2)) #define iso_data_len_pack(h, f) ((uint16_t) ((h) | ((f) << 14))) #define L2CAP_FEAT_FIXED_CHAN 0x00000080 @@ -224,6 +227,7 @@ void *cmd_complete_data; bthost_new_conn_cb new_conn_cb; void *new_conn_data; + bthost_accept_conn_cb accept_iso_cb; bthost_new_conn_cb new_iso_cb; void *new_iso_data; struct rfcomm_connection_data *rfcomm_conn_data; @@ -727,41 +731,58 @@ send_iov(bthost, handle, cid, iov, iovcnt); } -static void send_iso(struct bthost *bthost, uint16_t handle, +static void send_iso(struct bthost *bthost, uint16_t handle, bool ts, + uint16_t sn, uint32_t timestamp, const struct iovec *iov, int iovcnt) { struct bt_hci_iso_hdr iso_hdr; struct bt_hci_iso_data_start data_hdr; uint8_t pkt = BT_H4_ISO_PKT; - struct iovec pdu[3 + iovcnt]; + struct iovec pdu[4 + iovcnt]; + uint16_t flags, dlen; int i, len = 0; - static uint16_t sn; for (i = 0; i < iovcnt; i++) { - pdu[3 + i].iov_base = iov[i].iov_base; - pdu[3 + i].iov_len = iov[i].iov_len; + pdu[4 + i].iov_base = iov[i].iov_base; + pdu[4 + i].iov_len = iov[i].iov_len; len += iov[i].iov_len; } pdu[0].iov_base = &pkt; pdu[0].iov_len = sizeof(pkt); - iso_hdr.handle = acl_handle_pack(handle, 0x02); - iso_hdr.dlen = cpu_to_le16(len + sizeof(data_hdr)); + flags = iso_flags_pack(0x02, ts); + dlen = len + sizeof(data_hdr); + if (ts) + dlen += sizeof(timestamp); + + iso_hdr.handle = acl_handle_pack(handle, flags); + iso_hdr.dlen = cpu_to_le16(dlen); pdu[1].iov_base = &iso_hdr; pdu[1].iov_len = sizeof(iso_hdr); - data_hdr.sn = cpu_to_le16(sn++); + if (ts) { + timestamp = cpu_to_le32(timestamp); + + pdu[2].iov_base = ×tamp; + pdu[2].iov_len = sizeof(timestamp); + } else { + pdu[2].iov_base = NULL; + pdu[2].iov_len = 0; + } + + data_hdr.sn = cpu_to_le16(sn); data_hdr.slen = cpu_to_le16(iso_data_len_pack(len, 0)); - pdu[2].iov_base = &data_hdr; - pdu[2].iov_len = sizeof(data_hdr); + pdu[3].iov_base = &data_hdr; + pdu[3].iov_len = sizeof(data_hdr); - send_packet(bthost, pdu, 3 + iovcnt); + send_packet(bthost, pdu, 4 + iovcnt); } -void bthost_send_iso(struct bthost *bthost, uint16_t handle, +void bthost_send_iso(struct bthost *bthost, uint16_t handle, bool ts, + uint16_t sn, uint32_t timestamp, const struct iovec *iov, int iovcnt) { struct btconn *conn; @@ -770,7 +791,7 @@ if (!conn) return; - send_iso(bthost, handle, iov, iovcnt); + send_iso(bthost, handle, ts, sn, timestamp, iov, iovcnt); } bool bthost_l2cap_req(struct bthost *bthost, uint16_t handle, uint8_t code, @@ -1465,12 +1486,25 @@ { const struct bt_hci_evt_le_cis_req *ev = data; struct bt_hci_cmd_le_accept_cis cmd; + struct bt_hci_cmd_le_reject_cis rej; if (len < sizeof(*ev)) return; - memset(&cmd, 0, sizeof(cmd)); + if (bthost->accept_iso_cb) { + memset(&rej, 0, sizeof(rej)); + rej.reason = bthost->accept_iso_cb(le16_to_cpu(ev->cis_handle), + bthost->new_iso_data); + if (rej.reason) { + rej.handle = ev->cis_handle; + send_command(bthost, BT_HCI_CMD_LE_REJECT_CIS, + &rej, sizeof(rej)); + return; + } + } + + memset(&cmd, 0, sizeof(cmd)); cmd.handle = ev->cis_handle; send_command(bthost, BT_HCI_CMD_LE_ACCEPT_CIS, &cmd, sizeof(cmd)); @@ -2893,9 +2927,10 @@ bthost->new_conn_data = user_data; } -void bthost_set_iso_cb(struct bthost *bthost, bthost_new_conn_cb cb, - void *user_data) +void bthost_set_iso_cb(struct bthost *bthost, bthost_accept_conn_cb accept, + bthost_new_conn_cb cb, void *user_data) { + bthost->accept_iso_cb = accept; bthost->new_iso_cb = cb; bthost->new_iso_data = user_data; } @@ -3102,7 +3137,8 @@ send_command(bthost, BT_HCI_CMD_LE_SET_PA_ENABLE, &cp, sizeof(cp)); } -void bthost_create_big(struct bthost *bthost, uint8_t num_bis) +void bthost_create_big(struct bthost *bthost, uint8_t num_bis, + uint8_t enc, const uint8_t *bcode) { struct bt_hci_cmd_le_create_big cp; @@ -3115,6 +3151,8 @@ cp.bis.latency = cpu_to_le16(10); cp.bis.rtn = 0x02; cp.bis.phy = 0x02; + cp.bis.encryption = enc; + memcpy(cp.bis.bcode, bcode, sizeof(cp.bis.bcode)); send_command(bthost, BT_HCI_CMD_LE_CREATE_BIG, &cp, sizeof(cp)); } @@ -3144,22 +3182,24 @@ cp = malloc(sizeof(*cp) + sizeof(*cp->cis)); memset(cp, 0, sizeof(*cp) + sizeof(*cp->cis)); cp->cig_id = cig_id; - put_le24(qos->in.interval ? qos->in.interval : qos->out.interval, - cp->c_interval); - put_le24(qos->out.interval ? qos->out.interval : qos->in.interval, - cp->p_interval); - cp->c_latency = cpu_to_le16(qos->in.latency ? qos->in.latency : - qos->out.latency); - cp->p_latency = cpu_to_le16(qos->out.latency ? qos->out.latency : - qos->in.latency); + put_le24(qos->ucast.in.interval ? qos->ucast.in.interval : + qos->ucast.out.interval, cp->c_interval); + put_le24(qos->ucast.out.interval ? qos->ucast.out.interval : + qos->ucast.in.interval, cp->p_interval); + cp->c_latency = cpu_to_le16(qos->ucast.in.latency ? + qos->ucast.in.latency : qos->ucast.out.latency); + cp->p_latency = cpu_to_le16(qos->ucast.out.latency ? + qos->ucast.out.latency : qos->ucast.in.latency); cp->num_cis = 0x01; cp->cis[0].cis_id = cis_id; - cp->cis[0].c_sdu = qos->in.sdu; - cp->cis[0].p_sdu = qos->out.sdu; - cp->cis[0].c_phy = qos->in.phy ? qos->in.phy : qos->out.phy; - cp->cis[0].p_phy = qos->out.phy ? qos->out.phy : qos->in.phy; - cp->cis[0].c_rtn = qos->in.rtn; - cp->cis[0].p_rtn = qos->out.rtn; + cp->cis[0].c_sdu = qos->ucast.in.sdu; + cp->cis[0].p_sdu = qos->ucast.out.sdu; + cp->cis[0].c_phy = qos->ucast.in.phy ? qos->ucast.in.phy : + qos->ucast.out.phy; + cp->cis[0].p_phy = qos->ucast.out.phy ? qos->ucast.out.phy : + qos->ucast.in.phy; + cp->cis[0].c_rtn = qos->ucast.in.rtn; + cp->cis[0].p_rtn = qos->ucast.out.rtn; send_command(bthost, BT_HCI_CMD_LE_SET_CIG_PARAMS, cp, sizeof(*cp) + sizeof(*cp->cis)); diff -Nru bluez-5.66/emulator/bthost.h bluez-5.68/emulator/bthost.h --- bluez-5.66/emulator/bthost.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/emulator/bthost.h 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2011-2012 Intel Corporation * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -44,13 +45,14 @@ void bthost_set_cmd_complete_cb(struct bthost *bthost, bthost_cmd_complete_cb cb, void *user_data); +typedef uint8_t (*bthost_accept_conn_cb) (uint16_t handle, void *user_data); typedef void (*bthost_new_conn_cb) (uint16_t handle, void *user_data); void bthost_set_connect_cb(struct bthost *bthost, bthost_new_conn_cb cb, void *user_data); -void bthost_set_iso_cb(struct bthost *bthost, bthost_new_conn_cb cb, - void *user_data); +void bthost_set_iso_cb(struct bthost *bthost, bthost_accept_conn_cb accept, + bthost_new_conn_cb cb, void *user_data); void bthost_hci_connect(struct bthost *bthost, const uint8_t *bdaddr, uint8_t addr_type); @@ -78,7 +80,8 @@ const void *data, uint16_t len); void bthost_send_cid_v(struct bthost *bthost, uint16_t handle, uint16_t cid, const struct iovec *iov, int iovcnt); -void bthost_send_iso(struct bthost *bthost, uint16_t handle, +void bthost_send_iso(struct bthost *bthost, uint16_t handle, bool ts, + uint16_t sn, uint32_t timestamp, const struct iovec *iov, int iovcnt); typedef void (*bthost_l2cap_rsp_cb) (uint8_t code, const void *data, @@ -100,7 +103,8 @@ void bthost_set_ext_adv_enable(struct bthost *bthost, uint8_t enable); void bthost_set_pa_params(struct bthost *bthost); void bthost_set_pa_enable(struct bthost *bthost, uint8_t enable); -void bthost_create_big(struct bthost *bthost, uint8_t num_bis); +void bthost_create_big(struct bthost *bthost, uint8_t num_bis, uint8_t enc, + const uint8_t *bcode); bool bthost_search_ext_adv_addr(struct bthost *bthost, const uint8_t *addr); void bthost_set_cig_params(struct bthost *bthost, uint8_t cig_id, diff -Nru bluez-5.66/emulator/vhci.c bluez-5.68/emulator/vhci.c --- bluez-5.66/emulator/vhci.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/emulator/vhci.c 2023-06-30 08:10:20.000000000 +0000 @@ -22,6 +22,7 @@ #include #include #include +#include #include "lib/bluetooth.h" #include "lib/hci.h" @@ -32,6 +33,7 @@ #include "vhci.h" #define DEBUGFS_PATH "/sys/kernel/debug/bluetooth" +#define DEVCORE_PATH "/sys/class/devcoredump" struct vhci { enum btdev_type type; @@ -184,7 +186,7 @@ return vhci->btdev; } -static int vhci_debugfs_write(struct vhci *vhci, char *option, void *data, +static int vhci_debugfs_write(struct vhci *vhci, char *option, const void *data, size_t len) { char path[64]; @@ -267,3 +269,60 @@ return vhci_debugfs_write(vhci, "force_static_address", &val, sizeof(val)); } + +int vhci_force_devcd(struct vhci *vhci, const void *data, size_t len) +{ + return vhci_debugfs_write(vhci, "force_devcoredump", data, len); +} + +int vhci_read_devcd(struct vhci *vhci, void *buf, size_t size) +{ + DIR *dir; + struct dirent *entry; + char filename[PATH_MAX]; + int fd; + int ret; + + dir = opendir(DEVCORE_PATH); + if (dir == NULL) + return -errno; + + while ((entry = readdir(dir)) != NULL) { + if (strstr(entry->d_name, "devcd")) + break; + } + + if (entry == NULL) { + ret = -ENOENT; + goto close_dir; + } + + sprintf(filename, DEVCORE_PATH "/%s/data", entry->d_name); + fd = open(filename, O_RDWR); + if (fd < 0) { + ret = -errno; + goto close_dir; + } + + ret = read(fd, buf, size); + if (ret < 0) { + ret = -errno; + goto close_file; + } + + /* Once the devcoredump is read, write anything to it to mark it for + * cleanup. + */ + if (write(fd, "0", 1) < 0) { + ret = -errno; + goto close_file; + } + +close_file: + close(fd); + +close_dir: + closedir(dir); + + return ret; +} diff -Nru bluez-5.66/emulator/vhci.h bluez-5.68/emulator/vhci.h --- bluez-5.66/emulator/vhci.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/emulator/vhci.h 2023-06-30 08:10:20.000000000 +0000 @@ -29,3 +29,5 @@ int vhci_set_aosp_capable(struct vhci *vhci, bool enable); int vhci_set_emu_opcode(struct vhci *vhci, uint16_t opcode); int vhci_set_force_static_address(struct vhci *vhci, bool enable); +int vhci_force_devcd(struct vhci *vhci, const void *data, size_t len); +int vhci_read_devcd(struct vhci *vhci, void *buf, size_t size); diff -Nru bluez-5.66/lib/bluetooth.h bluez-5.68/lib/bluetooth.h --- bluez-5.66/lib/bluetooth.h 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/lib/bluetooth.h 2023-06-30 08:10:20.000000000 +0000 @@ -6,6 +6,7 @@ * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -146,6 +147,12 @@ #define BT_ISO_QOS_CIG_UNSET 0xff #define BT_ISO_QOS_CIS_UNSET 0xff +#define BT_ISO_QOS_BIG_UNSET 0xff +#define BT_ISO_QOS_BIS_UNSET 0xff + +#define BT_ISO_QOS_GROUP_UNSET 0xff +#define BT_ISO_QOS_STREAM_UNSET 0xff + struct bt_iso_io_qos { uint32_t interval; uint16_t latency; @@ -154,23 +161,46 @@ uint8_t rtn; }; -struct bt_iso_qos { - union { - uint8_t cig; - uint8_t big; - }; - union { - uint8_t cis; - uint8_t bis; - }; - union { - uint8_t sca; - uint8_t sync_interval; - }; +struct bt_iso_ucast_qos { + uint8_t cig; + uint8_t cis; + uint8_t sca; + uint8_t packing; + uint8_t framing; + struct bt_iso_io_qos in; + struct bt_iso_io_qos out; +}; + +struct bt_iso_bcast_qos { + uint8_t big; + uint8_t bis; + uint8_t sync_interval; uint8_t packing; uint8_t framing; struct bt_iso_io_qos in; struct bt_iso_io_qos out; + uint8_t encryption; + uint8_t bcode[16]; + uint8_t options; + uint16_t skip; + uint16_t sync_timeout; + uint8_t sync_cte_type; + uint8_t mse; + uint16_t timeout; +}; + +/* (HCI_MAX_PER_AD_LENGTH - EIR_SERVICE_DATA_LENGTH) */ +#define BASE_MAX_LENGTH 248 +struct bt_iso_base { + uint8_t base_len; + uint8_t base[BASE_MAX_LENGTH]; +}; + +struct bt_iso_qos { + union { + struct bt_iso_ucast_qos ucast; + struct bt_iso_bcast_qos bcast; + }; }; #define BT_CODEC 19 diff -Nru bluez-5.66/lib/l2cap.h bluez-5.68/lib/l2cap.h --- bluez-5.66/lib/l2cap.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/lib/l2cap.h 2023-06-30 08:10:20.000000000 +0000 @@ -184,6 +184,8 @@ #define L2CAP_MODE_FLOWCTL 0x02 #define L2CAP_MODE_ERTM 0x03 #define L2CAP_MODE_STREAMING 0x04 +#define L2CAP_MODE_LE_FLOWCTL 0x80 +#define L2CAP_MODE_ECRED 0x81 #define L2CAP_SERVTYPE_NOTRAFFIC 0x00 #define L2CAP_SERVTYPE_BESTEFFORT 0x01 diff -Nru bluez-5.66/lib/mgmt.h bluez-5.68/lib/mgmt.h --- bluez-5.66/lib/mgmt.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/lib/mgmt.h 2023-06-30 08:10:20.000000000 +0000 @@ -12,6 +12,10 @@ #define __packed __attribute__((packed)) #endif +#ifndef BIT +#define BIT(n) (1 << (n)) +#endif + #define MGMT_INDEX_NONE 0xFFFF #define MGMT_STATUS_SUCCESS 0x00 @@ -78,24 +82,28 @@ #define MGMT_MAX_NAME_LENGTH (248 + 1) #define MGMT_MAX_SHORT_NAME_LENGTH (10 + 1) -#define MGMT_SETTING_POWERED 0x00000001 -#define MGMT_SETTING_CONNECTABLE 0x00000002 -#define MGMT_SETTING_FAST_CONNECTABLE 0x00000004 -#define MGMT_SETTING_DISCOVERABLE 0x00000008 -#define MGMT_SETTING_BONDABLE 0x00000010 -#define MGMT_SETTING_LINK_SECURITY 0x00000020 -#define MGMT_SETTING_SSP 0x00000040 -#define MGMT_SETTING_BREDR 0x00000080 -#define MGMT_SETTING_HS 0x00000100 -#define MGMT_SETTING_LE 0x00000200 -#define MGMT_SETTING_ADVERTISING 0x00000400 -#define MGMT_SETTING_SECURE_CONN 0x00000800 -#define MGMT_SETTING_DEBUG_KEYS 0x00001000 -#define MGMT_SETTING_PRIVACY 0x00002000 -#define MGMT_SETTING_CONFIGURATION 0x00004000 -#define MGMT_SETTING_STATIC_ADDRESS 0x00008000 -#define MGMT_SETTING_PHY_CONFIGURATION 0x00010000 -#define MGMT_SETTING_WIDEBAND_SPEECH 0x00020000 +#define MGMT_SETTING_POWERED BIT(0) +#define MGMT_SETTING_CONNECTABLE BIT(1) +#define MGMT_SETTING_FAST_CONNECTABLE BIT(2) +#define MGMT_SETTING_DISCOVERABLE BIT(3) +#define MGMT_SETTING_BONDABLE BIT(4) +#define MGMT_SETTING_LINK_SECURITY BIT(5) +#define MGMT_SETTING_SSP BIT(6) +#define MGMT_SETTING_BREDR BIT(7) +#define MGMT_SETTING_HS BIT(8) +#define MGMT_SETTING_LE BIT(9) +#define MGMT_SETTING_ADVERTISING BIT(10) +#define MGMT_SETTING_SECURE_CONN BIT(11) +#define MGMT_SETTING_DEBUG_KEYS BIT(12) +#define MGMT_SETTING_PRIVACY BIT(13) +#define MGMT_SETTING_CONFIGURATION BIT(14) +#define MGMT_SETTING_STATIC_ADDRESS BIT(15) +#define MGMT_SETTING_PHY_CONFIGURATION BIT(16) +#define MGMT_SETTING_WIDEBAND_SPEECH BIT(17) +#define MGMT_SETTING_CIS_CENTRAL BIT(18) +#define MGMT_SETTING_CIS_PERIPHERAL BIT(19) +#define MGMT_SETTING_ISO_BROADCASTER BIT(20) +#define MGMT_SETTING_ISO_SYNC_RECEIVER BIT(21) #define MGMT_OP_READ_INFO 0x0004 struct mgmt_rp_read_info { @@ -493,23 +501,23 @@ uint8_t instance; } __packed; -#define MGMT_ADV_FLAG_CONNECTABLE (1 << 0) -#define MGMT_ADV_FLAG_DISCOV (1 << 1) -#define MGMT_ADV_FLAG_LIMITED_DISCOV (1 << 2) -#define MGMT_ADV_FLAG_MANAGED_FLAGS (1 << 3) -#define MGMT_ADV_FLAG_TX_POWER (1 << 4) -#define MGMT_ADV_FLAG_APPEARANCE (1 << 5) -#define MGMT_ADV_FLAG_LOCAL_NAME (1 << 6) -#define MGMT_ADV_FLAG_SEC_1M (1 << 7) -#define MGMT_ADV_FLAG_SEC_2M (1 << 8) -#define MGMT_ADV_FLAG_SEC_CODED (1 << 9) -#define MGMT_ADV_FLAG_CAN_SET_TX_POWER (1 << 10) -#define MGMT_ADV_FLAG_HW_OFFLOAD (1 << 11) -#define MGMT_ADV_PARAM_DURATION (1 << 12) -#define MGMT_ADV_PARAM_TIMEOUT (1 << 13) -#define MGMT_ADV_PARAM_INTERVALS (1 << 14) -#define MGMT_ADV_PARAM_TX_POWER (1 << 15) -#define MGMT_ADV_PARAM_SCAN_RSP (1 << 16) +#define MGMT_ADV_FLAG_CONNECTABLE BIT(0) +#define MGMT_ADV_FLAG_DISCOV BIT(1) +#define MGMT_ADV_FLAG_LIMITED_DISCOV BIT(2) +#define MGMT_ADV_FLAG_MANAGED_FLAGS BIT(3) +#define MGMT_ADV_FLAG_TX_POWER BIT(4) +#define MGMT_ADV_FLAG_APPEARANCE BIT(5) +#define MGMT_ADV_FLAG_LOCAL_NAME BIT(6) +#define MGMT_ADV_FLAG_SEC_1M BIT(7) +#define MGMT_ADV_FLAG_SEC_2M BIT(8) +#define MGMT_ADV_FLAG_SEC_CODED BIT(9) +#define MGMT_ADV_FLAG_CAN_SET_TX_POWER BIT(10) +#define MGMT_ADV_FLAG_HW_OFFLOAD BIT(11) +#define MGMT_ADV_PARAM_DURATION BIT(12) +#define MGMT_ADV_PARAM_TIMEOUT BIT(13) +#define MGMT_ADV_PARAM_INTERVALS BIT(14) +#define MGMT_ADV_PARAM_TX_POWER BIT(15) +#define MGMT_ADV_PARAM_SCAN_RSP BIT(16) #define MGMT_ADV_FLAG_SEC_MASK (MGMT_ADV_FLAG_SEC_1M | MGMT_ADV_FLAG_SEC_2M | \ MGMT_ADV_FLAG_SEC_CODED) @@ -561,21 +569,21 @@ uint32_t selected_phys; } __packed; -#define MGMT_PHY_BR_1M_1SLOT 0x00000001 -#define MGMT_PHY_BR_1M_3SLOT 0x00000002 -#define MGMT_PHY_BR_1M_5SLOT 0x00000004 -#define MGMT_PHY_EDR_2M_1SLOT 0x00000008 -#define MGMT_PHY_EDR_2M_3SLOT 0x00000010 -#define MGMT_PHY_EDR_2M_5SLOT 0x00000020 -#define MGMT_PHY_EDR_3M_1SLOT 0x00000040 -#define MGMT_PHY_EDR_3M_3SLOT 0x00000080 -#define MGMT_PHY_EDR_3M_5SLOT 0x00000100 -#define MGMT_PHY_LE_1M_TX 0x00000200 -#define MGMT_PHY_LE_1M_RX 0x00000400 -#define MGMT_PHY_LE_2M_TX 0x00000800 -#define MGMT_PHY_LE_2M_RX 0x00001000 -#define MGMT_PHY_LE_CODED_TX 0x00002000 -#define MGMT_PHY_LE_CODED_RX 0x00004000 +#define MGMT_PHY_BR_1M_1SLOT BIT(0) +#define MGMT_PHY_BR_1M_3SLOT BIT(1) +#define MGMT_PHY_BR_1M_5SLOT BIT(2) +#define MGMT_PHY_EDR_2M_1SLOT BIT(3) +#define MGMT_PHY_EDR_2M_3SLOT BIT(4) +#define MGMT_PHY_EDR_2M_5SLOT BIT(5) +#define MGMT_PHY_EDR_3M_1SLOT BIT(6) +#define MGMT_PHY_EDR_3M_3SLOT BIT(7) +#define MGMT_PHY_EDR_3M_5SLOT BIT(8) +#define MGMT_PHY_LE_1M_TX BIT(9) +#define MGMT_PHY_LE_1M_RX BIT(10) +#define MGMT_PHY_LE_2M_TX BIT(11) +#define MGMT_PHY_LE_2M_RX BIT(12) +#define MGMT_PHY_LE_CODED_TX BIT(13) +#define MGMT_PHY_LE_CODED_RX BIT(14) #define MGMT_PHY_LE_TX_MASK (MGMT_PHY_LE_1M_TX | MGMT_PHY_LE_2M_TX | \ MGMT_PHY_LE_CODED_TX) @@ -889,10 +897,12 @@ uint8_t status; } __packed; -#define MGMT_DEV_FOUND_CONFIRM_NAME 0x01 -#define MGMT_DEV_FOUND_LEGACY_PAIRING 0x02 -#define MGMT_DEV_FOUND_NOT_CONNECTABLE 0x04 -#define MGMT_DEV_FOUND_NAME_REQUEST_FAILED 0x10 +#define MGMT_DEV_FOUND_CONFIRM_NAME BIT(0) +#define MGMT_DEV_FOUND_LEGACY_PAIRING BIT(1) +#define MGMT_DEV_FOUND_NOT_CONNECTABLE BIT(2) +#define MGMT_DEV_FOUND_INITIATED_CONN BIT(3) +#define MGMT_DEV_FOUND_NAME_REQUEST_FAILED BIT(4) +#define MGMT_DEV_FOUND_SCAN_RSP BIT(5) #define MGMT_EV_DEVICE_FOUND 0x0012 struct mgmt_ev_device_found { diff -Nru bluez-5.66/lib/uuid.h bluez-5.68/lib/uuid.h --- bluez-5.66/lib/uuid.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/lib/uuid.h 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2011 Nokia Corporation * Copyright (C) 2011 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -156,6 +157,9 @@ #define PAC_SOURCE_UUID "00002bcb-0000-1000-8000-00805f9b34fb" #define PAC_SOURCE_LOC_CHRC_UUID 0x2bcc +#define BAA_SERVICE 0x1852 +#define BAA_SERVICE_UUID "00001852-0000-1000-8000-00805f9b34fb" + #define PAC_CONTEXT 0x2bcd #define PAC_SUPPORTED_CONTEXT 0x2bce @@ -164,6 +168,10 @@ #define ASE_SOURCE_UUID 0x2bc5 #define ASE_CP_UUID 0x2bc6 +#define BASS_UUID 0x184f +#define BCAST_AUDIO_SCAN_CP_UUID 0x2bc7 +#define BCAST_RECV_STATE_UUID 0x2bc8 + #define VCS_UUID 0x1844 #define VOL_OFFSET_CS_UUID 0x1845 #define AUDIO_INPUT_CS_UUID 0x1843 @@ -171,6 +179,11 @@ #define VOL_CP_CHRC_UUID 0x2B7E #define VOL_FLAG_CHRC_UUID 0x2B7F +#define VOCS_STATE_CHAR_UUID 0x2B80 +#define VOCS_AUDIO_LOC_CHRC_UUID 0x2B81 +#define VOCS_CP_CHRC_UUID 0x2B82 +#define VOCS_AUDIO_OP_DESC_CHAR_UUID 0x2B83 + #define GMCS_UUID 0x1849 #define MEDIA_PLAYER_NAME_CHRC_UUID 0x2b93 #define MEDIA_TRACK_CHNGD_CHRC_UUID 0x2b96 @@ -186,6 +199,13 @@ #define MEDIA_CP_OP_SUPPORTED_CHRC_UUID 0x2ba5 #define MEDIA_CONTENT_CONTROL_ID_CHRC_UUID 0x2bba +/* Coordinated Set Identification Profile(CSIP) */ +#define CSIS_UUID 0x1846 +#define CS_SIRK 0x2B84 +#define CS_SIZE 0x2B85 +#define CS_LOCK 0x2B86 +#define CS_RANK 0x2B87 + typedef struct { enum { BT_UUID_UNSPEC = 0, diff -Nru bluez-5.66/ltmain.sh bluez-5.68/ltmain.sh --- bluez-5.66/ltmain.sh 2022-11-10 20:24:57.000000000 +0000 +++ bluez-5.68/ltmain.sh 2023-06-30 22:15:29.000000000 +0000 @@ -31,7 +31,7 @@ PROGRAM=libtool PACKAGE=libtool -VERSION="2.4.7 Debian-2.4.7-4" +VERSION="2.4.7 Debian-2.4.7-5" package_revision=2.4.7 @@ -2308,7 +2308,7 @@ compiler: $LTCC compiler flags: $LTCFLAGS linker: $LD (gnu? $with_gnu_ld) - version: $progname $scriptversion Debian-2.4.7-4 + version: $progname $scriptversion Debian-2.4.7-5 automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` diff -Nru bluez-5.66/Makefile.am bluez-5.68/Makefile.am --- bluez-5.66/Makefile.am 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/Makefile.am 2023-06-30 08:10:19.000000000 +0000 @@ -82,7 +82,7 @@ lib_LTLIBRARIES += lib/libbluetooth.la lib_libbluetooth_la_SOURCES = $(lib_headers) $(lib_sources) -lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:8:19 +lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:9:19 lib_libbluetooth_la_DEPENDENCIES = $(local_headers) endif @@ -233,6 +233,8 @@ src/shared/bap.h src/shared/bap.c src/shared/ascs.h \ src/shared/mcs.h src/shared/mcp.h src/shared/mcp.c \ src/shared/vcp.c src/shared/vcp.h \ + src/shared/csip.c src/shared/csip.h \ + src/shared/bass.h src/shared/bass.c \ src/shared/lc3.h src/shared/tty.h if READLINE @@ -326,7 +328,8 @@ src/eir.h src/eir.c \ src/adv_monitor.h src/adv_monitor.c \ src/battery.h src/battery.c \ - src/settings.h src/settings.c + src/settings.h src/settings.c \ + src/set.h src/set.c src_bluetoothd_LDADD = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la \ src/libshared-glib.la \ @@ -565,9 +568,21 @@ unit_test_gattrib_SOURCES = unit/test-gattrib.c attrib/gattrib.c \ $(btio_sources) src/log.h src/log.c -unit_test_gattrib_LDADD = lib/libbluetooth-internal.la \ - src/libshared-glib.la \ - $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt +unit_test_gattrib_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt + +unit_tests += unit/test-bap + +unit_test_bap_SOURCES = unit/test-bap.c +unit_test_bap_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) + +unit_tests += unit/test-bass + +unit_test_bass_SOURCES = unit/test-bass.c +unit_test_bass_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) if MIDI unit_tests += unit/test-midi diff -Nru bluez-5.66/Makefile.in bluez-5.68/Makefile.in --- bluez-5.66/Makefile.in 2022-11-10 20:25:05.000000000 +0000 +++ bluez-5.68/Makefile.in 2023-06-30 22:15:37.000000000 +0000 @@ -168,19 +168,23 @@ @SIXAXIS_TRUE@am__append_29 = plugins/sixaxis.la @BAP_TRUE@am__append_30 = bap @BAP_TRUE@am__append_31 = profiles/audio/bap.c -@MCP_TRUE@am__append_32 = mcp -@MCP_TRUE@am__append_33 = profiles/audio/mcp.c -@VCP_TRUE@am__append_34 = vcp -@VCP_TRUE@am__append_35 = profiles/audio/vcp.c -@MAINTAINER_MODE_TRUE@am__append_36 = plugins/external-dummy.la +@BASS_TRUE@am__append_32 = bass +@BASS_TRUE@am__append_33 = profiles/audio/bass.c +@MCP_TRUE@am__append_34 = mcp +@MCP_TRUE@am__append_35 = profiles/audio/mcp.c +@VCP_TRUE@am__append_36 = vcp +@VCP_TRUE@am__append_37 = profiles/audio/vcp.c +@CSIP_TRUE@am__append_38 = csip +@CSIP_TRUE@am__append_39 = profiles/audio/csip.c +@MAINTAINER_MODE_TRUE@am__append_40 = plugins/external-dummy.la # SPDX-License-Identifier: GPL-2.0 -@CLIENT_TRUE@am__append_37 = client/bluetoothctl -@MONITOR_TRUE@am__append_38 = monitor/btmon -@MANPAGES_TRUE@@MONITOR_TRUE@am__append_39 = monitor/btmon.1 -@LOGGER_TRUE@am__append_40 = tools/btmon-logger -@LOGGER_TRUE@@SYSTEMD_TRUE@am__append_41 = tools/bluetooth-logger.service -@TESTING_TRUE@am__append_42 = emulator/btvirt emulator/b1ee emulator/hfp \ +@CLIENT_TRUE@am__append_41 = client/bluetoothctl +@MONITOR_TRUE@am__append_42 = monitor/btmon +@MANPAGES_TRUE@@MONITOR_TRUE@am__append_43 = monitor/btmon.1 +@LOGGER_TRUE@am__append_44 = tools/btmon-logger +@LOGGER_TRUE@@SYSTEMD_TRUE@am__append_45 = tools/bluetooth-logger.service +@TESTING_TRUE@am__append_46 = emulator/btvirt emulator/b1ee emulator/hfp \ @TESTING_TRUE@ peripheral/btsensor tools/3dsp \ @TESTING_TRUE@ tools/mgmt-tester tools/gap-tester \ @TESTING_TRUE@ tools/l2cap-tester tools/sco-tester \ @@ -189,10 +193,10 @@ @TESTING_TRUE@ tools/userchan-tester tools/iso-tester \ @TESTING_TRUE@ tools/mesh-tester tools/ioctl-tester -@TOOLS_TRUE@am__append_43 = tools/rctest tools/l2test tools/l2ping tools/bluemoon \ +@TOOLS_TRUE@am__append_47 = tools/rctest tools/l2test tools/l2ping tools/bluemoon \ @TOOLS_TRUE@ tools/hex2hcd tools/mpris-proxy tools/btattach tools/isotest -@TOOLS_TRUE@am__append_44 = tools/bdaddr tools/avinfo tools/avtest \ +@TOOLS_TRUE@am__append_48 = tools/bdaddr tools/avinfo tools/avtest \ @TOOLS_TRUE@ tools/scotest tools/amptest tools/hwdb \ @TOOLS_TRUE@ tools/hcieventmask tools/hcisecfilter \ @TOOLS_TRUE@ tools/btinfo tools/btconfig \ @@ -206,59 +210,59 @@ @TOOLS_TRUE@ tools/test-runner tools/check-selftest \ @TOOLS_TRUE@ tools/gatt-service profiles/iap/iapd -@MANPAGES_TRUE@@TOOLS_TRUE@am__append_45 = tools/rctest.1 tools/l2ping.1 tools/btattach.1 tools/isotest.1 -@DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@am__append_46 = tools/meshctl -@DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@am__append_47 = tools/mesh-gatt/local_node.json tools/mesh-gatt/prov_db.json -@MESH_TRUE@@TOOLS_TRUE@am__append_48 = tools/mesh-cfgclient \ +@MANPAGES_TRUE@@TOOLS_TRUE@am__append_49 = tools/rctest.1 tools/l2ping.1 tools/btattach.1 tools/isotest.1 +@DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@am__append_50 = tools/meshctl +@DEPRECATED_TRUE@@MESH_TRUE@@TOOLS_TRUE@am__append_51 = tools/mesh-gatt/local_node.json tools/mesh-gatt/prov_db.json +@MESH_TRUE@@TOOLS_TRUE@am__append_52 = tools/mesh-cfgclient \ @MESH_TRUE@@TOOLS_TRUE@ tools/mesh-cfgtest -@DEPRECATED_TRUE@@TOOLS_TRUE@am__append_49 = tools/hciattach tools/hciconfig tools/hcitool tools/hcidump \ +@DEPRECATED_TRUE@@TOOLS_TRUE@am__append_53 = tools/hciattach tools/hciconfig tools/hcitool tools/hcidump \ @DEPRECATED_TRUE@@TOOLS_TRUE@ tools/rfcomm tools/sdptool tools/ciptool -@DEPRECATED_TRUE@@MANPAGES_TRUE@@TOOLS_TRUE@am__append_50 = tools/hciattach.1 tools/hciconfig.1 \ +@DEPRECATED_TRUE@@MANPAGES_TRUE@@TOOLS_TRUE@am__append_54 = tools/hciattach.1 tools/hciconfig.1 \ @DEPRECATED_TRUE@@MANPAGES_TRUE@@TOOLS_TRUE@ tools/hcitool.1 tools/hcidump.1 \ @DEPRECATED_TRUE@@MANPAGES_TRUE@@TOOLS_TRUE@ tools/rfcomm.1 tools/sdptool.1 tools/ciptool.1 @HID2HCI_TRUE@udev_PROGRAMS = tools/hid2hci$(EXEEXT) -@HID2HCI_TRUE@@MANPAGES_TRUE@am__append_51 = tools/hid2hci.1 -@READLINE_TRUE@am__append_52 = tools/btmgmt tools/obex-client-tool tools/obex-server-tool \ +@HID2HCI_TRUE@@MANPAGES_TRUE@am__append_55 = tools/hid2hci.1 +@READLINE_TRUE@am__append_56 = tools/btmgmt tools/obex-client-tool tools/obex-server-tool \ @READLINE_TRUE@ tools/bluetooth-player tools/obexctl -@DEPRECATED_TRUE@@READLINE_TRUE@am__append_53 = attrib/gatttool +@DEPRECATED_TRUE@@READLINE_TRUE@am__append_57 = attrib/gatttool @CUPS_TRUE@cups_PROGRAMS = profiles/cups/bluetooth$(EXEEXT) -@BTPCLIENT_TRUE@am__append_54 = tools/btpclient tools/btpclientctl -@EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_55 = pcsuite -@EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_56 = obexd/plugins/pcsuite.c -@OBEX_TRUE@am__append_57 = obexd/plugins/phonebook-dummy.c obexd/plugins/phonebook-ebook.c \ +@BTPCLIENT_TRUE@am__append_58 = tools/btpclient tools/btpclientctl +@EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_59 = pcsuite +@EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_60 = obexd/plugins/pcsuite.c +@OBEX_TRUE@am__append_61 = obexd/plugins/phonebook-dummy.c obexd/plugins/phonebook-ebook.c \ @OBEX_TRUE@ obexd/plugins/phonebook-tracker.c -@OBEX_TRUE@am__append_58 = obexd/src/obexd -@ANDROID_TRUE@am__append_59 = -DANDROID_VERSION=0x050100 -@ANDROID_TRUE@am__append_60 = android/system-emulator \ +@OBEX_TRUE@am__append_62 = obexd/src/obexd +@ANDROID_TRUE@am__append_63 = -DANDROID_VERSION=0x050100 +@ANDROID_TRUE@am__append_64 = android/system-emulator \ @ANDROID_TRUE@ android/bluetoothd-snoop android/bluetoothd \ @ANDROID_TRUE@ android/avdtptest android/haltest \ @ANDROID_TRUE@ android/android-tester android/ipc-tester -@ANDROID_TRUE@am__append_61 = android/bluetooth.default.la \ +@ANDROID_TRUE@am__append_65 = android/bluetooth.default.la \ @ANDROID_TRUE@ android/audio.a2dp.default.la \ @ANDROID_TRUE@ android/audio.sco.default.la -@ANDROID_TRUE@am__append_62 = android/test-ipc +@ANDROID_TRUE@am__append_66 = android/test-ipc # SPDX-License-Identifier: GPL-2.0 -@DATAFILES_TRUE@@MESH_TRUE@am__append_63 = mesh/bluetooth-mesh.conf -@MESH_TRUE@@SYSTEMD_TRUE@am__append_64 = mesh/bluetooth-mesh.service -@MESH_TRUE@@SYSTEMD_TRUE@am__append_65 = mesh/org.bluez.mesh.service -@MESH_TRUE@am__append_66 = mesh/bluetooth-meshd -@MANPAGES_TRUE@@MESH_TRUE@am__append_67 = mesh/bluetooth-meshd.8 -@MESH_TRUE@am__append_68 = mesh/bluetooth-meshd.8 -@MESH_TRUE@am__append_69 = mesh/bluetooth-mesh.service -@HID2HCI_TRUE@am__append_70 = $(rules_DATA) -@OBEX_TRUE@am__append_71 = unit/test-gobex-header unit/test-gobex-packet unit/test-gobex \ +@DATAFILES_TRUE@@MESH_TRUE@am__append_67 = mesh/bluetooth-mesh.conf +@MESH_TRUE@@SYSTEMD_TRUE@am__append_68 = mesh/bluetooth-mesh.service +@MESH_TRUE@@SYSTEMD_TRUE@am__append_69 = mesh/org.bluez.mesh.service +@MESH_TRUE@am__append_70 = mesh/bluetooth-meshd +@MANPAGES_TRUE@@MESH_TRUE@am__append_71 = mesh/bluetooth-meshd.8 +@MESH_TRUE@am__append_72 = mesh/bluetooth-meshd.8 +@MESH_TRUE@am__append_73 = mesh/bluetooth-mesh.service +@HID2HCI_TRUE@am__append_74 = $(rules_DATA) +@OBEX_TRUE@am__append_75 = unit/test-gobex-header unit/test-gobex-packet unit/test-gobex \ @OBEX_TRUE@ unit/test-gobex-transfer unit/test-gobex-apparam -@MIDI_TRUE@am__append_72 = unit/test-midi -@MESH_TRUE@am__append_73 = unit/test-mesh-crypto -@MAINTAINER_MODE_TRUE@am__append_74 = $(unit_tests) +@MIDI_TRUE@am__append_76 = unit/test-midi +@MESH_TRUE@am__append_77 = unit/test-mesh-crypto +@MAINTAINER_MODE_TRUE@am__append_78 = $(unit_tests) TESTS = $(am__EXEEXT_17) -@DBUS_RUN_SESSION_TRUE@am__append_75 = dbus-run-session -- +@DBUS_RUN_SESSION_TRUE@am__append_79 = dbus-run-session -- subdir = . ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ @@ -371,7 +375,8 @@ unit/test-hfp$(EXEEXT) unit/test-gdbus-client$(EXEEXT) \ $(am__EXEEXT_14) unit/test-lib$(EXEEXT) \ unit/test-gatt$(EXEEXT) unit/test-hog$(EXEEXT) \ - unit/test-gattrib$(EXEEXT) $(am__EXEEXT_15) $(am__EXEEXT_16) + unit/test-gattrib$(EXEEXT) unit/test-bap$(EXEEXT) \ + unit/test-bass$(EXEEXT) $(am__EXEEXT_15) $(am__EXEEXT_16) @MAINTAINER_MODE_TRUE@am__EXEEXT_18 = $(am__EXEEXT_17) @LOGGER_TRUE@am__EXEEXT_19 = tools/btmon-logger$(EXEEXT) @OBEX_TRUE@am__EXEEXT_20 = obexd/src/obexd$(EXEEXT) @@ -604,10 +609,11 @@ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/mcs.h \ src/shared/mcp.h src/shared/mcp.c src/shared/vcp.c \ - src/shared/vcp.h src/shared/lc3.h src/shared/tty.h \ - src/shared/shell.c src/shared/shell.h src/shared/io-ell.c \ - src/shared/timeout-ell.c src/shared/mainloop.h \ - src/shared/mainloop-ell.c + src/shared/vcp.h src/shared/csip.c src/shared/csip.h \ + src/shared/bass.h src/shared/bass.c src/shared/lc3.h \ + src/shared/tty.h src/shared/shell.c src/shared/shell.h \ + src/shared/io-ell.c src/shared/timeout-ell.c \ + src/shared/mainloop.h src/shared/mainloop-ell.c @READLINE_TRUE@am__objects_5 = src/shared/libshared_ell_la-shell.lo am__objects_6 = src/shared/libshared_ell_la-queue.lo \ src/shared/libshared_ell_la-util.lo \ @@ -631,7 +637,9 @@ src/shared/libshared_ell_la-log.lo \ src/shared/libshared_ell_la-bap.lo \ src/shared/libshared_ell_la-mcp.lo \ - src/shared/libshared_ell_la-vcp.lo $(am__objects_5) + src/shared/libshared_ell_la-vcp.lo \ + src/shared/libshared_ell_la-csip.lo \ + src/shared/libshared_ell_la-bass.lo $(am__objects_5) @LIBSHARED_ELL_TRUE@am_src_libshared_ell_la_OBJECTS = \ @LIBSHARED_ELL_TRUE@ $(am__objects_6) \ @LIBSHARED_ELL_TRUE@ src/shared/libshared_ell_la-io-ell.lo \ @@ -663,11 +671,12 @@ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/mcs.h \ src/shared/mcp.h src/shared/mcp.c src/shared/vcp.c \ - src/shared/vcp.h src/shared/lc3.h src/shared/tty.h \ - src/shared/shell.c src/shared/shell.h src/shared/io-glib.c \ - src/shared/timeout-glib.c src/shared/mainloop-glib.c \ - src/shared/mainloop-notify.h src/shared/mainloop-notify.c \ - src/shared/tester.c + src/shared/vcp.h src/shared/csip.c src/shared/csip.h \ + src/shared/bass.h src/shared/bass.c src/shared/lc3.h \ + src/shared/tty.h src/shared/shell.c src/shared/shell.h \ + src/shared/io-glib.c src/shared/timeout-glib.c \ + src/shared/mainloop-glib.c src/shared/mainloop-notify.h \ + src/shared/mainloop-notify.c src/shared/tester.c @READLINE_TRUE@am__objects_7 = src/shared/libshared_glib_la-shell.lo am__objects_8 = src/shared/libshared_glib_la-queue.lo \ src/shared/libshared_glib_la-util.lo \ @@ -691,7 +700,9 @@ src/shared/libshared_glib_la-log.lo \ src/shared/libshared_glib_la-bap.lo \ src/shared/libshared_glib_la-mcp.lo \ - src/shared/libshared_glib_la-vcp.lo $(am__objects_7) + src/shared/libshared_glib_la-vcp.lo \ + src/shared/libshared_glib_la-csip.lo \ + src/shared/libshared_glib_la-bass.lo $(am__objects_7) am_src_libshared_glib_la_OBJECTS = $(am__objects_8) \ src/shared/libshared_glib_la-io-glib.lo \ src/shared/libshared_glib_la-timeout-glib.lo \ @@ -723,11 +734,12 @@ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/mcs.h \ src/shared/mcp.h src/shared/mcp.c src/shared/vcp.c \ - src/shared/vcp.h src/shared/lc3.h src/shared/tty.h \ - src/shared/shell.c src/shared/shell.h src/shared/io-mainloop.c \ - src/shared/timeout-mainloop.c src/shared/mainloop.h \ - src/shared/mainloop.c src/shared/mainloop-notify.h \ - src/shared/mainloop-notify.c + src/shared/vcp.h src/shared/csip.c src/shared/csip.h \ + src/shared/bass.h src/shared/bass.c src/shared/lc3.h \ + src/shared/tty.h src/shared/shell.c src/shared/shell.h \ + src/shared/io-mainloop.c src/shared/timeout-mainloop.c \ + src/shared/mainloop.h src/shared/mainloop.c \ + src/shared/mainloop-notify.h src/shared/mainloop-notify.c @READLINE_TRUE@am__objects_9 = \ @READLINE_TRUE@ src/shared/libshared_mainloop_la-shell.lo am__objects_10 = src/shared/libshared_mainloop_la-queue.lo \ @@ -752,7 +764,9 @@ src/shared/libshared_mainloop_la-log.lo \ src/shared/libshared_mainloop_la-bap.lo \ src/shared/libshared_mainloop_la-mcp.lo \ - src/shared/libshared_mainloop_la-vcp.lo $(am__objects_9) + src/shared/libshared_mainloop_la-vcp.lo \ + src/shared/libshared_mainloop_la-csip.lo \ + src/shared/libshared_mainloop_la-bass.lo $(am__objects_9) am_src_libshared_mainloop_la_OBJECTS = $(am__objects_10) \ src/shared/libshared_mainloop_la-io-mainloop.lo \ src/shared/libshared_mainloop_la-timeout-mainloop.lo \ @@ -979,13 +993,15 @@ @DEPRECATED_TRUE@@READLINE_TRUE@ lib/libbluetooth-internal.la \ @DEPRECATED_TRUE@@READLINE_TRUE@ src/libshared-glib.la \ @DEPRECATED_TRUE@@READLINE_TRUE@ $(am__DEPENDENCIES_1) -am__client_bluetoothctl_SOURCES_DIST = client/main.c client/display.h \ - client/display.c client/agent.h client/agent.c \ - client/advertising.h client/advertising.c client/adv_monitor.h \ - client/adv_monitor.c client/gatt.h client/gatt.c \ - client/admin.h client/admin.c client/player.h client/player.c +am__client_bluetoothctl_SOURCES_DIST = client/main.c client/print.h \ + client/print.c client/display.h client/display.c \ + client/agent.h client/agent.c client/advertising.h \ + client/advertising.c client/adv_monitor.h client/adv_monitor.c \ + client/gatt.h client/gatt.c client/admin.h client/admin.c \ + client/player.h client/player.c @CLIENT_TRUE@am_client_bluetoothctl_OBJECTS = client/main.$(OBJEXT) \ -@CLIENT_TRUE@ client/display.$(OBJEXT) client/agent.$(OBJEXT) \ +@CLIENT_TRUE@ client/print.$(OBJEXT) client/display.$(OBJEXT) \ +@CLIENT_TRUE@ client/agent.$(OBJEXT) \ @CLIENT_TRUE@ client/advertising.$(OBJEXT) \ @CLIENT_TRUE@ client/adv_monitor.$(OBJEXT) \ @CLIENT_TRUE@ client/gatt.$(OBJEXT) client/admin.$(OBJEXT) \ @@ -1028,12 +1044,13 @@ mesh/crypto.c mesh/friend.h mesh/friend.c mesh/appkey.h \ mesh/appkey.c mesh/node.h mesh/node.c mesh/provision.h \ mesh/prov.h mesh/model.h mesh/model.c mesh/cfgmod.h \ - mesh/cfgmod-server.c mesh/mesh-config.h \ - mesh/mesh-config-json.c mesh/util.h mesh/util.c mesh/dbus.h \ - mesh/dbus.c mesh/agent.h mesh/agent.c mesh/prov-acceptor.c \ - mesh/prov-initiator.c mesh/manager.h mesh/manager.c \ - mesh/pb-adv.h mesh/pb-adv.c mesh/keyring.h mesh/keyring.c \ - mesh/rpl.h mesh/rpl.c mesh/mesh-defs.h mesh/main.c + mesh/cfgmod-server.c mesh/remprv.h mesh/remprv-server.c \ + mesh/mesh-config.h mesh/mesh-config-json.c mesh/util.h \ + mesh/util.c mesh/dbus.h mesh/dbus.c mesh/agent.h mesh/agent.c \ + mesh/prov-acceptor.c mesh/prov-initiator.c mesh/manager.h \ + mesh/manager.c mesh/pb-adv.h mesh/pb-adv.c mesh/keyring.h \ + mesh/keyring.c mesh/rpl.h mesh/rpl.c mesh/prv-beacon.h \ + mesh/prvbeac-server.c mesh/mesh-defs.h mesh/main.c @MESH_TRUE@am__objects_11 = mesh/mesh.$(OBJEXT) \ @MESH_TRUE@ mesh/net-keys.$(OBJEXT) mesh/mesh-io.$(OBJEXT) \ @MESH_TRUE@ mesh/mesh-mgmt.$(OBJEXT) \ @@ -1043,12 +1060,14 @@ @MESH_TRUE@ mesh/crypto.$(OBJEXT) mesh/friend.$(OBJEXT) \ @MESH_TRUE@ mesh/appkey.$(OBJEXT) mesh/node.$(OBJEXT) \ @MESH_TRUE@ mesh/model.$(OBJEXT) mesh/cfgmod-server.$(OBJEXT) \ +@MESH_TRUE@ mesh/remprv-server.$(OBJEXT) \ @MESH_TRUE@ mesh/mesh-config-json.$(OBJEXT) mesh/util.$(OBJEXT) \ @MESH_TRUE@ mesh/dbus.$(OBJEXT) mesh/agent.$(OBJEXT) \ @MESH_TRUE@ mesh/prov-acceptor.$(OBJEXT) \ @MESH_TRUE@ mesh/prov-initiator.$(OBJEXT) \ @MESH_TRUE@ mesh/manager.$(OBJEXT) mesh/pb-adv.$(OBJEXT) \ -@MESH_TRUE@ mesh/keyring.$(OBJEXT) mesh/rpl.$(OBJEXT) +@MESH_TRUE@ mesh/keyring.$(OBJEXT) mesh/rpl.$(OBJEXT) \ +@MESH_TRUE@ mesh/prvbeac-server.$(OBJEXT) @MESH_TRUE@am_mesh_bluetooth_meshd_OBJECTS = $(am__objects_11) \ @MESH_TRUE@ mesh/main.$(OBJEXT) mesh_bluetooth_meshd_OBJECTS = $(am_mesh_bluetooth_meshd_OBJECTS) @@ -1256,15 +1275,16 @@ profiles/scanparam/scan.c profiles/deviceinfo/deviceinfo.c \ profiles/midi/midi.c profiles/midi/libmidi.h \ profiles/midi/libmidi.c profiles/battery/battery.c \ - profiles/audio/bap.c profiles/audio/mcp.c profiles/audio/vcp.c \ - attrib/att.h attrib/att-database.h attrib/att.c attrib/gatt.h \ - attrib/gatt.c attrib/gattrib.h attrib/gattrib.c btio/btio.h \ - btio/btio.c src/bluetooth.ver src/main.c src/log.h src/log.c \ - src/backtrace.h src/backtrace.c src/rfkill.c src/btd.h \ - src/sdpd.h src/sdpd-server.c src/sdpd-request.c \ - src/sdpd-service.c src/sdpd-database.c src/gatt-database.h \ - src/gatt-database.c src/sdp-xml.h src/sdp-xml.c \ - src/sdp-client.h src/sdp-client.c src/textfile.h \ + profiles/audio/bap.c profiles/audio/bass.c \ + profiles/audio/mcp.c profiles/audio/vcp.c \ + profiles/audio/csip.c attrib/att.h attrib/att-database.h \ + attrib/att.c attrib/gatt.h attrib/gatt.c attrib/gattrib.h \ + attrib/gattrib.c btio/btio.h btio/btio.c src/bluetooth.ver \ + src/main.c src/log.h src/log.c src/backtrace.h src/backtrace.c \ + src/rfkill.c src/btd.h src/sdpd.h src/sdpd-server.c \ + src/sdpd-request.c src/sdpd-service.c src/sdpd-database.c \ + src/gatt-database.h src/gatt-database.c src/sdp-xml.h \ + src/sdp-xml.c src/sdp-client.h src/sdp-client.c src/textfile.h \ src/textfile.c src/uuid-helper.h src/uuid-helper.c \ src/plugin.h src/plugin.c src/storage.h src/storage.c \ src/advertising.h src/advertising.c src/agent.h src/agent.c \ @@ -1273,7 +1293,8 @@ src/gatt-client.h src/gatt-client.c src/device.h src/device.c \ src/dbus-common.c src/dbus-common.h src/eir.h src/eir.c \ src/adv_monitor.h src/adv_monitor.c src/battery.h \ - src/battery.c src/settings.h src/settings.c + src/battery.c src/settings.h src/settings.c src/set.h \ + src/set.c @ADMIN_TRUE@am__objects_17 = plugins/bluetoothd-admin.$(OBJEXT) @NFC_TRUE@am__objects_18 = plugins/bluetoothd-neard.$(OBJEXT) @SAP_TRUE@am__objects_19 = profiles/sap/bluetoothd-main.$(OBJEXT) \ @@ -1316,9 +1337,11 @@ @MIDI_TRUE@am__objects_26 = profiles/midi/bluetoothd-midi.$(OBJEXT) \ @MIDI_TRUE@ profiles/midi/bluetoothd-libmidi.$(OBJEXT) @BAP_TRUE@am__objects_27 = profiles/audio/bluetoothd-bap.$(OBJEXT) -@MCP_TRUE@am__objects_28 = profiles/audio/bluetoothd-mcp.$(OBJEXT) -@VCP_TRUE@am__objects_29 = profiles/audio/bluetoothd-vcp.$(OBJEXT) -am__objects_30 = plugins/bluetoothd-hostname.$(OBJEXT) \ +@BASS_TRUE@am__objects_28 = profiles/audio/bluetoothd-bass.$(OBJEXT) +@MCP_TRUE@am__objects_29 = profiles/audio/bluetoothd-mcp.$(OBJEXT) +@VCP_TRUE@am__objects_30 = profiles/audio/bluetoothd-vcp.$(OBJEXT) +@CSIP_TRUE@am__objects_31 = profiles/audio/bluetoothd-csip.$(OBJEXT) +am__objects_32 = plugins/bluetoothd-hostname.$(OBJEXT) \ plugins/bluetoothd-wiimote.$(OBJEXT) \ plugins/bluetoothd-autopair.$(OBJEXT) \ plugins/bluetoothd-policy.$(OBJEXT) $(am__objects_17) \ @@ -1330,13 +1353,14 @@ profiles/deviceinfo/bluetoothd-deviceinfo.$(OBJEXT) \ $(am__objects_26) \ profiles/battery/bluetoothd-battery.$(OBJEXT) \ - $(am__objects_27) $(am__objects_28) $(am__objects_29) -am__objects_31 = attrib/bluetoothd-att.$(OBJEXT) \ + $(am__objects_27) $(am__objects_28) $(am__objects_29) \ + $(am__objects_30) $(am__objects_31) +am__objects_33 = attrib/bluetoothd-att.$(OBJEXT) \ attrib/bluetoothd-gatt.$(OBJEXT) \ attrib/bluetoothd-gattrib.$(OBJEXT) -am__objects_32 = btio/bluetoothd-btio.$(OBJEXT) -am_src_bluetoothd_OBJECTS = $(am__objects_30) $(am__objects_31) \ - $(am__objects_32) src/bluetoothd-main.$(OBJEXT) \ +am__objects_34 = btio/bluetoothd-btio.$(OBJEXT) +am_src_bluetoothd_OBJECTS = $(am__objects_32) $(am__objects_33) \ + $(am__objects_34) src/bluetoothd-main.$(OBJEXT) \ src/bluetoothd-log.$(OBJEXT) \ src/bluetoothd-backtrace.$(OBJEXT) \ src/bluetoothd-rfkill.$(OBJEXT) \ @@ -1362,7 +1386,7 @@ src/bluetoothd-eir.$(OBJEXT) \ src/bluetoothd-adv_monitor.$(OBJEXT) \ src/bluetoothd-battery.$(OBJEXT) \ - src/bluetoothd-settings.$(OBJEXT) + src/bluetoothd-settings.$(OBJEXT) src/bluetoothd-set.$(OBJEXT) nodist_src_bluetoothd_OBJECTS = $(am__objects_16) src_bluetoothd_OBJECTS = $(am_src_bluetoothd_OBJECTS) \ $(nodist_src_bluetoothd_OBJECTS) @@ -1404,10 +1428,10 @@ tools_bluemoon_OBJECTS = $(am_tools_bluemoon_OBJECTS) @TOOLS_TRUE@tools_bluemoon_DEPENDENCIES = src/libshared-mainloop.la am__tools_bluetooth_player_SOURCES_DIST = tools/bluetooth-player.c \ - client/player.c + client/print.c client/player.c @READLINE_TRUE@am_tools_bluetooth_player_OBJECTS = \ @READLINE_TRUE@ tools/bluetooth-player.$(OBJEXT) \ -@READLINE_TRUE@ client/player.$(OBJEXT) +@READLINE_TRUE@ client/print.$(OBJEXT) client/player.$(OBJEXT) tools_bluetooth_player_OBJECTS = $(am_tools_bluetooth_player_OBJECTS) @READLINE_TRUE@tools_bluetooth_player_DEPENDENCIES = \ @READLINE_TRUE@ gdbus/libgdbus-internal.la \ @@ -1814,12 +1838,12 @@ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h btio/btio.h \ btio/btio.c tools/obex-client-tool.c -am__objects_33 = gobex/gobex.$(OBJEXT) gobex/gobex-defs.$(OBJEXT) \ +am__objects_35 = gobex/gobex.$(OBJEXT) gobex/gobex-defs.$(OBJEXT) \ gobex/gobex-packet.$(OBJEXT) gobex/gobex-header.$(OBJEXT) \ gobex/gobex-transfer.$(OBJEXT) gobex/gobex-apparam.$(OBJEXT) -am__objects_34 = btio/btio.$(OBJEXT) -@READLINE_TRUE@am_tools_obex_client_tool_OBJECTS = $(am__objects_33) \ -@READLINE_TRUE@ $(am__objects_34) \ +am__objects_36 = btio/btio.$(OBJEXT) +@READLINE_TRUE@am_tools_obex_client_tool_OBJECTS = $(am__objects_35) \ +@READLINE_TRUE@ $(am__objects_36) \ @READLINE_TRUE@ tools/obex-client-tool.$(OBJEXT) tools_obex_client_tool_OBJECTS = $(am_tools_obex_client_tool_OBJECTS) @READLINE_TRUE@tools_obex_client_tool_DEPENDENCIES = \ @@ -1831,8 +1855,8 @@ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h btio/btio.h \ btio/btio.c tools/obex-server-tool.c -@READLINE_TRUE@am_tools_obex_server_tool_OBJECTS = $(am__objects_33) \ -@READLINE_TRUE@ $(am__objects_34) \ +@READLINE_TRUE@am_tools_obex_server_tool_OBJECTS = $(am__objects_35) \ +@READLINE_TRUE@ $(am__objects_36) \ @READLINE_TRUE@ tools/obex-server-tool.$(OBJEXT) tools_obex_server_tool_OBJECTS = $(am_tools_obex_server_tool_OBJECTS) @READLINE_TRUE@tools_obex_server_tool_DEPENDENCIES = \ @@ -1950,6 +1974,14 @@ unit_test_avrcp_OBJECTS = $(am_unit_test_avrcp_OBJECTS) unit_test_avrcp_DEPENDENCIES = lib/libbluetooth-internal.la \ src/libshared-glib.la $(am__DEPENDENCIES_1) +am_unit_test_bap_OBJECTS = unit/test-bap.$(OBJEXT) +unit_test_bap_OBJECTS = $(am_unit_test_bap_OBJECTS) +unit_test_bap_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) +am_unit_test_bass_OBJECTS = unit/test-bass.$(OBJEXT) +unit_test_bass_OBJECTS = $(am_unit_test_bass_OBJECTS) +unit_test_bass_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_crc_OBJECTS = unit/test-crc.$(OBJEXT) \ monitor/crc.$(OBJEXT) unit_test_crc_OBJECTS = $(am_unit_test_crc_OBJECTS) @@ -1973,10 +2005,10 @@ unit_test_gatt_DEPENDENCIES = src/libshared-glib.la \ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) am_unit_test_gattrib_OBJECTS = unit/test-gattrib.$(OBJEXT) \ - attrib/gattrib.$(OBJEXT) $(am__objects_34) src/log.$(OBJEXT) + attrib/gattrib.$(OBJEXT) $(am__objects_36) src/log.$(OBJEXT) unit_test_gattrib_OBJECTS = $(am_unit_test_gattrib_OBJECTS) -unit_test_gattrib_DEPENDENCIES = lib/libbluetooth-internal.la \ - src/libshared-glib.la $(am__DEPENDENCIES_1) \ +unit_test_gattrib_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) am_unit_test_gdbus_client_OBJECTS = unit/test-gdbus-client.$(OBJEXT) unit_test_gdbus_client_OBJECTS = $(am_unit_test_gdbus_client_OBJECTS) @@ -1989,7 +2021,7 @@ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex.c -@OBEX_TRUE@am_unit_test_gobex_OBJECTS = $(am__objects_33) \ +@OBEX_TRUE@am_unit_test_gobex_OBJECTS = $(am__objects_35) \ @OBEX_TRUE@ unit/util.$(OBJEXT) unit/test-gobex.$(OBJEXT) unit_test_gobex_OBJECTS = $(am_unit_test_gobex_OBJECTS) @OBEX_TRUE@unit_test_gobex_DEPENDENCIES = src/libshared-glib.la \ @@ -2000,7 +2032,7 @@ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex-apparam.c -@OBEX_TRUE@am_unit_test_gobex_apparam_OBJECTS = $(am__objects_33) \ +@OBEX_TRUE@am_unit_test_gobex_apparam_OBJECTS = $(am__objects_35) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-apparam.$(OBJEXT) unit_test_gobex_apparam_OBJECTS = \ @@ -2013,7 +2045,7 @@ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex-header.c -@OBEX_TRUE@am_unit_test_gobex_header_OBJECTS = $(am__objects_33) \ +@OBEX_TRUE@am_unit_test_gobex_header_OBJECTS = $(am__objects_35) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-header.$(OBJEXT) unit_test_gobex_header_OBJECTS = $(am_unit_test_gobex_header_OBJECTS) @@ -2025,7 +2057,7 @@ gobex/gobex-transfer.c gobex/gobex-debug.h \ gobex/gobex-apparam.c gobex/gobex-apparam.h unit/util.c \ unit/util.h unit/test-gobex-packet.c -@OBEX_TRUE@am_unit_test_gobex_packet_OBJECTS = $(am__objects_33) \ +@OBEX_TRUE@am_unit_test_gobex_packet_OBJECTS = $(am__objects_35) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-packet.$(OBJEXT) unit_test_gobex_packet_OBJECTS = $(am_unit_test_gobex_packet_OBJECTS) @@ -2038,7 +2070,7 @@ gobex/gobex-debug.h gobex/gobex-apparam.c \ gobex/gobex-apparam.h unit/util.c unit/util.h \ unit/test-gobex-transfer.c -@OBEX_TRUE@am_unit_test_gobex_transfer_OBJECTS = $(am__objects_33) \ +@OBEX_TRUE@am_unit_test_gobex_transfer_OBJECTS = $(am__objects_35) \ @OBEX_TRUE@ unit/util.$(OBJEXT) \ @OBEX_TRUE@ unit/test-gobex-transfer.$(OBJEXT) unit_test_gobex_transfer_OBJECTS = \ @@ -2049,7 +2081,7 @@ unit_test_hfp_OBJECTS = $(am_unit_test_hfp_OBJECTS) unit_test_hfp_DEPENDENCIES = src/libshared-glib.la \ $(am__DEPENDENCIES_1) -am_unit_test_hog_OBJECTS = unit/test-hog.$(OBJEXT) $(am__objects_34) \ +am_unit_test_hog_OBJECTS = unit/test-hog.$(OBJEXT) $(am__objects_36) \ profiles/input/hog-lib.$(OBJEXT) \ profiles/scanparam/scpp.$(OBJEXT) \ profiles/battery/bas.$(OBJEXT) \ @@ -2213,10 +2245,11 @@ client/$(DEPDIR)/advertising.Po client/$(DEPDIR)/agent.Po \ client/$(DEPDIR)/display.Po client/$(DEPDIR)/gatt.Po \ client/$(DEPDIR)/main.Po client/$(DEPDIR)/player.Po \ - ell/$(DEPDIR)/base64.Plo ell/$(DEPDIR)/cert-crypto.Plo \ - ell/$(DEPDIR)/cert.Plo ell/$(DEPDIR)/checksum.Plo \ - ell/$(DEPDIR)/cipher.Plo ell/$(DEPDIR)/dbus-client.Plo \ - ell/$(DEPDIR)/dbus-filter.Plo ell/$(DEPDIR)/dbus-message.Plo \ + client/$(DEPDIR)/print.Po ell/$(DEPDIR)/base64.Plo \ + ell/$(DEPDIR)/cert-crypto.Plo ell/$(DEPDIR)/cert.Plo \ + ell/$(DEPDIR)/checksum.Plo ell/$(DEPDIR)/cipher.Plo \ + ell/$(DEPDIR)/dbus-client.Plo ell/$(DEPDIR)/dbus-filter.Plo \ + ell/$(DEPDIR)/dbus-message.Plo \ ell/$(DEPDIR)/dbus-name-cache.Plo \ ell/$(DEPDIR)/dbus-service.Plo ell/$(DEPDIR)/dbus-util.Plo \ ell/$(DEPDIR)/dbus.Plo ell/$(DEPDIR)/ecc-external.Plo \ @@ -2276,7 +2309,9 @@ mesh/$(DEPDIR)/net-keys.Po mesh/$(DEPDIR)/net.Po \ mesh/$(DEPDIR)/node.Po mesh/$(DEPDIR)/pb-adv.Po \ mesh/$(DEPDIR)/prov-acceptor.Po \ - mesh/$(DEPDIR)/prov-initiator.Po mesh/$(DEPDIR)/rpl.Po \ + mesh/$(DEPDIR)/prov-initiator.Po \ + mesh/$(DEPDIR)/prvbeac-server.Po \ + mesh/$(DEPDIR)/remprv-server.Po mesh/$(DEPDIR)/rpl.Po \ mesh/$(DEPDIR)/util.Po monitor/$(DEPDIR)/a2dp.Po \ monitor/$(DEPDIR)/analyze.Po monitor/$(DEPDIR)/att.Po \ monitor/$(DEPDIR)/avctp.Po monitor/$(DEPDIR)/avdtp.Po \ @@ -2339,7 +2374,9 @@ profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-bap.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-bass.Po \ profiles/audio/$(DEPDIR)/bluetoothd-control.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-csip.Po \ profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po \ profiles/audio/$(DEPDIR)/bluetoothd-media.Po \ profiles/audio/$(DEPDIR)/bluetoothd-player.Po \ @@ -2411,6 +2448,7 @@ src/$(DEPDIR)/bluetoothd-sdpd-server.Po \ src/$(DEPDIR)/bluetoothd-sdpd-service.Po \ src/$(DEPDIR)/bluetoothd-service.Po \ + src/$(DEPDIR)/bluetoothd-set.Po \ src/$(DEPDIR)/bluetoothd-settings.Po \ src/$(DEPDIR)/bluetoothd-storage.Po \ src/$(DEPDIR)/bluetoothd-textfile.Po \ @@ -2428,8 +2466,10 @@ src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-att.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo \ + src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo \ + src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo \ src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo \ @@ -2455,8 +2495,10 @@ src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-att.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo \ + src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo \ + src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo \ src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo \ @@ -2484,8 +2526,10 @@ src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo \ + src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo \ + src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo \ src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo \ @@ -2582,6 +2626,7 @@ tools/parser/$(DEPDIR)/sdp.Po tools/parser/$(DEPDIR)/smp.Po \ tools/parser/$(DEPDIR)/tcpip.Po unit/$(DEPDIR)/test-avctp.Po \ unit/$(DEPDIR)/test-avdtp.Po unit/$(DEPDIR)/test-avrcp.Po \ + unit/$(DEPDIR)/test-bap.Po unit/$(DEPDIR)/test-bass.Po \ unit/$(DEPDIR)/test-crc.Po unit/$(DEPDIR)/test-crypto.Po \ unit/$(DEPDIR)/test-ecc.Po unit/$(DEPDIR)/test-eir.Po \ unit/$(DEPDIR)/test-gatt.Po unit/$(DEPDIR)/test-gattrib.Po \ @@ -2675,6 +2720,7 @@ $(tools_smp_tester_SOURCES) tools/test-runner.c \ $(tools_userchan_tester_SOURCES) $(unit_test_avctp_SOURCES) \ $(unit_test_avdtp_SOURCES) $(unit_test_avrcp_SOURCES) \ + $(unit_test_bap_SOURCES) $(unit_test_bass_SOURCES) \ $(unit_test_crc_SOURCES) $(unit_test_crypto_SOURCES) \ $(unit_test_ecc_SOURCES) $(unit_test_eir_SOURCES) \ $(unit_test_gatt_SOURCES) $(unit_test_gattrib_SOURCES) \ @@ -2776,7 +2822,8 @@ $(am__tools_smp_tester_SOURCES_DIST) tools/test-runner.c \ $(am__tools_userchan_tester_SOURCES_DIST) \ $(unit_test_avctp_SOURCES) $(unit_test_avdtp_SOURCES) \ - $(unit_test_avrcp_SOURCES) $(unit_test_crc_SOURCES) \ + $(unit_test_avrcp_SOURCES) $(unit_test_bap_SOURCES) \ + $(unit_test_bass_SOURCES) $(unit_test_crc_SOURCES) \ $(unit_test_crypto_SOURCES) $(unit_test_ecc_SOURCES) \ $(unit_test_eir_SOURCES) $(unit_test_gatt_SOURCES) \ $(unit_test_gattrib_SOURCES) $(unit_test_gdbus_client_SOURCES) \ @@ -3209,7 +3256,7 @@ # SPDX-License-Identifier: GPL-2.0 AM_MAKEFLAGS = --no-print-directory -AM_CPPFLAGS = $(am__append_59) $(DBUS_CFLAGS) $(GLIB_CFLAGS) \ +AM_CPPFLAGS = $(am__append_63) $(DBUS_CFLAGS) $(GLIB_CFLAGS) \ -I$(builddir)/lib lib_LTLIBRARIES = $(am__append_2) noinst_LIBRARIES = @@ -3220,13 +3267,13 @@ CLEANFILES = $(ell_built_sources) $(builtin_files) \ src/bluetooth.service tools/bluetooth-logger.service \ obexd/src/builtin.h $(builtin_files) obexd/src/obex.service \ - $(am__append_69) $(am__append_70) + $(am__append_73) $(am__append_74) EXTRA_DIST = src/bluetooth.service.in src/org.bluez.service \ $(am__append_22) src/genbuiltin src/bluetooth.conf \ src/main.conf profiles/network/network.conf \ profiles/input/input.conf tools/bluetooth-logger.service.in \ - $(am__append_47) obexd/src/obex.service.in \ - obexd/src/org.bluez.obex.service $(am__append_57) \ + $(am__append_51) obexd/src/obex.service.in \ + obexd/src/org.bluez.obex.service $(am__append_61) \ obexd/src/genbuiltin android/Android.mk android/README \ android/compat/readline/history.h \ android/compat/readline/readline.h android/compat/wordexp.h \ @@ -3292,28 +3339,28 @@ confdir = $(sysconfdir)/bluetooth statedir = $(localstatedir)/lib/bluetooth @DATAFILES_TRUE@dbusdir = $(DBUS_CONFDIR)/dbus-1/system.d -@DATAFILES_TRUE@dbus_DATA = src/bluetooth.conf $(am__append_63) +@DATAFILES_TRUE@dbus_DATA = src/bluetooth.conf $(am__append_67) @DATAFILES_TRUE@conf_DATA = @DATAFILES_TRUE@state_DATA = @SYSTEMD_TRUE@systemdsystemunitdir = $(SYSTEMD_SYSTEMUNITDIR) @SYSTEMD_TRUE@systemdsystemunit_DATA = src/bluetooth.service \ -@SYSTEMD_TRUE@ $(am__append_41) $(am__append_64) +@SYSTEMD_TRUE@ $(am__append_45) $(am__append_68) @SYSTEMD_TRUE@dbussystembusdir = $(DBUS_SYSTEMBUSDIR) @SYSTEMD_TRUE@dbussystembus_DATA = src/org.bluez.service \ -@SYSTEMD_TRUE@ $(am__append_65) +@SYSTEMD_TRUE@ $(am__append_69) plugindir = $(libdir)/bluetooth/plugins @MAINTAINER_MODE_FALSE@build_plugindir = $(plugindir) @MAINTAINER_MODE_TRUE@build_plugindir = $(abs_top_srcdir)/plugins/.libs -@MANPAGES_TRUE@man_MANS = src/bluetoothd.8 $(am__append_39) \ -@MANPAGES_TRUE@ $(am__append_45) $(am__append_50) \ -@MANPAGES_TRUE@ $(am__append_51) $(am__append_67) +@MANPAGES_TRUE@man_MANS = src/bluetoothd.8 $(am__append_43) \ +@MANPAGES_TRUE@ $(am__append_49) $(am__append_54) \ +@MANPAGES_TRUE@ $(am__append_55) $(am__append_71) manual_pages = src/bluetoothd.8 monitor/btmon.1 tools/hciattach.1 \ tools/hciconfig.1 tools/hcitool.1 tools/hcidump.1 \ tools/rfcomm.1 tools/sdptool.1 tools/ciptool.1 tools/rctest.1 \ tools/l2ping.1 tools/btattach.1 tools/bdaddr.1 tools/isotest.1 \ - tools/hid2hci.1 $(am__append_68) -plugin_LTLIBRARIES = $(am__append_29) $(am__append_36) \ - $(am__append_61) + tools/hid2hci.1 $(am__append_72) +plugin_LTLIBRARIES = $(am__append_29) $(am__append_40) \ + $(am__append_65) lib_sources = lib/bluetooth.c lib/hci.c lib/sdp.c lib_headers = lib/bluetooth.h lib/hci.h lib/hci_lib.h \ lib/sco.h lib/l2cap.h lib/sdp.h lib/sdp_lib.h \ @@ -3325,7 +3372,7 @@ BUILT_SOURCES = $(local_headers) $(ell_built_sources) src/builtin.h \ obexd/src/builtin.h @LIBRARY_TRUE@lib_libbluetooth_la_SOURCES = $(lib_headers) $(lib_sources) -@LIBRARY_TRUE@lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:8:19 +@LIBRARY_TRUE@lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:9:19 @LIBRARY_TRUE@lib_libbluetooth_la_DEPENDENCIES = $(local_headers) lib_libbluetooth_internal_la_SOURCES = $(lib_headers) $(lib_sources) \ $(extra_headers) $(extra_sources) @@ -3447,8 +3494,9 @@ src/shared/log.h src/shared/log.c src/shared/bap.h \ src/shared/bap.c src/shared/ascs.h src/shared/mcs.h \ src/shared/mcp.h src/shared/mcp.c src/shared/vcp.c \ - src/shared/vcp.h src/shared/lc3.h src/shared/tty.h \ - $(am__append_5) + src/shared/vcp.h src/shared/csip.c src/shared/csip.h \ + src/shared/bass.h src/shared/bass.c src/shared/lc3.h \ + src/shared/tty.h $(am__append_5) src_libshared_glib_la_SOURCES = $(shared_sources) \ src/shared/io-glib.c \ src/shared/timeout-glib.c \ @@ -3495,7 +3543,7 @@ $(am__append_14) $(am__append_16) $(am__append_18) \ $(am__append_20) $(am__append_23) gap scanparam deviceinfo \ $(am__append_25) battery $(am__append_30) $(am__append_32) \ - $(am__append_34) + $(am__append_34) $(am__append_36) $(am__append_38) builtin_sources = plugins/hostname.c plugins/wiimote.c \ plugins/autopair.c plugins/policy.c $(am__append_7) \ $(am__append_9) $(am__append_11) $(am__append_13) \ @@ -3503,7 +3551,8 @@ $(am__append_21) $(am__append_24) profiles/gap/gas.c \ profiles/scanparam/scan.c profiles/deviceinfo/deviceinfo.c \ $(am__append_26) profiles/battery/battery.c $(am__append_31) \ - $(am__append_33) $(am__append_35) + $(am__append_33) $(am__append_35) $(am__append_37) \ + $(am__append_39) builtin_cppflags = $(am__append_27) builtin_nodist = builtin_ldadd = $(am__append_28) @@ -3543,7 +3592,8 @@ src/eir.h src/eir.c \ src/adv_monitor.h src/adv_monitor.c \ src/battery.h src/battery.c \ - src/settings.h src/settings.c + src/settings.h src/settings.c \ + src/set.h src/set.c src_bluetoothd_LDADD = lib/libbluetooth-internal.la \ gdbus/libgdbus-internal.la \ @@ -3578,15 +3628,16 @@ test/map-client test/example-advertisement \ test/example-gatt-server test/example-gatt-client \ test/test-gatt-profile test/test-mesh test/agent.py -unit_tests = $(am__append_62) unit/test-tester unit/test-eir \ +unit_tests = $(am__append_66) unit/test-tester unit/test-eir \ unit/test-uuid unit/test-textfile unit/test-crc \ unit/test-crypto unit/test-ecc unit/test-ringbuf \ unit/test-queue unit/test-mgmt unit/test-uhid unit/test-sdp \ unit/test-avdtp unit/test-avctp unit/test-avrcp unit/test-hfp \ - unit/test-gdbus-client $(am__append_71) unit/test-lib \ - unit/test-gatt unit/test-hog unit/test-gattrib \ - $(am__append_72) $(am__append_73) + unit/test-gdbus-client $(am__append_75) unit/test-lib \ + unit/test-gatt unit/test-hog unit/test-gattrib unit/test-bap \ + unit/test-bass $(am__append_76) $(am__append_77) @CLIENT_TRUE@client_bluetoothctl_SOURCES = client/main.c \ +@CLIENT_TRUE@ client/print.h client/print.c \ @CLIENT_TRUE@ client/display.h client/display.c \ @CLIENT_TRUE@ client/agent.h client/agent.c \ @CLIENT_TRUE@ client/advertising.h \ @@ -3956,7 +4007,9 @@ @READLINE_TRUE@tools_obex_server_tool_LDADD = lib/libbluetooth-internal.la \ @READLINE_TRUE@ src/libshared-glib.la $(GLIB_LIBS) -@READLINE_TRUE@tools_bluetooth_player_SOURCES = tools/bluetooth-player.c client/player.c +@READLINE_TRUE@tools_bluetooth_player_SOURCES = tools/bluetooth-player.c client/print.c \ +@READLINE_TRUE@ client/player.c + @READLINE_TRUE@tools_bluetooth_player_LDADD = gdbus/libgdbus-internal.la \ @READLINE_TRUE@ src/libshared-glib.la \ @READLINE_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline @@ -4006,10 +4059,10 @@ @SYSTEMD_TRUE@dbussessionbus_DATA = obexd/src/org.bluez.obex.service @OBEX_TRUE@obex_plugindir = $(libdir)/obex/plugins @OBEX_TRUE@obexd_builtin_modules = filesystem bluetooth \ -@OBEX_TRUE@ $(am__append_55) opp ftp irmc pbap mas mns +@OBEX_TRUE@ $(am__append_59) opp ftp irmc pbap mas mns @OBEX_TRUE@obexd_builtin_sources = obexd/plugins/filesystem.c \ @OBEX_TRUE@ obexd/plugins/filesystem.h \ -@OBEX_TRUE@ obexd/plugins/bluetooth.c $(am__append_56) \ +@OBEX_TRUE@ obexd/plugins/bluetooth.c $(am__append_60) \ @OBEX_TRUE@ obexd/plugins/opp.c obexd/plugins/ftp.c \ @OBEX_TRUE@ obexd/plugins/ftp.h obexd/plugins/irmc.c \ @OBEX_TRUE@ obexd/plugins/pbap.c obexd/plugins/vcard.h \ @@ -4285,6 +4338,7 @@ @MESH_TRUE@ mesh/provision.h mesh/prov.h \ @MESH_TRUE@ mesh/model.h mesh/model.c \ @MESH_TRUE@ mesh/cfgmod.h mesh/cfgmod-server.c \ +@MESH_TRUE@ mesh/remprv.h mesh/remprv-server.c \ @MESH_TRUE@ mesh/mesh-config.h mesh/mesh-config-json.c \ @MESH_TRUE@ mesh/util.h mesh/util.c \ @MESH_TRUE@ mesh/dbus.h mesh/dbus.c \ @@ -4294,6 +4348,7 @@ @MESH_TRUE@ mesh/pb-adv.h mesh/pb-adv.c \ @MESH_TRUE@ mesh/keyring.h mesh/keyring.c \ @MESH_TRUE@ mesh/rpl.h mesh/rpl.c \ +@MESH_TRUE@ mesh/prv-beacon.h mesh/prvbeac-server.c \ @MESH_TRUE@ mesh/mesh-defs.h @MESH_TRUE@mesh_bluetooth_meshd_SOURCES = $(mesh_sources) mesh/main.c @@ -4410,9 +4465,17 @@ unit_test_gattrib_SOURCES = unit/test-gattrib.c attrib/gattrib.c \ $(btio_sources) src/log.h src/log.c -unit_test_gattrib_LDADD = lib/libbluetooth-internal.la \ - src/libshared-glib.la \ - $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt +unit_test_gattrib_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt + +unit_test_bap_SOURCES = unit/test-bap.c +unit_test_bap_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) + +unit_test_bass_SOURCES = unit/test-bass.c +unit_test_bass_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) @MIDI_TRUE@unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST @MIDI_TRUE@unit_test_midi_SOURCES = unit/test-midi.c \ @@ -4428,7 +4491,7 @@ @MESH_TRUE@unit_test_mesh_crypto_LDADD = $(ell_ldadd) AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69 \ - $(am__append_75) + $(am__append_79) @VALGRIND_TRUE@LOG_COMPILER = valgrind --error-exitcode=1 --num-callers=30 @VALGRIND_TRUE@LOG_FLAGS = --trace-children=yes --leak-check=full --show-reachable=no \ @VALGRIND_TRUE@ --suppressions=$(srcdir)/tools/valgrind.supp --quiet @@ -5029,6 +5092,10 @@ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-vcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/libshared_ell_la-csip.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/libshared_ell_la-bass.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-shell.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_ell_la-io-ell.lo: src/shared/$(am__dirstamp) \ @@ -5095,6 +5162,10 @@ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-vcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/libshared_glib_la-csip.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/libshared_glib_la-bass.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-shell.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_glib_la-io-glib.lo: src/shared/$(am__dirstamp) \ @@ -5167,6 +5238,10 @@ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-vcp.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/libshared_mainloop_la-csip.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/libshared_mainloop_la-bass.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-shell.lo: src/shared/$(am__dirstamp) \ src/shared/$(DEPDIR)/$(am__dirstamp) src/shared/libshared_mainloop_la-io-mainloop.lo: \ @@ -5511,6 +5586,8 @@ $(AM_V_CCLD)$(LINK) $(attrib_gatttool_OBJECTS) $(attrib_gatttool_LDADD) $(LIBS) client/main.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) +client/print.$(OBJEXT): client/$(am__dirstamp) \ + client/$(DEPDIR)/$(am__dirstamp) client/agent.$(OBJEXT): client/$(am__dirstamp) \ client/$(DEPDIR)/$(am__dirstamp) client/advertising.$(OBJEXT): client/$(am__dirstamp) \ @@ -5597,6 +5674,8 @@ mesh/$(DEPDIR)/$(am__dirstamp) mesh/cfgmod-server.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) +mesh/remprv-server.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) mesh/mesh-config-json.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) mesh/util.$(OBJEXT): mesh/$(am__dirstamp) \ @@ -5617,6 +5696,8 @@ mesh/$(DEPDIR)/$(am__dirstamp) mesh/rpl.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) +mesh/prvbeac-server.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) mesh/main.$(OBJEXT): mesh/$(am__dirstamp) \ mesh/$(DEPDIR)/$(am__dirstamp) @@ -6002,12 +6083,18 @@ profiles/audio/bluetoothd-bap.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-bass.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-mcp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) profiles/audio/bluetoothd-vcp.$(OBJEXT): \ profiles/audio/$(am__dirstamp) \ profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-csip.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) attrib/bluetoothd-att.$(OBJEXT): attrib/$(am__dirstamp) \ attrib/$(DEPDIR)/$(am__dirstamp) attrib/bluetoothd-gatt.$(OBJEXT): attrib/$(am__dirstamp) \ @@ -6072,6 +6159,8 @@ src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd-settings.$(OBJEXT): src/$(am__dirstamp) \ src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-set.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) src/bluetoothd$(EXEEXT): $(src_bluetoothd_OBJECTS) $(src_bluetoothd_DEPENDENCIES) $(EXTRA_src_bluetoothd_DEPENDENCIES) src/$(am__dirstamp) @rm -f src/bluetoothd$(EXEEXT) @@ -6660,6 +6749,18 @@ unit/test-avrcp$(EXEEXT): $(unit_test_avrcp_OBJECTS) $(unit_test_avrcp_DEPENDENCIES) $(EXTRA_unit_test_avrcp_DEPENDENCIES) unit/$(am__dirstamp) @rm -f unit/test-avrcp$(EXEEXT) $(AM_V_CCLD)$(LINK) $(unit_test_avrcp_OBJECTS) $(unit_test_avrcp_LDADD) $(LIBS) +unit/test-bap.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-bap$(EXEEXT): $(unit_test_bap_OBJECTS) $(unit_test_bap_DEPENDENCIES) $(EXTRA_unit_test_bap_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-bap$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_bap_OBJECTS) $(unit_test_bap_LDADD) $(LIBS) +unit/test-bass.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-bass$(EXEEXT): $(unit_test_bass_OBJECTS) $(unit_test_bass_DEPENDENCIES) $(EXTRA_unit_test_bass_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-bass$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_bass_OBJECTS) $(unit_test_bass_LDADD) $(LIBS) unit/test-crc.$(OBJEXT): unit/$(am__dirstamp) \ unit/$(DEPDIR)/$(am__dirstamp) @@ -7001,6 +7102,7 @@ @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/player.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/print.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/base64.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/cert-crypto.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/cert.Plo@am__quote@ # am--include-marker @@ -7108,6 +7210,8 @@ @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/pb-adv.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prov-acceptor.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prov-initiator.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prvbeac-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/remprv-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/rpl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/a2dp.Po@am__quote@ # am--include-marker @@ -7187,7 +7291,9 @@ @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-bap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-bass.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-control.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-csip.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-media.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-player.Po@am__quote@ # am--include-marker @@ -7260,6 +7366,7 @@ @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-service.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-set.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-storage.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-textfile.Po@am__quote@ # am--include-marker @@ -7283,8 +7390,10 @@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-att.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo@am__quote@ # am--include-marker @@ -7310,8 +7419,10 @@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-att.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo@am__quote@ # am--include-marker @@ -7339,8 +7450,10 @@ @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo@am__quote@ # am--include-marker @@ -7484,6 +7597,8 @@ @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avctp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avdtp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avrcp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-bap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-bass.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-crc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-crypto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-ecc.Po@am__quote@ # am--include-marker @@ -7856,6 +7971,20 @@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c +src/shared/libshared_ell_la-csip.lo: src/shared/csip.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-csip.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-csip.Tpo -c -o src/shared/libshared_ell_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-csip.Tpo src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/csip.c' object='src/shared/libshared_ell_la-csip.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c + +src/shared/libshared_ell_la-bass.lo: src/shared/bass.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-bass.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-bass.Tpo -c -o src/shared/libshared_ell_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-bass.Tpo src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bass.c' object='src/shared/libshared_ell_la-bass.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_ell_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c + src/shared/libshared_ell_la-shell.lo: src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_ell_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_ell_la-shell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_ell_la-shell.Tpo -c -o src/shared/libshared_ell_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_ell_la-shell.Tpo src/shared/$(DEPDIR)/libshared_ell_la-shell.Plo @@ -8045,6 +8174,20 @@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c +src/shared/libshared_glib_la-csip.lo: src/shared/csip.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-csip.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-csip.Tpo -c -o src/shared/libshared_glib_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-csip.Tpo src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/csip.c' object='src/shared/libshared_glib_la-csip.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c + +src/shared/libshared_glib_la-bass.lo: src/shared/bass.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-bass.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-bass.Tpo -c -o src/shared/libshared_glib_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-bass.Tpo src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bass.c' object='src/shared/libshared_glib_la-bass.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_glib_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c + src/shared/libshared_glib_la-shell.lo: src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_glib_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_glib_la-shell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_glib_la-shell.Tpo -c -o src/shared/libshared_glib_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_glib_la-shell.Tpo src/shared/$(DEPDIR)/libshared_glib_la-shell.Plo @@ -8248,6 +8391,20 @@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-vcp.lo `test -f 'src/shared/vcp.c' || echo '$(srcdir)/'`src/shared/vcp.c +src/shared/libshared_mainloop_la-csip.lo: src/shared/csip.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-csip.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Tpo -c -o src/shared/libshared_mainloop_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/csip.c' object='src/shared/libshared_mainloop_la-csip.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-csip.lo `test -f 'src/shared/csip.c' || echo '$(srcdir)/'`src/shared/csip.c + +src/shared/libshared_mainloop_la-bass.lo: src/shared/bass.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-bass.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Tpo -c -o src/shared/libshared_mainloop_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/bass.c' object='src/shared/libshared_mainloop_la-bass.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -c -o src/shared/libshared_mainloop_la-bass.lo `test -f 'src/shared/bass.c' || echo '$(srcdir)/'`src/shared/bass.c + src/shared/libshared_mainloop_la-shell.lo: src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(src_libshared_mainloop_la_CFLAGS) $(CFLAGS) -MT src/shared/libshared_mainloop_la-shell.lo -MD -MP -MF src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Tpo -c -o src/shared/libshared_mainloop_la-shell.lo `test -f 'src/shared/shell.c' || echo '$(srcdir)/'`src/shared/shell.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Tpo src/shared/$(DEPDIR)/libshared_mainloop_la-shell.Plo @@ -10201,6 +10358,20 @@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-bap.obj `if test -f 'profiles/audio/bap.c'; then $(CYGPATH_W) 'profiles/audio/bap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/bap.c'; fi` +profiles/audio/bluetoothd-bass.o: profiles/audio/bass.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-bass.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo -c -o profiles/audio/bluetoothd-bass.o `test -f 'profiles/audio/bass.c' || echo '$(srcdir)/'`profiles/audio/bass.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo profiles/audio/$(DEPDIR)/bluetoothd-bass.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/bass.c' object='profiles/audio/bluetoothd-bass.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-bass.o `test -f 'profiles/audio/bass.c' || echo '$(srcdir)/'`profiles/audio/bass.c + +profiles/audio/bluetoothd-bass.obj: profiles/audio/bass.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-bass.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo -c -o profiles/audio/bluetoothd-bass.obj `if test -f 'profiles/audio/bass.c'; then $(CYGPATH_W) 'profiles/audio/bass.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/bass.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-bass.Tpo profiles/audio/$(DEPDIR)/bluetoothd-bass.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/bass.c' object='profiles/audio/bluetoothd-bass.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-bass.obj `if test -f 'profiles/audio/bass.c'; then $(CYGPATH_W) 'profiles/audio/bass.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/bass.c'; fi` + profiles/audio/bluetoothd-mcp.o: profiles/audio/mcp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-mcp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-mcp.Tpo -c -o profiles/audio/bluetoothd-mcp.o `test -f 'profiles/audio/mcp.c' || echo '$(srcdir)/'`profiles/audio/mcp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-mcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po @@ -10229,6 +10400,20 @@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-vcp.obj `if test -f 'profiles/audio/vcp.c'; then $(CYGPATH_W) 'profiles/audio/vcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/vcp.c'; fi` +profiles/audio/bluetoothd-csip.o: profiles/audio/csip.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-csip.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo -c -o profiles/audio/bluetoothd-csip.o `test -f 'profiles/audio/csip.c' || echo '$(srcdir)/'`profiles/audio/csip.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo profiles/audio/$(DEPDIR)/bluetoothd-csip.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/csip.c' object='profiles/audio/bluetoothd-csip.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-csip.o `test -f 'profiles/audio/csip.c' || echo '$(srcdir)/'`profiles/audio/csip.c + +profiles/audio/bluetoothd-csip.obj: profiles/audio/csip.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-csip.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo -c -o profiles/audio/bluetoothd-csip.obj `if test -f 'profiles/audio/csip.c'; then $(CYGPATH_W) 'profiles/audio/csip.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/csip.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-csip.Tpo profiles/audio/$(DEPDIR)/bluetoothd-csip.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/csip.c' object='profiles/audio/bluetoothd-csip.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-csip.obj `if test -f 'profiles/audio/csip.c'; then $(CYGPATH_W) 'profiles/audio/csip.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/csip.c'; fi` + attrib/bluetoothd-att.o: attrib/att.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-att.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-att.Tpo -c -o attrib/bluetoothd-att.o `test -f 'attrib/att.c' || echo '$(srcdir)/'`attrib/att.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-att.Tpo attrib/$(DEPDIR)/bluetoothd-att.Po @@ -10677,6 +10862,20 @@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-settings.obj `if test -f 'src/settings.c'; then $(CYGPATH_W) 'src/settings.c'; else $(CYGPATH_W) '$(srcdir)/src/settings.c'; fi` +src/bluetoothd-set.o: src/set.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-set.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-set.Tpo -c -o src/bluetoothd-set.o `test -f 'src/set.c' || echo '$(srcdir)/'`src/set.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-set.Tpo src/$(DEPDIR)/bluetoothd-set.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/set.c' object='src/bluetoothd-set.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-set.o `test -f 'src/set.c' || echo '$(srcdir)/'`src/set.c + +src/bluetoothd-set.obj: src/set.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-set.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-set.Tpo -c -o src/bluetoothd-set.obj `if test -f 'src/set.c'; then $(CYGPATH_W) 'src/set.c'; else $(CYGPATH_W) '$(srcdir)/src/set.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-set.Tpo src/$(DEPDIR)/bluetoothd-set.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/set.c' object='src/bluetoothd-set.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-set.obj `if test -f 'src/set.c'; then $(CYGPATH_W) 'src/set.c'; else $(CYGPATH_W) '$(srcdir)/src/set.c'; fi` + unit/test_mesh_crypto-test-mesh-crypto.o: unit/test-mesh-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_mesh_crypto-test-mesh-crypto.o -MD -MP -MF unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo -c -o unit/test_mesh_crypto-test-mesh-crypto.o `test -f 'unit/test-mesh-crypto.c' || echo '$(srcdir)/'`unit/test-mesh-crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po @@ -11453,6 +11652,20 @@ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-bap.log: unit/test-bap$(EXEEXT) + @p='unit/test-bap$(EXEEXT)'; \ + b='unit/test-bap'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-bass.log: unit/test-bass$(EXEEXT) + @p='unit/test-bass$(EXEEXT)'; \ + b='unit/test-bass'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) unit/test-midi.log: unit/test-midi$(EXEEXT) @p='unit/test-midi$(EXEEXT)'; \ b='unit/test-midi'; \ @@ -11896,6 +12109,7 @@ -rm -f client/$(DEPDIR)/gatt.Po -rm -f client/$(DEPDIR)/main.Po -rm -f client/$(DEPDIR)/player.Po + -rm -f client/$(DEPDIR)/print.Po -rm -f ell/$(DEPDIR)/base64.Plo -rm -f ell/$(DEPDIR)/cert-crypto.Plo -rm -f ell/$(DEPDIR)/cert.Plo @@ -12003,6 +12217,8 @@ -rm -f mesh/$(DEPDIR)/pb-adv.Po -rm -f mesh/$(DEPDIR)/prov-acceptor.Po -rm -f mesh/$(DEPDIR)/prov-initiator.Po + -rm -f mesh/$(DEPDIR)/prvbeac-server.Po + -rm -f mesh/$(DEPDIR)/remprv-server.Po -rm -f mesh/$(DEPDIR)/rpl.Po -rm -f mesh/$(DEPDIR)/util.Po -rm -f monitor/$(DEPDIR)/a2dp.Po @@ -12082,7 +12298,9 @@ -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bap.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bass.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-control.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-csip.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-media.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-player.Po @@ -12155,6 +12373,7 @@ -rm -f src/$(DEPDIR)/bluetoothd-sdpd-server.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-service.Po -rm -f src/$(DEPDIR)/bluetoothd-service.Po + -rm -f src/$(DEPDIR)/bluetoothd-set.Po -rm -f src/$(DEPDIR)/bluetoothd-settings.Po -rm -f src/$(DEPDIR)/bluetoothd-storage.Po -rm -f src/$(DEPDIR)/bluetoothd-textfile.Po @@ -12178,8 +12397,10 @@ -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo + -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo + -rm -f src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo @@ -12205,8 +12426,10 @@ -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo + -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo + -rm -f src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo @@ -12234,8 +12457,10 @@ -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo + -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo + -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo @@ -12379,6 +12604,8 @@ -rm -f unit/$(DEPDIR)/test-avctp.Po -rm -f unit/$(DEPDIR)/test-avdtp.Po -rm -f unit/$(DEPDIR)/test-avrcp.Po + -rm -f unit/$(DEPDIR)/test-bap.Po + -rm -f unit/$(DEPDIR)/test-bass.Po -rm -f unit/$(DEPDIR)/test-crc.Po -rm -f unit/$(DEPDIR)/test-crypto.Po -rm -f unit/$(DEPDIR)/test-ecc.Po @@ -12558,6 +12785,7 @@ -rm -f client/$(DEPDIR)/gatt.Po -rm -f client/$(DEPDIR)/main.Po -rm -f client/$(DEPDIR)/player.Po + -rm -f client/$(DEPDIR)/print.Po -rm -f ell/$(DEPDIR)/base64.Plo -rm -f ell/$(DEPDIR)/cert-crypto.Plo -rm -f ell/$(DEPDIR)/cert.Plo @@ -12665,6 +12893,8 @@ -rm -f mesh/$(DEPDIR)/pb-adv.Po -rm -f mesh/$(DEPDIR)/prov-acceptor.Po -rm -f mesh/$(DEPDIR)/prov-initiator.Po + -rm -f mesh/$(DEPDIR)/prvbeac-server.Po + -rm -f mesh/$(DEPDIR)/remprv-server.Po -rm -f mesh/$(DEPDIR)/rpl.Po -rm -f mesh/$(DEPDIR)/util.Po -rm -f monitor/$(DEPDIR)/a2dp.Po @@ -12744,7 +12974,9 @@ -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bap.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-bass.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-control.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-csip.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-mcp.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-media.Po -rm -f profiles/audio/$(DEPDIR)/bluetoothd-player.Po @@ -12817,6 +13049,7 @@ -rm -f src/$(DEPDIR)/bluetoothd-sdpd-server.Po -rm -f src/$(DEPDIR)/bluetoothd-sdpd-service.Po -rm -f src/$(DEPDIR)/bluetoothd-service.Po + -rm -f src/$(DEPDIR)/bluetoothd-set.Po -rm -f src/$(DEPDIR)/bluetoothd-settings.Po -rm -f src/$(DEPDIR)/bluetoothd-storage.Po -rm -f src/$(DEPDIR)/bluetoothd-textfile.Po @@ -12840,8 +13073,10 @@ -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bap.Plo + -rm -f src/shared/$(DEPDIR)/libshared_ell_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-crypto.Plo + -rm -f src/shared/$(DEPDIR)/libshared_ell_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_ell_la-gatt-client.Plo @@ -12867,8 +13102,10 @@ -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bap.Plo + -rm -f src/shared/$(DEPDIR)/libshared_glib_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-crypto.Plo + -rm -f src/shared/$(DEPDIR)/libshared_glib_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_glib_la-gatt-client.Plo @@ -12896,8 +13133,10 @@ -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ad.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-att.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bap.Plo + -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-bass.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-btsnoop.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-crypto.Plo + -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-csip.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-ecc.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gap.Plo -rm -f src/shared/$(DEPDIR)/libshared_mainloop_la-gatt-client.Plo @@ -13041,6 +13280,8 @@ -rm -f unit/$(DEPDIR)/test-avctp.Po -rm -f unit/$(DEPDIR)/test-avdtp.Po -rm -f unit/$(DEPDIR)/test-avrcp.Po + -rm -f unit/$(DEPDIR)/test-bap.Po + -rm -f unit/$(DEPDIR)/test-bass.Po -rm -f unit/$(DEPDIR)/test-crc.Po -rm -f unit/$(DEPDIR)/test-crypto.Po -rm -f unit/$(DEPDIR)/test-ecc.Po diff -Nru bluez-5.66/Makefile.mesh bluez-5.68/Makefile.mesh --- bluez-5.66/Makefile.mesh 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/Makefile.mesh 2023-06-30 08:10:19.000000000 +0000 @@ -26,6 +26,7 @@ mesh/provision.h mesh/prov.h \ mesh/model.h mesh/model.c \ mesh/cfgmod.h mesh/cfgmod-server.c \ + mesh/remprv.h mesh/remprv-server.c \ mesh/mesh-config.h mesh/mesh-config-json.c \ mesh/util.h mesh/util.c \ mesh/dbus.h mesh/dbus.c \ @@ -35,6 +36,7 @@ mesh/pb-adv.h mesh/pb-adv.c \ mesh/keyring.h mesh/keyring.c \ mesh/rpl.h mesh/rpl.c \ + mesh/prv-beacon.h mesh/prvbeac-server.c \ mesh/mesh-defs.h pkglibexec_PROGRAMS += mesh/bluetooth-meshd diff -Nru bluez-5.66/Makefile.plugins bluez-5.68/Makefile.plugins --- bluez-5.66/Makefile.plugins 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/Makefile.plugins 2023-06-30 08:10:19.000000000 +0000 @@ -122,6 +122,11 @@ builtin_sources += profiles/audio/bap.c endif +if BASS +builtin_modules += bass +builtin_sources += profiles/audio/bass.c +endif + if MCP builtin_modules += mcp builtin_sources += profiles/audio/mcp.c @@ -131,3 +136,8 @@ builtin_modules += vcp builtin_sources += profiles/audio/vcp.c endif + +if CSIP +builtin_modules += csip +builtin_sources += profiles/audio/csip.c +endif diff -Nru bluez-5.66/Makefile.tools bluez-5.68/Makefile.tools --- bluez-5.66/Makefile.tools 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/Makefile.tools 2023-06-30 08:10:19.000000000 +0000 @@ -3,6 +3,7 @@ bin_PROGRAMS += client/bluetoothctl client_bluetoothctl_SOURCES = client/main.c \ + client/print.h client/print.c \ client/display.h client/display.c \ client/agent.h client/agent.c \ client/advertising.h \ @@ -494,7 +495,8 @@ tools_obex_server_tool_LDADD = lib/libbluetooth-internal.la \ src/libshared-glib.la $(GLIB_LIBS) -tools_bluetooth_player_SOURCES = tools/bluetooth-player.c client/player.c +tools_bluetooth_player_SOURCES = tools/bluetooth-player.c client/print.c \ + client/player.c tools_bluetooth_player_LDADD = gdbus/libgdbus-internal.la \ src/libshared-glib.la \ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline diff -Nru bluez-5.66/mesh/bluetooth-meshd.rst.in bluez-5.68/mesh/bluetooth-meshd.rst.in --- bluez-5.66/mesh/bluetooth-meshd.rst.in 2021-06-13 19:56:36.000000000 +0000 +++ bluez-5.68/mesh/bluetooth-meshd.rst.in 2023-06-30 08:10:20.000000000 +0000 @@ -36,14 +36,17 @@ -i , --io Specifies I/O interface type: - *hci* - Use generic HCI io on interface hci, - or, if no idex is specified, the first available one. + *auto* - Use first available controller: via MGMT interface + if kernel supports it, otherwise, via raw HCI socket. + + *generic:[hci]* - Use generic HCI io on interface + hci. *unit:*- Specifies open file descriptor for daemon testing. - By default, if no type is specified, uses generic I/O - on the first available HCI interface. + By default, if no type is specified, uses auto I/O + on the first available controller. -c , --config Specifies an explicit config file path instead of relying on the diff -Nru bluez-5.66/mesh/cfgmod-server.c bluez-5.68/mesh/cfgmod-server.c --- bluez-5.66/mesh/cfgmod-server.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/cfgmod-server.c 2023-06-30 08:10:20.000000000 +0000 @@ -30,8 +30,8 @@ (SET_ID(SIG_VENDOR, l_get_le16(pkt)))) /* Supported composition pages, sorted high to low */ -/* Only page 0 is currently supported */ static const uint8_t supported_pages[] = { + 128, 0 }; @@ -736,7 +736,7 @@ static uint16_t get_composition(struct mesh_node *node, uint8_t page, uint8_t *buf) { - const uint8_t *comp; + const uint8_t *comp = NULL; uint16_t len = 0; size_t i; @@ -751,7 +751,7 @@ break; } - if (!len) + if (!len || !comp) return 0; *buf++ = page; diff -Nru bluez-5.66/mesh/crypto.c bluez-5.68/mesh/crypto.c --- bluez-5.66/mesh/crypto.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/mesh/crypto.c 2023-06-30 08:10:20.000000000 +0000 @@ -94,13 +94,6 @@ result = l_aead_cipher_encrypt(cipher, msg, msg_len, aad, aad_len, nonce, 13, out_msg, msg_len + mic_size); - if (result && out_mic) { - if (mic_size == 4) - *(uint32_t *)out_mic = l_get_be32(out_msg + msg_len); - else - *(uint64_t *)out_mic = l_get_be64(out_msg + msg_len); - } - l_aead_cipher_free(cipher); return result; @@ -251,9 +244,9 @@ return crypto_128(n, "nkbk", beacon_key); } -bool mesh_crypto_nkpk(const uint8_t n[16], uint8_t proxy_key[16]) +bool mesh_crypto_nkpk(const uint8_t n[16], uint8_t private_key[16]) { - return crypto_128(n, "nkpk", proxy_key); + return crypto_128(n, "nkpk", private_key); } bool mesh_crypto_k3(const uint8_t n[16], uint8_t out64[8]) @@ -1020,7 +1013,7 @@ 0x9a, 0x2a, 0xbf, 0x96 }; -bool mesh_crypto_check_avail() +bool mesh_crypto_check_avail(void) { void *cipher; bool result; diff -Nru bluez-5.66/mesh/crypto.h bluez-5.68/mesh/crypto.h --- bluez-5.66/mesh/crypto.h 2021-06-13 19:56:36.000000000 +0000 +++ bluez-5.68/mesh/crypto.h 2023-06-30 08:10:20.000000000 +0000 @@ -26,7 +26,7 @@ const uint8_t plaintext[16], uint8_t encrypted[16]); bool mesh_crypto_nkik(const uint8_t network_key[16], uint8_t identity_key[16]); bool mesh_crypto_nkbk(const uint8_t network_key[16], uint8_t beacon_key[16]); -bool mesh_crypto_nkpk(const uint8_t network_key[16], uint8_t proxy_key[16]); +bool mesh_crypto_nkpk(const uint8_t network_key[16], uint8_t private_key[16]); bool mesh_crypto_identity(const uint8_t net_key[16], uint16_t addr, uint8_t id[16]); bool mesh_crypto_beacon_cmac(const uint8_t encryption_key[16], diff -Nru bluez-5.66/mesh/keyring.c bluez-5.68/mesh/keyring.c --- bluez-5.66/mesh/keyring.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/mesh/keyring.c 2023-06-30 08:10:20.000000000 +0000 @@ -30,9 +30,9 @@ #include "mesh/node.h" #include "mesh/keyring.h" -const char *dev_key_dir = "/dev_keys"; -const char *app_key_dir = "/app_keys"; -const char *net_key_dir = "/net_keys"; +static const char *dev_key_dir = "/dev_keys"; +static const char *app_key_dir = "/app_keys"; +static const char *net_key_dir = "/net_keys"; static int open_key_file(struct mesh_node *node, const char *key_dir, uint16_t idx, int flags) @@ -370,6 +370,28 @@ return true; } + +bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast) +{ + uint8_t dev_key[16]; + uint8_t test_key[16]; + uint8_t cnt = 1; + + if (!keyring_get_remote_dev_key(node, unicast, dev_key)) + return false; + + while (keyring_get_remote_dev_key(node, unicast + cnt, test_key)) { + if (memcmp(dev_key, test_key, sizeof(dev_key))) + break; + + cnt++; + } + + if (cnt > 1) + return keyring_del_remote_dev_key(node, unicast + 1, cnt - 1); + + return true; +} static DIR *open_key_dir(const char *node_path, const char *key_dir_name) { diff -Nru bluez-5.66/mesh/keyring.h bluez-5.68/mesh/keyring.h --- bluez-5.66/mesh/keyring.h 2021-10-13 18:38:34.000000000 +0000 +++ bluez-5.68/mesh/keyring.h 2023-06-30 08:10:20.000000000 +0000 @@ -39,5 +39,6 @@ uint8_t count, uint8_t dev_key[16]); bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t count); +bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast); bool keyring_build_export_keys_reply(struct mesh_node *node, struct l_dbus_message_builder *builder); diff -Nru bluez-5.66/mesh/main.c bluez-5.68/mesh/main.c --- bluez-5.66/mesh/main.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/main.c 2023-06-30 08:10:20.000000000 +0000 @@ -48,6 +48,12 @@ { } }; +static const char *io_usage = + "\t(auto | generic:[hci] | unit:)\n" + "\t\tauto - Use first available controller (MGMT or raw HCI)\n" + "\t\tgeneric - Use raw HCI io on interface hci\n" + "\t\tunit - Use test IO (for automatic testing only)\n"; + static void usage(void) { fprintf(stderr, @@ -55,18 +61,14 @@ "\tbluetooth-meshd [options]\n"); fprintf(stderr, "Options:\n" - "\t--io Use specified io (default: generic)\n" + "\t--io Use specified io (default: auto)\n" "\t--config Daemon configuration directory\n" "\t--storage Mesh node(s) configuration directory\n" "\t--nodetach Run in foreground\n" "\t--debug Enable debug output\n" "\t--dbus-debug Enable D-Bus debugging\n" "\t--help Show %s information\n", __func__); - fprintf(stderr, - "io:\n" - "\t([hci] | generic[:[hci]] | unit:)\n" - "\t\tUse generic HCI io on interface hci, or the first\n" - "\t\tavailable one\n"); + fprintf(stderr, "\n\t io: %s", io_usage); } static void do_debug(const char *str, void *user_data) @@ -157,21 +159,8 @@ *opts = index; optarg += strlen("auto"); - if (!*optarg) { - *index = MGMT_INDEX_NONE; - return true; - } - - if (*optarg != ':') - return false; - - optarg++; - - if (sscanf(optarg, "hci%d", index) == 1) - return true; - - if (sscanf(optarg, "%d", index) == 1) - return true; + *index = MGMT_INDEX_NONE; + return true; return false; } else if (strstr(optarg, "generic") == optarg) { @@ -181,12 +170,7 @@ *opts = index; optarg += strlen("generic"); - if (!*optarg) { - *index = MGMT_INDEX_NONE; - return true; - } - - if (*optarg != ':') + if (!*optarg || *optarg != ':') return false; optarg++; @@ -291,7 +275,7 @@ io = l_strdup_printf("auto"); if (!parse_io(io, &io_type, &io_opts)) { - l_error("Invalid io: %s", io); + l_error("Invalid io: %s\n%s", io, io_usage); status = EXIT_FAILURE; goto done; } diff -Nru bluez-5.66/mesh/manager.c bluez-5.68/mesh/manager.c --- bluez-5.66/mesh/manager.c 2021-10-13 18:38:34.000000000 +0000 +++ bluez-5.68/mesh/manager.c 2023-06-30 08:10:20.000000000 +0000 @@ -21,75 +21,137 @@ #include "mesh/mesh.h" #include "mesh/mesh-io.h" #include "mesh/node.h" +#include "mesh/model.h" #include "mesh/net.h" #include "mesh/keyring.h" #include "mesh/agent.h" #include "mesh/provision.h" +#include "mesh/prov.h" +#include "mesh/remprv.h" #include "mesh/manager.h" -struct add_data{ +struct prov_remote_data { struct l_dbus_message *msg; struct mesh_agent *agent; struct mesh_node *node; uint32_t disc_watch; + uint16_t original; uint16_t primary; uint16_t net_idx; + uint8_t transport; uint8_t num_ele; uint8_t uuid[16]; }; -static int8_t scan_rssi; -static uint8_t scan_uuid[16]; -static struct mesh_node *scan_node; -static struct l_timeout *scan_timeout; -static struct add_data *add_pending; +struct scan_req { + struct mesh_node *node; + struct l_timeout *timeout; + uint16_t server; + uint16_t net_idx; + uint8_t uuid[16]; + int8_t rssi; + bool ext; +}; + +static struct l_queue *scans; +static struct prov_remote_data *prov_pending; static const uint8_t prvb[2] = {MESH_AD_TYPE_BEACON, 0x00}; +static bool by_scan(const void *a, const void *b) +{ + return a == b; +} + +static bool by_node(const void *a, const void *b) +{ + const struct scan_req *req = a; + const struct mesh_node *node = b; + + return req->node == node; +} + +static bool by_node_svr(const void *a, const void *b) +{ + const struct scan_req *req = a; + const struct scan_req *test = b; + + return req->node == test->node && req->server == test->server; +} + static void scan_cancel(struct l_timeout *timeout, void *user_data) { - struct mesh_node *node = user_data; + struct scan_req *req = user_data; struct mesh_io *io; struct mesh_net *net; + uint8_t msg[4]; + int n; l_debug(""); - if (scan_timeout) - l_timeout_remove(scan_timeout); + req = l_queue_remove_if(scans, by_scan, req); + + if (!req) + return; - net = node_get_net(node); - io = mesh_net_get_io(net); - mesh_io_deregister_recv_cb(io, prvb, sizeof(prvb)); - scan_node = NULL; - scan_timeout = NULL; + l_timeout_remove(req->timeout); + + if (req->server) { + n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STOP, msg); + mesh_model_send(req->node, 0, req->server, APP_IDX_DEV_REMOTE, + req->net_idx, DEFAULT_TTL, + true, n, msg); + } else { + net = node_get_net(req->node); + io = mesh_net_get_io(net); + mesh_io_deregister_recv_cb(io, prvb, sizeof(prvb)); + } + + initiator_scan_unreg(req->node); + l_free(req); } -static void free_pending_add_call() +static void free_pending_add_call(void) { - if (!add_pending) + if (!prov_pending) return; - if (add_pending->disc_watch) + if (prov_pending->disc_watch) l_dbus_remove_watch(dbus_get_bus(), - add_pending->disc_watch); + prov_pending->disc_watch); - if (add_pending->msg) - l_dbus_message_unref(add_pending->msg); + if (prov_pending->msg) + l_dbus_message_unref(prov_pending->msg); - l_free(add_pending); - add_pending = NULL; + l_free(prov_pending); + prov_pending = NULL; } static void prov_disc_cb(struct l_dbus *bus, void *user_data) { - if (!add_pending) + if (!prov_pending) return; - initiator_cancel(add_pending); - add_pending->disc_watch = 0; + initiator_cancel(prov_pending); + prov_pending->disc_watch = 0; free_pending_add_call(); } +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 send_add_failed(const char *owner, const char *path, uint8_t status) { @@ -102,7 +164,7 @@ "AddNodeFailed"); builder = l_dbus_message_builder_new(msg); - dbus_append_byte_array(builder, add_pending->uuid, 16); + dbus_append_byte_array(builder, prov_pending->uuid, 16); l_dbus_message_builder_append_basic(builder, 's', mesh_prov_status_str(status)); l_dbus_message_builder_finalize(builder); @@ -115,14 +177,14 @@ static bool add_cmplt(void *user_data, uint8_t status, struct mesh_prov_node_info *info) { - struct add_data *pending = user_data; + struct prov_remote_data *pending = user_data; struct mesh_node *node = pending->node; struct l_dbus *dbus = dbus_get_bus(); struct l_dbus_message_builder *builder; struct l_dbus_message *msg; bool result; - if (pending != add_pending) + if (pending != prov_pending) return false; if (status != PROV_ERR_SUCCESS) { @@ -131,7 +193,12 @@ return false; } - result = keyring_put_remote_dev_key(add_pending->node, info->unicast, + /* If Unicast address changing, delete old dev key */ + if (pending->transport == PB_NPPI_01) + keyring_del_remote_dev_key_all(pending->node, + pending->original); + + result = keyring_put_remote_dev_key(pending->node, info->unicast, info->num_ele, info->device_key); if (!result) { @@ -140,13 +207,29 @@ return false; } - msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), + if (pending->transport > PB_NPPI_02) + msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), node_get_app_path(node), MESH_PROVISIONER_INTERFACE, "AddNodeComplete"); + else + msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), + node_get_app_path(node), + MESH_PROVISIONER_INTERFACE, + "ReprovComplete"); builder = l_dbus_message_builder_new(msg); - dbus_append_byte_array(builder, add_pending->uuid, 16); + + if (pending->transport > PB_NPPI_02) + dbus_append_byte_array(builder, pending->uuid, 16); + else { + uint8_t nppi = (uint8_t) pending->transport; + + l_dbus_message_builder_append_basic(builder, 'q', + &pending->original); + l_dbus_message_builder_append_basic(builder, 'y', &nppi); + } + l_dbus_message_builder_append_basic(builder, 'q', &info->unicast); l_dbus_message_builder_append_basic(builder, 'y', &info->num_ele); l_dbus_message_builder_finalize(builder); @@ -161,47 +244,66 @@ static void mgr_prov_data (struct l_dbus_message *reply, void *user_data) { - struct add_data *pending = user_data; + struct prov_remote_data *pending = user_data; uint16_t net_idx; uint16_t primary; - if (pending != add_pending) + if (pending != prov_pending) return; if (l_dbus_message_is_error(reply)) return; - if (!l_dbus_message_get_arguments(reply, "qq", &net_idx, &primary)) + if (pending->transport == PB_NPPI_01) { + /* If performing NPPI, we only get new primary unicast here */ + if (!l_dbus_message_get_arguments(reply, "q", &primary)) + return; + + net_idx = pending->net_idx; + + } else if (!l_dbus_message_get_arguments(reply, "qq", &net_idx, + &primary)) return; - add_pending->primary = primary; - add_pending->net_idx = net_idx; - initiator_prov_data(net_idx, primary, add_pending); + pending->primary = primary; + pending->net_idx = net_idx; + initiator_prov_data(net_idx, primary, pending); } static bool add_data_get(void *user_data, uint8_t num_ele) { - struct add_data *pending = user_data; + struct prov_remote_data *pending = user_data; struct l_dbus_message *msg; struct l_dbus *dbus; const char *app_path; const char *sender; - if (pending != add_pending) + if (pending != prov_pending) return false; dbus = dbus_get_bus(); - app_path = node_get_app_path(add_pending->node); - sender = node_get_owner(add_pending->node); + app_path = node_get_app_path(pending->node); + sender = node_get_owner(pending->node); - msg = l_dbus_message_new_method_call(dbus, sender, app_path, + if (pending->transport > PB_NPPI_02) { + msg = l_dbus_message_new_method_call(dbus, sender, app_path, MESH_PROVISIONER_INTERFACE, "RequestProvData"); - l_dbus_message_set_arguments(msg, "y", num_ele); - l_dbus_send_with_reply(dbus, msg, mgr_prov_data, add_pending, NULL); + l_dbus_message_set_arguments(msg, "y", num_ele); + } else if (pending->transport == PB_NPPI_01) { + msg = l_dbus_message_new_method_call(dbus, sender, app_path, + MESH_PROVISIONER_INTERFACE, + "RequestReprovData"); + + l_dbus_message_set_arguments(msg, "qy", pending->original, + num_ele); + } else + return false; + + l_dbus_send_with_reply(dbus, msg, mgr_prov_data, pending, NULL); - add_pending->num_ele = num_ele; + pending->num_ele = num_ele; return true; } @@ -213,15 +315,95 @@ l_debug("Start callback"); if (err == MESH_ERROR_NONE) - reply = l_dbus_message_new_method_return(add_pending->msg); + reply = l_dbus_message_new_method_return(prov_pending->msg); else - reply = dbus_error(add_pending->msg, MESH_ERROR_FAILED, + reply = dbus_error(prov_pending->msg, MESH_ERROR_FAILED, "Failed to start provisioning initiator"); l_dbus_send(dbus_get_bus(), reply); - l_dbus_message_unref(add_pending->msg); + l_dbus_message_unref(prov_pending->msg); - add_pending->msg = NULL; + prov_pending->msg = NULL; +} + +static struct l_dbus_message *reprovision_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct l_dbus_message_iter options, var; + struct l_dbus_message *reply; + struct mesh_net *net = node_get_net(node); + const char *key; + uint16_t subidx; + uint16_t server = 0; + uint8_t nppi = 0; + + l_debug("Reprovision request"); + + if (!l_dbus_message_get_arguments(msg, "qa{sv}", &server, &options)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!IS_UNICAST(server)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad Unicast"); + + /* Default to nodes primary subnet index */ + subidx = mesh_net_get_primary_idx(net); + + /* Get Provisioning Options */ + while (l_dbus_message_iter_next_entry(&options, &key, &var)) { + bool failed = true; + + if (!strcmp(key, "NPPI")) { + if (l_dbus_message_iter_get_variant(&var, "y", &nppi)) { + if (nppi <= 2) + failed = false; + } + } else if (!strcmp(key, "Subnet")) { + if (l_dbus_message_iter_get_variant(&var, "q", + &subidx)) { + if (subidx <= MAX_KEY_IDX) + failed = false; + } + } + + if (failed) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Invalid options"); + } + + /* AddNode cancels all outstanding Scanning from node */ + manager_scan_cancel(node); + + /* Invoke Prov Initiator */ + prov_pending = l_new(struct prov_remote_data, 1); + + prov_pending->transport = nppi; + prov_pending->node = node; + prov_pending->original = server; + prov_pending->agent = node_get_agent(node); + + if (!node_is_provisioner(node) || (prov_pending->agent == NULL)) { + reply = dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, + "Missing Interfaces"); + goto fail; + } + + prov_pending->msg = l_dbus_message_ref(msg); + initiator_start(prov_pending->transport, server, subidx, NULL, 99, 60, + prov_pending->agent, add_start, + add_data_get, add_cmplt, node, + prov_pending); + + prov_pending->disc_watch = l_dbus_add_disconnect_watch(dbus, + node_get_owner(node), + prov_disc_cb, NULL, NULL); + + return NULL; +fail: + l_free(prov_pending); + prov_pending = NULL; + return reply; } static struct l_dbus_message *add_node_call(struct l_dbus *dbus, @@ -229,55 +411,101 @@ void *user_data) { struct mesh_node *node = user_data; - struct l_dbus_message_iter iter_uuid, options; + struct l_dbus_message_iter iter_uuid, options, var; struct l_dbus_message *reply; + struct mesh_net *net = node_get_net(node); + const char *key; uint8_t *uuid; - uint32_t n = 22; + uint32_t n = 0; + uint16_t subidx; + uint16_t sec = 60; + uint16_t server = 0; l_debug("AddNode request"); if (!l_dbus_message_get_arguments(msg, "aya{sv}", &iter_uuid, &options)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); - if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) - || n != 16) + if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) || + n != 16) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad device UUID"); - /* Allow AddNode to cancel Scanning if from the same node */ - if (scan_node) { - if (scan_node != node) - return dbus_error(msg, MESH_ERROR_BUSY, NULL); + /* Default to nodes primary subnet index */ + subidx = mesh_net_get_primary_idx(net); + + /* Get Provisioning Options */ + while (l_dbus_message_iter_next_entry(&options, &key, &var)) { + bool failed = true; + + if (!strcmp(key, "Seconds")) { + if (l_dbus_message_iter_get_variant(&var, "q", &sec)) + failed = false; + } else if (!strcmp(key, "Server")) { + if (l_dbus_message_iter_get_variant(&var, "q", + &server)) { + if (server < 0x8000) + failed = false; + } + } else if (!strcmp(key, "Subnet")) { + if (l_dbus_message_iter_get_variant(&var, "q", + &subidx)) { + if (subidx <= MAX_KEY_IDX) + failed = false; + } + } - scan_cancel(NULL, node); + if (failed) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Invalid options"); } + /* Device Key update/Composition update requires remote server */ + if (!n && !server) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Invalid options"); + + /* If no server specified, use local */ + if (!server) + server = node_get_primary(node); + + /* AddNode cancels all outstanding Scanning from node */ + manager_scan_cancel(node); + /* Invoke Prov Initiator */ - add_pending = l_new(struct add_data, 1); - memcpy(add_pending->uuid, uuid, 16); - add_pending->node = node; - add_pending->agent = node_get_agent(node); + prov_pending = l_new(struct prov_remote_data, 1); - if (!node_is_provisioner(node) || (add_pending->agent == NULL)) { + if (n) + memcpy(prov_pending->uuid, uuid, 16); + else + uuid = NULL; + + prov_pending->transport = PB_ADV; + prov_pending->node = node; + prov_pending->agent = node_get_agent(node); + + if (!node_is_provisioner(node) || (prov_pending->agent == NULL)) { l_debug("Provisioner: %d", node_is_provisioner(node)); - l_debug("Agent: %p", add_pending->agent); + l_debug("Agent: %p", prov_pending->agent); reply = dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, "Missing Interfaces"); goto fail; } - add_pending->msg = l_dbus_message_ref(msg); - initiator_start(PB_ADV, uuid, 99, 60, add_pending->agent, add_start, - add_data_get, add_cmplt, node, add_pending); + prov_pending->msg = l_dbus_message_ref(msg); + initiator_start(PB_ADV, server, subidx, uuid, 99, sec, + prov_pending->agent, add_start, + add_data_get, add_cmplt, node, + prov_pending); - add_pending->disc_watch = l_dbus_add_disconnect_watch(dbus, + prov_pending->disc_watch = l_dbus_add_disconnect_watch(dbus, node_get_owner(node), prov_disc_cb, NULL, NULL); return NULL; fail: - l_free(add_pending); - add_pending = NULL; + l_free(prov_pending); + prov_pending = NULL; return reply; } @@ -337,38 +565,50 @@ return l_dbus_message_new_method_return(msg); } -static void prov_beacon_recv(void *user_data, struct mesh_io_recv_info *info, +static void manager_scan_result(void *user_data, uint16_t server, bool ext, const uint8_t *data, uint16_t len) { - struct mesh_node *node = user_data; + struct scan_req node_svr = { + .node = user_data, + .server = server, + }; + struct scan_req *req; struct l_dbus_message_builder *builder; struct l_dbus_message *msg; struct l_dbus *dbus; int16_t rssi; - if (scan_node != node || len < sizeof(scan_uuid) + 2 || data[1] != 0x00) + l_debug("scan_result %4.4x %p", server, user_data); + req = l_queue_find(scans, by_node_svr, &node_svr); + if (!req) { + l_debug("No scan_result req"); return; + } - if (!memcmp(data + 2, scan_uuid, sizeof(scan_uuid))) { - if (info->rssi <= scan_rssi) + /* Filter repeats with weaker signal */ + if (!memcmp(data + 1, req->uuid, sizeof(req->uuid))) { + if (!ext && ((int8_t) data[0] <= req->rssi)) { + l_debug("Already Seen"); return; + } } - memcpy(scan_uuid, data + 2, sizeof(scan_uuid)); - scan_rssi = info->rssi; - rssi = info->rssi; + if (!ext && ((int8_t) data[0] > req->rssi)) + req->rssi = (int8_t) data[0]; + rssi = req->rssi; + memcpy(req->uuid, data + 1, sizeof(req->uuid)); dbus = dbus_get_bus(); - msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), - node_get_app_path(node), + msg = l_dbus_message_new_method_call(dbus, node_get_owner(req->node), + node_get_app_path(req->node), MESH_PROVISIONER_INTERFACE, "ScanResult"); builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_append_basic(builder, 'n', &rssi); - dbus_append_byte_array(builder, data + 2, len -2); + dbus_append_byte_array(builder, data + 1, len - 1); l_dbus_message_builder_enter_array(builder, "{sv}"); - /* TODO: populate with options when defined */ + append_dict_entry_basic(builder, "Server", "q", &server); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); @@ -380,27 +620,71 @@ struct l_dbus_message *msg, void *user_data) { - struct mesh_node *node = user_data; - uint16_t duration = 0; - struct mesh_io *io; + struct scan_req new_req = { + .node = user_data, + .server = 0, + .timeout = NULL, + .ext = false, + }; + struct scan_req *req; struct mesh_net *net; + uint8_t *uuid, *ext = NULL; + uint8_t scan_req[21]; + int n; + uint32_t ext_len; + uint32_t flen = 0; + uint16_t sec = 60; const char *key; struct l_dbus_message_iter options, var; const char *sender = l_dbus_message_get_sender(msg); - if (strcmp(sender, node_get_owner(node))) + if (strcmp(sender, node_get_owner(new_req.node))) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); if (!l_dbus_message_get_arguments(msg, "a{sv}", &options)) return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + if (!node_is_provisioner(new_req.node)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + net = node_get_net(new_req.node); + new_req.net_idx = mesh_net_get_primary_idx(net); + memset(new_req.uuid, 0, sizeof(new_req.uuid)); + while (l_dbus_message_iter_next_entry(&options, &key, &var)) { bool failed = true; if (!strcmp(key, "Seconds")) { - if (l_dbus_message_iter_get_variant(&var, "q", - &duration)) { + if (l_dbus_message_iter_get_variant(&var, "q", &sec)) failed = false; + } else if (!strcmp(key, "Subnet")) { + if (l_dbus_message_iter_get_variant(&var, "q", + &new_req.net_idx)) { + if (new_req.net_idx <= MAX_KEY_IDX) + failed = false; + } + } else if (!strcmp(key, "Server")) { + if (l_dbus_message_iter_get_variant(&var, "q", + &new_req.server)) { + if (new_req.server < 0x8000) + failed = false; + } + } else if (!strcmp(key, "Filter")) { + if (l_dbus_message_iter_get_variant(&var, "ay", &var)) { + if (l_dbus_message_iter_get_fixed_array(&var, + &uuid, &flen)) { + if (flen == 16) { + memcpy(new_req.uuid, uuid, + flen); + failed = false; + } + } + } + } else if (!strcmp(key, "Extended")) { + if (l_dbus_message_iter_get_variant(&var, "ay", &var)) { + if (l_dbus_message_iter_get_fixed_array(&var, + &ext, &ext_len)) + failed = false; } } @@ -409,27 +693,51 @@ "Invalid options"); } - if (scan_node && scan_node != node) - return dbus_error(msg, MESH_ERROR_BUSY, NULL); + if (!scans) + scans = l_queue_new(); - if (!node_is_provisioner(node)) - return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + if (new_req.server) { + if (!sec || sec > 60) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Invalid options"); + } else { + new_req.server = node_get_primary(new_req.node); + if (!sec || sec > 60) + sec = 60; + } + + req = l_queue_remove_if(scans, by_node_svr, &new_req); + + if (!req) + req = l_new(struct scan_req, 1); + + if (req->timeout) { + l_timeout_remove(req->timeout); + req->timeout = NULL; + } - if (scan_timeout) - l_timeout_remove(scan_timeout); + *req = new_req; + req->rssi = -128; - memset(scan_uuid, 0, sizeof(scan_uuid)); - scan_rssi = -128; - scan_timeout = NULL; - net = node_get_net(node); - io = mesh_net_get_io(net); - scan_node = node; - mesh_io_register_recv_cb(io, prvb, sizeof(prvb), - prov_beacon_recv, node); - - if (duration) - scan_timeout = l_timeout_create(duration, scan_cancel, - node, NULL); + if (sec) + req->timeout = l_timeout_create(sec, scan_cancel, req, NULL); + + + n = mesh_model_opcode_set(OP_REM_PROV_SCAN_START, scan_req); + scan_req[n++] = 5; + scan_req[n++] = sec; + if (flen) { + memcpy(scan_req + n, req->uuid, flen); + n += flen; + } + + mesh_model_send(req->node, 0, req->server, APP_IDX_DEV_REMOTE, + req->net_idx, DEFAULT_TTL, + true, n, scan_req); + + initiator_scan_reg(manager_scan_result, req->node); + + l_queue_push_tail(scans, req); return l_dbus_message_new_method_return(msg); } @@ -444,12 +752,7 @@ if (strcmp(sender, node_get_owner(node)) || !node_is_provisioner(node)) return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); - if (scan_node) { - if (scan_node != node) - return dbus_error(msg, MESH_ERROR_BUSY, NULL); - - scan_cancel(NULL, node); - } + manager_scan_cancel(node); return l_dbus_message_new_method_return(msg); } @@ -814,6 +1117,8 @@ "aya{sv}", "uuid", "options"); l_dbus_interface_method(iface, "ImportRemoteNode", 0, import_node_call, "", "qyay", "primary", "count", "dev_key"); + l_dbus_interface_method(iface, "Reprovision", 0, reprovision_call, + "", "qa{sv}", "unicast", "options"); l_dbus_interface_method(iface, "DeleteRemoteNode", 0, delete_node_call, "", "qy", "primary", "count"); l_dbus_interface_method(iface, "UnprovisionedScan", 0, start_scan_call, @@ -849,7 +1154,7 @@ if (!l_dbus_register_interface(bus, MESH_MANAGEMENT_INTERFACE, setup_management_interface, NULL, false)) { - l_info("Unable to register %s interface", + l_debug("Unable to register %s interface", MESH_MANAGEMENT_INTERFACE); return false; } @@ -859,8 +1164,8 @@ void manager_scan_cancel(struct mesh_node *node) { - if (scan_node != node) - return; + struct scan_req *req; - scan_cancel(NULL, node); + while ((req = l_queue_find(scans, by_node, node))) + scan_cancel(NULL, req); } diff -Nru bluez-5.66/mesh/mesh-config.h bluez-5.68/mesh/mesh-config.h --- bluez-5.66/mesh/mesh-config.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/mesh/mesh-config.h 2023-06-30 08:10:20.000000000 +0000 @@ -60,6 +60,8 @@ uint8_t friend; uint8_t proxy; uint8_t beacon; + uint8_t mpb; + uint8_t mpb_period; }; struct mesh_config_netkey { @@ -119,6 +121,7 @@ void mesh_config_destroy_nvm(struct mesh_config *cfg); bool mesh_config_save(struct mesh_config *cfg, bool no_wait, mesh_config_status_func_t cb, void *user_data); +void mesh_config_reset(struct mesh_config *cfg, struct mesh_config_node *node); struct mesh_config *mesh_config_create(const char *cfgdir_name, const uint8_t uuid[16], struct mesh_config_node *node); @@ -126,6 +129,9 @@ bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt, uint16_t interval); bool mesh_config_write_device_key(struct mesh_config *cfg, uint8_t *key); +bool mesh_config_write_candidate(struct mesh_config *cfg, uint8_t *key); +bool mesh_config_read_candidate(struct mesh_config *cfg, uint8_t *key); +bool mesh_config_finalize_candidate(struct mesh_config *cfg); bool mesh_config_write_token(struct mesh_config *cfg, uint8_t *token); bool mesh_config_write_network_key(struct mesh_config *cfg, uint16_t idx, uint8_t *key, uint8_t *new_key, int phase); @@ -136,12 +142,16 @@ bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast); bool mesh_config_write_relay_mode(struct mesh_config *cfg, uint8_t mode, uint8_t count, uint16_t interval); +bool mesh_config_write_mpb(struct mesh_config *cfg, uint8_t mode, + uint8_t period); bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl); bool mesh_config_write_mode(struct mesh_config *cfg, const char *keyword, int value); +bool mesh_config_write_mode_ex(struct mesh_config *cfg, const char *keyword, + int value, bool save); bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page, uint8_t *data, uint16_t size); -bool mesh_config_comp_page_mv(struct mesh_config *cfg, uint8_t old, uint8_t nw); +void mesh_config_comp_page_del(struct mesh_config *cfg, uint8_t page); bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, uint16_t app_idx); diff -Nru bluez-5.66/mesh/mesh-config-json.c bluez-5.68/mesh/mesh-config-json.c --- bluez-5.66/mesh/mesh-config-json.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-config-json.c 2023-06-30 08:10:20.000000000 +0000 @@ -58,6 +58,33 @@ static const char *bak_ext = ".bak"; static const char *tmp_ext = ".tmp"; +/* JSON key words */ +static const char *unicastAddress = "unicastAddress"; +static const char *deviceCan = "deviceCan"; +static const char *deviceKey = "deviceKey"; +static const char *defaultTTL = "defaultTTL"; +static const char *sequenceNumber = "sequenceNumber"; +static const char *netKeys = "netKeys"; +static const char *appKeys = "appKeys"; +static const char *elements = "elements"; +static const char *models = "models"; +static const char *modelId = "modelId"; +static const char *address = "address"; +static const char *bind = "bind"; +static const char *publish = "publish"; +static const char *subscribe = "subscribe"; +static const char *boundNetKey = "boundNetKey"; +static const char *keyRefresh = "keyRefresh"; +static const char *subEnabled = "subEnabled"; +static const char *pubEnabled = "pubEnabled"; +static const char *retransmit = "retransmit"; + +/* Common JSON values */ +static const char *enabled = "enabled"; +static const char *disabled = "disabled"; +static const char *unsupported = "unsupported"; + + static bool save_config(json_object *jnode, const char *fname) { FILE *outfile; @@ -134,14 +161,14 @@ uint16_t addr, num_ele; char *str; - if (!json_object_object_get_ex(jnode, "unicastAddress", &jvalue)) + if (!json_object_object_get_ex(jnode, unicastAddress, &jvalue)) return -1; str = (char *)json_object_get_string(jvalue); if (sscanf(str, "%04hx", &addr) != 1) return -1; - if (!json_object_object_get_ex(jnode, "elements", &jelements)) + if (!json_object_object_get_ex(jnode, elements, &jelements)) return -1; num_ele = json_object_array_length(jelements); @@ -160,14 +187,14 @@ size_t len; char buf[9]; - if (!json_object_object_get_ex(jnode, "elements", &jelements)) + if (!json_object_object_get_ex(jnode, elements, &jelements)) return NULL; jelement = json_object_array_get_idx(jelements, ele_idx); if (!jelement) return NULL; - if (!json_object_object_get_ex(jelement, "models", &jmodels)) + if (!json_object_object_get_ex(jelement, models, &jmodels)) return NULL; num_mods = json_object_array_length(jmodels); @@ -189,7 +216,7 @@ char *str; jmodel = json_object_array_get_idx(jmodels, i); - if (!json_object_object_get_ex(jmodel, "modelId", &jvalue)) + if (!json_object_object_get_ex(jmodel, modelId, &jvalue)) return NULL; str = (char *)json_object_get_string(jvalue); @@ -298,7 +325,7 @@ json_object *jvalue; char *str; - if (!json_object_object_get_ex(jobj, "unicastAddress", &jvalue)) + if (!json_object_object_get_ex(jobj, unicastAddress, &jvalue)) return false; str = (char *)json_object_get_string(jvalue); @@ -314,7 +341,7 @@ int val; /* defaultTTL is optional */ - if (!json_object_object_get_ex(jobj, "defaultTTL", &jvalue)) + if (!json_object_object_get_ex(jobj, defaultTTL, &jvalue)) return true; val = json_object_get_int(jvalue); @@ -336,7 +363,7 @@ int val; /* sequenceNumber is optional */ - if (!json_object_object_get_ex(jobj, "sequenceNumber", &jvalue)) + if (!json_object_object_get_ex(jobj, sequenceNumber, &jvalue)) return true; val = json_object_get_int(jvalue); @@ -396,7 +423,25 @@ if (!key_buf) return false; - if (!json_object_object_get_ex(jobj, "deviceKey", &jvalue)) + if (!json_object_object_get_ex(jobj, deviceKey, &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (!str2hex(str, strlen(str), key_buf, 16)) + return false; + + return true; +} + +static bool read_candidate(json_object *jobj, uint8_t key_buf[16]) +{ + json_object *jvalue; + char *str; + + if (!key_buf) + return false; + + if (!json_object_object_get_ex(jobj, deviceCan, &jvalue)) return false; str = (char *)json_object_get_string(jvalue); @@ -460,7 +505,7 @@ int len; int i; - if (!json_object_object_get_ex(jobj, "appKeys", &jarray)) + if (!json_object_object_get_ex(jobj, appKeys, &jarray)) return true; if (json_object_get_type(jarray) != json_type_array) @@ -484,7 +529,7 @@ if (!get_key_index(jtemp, "index", &appkey->app_idx)) goto fail; - if (!get_key_index(jtemp, "boundNetKey", &appkey->net_idx)) + if (!get_key_index(jtemp, boundNetKey, &appkey->net_idx)) goto fail; if (!json_object_object_get_ex(jtemp, "key", &jvalue)) @@ -516,7 +561,7 @@ int i; /* At least one NetKey must be present for a provisioned node */ - if (!json_object_object_get_ex(jobj, "netKeys", &jarray)) + if (!json_object_object_get_ex(jobj, netKeys, &jarray)) return false; if (json_object_get_type(jarray) != json_type_array) @@ -547,7 +592,7 @@ if (!str2hex(str, strlen(str), netkey->new_key, 16)) goto fail; - if (!json_object_object_get_ex(jtemp, "keyRefresh", &jvalue)) + if (!json_object_object_get_ex(jtemp, keyRefresh, &jvalue)) netkey->phase = KEY_REFRESH_PHASE_NONE; else netkey->phase = (uint8_t) json_object_get_int(jvalue); @@ -598,7 +643,7 @@ jnode = cfg->jnode; l_debug("netKey %4.4x", idx); - json_object_object_get_ex(jnode, "netKeys", &jarray); + json_object_object_get_ex(jnode, netKeys, &jarray); if (jarray) jentry = get_key_object(jarray, idx); @@ -616,14 +661,14 @@ if (!add_key_value(jentry, "key", key)) goto fail; - json_object_object_add(jentry, "keyRefresh", + json_object_object_add(jentry, keyRefresh, json_object_new_int(KEY_REFRESH_PHASE_NONE)); if (!jarray) { jarray = json_object_new_array(); if (!jarray) goto fail; - json_object_object_add(jnode, "netKeys", jarray); + json_object_object_add(jnode, netKeys, jarray); } json_object_array_add(jarray, jentry); @@ -648,7 +693,7 @@ jnode = cfg->jnode; - if (!json_object_object_get_ex(jnode, "netKeys", &jarray)) + if (!json_object_object_get_ex(jnode, netKeys, &jarray)) return false; jentry = get_key_object(jarray, idx); @@ -667,7 +712,7 @@ if (!add_key_value(jentry, "key", key)) return false; - json_object_object_add(jentry, "keyRefresh", + json_object_object_add(jentry, keyRefresh, json_object_new_int(KEY_REFRESH_PHASE_ONE)); return save_config(jnode, cfg->node_dir_path); @@ -682,20 +727,55 @@ jnode = cfg->jnode; - if (!json_object_object_get_ex(jnode, "netKeys", &jarray)) + if (!json_object_object_get_ex(jnode, netKeys, &jarray)) return true; jarray_key_del(jarray, idx); if (!json_object_array_length(jarray)) - json_object_object_del(jnode, "netKeys"); + json_object_object_del(jnode, netKeys); return save_config(jnode, cfg->node_dir_path); } bool mesh_config_write_device_key(struct mesh_config *cfg, uint8_t *key) { - if (!cfg || !add_key_value(cfg->jnode, "deviceKey", key)) + if (!cfg || !add_key_value(cfg->jnode, deviceKey, key)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_write_candidate(struct mesh_config *cfg, uint8_t *key) +{ + if (!cfg || !add_key_value(cfg->jnode, deviceCan, key)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_read_candidate(struct mesh_config *cfg, uint8_t *key) +{ + if (!cfg) + return false; + + return read_candidate(cfg->jnode, key); +} + +bool mesh_config_finalize_candidate(struct mesh_config *cfg) +{ + uint8_t key[16]; + + if (!cfg) + return false; + + if (!read_candidate(cfg->jnode, key)) + return false; + + json_object_object_del(cfg->jnode, deviceCan); + json_object_object_del(cfg->jnode, deviceKey); + + if (!add_key_value(cfg->jnode, deviceKey, key)) return false; return save_config(cfg->jnode, cfg->node_dir_path); @@ -719,7 +799,7 @@ jnode = cfg->jnode; - json_object_object_get_ex(jnode, "appKeys", &jarray); + json_object_object_get_ex(jnode, appKeys, &jarray); if (jarray) jentry = get_key_object(jarray, app_idx); @@ -734,7 +814,7 @@ if (!write_int(jentry, "index", app_idx)) goto fail; - if (!write_int(jentry, "boundNetKey", net_idx)) + if (!write_int(jentry, boundNetKey, net_idx)) goto fail; if (!add_key_value(jentry, "key", key)) @@ -744,7 +824,7 @@ jarray = json_object_new_array(); if (!jarray) goto fail; - json_object_object_add(jnode, "appKeys", jarray); + json_object_object_add(jnode, appKeys, jarray); } json_object_array_add(jarray, jentry); @@ -770,7 +850,7 @@ jnode = cfg->jnode; - if (!json_object_object_get_ex(jnode, "appKeys", &jarray)) + if (!json_object_object_get_ex(jnode, appKeys, &jarray)) return false; /* The key entry should exist if the key is updated */ @@ -804,13 +884,13 @@ jnode = cfg->jnode; - if (!json_object_object_get_ex(jnode, "appKeys", &jarray)) + if (!json_object_object_get_ex(jnode, appKeys, &jarray)) return true; jarray_key_del(jarray, idx); if (!json_object_array_length(jarray)) - json_object_object_del(jnode, "appKeys"); + json_object_object_del(jnode, appKeys); return save_config(jnode, cfg->node_dir_path); } @@ -840,7 +920,7 @@ if (!jmodel) return false; - json_object_object_get_ex(jmodel, "bind", &jarray); + json_object_object_get_ex(jmodel, bind, &jarray); if (jarray && jarray_has_string(jarray, buf, 4)) return true; @@ -854,7 +934,7 @@ json_object_put(jstring); return false; } - json_object_object_add(jmodel, "bind", jarray); + json_object_object_add(jmodel, bind, jarray); } json_object_array_add(jarray, jstring); @@ -887,13 +967,13 @@ if (!jmodel) return false; - if (!json_object_object_get_ex(jmodel, "bind", &jarray)) + if (!json_object_object_get_ex(jmodel, bind, &jarray)) return true; jarray_string_del(jarray, buf, 4); if (!json_object_array_length(jarray)) - json_object_object_del(jmodel, "bind"); + json_object_object_del(jmodel, bind); return save_config(jnode, cfg->node_dir_path); } @@ -963,7 +1043,7 @@ int len, value; char *str; - if (!json_object_object_get_ex(jpub, "address", &jvalue)) + if (!json_object_object_get_ex(jpub, address, &jvalue)) return NULL; str = (char *)json_object_get_string(jvalue); @@ -998,9 +1078,10 @@ if (!get_int(jpub, "credentials", &value)) goto fail; + pub->credential = (uint8_t) value; - if (!json_object_object_get_ex(jpub, "retransmit", &jvalue)) + if (!json_object_object_get_ex(jpub, retransmit, &jvalue)) goto fail; if (!get_int(jvalue, "count", &value)) @@ -1093,7 +1174,7 @@ l_queue_push_tail(ele->models, mod); - if (!json_object_object_get_ex(jmodel, "modelId", &jvalue)) + if (!json_object_object_get_ex(jmodel, modelId, &jvalue)) goto fail; str = (char *)json_object_get_string(jvalue); @@ -1112,29 +1193,32 @@ mod->id = id; - if (json_object_object_get_ex(jmodel, "bind", &jarray)) { + if (len == 8) + mod->vendor = true; + + if (json_object_object_get_ex(jmodel, bind, &jarray)) { if (json_object_get_type(jarray) != json_type_array || !parse_bindings(jarray, mod)) goto fail; } - if (json_object_object_get_ex(jmodel, "pubEnabled", &jvalue)) + if (json_object_object_get_ex(jmodel, pubEnabled, &jvalue)) mod->pub_enabled = json_object_get_boolean(jvalue); else mod->pub_enabled = true; - if (json_object_object_get_ex(jmodel, "subEnabled", &jvalue)) + if (json_object_object_get_ex(jmodel, subEnabled, &jvalue)) mod->sub_enabled = json_object_get_boolean(jvalue); else mod->sub_enabled = true; - if (json_object_object_get_ex(jmodel, "publish", &jvalue)) { + if (json_object_object_get_ex(jmodel, publish, &jvalue)) { mod->pub = parse_model_publication(jvalue); if (!mod->pub) goto fail; } - if (json_object_object_get_ex(jmodel, "subscribe", &jarray)) { + if (json_object_object_get_ex(jmodel, subscribe, &jarray)) { if (!parse_model_subscriptions(jarray, mod)) goto fail; } @@ -1187,7 +1271,7 @@ if (sscanf(str, "%04hx", &(ele->location)) != 1) goto fail; - if (json_object_object_get_ex(jelement, "models", &jmodels)) { + if (json_object_object_get_ex(jelement, models, &jmodels)) { if (json_object_get_type(jmodels) != json_type_array || !parse_models(jmodels, ele)) goto fail; @@ -1211,13 +1295,13 @@ if (!str) return 0xffffffff; - if (!strncasecmp(str, "disabled", strlen("disabled"))) + if (!strncasecmp(str, disabled, strlen(disabled))) return MESH_MODE_DISABLED; - if (!strncasecmp(str, "enabled", strlen("enabled"))) + if (!strncasecmp(str, enabled, strlen(enabled))) return MESH_MODE_ENABLED; - if (!strncasecmp(str, "unsupported", strlen("unsupported"))) + if (!strncasecmp(str, unsupported, strlen(unsupported))) return MESH_MODE_UNSUPPORTED; return 0xffffffff; @@ -1253,6 +1337,19 @@ node->modes.beacon = mode; } + if (json_object_object_get_ex(jconfig, "mpb", &jvalue)) { + mode = get_mode(jvalue); + if (mode <= MESH_MODE_UNSUPPORTED) + node->modes.mpb = mode; + + if (node->modes.mpb == MESH_MODE_ENABLED) { + if (json_object_object_get_ex(jconfig, "mpbPeriod", + &jvalue)) + node->modes.mpb_period = + json_object_get_int(jvalue); + } + } + if (!json_object_object_get_ex(jconfig, "relay", &jrelay)) return; @@ -1323,7 +1420,7 @@ uint16_t interval; uint8_t cnt; - if (!json_object_object_get_ex(jobj, "retransmit", &jrtx)) + if (!json_object_object_get_ex(jobj, retransmit, &jrtx)) return true; if (!json_object_object_get_ex(jrtx, "count", &jvalue)) @@ -1386,7 +1483,7 @@ } /* Check for required "elements" property */ - if (!json_object_object_get_ex(jnode, "elements", &jvalue)) + if (!json_object_object_get_ex(jnode, elements, &jvalue)) return false; if (!read_net_transmit(jnode, node)) { @@ -1460,11 +1557,11 @@ { switch (mode) { case MESH_MODE_DISABLED: - return "disabled"; + return disabled; case MESH_MODE_ENABLED: - return "enabled"; + return enabled; default: - return "unsupported"; + return unsupported; } } @@ -1492,6 +1589,18 @@ return save_config(cfg->jnode, cfg->node_dir_path); } +bool mesh_config_write_mode_ex(struct mesh_config *cfg, const char *keyword, + int value, bool save) +{ + if (!cfg) + return false; + + if (save) + return mesh_config_write_mode(cfg, keyword, value); + else + return write_mode(cfg->jnode, keyword, value); +} + static bool write_relay_mode(json_object *jobj, uint8_t mode, uint8_t count, uint16_t interval) { @@ -1522,7 +1631,7 @@ bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast) { - if (!cfg || !write_uint16_hex(cfg->jnode, "unicastAddress", unicast)) + if (!cfg || !write_uint16_hex(cfg->jnode, unicastAddress, unicast)) return false; return save_config(cfg->jnode, cfg->node_dir_path); @@ -1538,6 +1647,21 @@ return save_config(cfg->jnode, cfg->node_dir_path); } +bool mesh_config_write_mpb(struct mesh_config *cfg, uint8_t mode, + uint8_t period) +{ + + if (!cfg || !write_mode(cfg->jnode, "mpb", mode)) + return false; + + if (mode) { + if (!write_int(cfg->jnode, "mpbPeriod", period)) + return false; + } + + return save_config(cfg->jnode, cfg->node_dir_path); +} + bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt, uint16_t interval) { @@ -1558,8 +1682,8 @@ if (!write_int(jrtx, "interval", interval)) goto fail; - json_object_object_del(jnode, "retransmit"); - json_object_object_add(jnode, "retransmit", jrtx); + json_object_object_del(jnode, retransmit); + json_object_object_add(jnode, retransmit, jrtx); return save_config(cfg->jnode, cfg->node_dir_path); @@ -1599,8 +1723,8 @@ if (!jmodel) return; - result = (mod->vendor) ? write_uint32_hex(jmodel, "modelId", mod->id) : - write_uint16_hex(jmodel, "modelId", (uint16_t) mod->id); + result = (mod->vendor) ? write_uint32_hex(jmodel, modelId, mod->id) : + write_uint16_hex(jmodel, modelId, (uint16_t) mod->id); if (!result) { json_object_put(jmodel); @@ -1608,10 +1732,10 @@ } jval = json_object_new_boolean(mod->sub_enabled); - json_object_object_add(jmodel, "subEnabled", jval); + json_object_object_add(jmodel, subEnabled, jval); jval = json_object_new_boolean(mod->pub_enabled); - json_object_object_add(jmodel, "pubEnabled", jval); + json_object_object_add(jmodel, pubEnabled, jval); json_object_array_add(jmodels, jmodel); } @@ -1662,12 +1786,20 @@ if (!write_mode(jnode, "beacon", modes->beacon)) return NULL; + if (!write_mode(jnode, "mpb", modes->mpb)) + return NULL; + + if (modes->mpb) { + if (!write_int(jnode, "mpbPeriod", modes->mpb_period)) + return NULL; + } + /* Sequence number */ - json_object_object_add(jnode, "sequenceNumber", + json_object_object_add(jnode, sequenceNumber, json_object_new_int(node->seq_number)); /* Default TTL */ - json_object_object_add(jnode, "defaultTTL", + json_object_object_add(jnode, defaultTTL, json_object_new_int(node->ttl)); /* Elements */ @@ -1702,11 +1834,11 @@ if (!jmodels) goto fail; - json_object_object_add(jelement, "models", jmodels); + json_object_object_add(jelement, models, jmodels); l_queue_foreach(ele->models, add_model, jmodels); } - json_object_object_add(jnode, "elements", jelems); + json_object_object_add(jnode, elements, jelems); cfg = l_new(struct mesh_config, 1); @@ -1724,6 +1856,55 @@ return NULL; } +void mesh_config_reset(struct mesh_config *cfg, struct mesh_config_node *node) +{ + json_object *jelems; + const struct l_queue_entry *entry; + + if (!cfg || !cfg->jnode) + return; + + /* TODO: Recreate Element Array */ + jelems = json_object_new_array(); + if (!jelems) + return; + + entry = l_queue_get_entries(node->elements); + + for (; entry; entry = entry->next) { + struct mesh_config_element *ele = entry->data; + json_object *jelement, *jmodels; + + jelement = json_object_new_object(); + + if (!jelement) { + json_object_put(jelems); + return; + } + + write_int(jelement, "elementIndex", ele->index); + write_uint16_hex(jelement, "location", ele->location); + json_object_array_add(jelems, jelement); + + /* Models */ + if (l_queue_isempty(ele->models)) + continue; + + jmodels = json_object_new_array(); + if (!jmodels) { + json_object_put(jelems); + return; + } + + json_object_object_add(jelement, models, jmodels); + l_queue_foreach(ele->models, add_model, jmodels); + } + + /* Replace element array */ + json_object_object_del(cfg->jnode, elements); + json_object_object_add(cfg->jnode, elements, jelems); +} + struct mesh_config *mesh_config_create(const char *cfgdir_name, const uint8_t uuid[16], struct mesh_config_node *db_node) { @@ -1768,7 +1949,7 @@ int i, len; /* Clean up all the bound appkeys */ - if (!json_object_object_get_ex(jobj, "appKeys", &jarray)) + if (!json_object_object_get_ex(jobj, appKeys, &jarray)) return; len = json_object_array_length(jarray); @@ -1779,7 +1960,7 @@ jentry = json_object_array_get_idx(jarray, i); - if (!get_key_index(jentry, "boundNetKey", &idx)) + if (!get_key_index(jentry, boundNetKey, &idx)) continue; if (idx != net_idx) @@ -1803,14 +1984,14 @@ jnode = cfg->jnode; - if (json_object_object_get_ex(jnode, "netKeys", &jarray)) + if (json_object_object_get_ex(jnode, netKeys, &jarray)) jentry = get_key_object(jarray, idx); if (!jentry) return false; - json_object_object_del(jentry, "keyRefresh"); - json_object_object_add(jentry, "keyRefresh", + json_object_object_del(jentry, keyRefresh); + json_object_object_add(jentry, keyRefresh, json_object_new_int(phase)); if (phase == KEY_REFRESH_PHASE_NONE) { @@ -1842,16 +2023,16 @@ if (!jmodel) return false; - json_object_object_del(jmodel, "publish"); + json_object_object_del(jmodel, publish); jpub = json_object_new_object(); if (!jpub) return false; if (pub->virt) - res = add_key_value(jpub, "address", pub->virt_addr); + res = add_key_value(jpub, address, pub->virt_addr); else - res = write_uint16_hex(jpub, "address", pub->addr); + res = write_uint16_hex(jpub, address, pub->addr); if (!res) goto fail; @@ -1878,8 +2059,8 @@ if (!write_int(jrtx, "interval", pub->interval)) goto fail; - json_object_object_add(jpub, "retransmit", jrtx); - json_object_object_add(jmodel, "publish", jpub); + json_object_object_add(jpub, retransmit, jrtx); + json_object_object_add(jmodel, publish, jpub); return save_config(jnode, cfg->node_dir_path); @@ -1911,23 +2092,23 @@ uint32_t mod_id, bool vendor) { if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor, - "publish")) + publish)) return false; return save_config(cfg->jnode, cfg->node_dir_path); } -static void del_page(json_object *jarray, uint8_t page) +static bool del_page(json_object *jarray, uint8_t page) { char buf[3]; int i, len, ret; if (!jarray) - return; + return false; ret = snprintf(buf, 3, "%2.2x", page); if (ret < 0) - return; + return false; len = json_object_array_length(jarray); @@ -1938,10 +2119,29 @@ jentry = json_object_array_get_idx(jarray, i); str = (char *)json_object_get_string(jentry); - /* Delete matching page(s) */ - if (!memcmp(str, buf, 2)) + /* Delete matching page */ + if (!memcmp(str, buf, 2)) { json_object_array_del_idx(jarray, i, 1); + break; + } } + + return true; +} + +void mesh_config_comp_page_del(struct mesh_config *cfg, uint8_t page) +{ + json_object *jnode, *jarray = NULL; + + if (!cfg) + return; + + jnode = cfg->jnode; + + json_object_object_get_ex(jnode, "pages", &jarray); + + if (del_page(jarray, page)) + save_config(jnode, cfg->node_dir_path); } bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page, @@ -1984,56 +2184,6 @@ return save_config(jnode, cfg->node_dir_path); } -bool mesh_config_comp_page_mv(struct mesh_config *cfg, uint8_t old, uint8_t nw) -{ - json_object *jnode, *jarray = NULL; - uint8_t *data; - char *str; - char old_buf[3]; - int i, len, ret, dlen = 0; - bool status = true; - - if (!cfg || old == nw) - return false; - - ret = snprintf(old_buf, 3, "%2.2x", old); - if (ret < 0) - return false; - - jnode = cfg->jnode; - - json_object_object_get_ex(jnode, "pages", &jarray); - - if (!jarray) - return false; - - data = l_malloc(MAX_MSG_LEN); - - len = json_object_array_length(jarray); - - for (i = 0; i < len; i++) { - json_object *jentry; - - jentry = json_object_array_get_idx(jarray, i); - str = (char *)json_object_get_string(jentry); - - /* Delete matching page(s) but save data*/ - if (!memcmp(str, old_buf, 2)) { - dlen = strlen(str + 2); - str2hex(str + 2, dlen, data, MAX_MSG_LEN); - dlen /= 2; - json_object_array_del_idx(jarray, i, 1); - } - } - - if (dlen) - status = mesh_config_comp_page_add(cfg, nw, data, dlen); - - l_free(data); - - return status; -} - bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr, uint32_t mod_id, bool vendor, struct mesh_config_sub *sub) @@ -2064,7 +2214,7 @@ len = 32; } - json_object_object_get_ex(jmodel, "subscribe", &jarray); + json_object_object_get_ex(jmodel, subscribe, &jarray); if (jarray && jarray_has_string(jarray, buf, len)) return true; @@ -2078,7 +2228,7 @@ json_object_put(jstring); return false; } - json_object_object_add(jmodel, "subscribe", jarray); + json_object_object_add(jmodel, subscribe, jarray); } json_object_array_add(jarray, jstring); @@ -2107,7 +2257,7 @@ if (!jmodel) return false; - if (!json_object_object_get_ex(jmodel, "subscribe", &jarray)) + if (!json_object_object_get_ex(jmodel, subscribe, &jarray)) return true; if (!sub->virt) { @@ -2122,7 +2272,7 @@ jarray_string_del(jarray, buf, len); if (!json_object_array_length(jarray)) - json_object_object_del(jmodel, "subscribe"); + json_object_object_del(jmodel, subscribe); return save_config(jnode, cfg->node_dir_path); } @@ -2131,7 +2281,7 @@ uint32_t mod_id, bool vendor) { if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor, - "subscribe")) + subscribe)) return false; return save_config(cfg->jnode, cfg->node_dir_path); @@ -2161,7 +2311,7 @@ json_object_object_add(jmodel, "pubDisabled", jval); if (!enable) - json_object_object_del(jmodel, "publish"); + json_object_object_del(jmodel, publish); return save_config(cfg->jnode, cfg->node_dir_path); } @@ -2184,13 +2334,13 @@ if (!jmodel) return false; - json_object_object_del(jmodel, "subEnabled"); + json_object_object_del(jmodel, subEnabled); jval = json_object_new_boolean(enable); - json_object_object_add(jmodel, "subEnabled", jval); + json_object_object_add(jmodel, subEnabled, jval); if (!enable) - json_object_object_del(jmodel, "subscribe"); + json_object_object_del(jmodel, subscribe); return save_config(cfg->jnode, cfg->node_dir_path); } @@ -2205,14 +2355,14 @@ return false; if (!cache) { - if (!write_int(cfg->jnode, "sequenceNumber", seq)) + if (!write_int(cfg->jnode, sequenceNumber, seq)) return false; return mesh_config_save(cfg, true, NULL, NULL); } /* If resetting seq to Zero, make sure cached value reset as well */ - if (seq && get_int(cfg->jnode, "sequenceNumber", &value)) + if (seq && get_int(cfg->jnode, sequenceNumber, &value)) cached = (uint32_t)value; /* @@ -2262,8 +2412,8 @@ l_debug("Seq Cache: %d -> %d", seq, cached); - if (!write_int(cfg->jnode, "sequenceNumber", cached)) - return false; + if (!write_int(cfg->jnode, sequenceNumber, cached)) + return false; return mesh_config_save(cfg, false, NULL, NULL); } @@ -2273,7 +2423,7 @@ bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl) { - if (!cfg || !write_int(cfg->jnode, "defaultTTL", ttl)) + if (!cfg || !write_int(cfg->jnode, defaultTTL, ttl)) return false; return save_config(cfg->jnode, cfg->node_dir_path); diff -Nru bluez-5.66/mesh/mesh-io-api.h bluez-5.68/mesh/mesh-io-api.h --- bluez-5.66/mesh/mesh-io-api.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-io-api.h 2023-06-30 08:10:20.000000000 +0000 @@ -34,6 +34,13 @@ mesh_io_tx_cancel_t cancel; }; +struct mesh_io_reg { + mesh_io_recv_func_t cb; + void *user_data; + uint8_t len; + uint8_t filter[]; +}; + struct mesh_io { int index; int favored_index; diff -Nru bluez-5.66/mesh/mesh-io.c bluez-5.68/mesh/mesh-io.c --- bluez-5.66/mesh/mesh-io.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-io.c 2023-06-30 08:10:20.000000000 +0000 @@ -28,12 +28,10 @@ #include "mesh/mesh-io-generic.h" #include "mesh/mesh-io-unit.h" -struct mesh_io_reg { - mesh_io_recv_func_t cb; - void *user_data; - uint8_t len; - uint8_t filter[]; -} packed; +struct loop_data { + uint16_t len; + uint8_t data[]; +}; /* List of Supported Mesh-IO Types */ static const struct mesh_io_table table[] = { @@ -42,7 +40,10 @@ {MESH_IO_TYPE_UNIT_TEST, &mesh_io_unit}, }; +static const uint8_t unprv_filter[] = { MESH_AD_TYPE_BEACON, 0 }; + static struct mesh_io *default_io; +static struct l_timeout *loop_adv_to; static const struct mesh_io_api *io_api(enum mesh_io_type type) { @@ -71,12 +72,20 @@ enum mesh_io_type type = L_PTR_TO_UINT(user_data); const struct mesh_io_api *api = NULL; - l_warn("up:%d pwr: %d mesh: %d", up, pwr, mesh); + l_warn("index %u up:%d pwr: %d mesh: %d", index, up, pwr, mesh); /* If specific IO controller requested, honor it */ - if (default_io->favored_index != MGMT_INDEX_NONE && - default_io->favored_index != index) - return; + if (default_io->favored_index != MGMT_INDEX_NONE) { + if (default_io->favored_index != index) + return; + + if (!up | pwr) { + l_warn("HCI%u failed to start generic IO %s", + index, pwr ? ": already powered on" : ""); + if (default_io->ready) + default_io->ready(default_io->user_data, false); + } + } if (!up && default_io->index == index) { /* Our controller has disappeared */ @@ -96,7 +105,6 @@ if (mesh && type != MESH_IO_TYPE_GENERIC) api = io_api(MESH_IO_TYPE_MGMT); - else if (!pwr) api = io_api(MESH_IO_TYPE_GENERIC); @@ -104,7 +112,6 @@ default_io->index = index; default_io->api = api; api->init(default_io, &index, default_io->user_data); - l_queue_foreach(default_io->rx_regs, refresh_rx, default_io); } } @@ -122,6 +129,23 @@ } } +static struct mesh_io_reg *find_by_filter(struct l_queue *rx_regs, + const uint8_t *filter, uint8_t len) +{ + const struct l_queue_entry *entry; + + entry = l_queue_get_entries(rx_regs); + + for (; entry; entry = entry->next) { + struct mesh_io_reg *rx_reg = entry->data; + + if (rx_reg->len == len && !memcmp(rx_reg->filter, filter, len)) + return rx_reg; + } + + return NULL; +} + struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts, mesh_io_ready_func_t cb, void *user_data) { @@ -183,14 +207,23 @@ { struct mesh_io_reg *rx_reg; - if (io != default_io) + if (io == NULL) + io = default_io; + + if (io != default_io || !cb || !filter || !len) return false; + rx_reg = find_by_filter(io->rx_regs, filter, len); + + l_free(rx_reg); + l_queue_remove(io->rx_regs, rx_reg); + rx_reg = l_malloc(sizeof(struct mesh_io_reg) + len); rx_reg->cb = cb; rx_reg->len = len; rx_reg->user_data = user_data; memcpy(rx_reg->filter, filter, len); + l_queue_push_head(io->rx_regs, rx_reg); if (io && io->api && io->api->reg) @@ -199,14 +232,6 @@ return false; } -static bool by_filter(const void *a, const void *b) -{ - const struct mesh_io_reg *rx_reg = a; - const uint8_t *filter = b; - - return rx_reg->filter[0] == filter[0]; -} - bool mesh_io_deregister_recv_cb(struct mesh_io *io, const uint8_t *filter, uint8_t len) { @@ -215,7 +240,9 @@ if (io != default_io) return false; - rx_reg = l_queue_remove_if(io->rx_regs, by_filter, filter); + rx_reg = find_by_filter(io->rx_regs, filter, len); + + l_queue_remove(io->rx_regs, rx_reg); l_free(rx_reg); if (io && io->api && io->api->dereg) @@ -224,6 +251,38 @@ return false; } +static void loop_foreach(void *data, void *user_data) +{ + struct mesh_io_reg *rx_reg = data; + struct loop_data *rx = user_data; + + if (!memcmp(rx_reg->filter, unprv_filter, sizeof(unprv_filter))) + rx_reg->cb(rx_reg->user_data, NULL, rx->data, rx->len); +} + +static void loop_rx(struct l_timeout *timeout, void *user_data) +{ + struct loop_data *rx = user_data; + + l_queue_foreach(default_io->rx_regs, loop_foreach, rx); + l_timeout_modify_ms(loop_adv_to, 500); +} + +static void loop_destroy(void *user_data) +{ + l_free(user_data); +} + +static void loop_unprv_beacon(const uint8_t *data, uint16_t len) +{ + struct loop_data *pkt = l_malloc(len + sizeof(struct loop_data)); + + memcpy(pkt->data, data, len); + pkt->len = len; + l_timeout_remove(loop_adv_to); + loop_adv_to = l_timeout_create_ms(500, loop_rx, pkt, loop_destroy); +} + bool mesh_io_send(struct mesh_io *io, struct mesh_io_send_info *info, const uint8_t *data, uint16_t len) { @@ -233,6 +292,10 @@ if (!io) io = default_io; + /* Loop unprovisioned beacons for local clients */ + if (!memcmp(data, unprv_filter, sizeof(unprv_filter))) + loop_unprv_beacon(data, len); + if (io && io->api && io->api->send) return io->api->send(io, info, data, len); @@ -248,6 +311,11 @@ if (!io) io = default_io; + if (loop_adv_to && len >= 2 && !memcmp(pattern, unprv_filter, 2)) { + l_timeout_remove(loop_adv_to); + loop_adv_to = NULL; + } + if (io && io->api && io->api->cancel) return io->api->cancel(io, pattern, len); diff -Nru bluez-5.66/mesh/mesh-io-generic.c bluez-5.68/mesh/mesh-io-generic.c --- bluez-5.66/mesh/mesh-io-generic.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-io-generic.c 2023-06-30 08:10:20.000000000 +0000 @@ -33,7 +33,6 @@ struct mesh_io *io; struct bt_hci *hci; struct l_timeout *tx_timeout; - struct l_queue *rx_regs; struct l_queue *tx_pkts; struct tx_pkt *tx; uint16_t interval; @@ -41,13 +40,6 @@ bool active; }; -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; @@ -87,7 +79,7 @@ static void process_rx_callbacks(void *v_reg, void *v_rx) { - struct pvt_rx_reg *rx_reg = v_reg; + struct mesh_io_reg *rx_reg = v_reg; struct process_data *rx = v_rx; if (!memcmp(rx->data, rx_reg->filter, rx_reg->len)) @@ -108,7 +100,7 @@ .info.rssi = rssi, }; - l_queue_foreach(pvt->rx_regs, process_rx_callbacks, &rx); + l_queue_foreach(pvt->io->rx_regs, process_rx_callbacks, &rx); } static void event_adv_report(struct mesh_io *io, const void *buf, uint8_t size) @@ -354,7 +346,7 @@ static bool find_active(const void *a, const void *b) { - const struct pvt_rx_reg *rx_reg = a; + const struct mesh_io_reg *rx_reg = a; /* Mesh specific AD types do *not* require active scanning, * so do not turn on Active Scanning on their account. @@ -370,10 +362,10 @@ { struct bt_hci_cmd_le_set_scan_enable cmd; - if (l_queue_isempty(pvt->rx_regs)) + if (l_queue_isempty(pvt->io->rx_regs)) return; - pvt->active = l_queue_find(pvt->rx_regs, find_active, NULL); + pvt->active = l_queue_find(pvt->io->rx_regs, find_active, NULL); cmd.enable = 0x00; /* Disable scanning */ cmd.filter_dup = 0x00; /* Report duplicates */ bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, @@ -388,6 +380,9 @@ if (io->pvt->hci) bt_hci_unref(io->pvt->hci); + /* Clear controller HCI list to suppress mgmt interface warnings */ + mesh_mgmt_clear(); + io->pvt->hci = bt_hci_new_user_channel(io->index); if (!io->pvt->hci) { l_error("Failed to start mesh io (hci %u): %s", io->index, @@ -417,7 +412,6 @@ io->pvt = l_new(struct mesh_io_private, 1); - io->pvt->rx_regs = l_queue_new(); io->pvt->tx_pkts = l_queue_new(); io->pvt->io = io; @@ -436,7 +430,6 @@ bt_hci_unref(pvt->hci); l_timeout_remove(pvt->tx_timeout); - l_queue_destroy(pvt->rx_regs, l_free); l_queue_remove_if(pvt->tx_pkts, simple_match, pvt->tx); l_queue_destroy(pvt->tx_pkts, l_free); l_free(pvt->tx); @@ -726,7 +719,7 @@ l_queue_push_tail(pvt->tx_pkts, tx); } - /* If not already sending, schedule the tx worker */ + /* If not already sending, schedule the tx worker */ if (!pvt->tx) { l_timeout_remove(pvt->tx_timeout); pvt->tx_timeout = NULL; @@ -780,46 +773,18 @@ return true; } -static bool find_by_filter(const void *a, const void *b) -{ - const struct pvt_rx_reg *rx_reg_old = a; - const struct pvt_rx_reg *rx_reg = b; - - if (rx_reg_old->len != rx_reg->len) - return false; - - return !memcmp(rx_reg_old->filter, rx_reg->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 bt_hci_cmd_le_set_scan_enable cmd; struct mesh_io_private *pvt = io->pvt; - struct pvt_rx_reg *rx_reg, *rx_reg_old; bool already_scanning; bool active = false; - if (!cb || !filter || !len) - return false; - - 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; - - rx_reg_old = l_queue_remove_if(pvt->rx_regs, find_by_filter, rx_reg); - - l_free(rx_reg_old); - - already_scanning = !l_queue_isempty(pvt->rx_regs); - - l_queue_push_head(pvt->rx_regs, rx_reg); + already_scanning = !l_queue_isempty(io->rx_regs); /* Look for any AD types requiring Active Scanning */ - if (l_queue_find(pvt->rx_regs, find_active, NULL)) + if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; if (!already_scanning || pvt->active != active) { @@ -839,25 +804,13 @@ { struct bt_hci_cmd_le_set_scan_enable cmd = {0, 0}; struct mesh_io_private *pvt = io->pvt; - struct pvt_rx_reg *rx_reg, *rx_reg_tmp; bool active = false; - rx_reg_tmp = l_malloc(sizeof(*rx_reg_tmp) + len); - memcpy(&rx_reg_tmp->filter, filter, len); - rx_reg_tmp->len = len; - - rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter, rx_reg_tmp); - - if (rx_reg) - l_free(rx_reg); - - l_free(rx_reg_tmp); - /* Look for any AD types requiring Active Scanning */ - if (l_queue_find(pvt->rx_regs, find_active, NULL)) + if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; - if (l_queue_isempty(pvt->rx_regs)) { + if (l_queue_isempty(io->rx_regs)) { bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, &cmd, sizeof(cmd), NULL, NULL, NULL); diff -Nru bluez-5.66/mesh/mesh-io.h bluez-5.68/mesh/mesh-io.h --- bluez-5.66/mesh/mesh-io.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-io.h 2023-06-30 08:10:20.000000000 +0000 @@ -8,8 +8,6 @@ * */ -struct mesh_io; - #define MESH_IO_TX_COUNT_UNLIMITED 0 enum mesh_io_type { diff -Nru bluez-5.66/mesh/mesh-io-mgmt.c bluez-5.68/mesh/mesh-io-mgmt.c --- bluez-5.66/mesh/mesh-io-mgmt.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-io-mgmt.c 2023-06-30 08:10:20.000000000 +0000 @@ -35,8 +35,8 @@ struct mesh_io *io; void *user_data; struct l_timeout *tx_timeout; + struct l_timeout *dup_timeout; struct l_queue *dup_filters; - struct l_queue *rx_regs; struct l_queue *tx_pkts; struct tx_pkt *tx; unsigned int tx_id; @@ -48,13 +48,6 @@ bool active; }; -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; @@ -82,6 +75,8 @@ uint8_t addr[6]; } __packed; +static const uint8_t zero_addr[] = {0, 0, 0, 0, 0, 0}; + static struct mesh_io_private *pvt; static uint32_t get_instant(void) @@ -110,6 +105,14 @@ return !memcmp(filter->addr, b, 6); } +static bool find_by_adv(const void *a, const void *b) +{ + const struct dup_filter *filter = a; + uint64_t data = l_get_be64(b); + + return !memcmp(filter->addr, zero_addr, 6) && filter->data == data; +} + static void filter_timeout(struct l_timeout *timeout, void *user_data) { struct dup_filter *filter; @@ -136,6 +139,7 @@ done: l_timeout_remove(timeout); + pvt->dup_timeout = NULL; } /* Ignore consequtive duplicate advertisements within timeout period */ @@ -146,7 +150,22 @@ uint32_t instant_delta; uint64_t data = l_get_be64(adv); - filter = l_queue_remove_if(pvt->dup_filters, find_by_addr, addr); + if (!addr) + addr = zero_addr; + + if (adv[1] == MESH_AD_TYPE_PROVISION) { + filter = l_queue_find(pvt->dup_filters, find_by_adv, adv); + + if (!filter && addr != zero_addr) + return false; + + l_queue_remove(pvt->dup_filters, filter); + + } else { + filter = l_queue_remove_if(pvt->dup_filters, find_by_addr, + addr); + } + if (!filter) { filter = l_new(struct dup_filter, 1); memcpy(filter->addr, addr, 6); @@ -154,7 +173,8 @@ /* Start filter expiration timer */ if (!l_queue_length(pvt->dup_filters)) - l_timeout_create(1, filter_timeout, NULL, NULL); + pvt->dup_timeout = l_timeout_create(1, filter_timeout, NULL, + NULL); l_queue_push_head(pvt->dup_filters, filter); instant_delta = instant - filter->instant; @@ -170,14 +190,14 @@ static void process_rx_callbacks(void *v_reg, void *v_rx) { - struct pvt_rx_reg *rx_reg = v_reg; + struct mesh_io_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, +static void process_rx(uint16_t index, struct mesh_io_private *pvt, int8_t rssi, uint32_t instant, const uint8_t *addr, const uint8_t *data, uint8_t len) { @@ -191,21 +211,25 @@ .info.rssi = rssi, }; + /* Accept all traffic except beacons from any controller */ + if (index != pvt->send_idx && data[0] == MESH_AD_TYPE_BEACON) + return; + print_packet("RX", data, len); - l_queue_foreach(pvt->rx_regs, process_rx_callbacks, &rx); + l_queue_foreach(pvt->io->rx_regs, process_rx_callbacks, &rx); } static void send_cmplt(uint16_t index, uint16_t length, const void *param, void *user_data) { - print_packet("Mesh Send Complete", param, length); + /* print_packet("Mesh Send Complete", param, length); */ } static void event_device_found(uint16_t index, uint16_t length, const void *param, void *user_data) { const struct mgmt_ev_mesh_device_found *ev = param; - struct mesh_io *io = user_data; + struct mesh_io_private *pvt = user_data; const uint8_t *adv; const uint8_t *addr; uint32_t instant; @@ -236,9 +260,10 @@ if (len > adv_len) break; - if (adv[1] >= 0x29 && adv[1] <= 0x2B) - process_rx(io->pvt, ev->rssi, instant, addr, adv + 1, - adv[0]); + if (adv[1] >= MESH_AD_TYPE_PROVISION && + adv[1] <= MESH_AD_TYPE_BEACON) + process_rx(index, pvt, ev->rssi, instant, addr, + adv + 1, adv[0]); adv += field_len + 1; } @@ -270,7 +295,7 @@ static bool find_active(const void *a, const void *b) { - const struct pvt_rx_reg *rx_reg = a; + const struct mesh_io_reg *rx_reg = a; /* Mesh specific AD types do *not* require active scanning, * so do not turn on Active Scanning on their account. @@ -320,6 +345,12 @@ mesh->num_ad_types = sizeof(mesh_ad_types); memcpy(mesh->ad_types, mesh_ad_types, sizeof(mesh_ad_types)); + pvt->rx_id = mesh_mgmt_register(MGMT_EV_MESH_DEVICE_FOUND, + MGMT_INDEX_NONE, event_device_found, pvt, + NULL); + pvt->tx_id = mesh_mgmt_register(MGMT_EV_MESH_PACKET_CMPLT, + index, send_cmplt, pvt, NULL); + mesh_mgmt_send(MGMT_OP_SET_MESH_RECEIVER, index, len, mesh, mesh_up, L_UINT_TO_PTR(index), NULL); l_debug("done %d mesh startup", index); @@ -407,13 +438,7 @@ mesh_mgmt_send(MGMT_OP_READ_INFO, index, 0, NULL, read_info_cb, L_UINT_TO_PTR(index), NULL); - pvt->rx_id = mesh_mgmt_register(MGMT_EV_MESH_DEVICE_FOUND, - MGMT_INDEX_NONE, event_device_found, io, NULL); - pvt->tx_id = mesh_mgmt_register(MGMT_EV_MESH_PACKET_CMPLT, - MGMT_INDEX_NONE, send_cmplt, io, NULL); - pvt->dup_filters = l_queue_new(); - pvt->rx_regs = l_queue_new(); pvt->tx_pkts = l_queue_new(); pvt->io = io; @@ -422,14 +447,6 @@ return true; } -static void free_rx_reg(void *user_data) -{ - struct pvt_rx_reg *rx_reg = user_data; - - l_free(rx_reg); -} - - static bool dev_destroy(struct mesh_io *io) { unsigned char param[] = { 0x00 }; @@ -443,8 +460,8 @@ mesh_mgmt_unregister(pvt->rx_id); mesh_mgmt_unregister(pvt->tx_id); l_timeout_remove(pvt->tx_timeout); + l_timeout_remove(pvt->dup_timeout); l_queue_destroy(pvt->dup_filters, l_free); - l_queue_destroy(pvt->rx_regs, free_rx_reg); l_queue_destroy(pvt->tx_pkts, l_free); io->pvt = NULL; l_free(pvt); @@ -475,7 +492,7 @@ if (pvt->handle) { remove.handle = pvt->handle; - l_debug("Cancel TX"); + /* l_debug("Cancel TX"); */ mesh_mgmt_send(MGMT_OP_MESH_SEND_CANCEL, pvt->send_idx, sizeof(remove), &remove, NULL, NULL, NULL); @@ -522,9 +539,14 @@ send->adv_data_len = tx->len + 1; send->adv_data[0] = tx->len; memcpy(send->adv_data + 1, tx->pkt, tx->len); + + /* Filter looped back Provision packets */ + if (tx->pkt[0] == MESH_AD_TYPE_PROVISION) + filter_dups(NULL, send->adv_data, get_instant()); + mesh_mgmt_send(MGMT_OP_MESH_SEND, index, len, send, send_queued, tx, NULL); - print_packet("Mesh Send Start", tx->pkt, tx->len); + /* print_packet("Mesh Send Start", tx->pkt, tx->len); */ pvt->tx = tx; } @@ -711,37 +733,16 @@ 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 pvt_rx_reg *rx_reg; bool active = false; - if (!cb || !filter || !len || io->pvt != pvt) + if (io->pvt != pvt) return false; - rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter, filter); - - free_rx_reg(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); - /* Look for any AD types requiring Active Scanning */ - if (l_queue_find(pvt->rx_regs, find_active, NULL)) + if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; if (pvt->active != active) { @@ -755,18 +756,13 @@ static bool recv_deregister(struct mesh_io *io, const uint8_t *filter, uint8_t len) { - struct pvt_rx_reg *rx_reg; bool active = false; if (io->pvt != pvt) return false; - rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter, filter); - - free_rx_reg(rx_reg); - /* Look for any AD types requiring Active Scanning */ - if (l_queue_find(pvt->rx_regs, find_active, NULL)) + if (l_queue_find(io->rx_regs, find_active, NULL)) active = true; if (active != pvt->active) { diff -Nru bluez-5.66/mesh/mesh-io-unit.c bluez-5.68/mesh/mesh-io-unit.c --- bluez-5.66/mesh/mesh-io-unit.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-io-unit.c 2023-06-30 08:10:20.000000000 +0000 @@ -485,39 +485,9 @@ return true; } -static bool find_by_filter(const void *a, const void *b) -{ - const struct pvt_rx_reg *rx_reg_old = a; - const struct pvt_rx_reg *rx_reg = b; - - if (rx_reg_old->len != rx_reg->len) - return false; - - return !memcmp(rx_reg_old->filter, rx_reg->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, *rx_reg_old; - - if (!cb || !filter || !len) - return false; - - 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; - - rx_reg_old = l_queue_remove_if(pvt->rx_regs, find_by_filter, rx_reg); - - l_free(rx_reg_old); - - l_queue_push_head(pvt->rx_regs, rx_reg); - return true; } diff -Nru bluez-5.66/mesh/mesh-mgmt.c bluez-5.68/mesh/mesh-mgmt.c --- bluez-5.66/mesh/mesh-mgmt.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-mgmt.c 2023-06-30 08:10:20.000000000 +0000 @@ -271,3 +271,8 @@ { return mgmt_unregister(mgmt_mesh, id); } + +void mesh_mgmt_clear(void) +{ + l_queue_clear(ctl_list, l_free); +} diff -Nru bluez-5.66/mesh/mesh-mgmt.h bluez-5.68/mesh/mesh-mgmt.h --- bluez-5.66/mesh/mesh-mgmt.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/mesh-mgmt.h 2023-06-30 08:10:20.000000000 +0000 @@ -22,3 +22,4 @@ void *user_data, mgmt_destroy_func_t destroy); bool mesh_mgmt_unregister(unsigned int id); void mesh_mgmt_destroy(void); +void mesh_mgmt_clear(void); diff -Nru bluez-5.66/mesh/model.c bluez-5.68/mesh/model.c --- bluez-5.66/mesh/model.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/model.c 2023-06-30 08:10:20.000000000 +0000 @@ -24,6 +24,9 @@ #include "mesh/net.h" #include "mesh/appkey.h" #include "mesh/cfgmod.h" +#include "mesh/prov.h" +#include "mesh/remprv.h" +#include "mesh/prv-beacon.h" #include "mesh/error.h" #include "mesh/dbus.h" #include "mesh/util.h" @@ -76,6 +79,12 @@ if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL) return true; + if (id == REM_PROV_SRV_MODEL || id == REM_PROV_CLI_MODEL) + return true; + + if (id == PRV_BEACON_SRV_MODEL || id == PRV_BEACON_CLI_MODEL) + return true; + return false; } @@ -457,13 +466,25 @@ dst, key_aid, seq, iv_idx, out, key)) return APP_IDX_DEV_LOCAL; - if (!keyring_get_remote_dev_key(node, src, dev_key)) + key = dev_key; + + if (keyring_get_remote_dev_key(node, src, dev_key)) { + if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, + src, dst, key_aid, seq, iv_idx, out, key)) + return APP_IDX_DEV_REMOTE; + } + + /* See if there is a local Device Key Candidate as last resort */ + if (!node_get_device_key_candidate(node, dev_key)) return -1; - key = dev_key; - if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, src, - dst, key_aid, seq, iv_idx, out, key)) - return APP_IDX_DEV_REMOTE; + if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, + src, dst, key_aid, seq, iv_idx, out, key)) { + + /* If candidate dev_key worked, it is considered finalized */ + node_finalize_candidate(node); + return APP_IDX_DEV_LOCAL; + } return -1; } @@ -630,6 +651,9 @@ if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL) return MESH_STATUS_INVALID_MODEL; + if (id == PRV_BEACON_SRV_MODEL || id == PRV_BEACON_CLI_MODEL) + return MESH_STATUS_INVALID_MODEL; + if (!appkey_have_key(node_get_net(node), app_idx)) return MESH_STATUS_INVALID_APPKEY; @@ -1638,7 +1662,8 @@ SET_ID(SIG_VENDOR, db_mod->id)); /* Implicitly bind config server model to device key */ - if (db_mod->id == CONFIG_SRV_MODEL) { + if (db_mod->id == CONFIG_SRV_MODEL || + db_mod->id == PRV_BEACON_SRV_MODEL) { if (ele_idx != PRIMARY_ELE_IDX) { l_free(mod); diff -Nru bluez-5.66/mesh/net.c bluez-5.68/mesh/net.c --- bluez-5.66/mesh/net.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/net.c 2023-06-30 08:10:20.000000000 +0000 @@ -102,7 +102,8 @@ unsigned int sar_id_next; bool friend_enable; - bool beacon_enable; + bool snb_enable; + bool mpb_enable; bool proxy_enable; bool friend_seq; struct l_timeout *iv_update_timeout; @@ -119,6 +120,7 @@ uint8_t chan; /* Channel of recent Rx */ uint8_t default_ttl; uint8_t tid; + uint8_t mpb_period; struct { bool enable; @@ -217,6 +219,7 @@ bool ivu; bool kr; bool processed; + bool local; }; static struct l_queue *fast_cache; @@ -526,6 +529,13 @@ static void subnet_free(void *data) { struct mesh_subnet *subnet = data; + struct mesh_net *net = subnet->net; + + if (net->snb_enable) + net_key_beacon_disable(subnet->net_key_tx, false); + + if (net->mpb_enable) + net_key_beacon_disable(subnet->net_key_tx, true); net_key_unref(subnet->net_key_cur); net_key_unref(subnet->net_key_upd); @@ -545,15 +555,27 @@ return subnet; } -static void enable_beacon(void *a, void *b) +static void enable_snb(void *a, void *b) +{ + struct mesh_subnet *subnet = a; + struct mesh_net *net = b; + + if (net->snb_enable) + net_key_beacon_enable(subnet->net_key_tx, false, 0); + else + net_key_beacon_disable(subnet->net_key_tx, false); +} + +static void enable_mpb(void *a, void *b) { struct mesh_subnet *subnet = a; struct mesh_net *net = b; - if (net->beacon_enable) - net_key_beacon_enable(subnet->net_key_tx); + if (net->mpb_enable) + net_key_beacon_enable(subnet->net_key_tx, true, + net->mpb_period); else - net_key_beacon_disable(subnet->net_key_tx); + net_key_beacon_disable(subnet->net_key_tx, true); } static void enqueue_update(void *a, void *b); @@ -602,7 +624,8 @@ struct mesh_net *net = b; net_key_beacon_refresh(subnet->net_key_tx, net->iv_index, - !!(subnet->kr_phase == KEY_REFRESH_PHASE_TWO), net->iv_update); + !!(subnet->kr_phase == KEY_REFRESH_PHASE_TWO), net->iv_update, + false); } struct mesh_net *mesh_net_new(struct mesh_node *node) @@ -826,7 +849,7 @@ if (idx == net->hb_pub.net_idx) net->hb_pub.dst = UNASSIGNED_ADDRESS; - /* TODO: cancel beacon_enable on this subnet */ + /* TODO: cancel snb_enable on this subnet */ l_queue_remove(net->subnets, subnet); subnet_free(subnet); @@ -853,10 +876,14 @@ } net_key_beacon_refresh(subnet->net_key_tx, net->iv_index, - false, net->iv_update); + false, net->iv_update, false); - if (net->beacon_enable) - net_key_beacon_enable(subnet->net_key_tx); + if (net->snb_enable) + net_key_beacon_enable(subnet->net_key_tx, false, 0); + + if (net->mpb_enable) + net_key_beacon_enable(subnet->net_key_tx, true, + net->mpb_period); l_queue_push_tail(net->subnets, subnet); @@ -2598,6 +2625,13 @@ if (subnet->kr_phase == KEY_REFRESH_PHASE_TWO) return MESH_STATUS_SUCCESS; + /* Stop beaconing on old key */ + if (net->snb_enable) + net_key_beacon_disable(subnet->net_key_tx, false); + + if (net->mpb_enable) + net_key_beacon_disable(subnet->net_key_tx, true); + subnet->key_refresh = 1; subnet->net_key_tx = subnet->net_key_upd; /* @@ -2605,6 +2639,15 @@ * it hears beacons from all the nodes */ subnet->kr_phase = KEY_REFRESH_PHASE_TWO; + + /* Start beaconing on new key */ + if (net->snb_enable) + net_key_beacon_enable(subnet->net_key_tx, false, 0); + + if (net->mpb_enable) + net_key_beacon_enable(subnet->net_key_tx, true, + net->mpb_period); + refresh_beacon(subnet, net); queue_friend_update(net); @@ -2794,83 +2837,111 @@ beacon_data->processed = true; /* - * Ignore the beacon if it doesn't change anything, unless we're - * doing IV Recovery + * Ignore local beacons and beacons that don't change anything, + * unless we're doing IV Recovery */ - if (net->iv_upd_state == IV_UPD_INIT || ivi != net->iv_index || + if (!beacon_data->local) { + if (net->iv_upd_state == IV_UPD_INIT || ivi != net->iv_index || ivu != net->iv_update) - updated |= update_iv_ivu_state(net, ivi, ivu); + updated |= update_iv_ivu_state(net, ivi, ivu); + + if (kr != local_kr) + updated |= update_kr_state(subnet, kr, + beacon_data->net_key_id); - if (kr != local_kr || beacon_data->net_key_id != subnet->net_key_cur) - updated |= update_kr_state(subnet, kr, beacon_data->net_key_id); - if (updated) - net_key_beacon_refresh(subnet->net_key_tx, net->iv_index, + if (updated) + net_key_beacon_refresh(beacon_data->net_key_id, + net->iv_index, !!(subnet->kr_phase == KEY_REFRESH_PHASE_TWO), - net->iv_update); + net->iv_update, false); + } } static void beacon_recv(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len) { struct net_beacon_data beacon_data = { + .local = false, .processed = false, }; - if (len != 23 || data[1] != 0x01) - return; + beacon_data.net_key_id = net_key_beacon(data, len, &beacon_data.ivi, + &beacon_data.ivu, &beacon_data.kr); - /* Ignore Network IDs unknown to this daemon */ - beacon_data.net_key_id = net_key_network_id(data + 3); if (!beacon_data.net_key_id) return; - /* Get data bits from beacon */ - beacon_data.ivu = !!(data[2] & 0x02); - beacon_data.kr = !!(data[2] & 0x01); - beacon_data.ivi = l_get_be32(data + 11); - - /* Validate beacon before accepting */ - if (!net_key_snb_check(beacon_data.net_key_id, beacon_data.ivi, - beacon_data.kr, beacon_data.ivu, - l_get_be64(data + 15))) { - l_error("mesh_crypto_beacon verify failed"); - return; - } - l_queue_foreach(nets, process_beacon, &beacon_data); if (beacon_data.processed) net_key_beacon_seen(beacon_data.net_key_id); } -void net_local_beacon(uint32_t net_key_id, uint8_t *beacon) +void net_local_beacon(uint32_t net_key_id, uint32_t ivi, bool ivu, bool kr) { struct net_beacon_data beacon_data = { + .local = true, + .processed = false, .net_key_id = net_key_id, - .ivu = !!(beacon[2] & 0x02), - .kr = !!(beacon[2] & 0x01), - .ivi = l_get_be32(beacon + 11), + .ivu = ivu, + .kr = kr, + .ivi = ivi, }; /* Deliver locally generated beacons to all nodes */ l_queue_foreach(nets, process_beacon, &beacon_data); } -bool mesh_net_set_beacon_mode(struct mesh_net *net, bool enable) +bool mesh_net_set_snb_mode(struct mesh_net *net, bool enable) +{ + if (!net) + return false; + + if (net->snb_enable == enable) + return true; + + net->snb_enable = enable; + + if (enable) + l_queue_foreach(net->subnets, refresh_beacon, net); + + l_queue_foreach(net->subnets, enable_snb, net); + queue_friend_update(net); + + return true; +} + +bool mesh_net_set_mpb_mode(struct mesh_net *net, bool enable, uint8_t period, + bool initialize) { + uint8_t old_period; + bool old_enable; + if (!net) return false; - if (net->beacon_enable == enable) + old_enable = net->mpb_enable; + old_period = net->mpb_period; + + if (enable) + net->mpb_period = period; + + if (old_enable == enable && old_period == net->mpb_period) return true; - net->beacon_enable = enable; + if (enable && !initialize) { + /* If enable with different period, disable and re-enable */ + net->mpb_enable = false; + l_queue_foreach(net->subnets, enable_mpb, net); + } + + net->mpb_enable = enable; if (enable) l_queue_foreach(net->subnets, refresh_beacon, net); - l_queue_foreach(net->subnets, enable_beacon, net); + l_queue_foreach(net->subnets, enable_mpb, net); queue_friend_update(net); return true; @@ -2908,17 +2979,25 @@ subnet->key_refresh = 1; subnet->net_key_tx = subnet->net_key_upd; - if (net->beacon_enable) { + if (net->snb_enable) { /* Switch beaconing key */ - net_key_beacon_disable(subnet->net_key_cur); - net_key_beacon_enable(subnet->net_key_upd); + net_key_beacon_disable(subnet->net_key_cur, false); + net_key_beacon_enable(subnet->net_key_upd, false, 0); + } + + if (net->mpb_enable) { + /* Switch beaconing key */ + net_key_beacon_disable(subnet->net_key_cur, true); + net_key_beacon_enable(subnet->net_key_upd, true, + net->mpb_period); } } subnet->kr_phase = phase; net_key_beacon_refresh(subnet->net_key_tx, net->iv_index, - !!(subnet->kr_phase == KEY_REFRESH_PHASE_TWO), net->iv_update); + !!(subnet->kr_phase == KEY_REFRESH_PHASE_TWO), net->iv_update, + false); return true; @@ -2933,8 +3012,9 @@ first = l_queue_isempty(nets); if (first) { - uint8_t snb[] = {MESH_AD_TYPE_BEACON, 0x01}; - uint8_t pkt[] = {MESH_AD_TYPE_NETWORK}; + const uint8_t snb[] = {MESH_AD_TYPE_BEACON, 1}; + const uint8_t mpb[] = {MESH_AD_TYPE_BEACON, 2}; + const uint8_t pkt[] = {MESH_AD_TYPE_NETWORK}; if (!nets) nets = l_queue_new(); @@ -2944,6 +3024,8 @@ mesh_io_register_recv_cb(io, snb, sizeof(snb), beacon_recv, NULL); + mesh_io_register_recv_cb(io, mpb, sizeof(mpb), + beacon_recv, NULL); mesh_io_register_recv_cb(io, pkt, sizeof(pkt), net_msg_recv, NULL); } @@ -2960,8 +3042,9 @@ struct mesh_io *mesh_net_detach(struct mesh_net *net) { - uint8_t snb[] = {MESH_AD_TYPE_BEACON, 0x01}; - uint8_t pkt[] = {MESH_AD_TYPE_NETWORK}; + const uint8_t snb[] = {MESH_AD_TYPE_BEACON, 1}; + const uint8_t mpb[] = {MESH_AD_TYPE_BEACON, 2}; + const uint8_t pkt[] = {MESH_AD_TYPE_NETWORK}; struct mesh_io *io; uint8_t type = 0; @@ -2975,6 +3058,7 @@ /* Only deregister io if this is the last network detached.*/ if (l_queue_length(nets) < 2) { mesh_io_deregister_recv_cb(io, snb, sizeof(snb)); + mesh_io_deregister_recv_cb(io, mpb, sizeof(mpb)); mesh_io_deregister_recv_cb(io, pkt, sizeof(pkt)); } diff -Nru bluez-5.66/mesh/net.h bluez-5.68/mesh/net.h --- bluez-5.66/mesh/net.h 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/mesh/net.h 2023-06-30 08:10:20.000000000 +0000 @@ -236,8 +236,10 @@ uint16_t mesh_net_get_address(struct mesh_net *net); bool mesh_net_register_unicast(struct mesh_net *net, uint16_t unicast, uint8_t num_ele); -void net_local_beacon(uint32_t net_key_id, uint8_t *beacon); -bool mesh_net_set_beacon_mode(struct mesh_net *net, bool enable); +void net_local_beacon(uint32_t key_id, uint32_t ivi, bool ivu, bool kr); +bool mesh_net_set_snb_mode(struct mesh_net *net, bool enable); +bool mesh_net_set_mpb_mode(struct mesh_net *net, bool enabla, uint8_t period, + bool init); bool mesh_net_set_proxy_mode(struct mesh_net *net, bool enable); bool mesh_net_set_relay_mode(struct mesh_net *net, bool enable, uint8_t cnt, uint8_t interval); diff -Nru bluez-5.66/mesh/net-keys.c bluez-5.68/mesh/net-keys.c --- bluez-5.66/mesh/net-keys.c 2021-10-13 18:38:34.000000000 +0000 +++ bluez-5.68/mesh/net-keys.c 2023-06-30 08:10:20.000000000 +0000 @@ -21,39 +21,55 @@ #include "mesh/net.h" #include "mesh/net-keys.h" -#define BEACON_TYPE_SNB 0x01 -#define KEY_REFRESH 0x01 -#define IV_INDEX_UPDATE 0x02 - #define BEACON_INTERVAL_MIN 10 #define BEACON_INTERVAL_MAX 600 -struct net_beacon { +/* This allows daemon to skip decryption on recently seen beacons */ +#define BEACON_CACHE_MAX 10 + +struct beacon_rx { + uint8_t data[28]; + uint32_t id; + uint32_t ivi; + bool kr; + bool ivu; +}; + +struct beacon_observe { struct l_timeout *timeout; uint32_t ts; - uint16_t observe_period; - uint16_t observed; + uint16_t period; + uint16_t seen; uint16_t expected; bool half_period; - uint8_t beacon[23]; }; struct net_key { uint32_t id; - struct net_beacon snb; + struct l_timeout *mpb_to; + uint8_t *mpb; + uint8_t *snb; + struct beacon_observe observe; + uint32_t ivi; uint16_t ref_cnt; - uint16_t beacon_enables; + uint16_t mpb_enables; + uint16_t snb_enables; + uint8_t mpb_refresh; uint8_t friend_key; uint8_t nid; uint8_t flooding[16]; - uint8_t encrypt[16]; - uint8_t privacy[16]; - uint8_t beacon[16]; - uint8_t network[8]; + uint8_t enc_key[16]; + uint8_t prv_key[16]; + uint8_t snb_key[16]; + uint8_t pvt_key[16]; + uint8_t net_id[8]; + bool kr; + bool ivu; }; -static struct l_queue *keys = NULL; -static uint32_t last_flooding_id = 0; +static struct l_queue *beacons; +static struct l_queue *keys; +static uint32_t last_flooding_id; /* To avoid re-decrypting same packet for multiple nodes, cache and check */ static uint8_t cache_pkt[29]; @@ -81,9 +97,9 @@ static bool match_network(const void *a, const void *b) { const struct net_key *key = a; - const uint8_t *network = b; + const uint8_t *net_id = b; - return memcmp(key->network, network, sizeof(key->network)) == 0; + return memcmp(key->net_id, net_id, sizeof(key->net_id)) == 0; } /* Key added from Provisioning, NetKey Add or NetKey update */ @@ -101,19 +117,27 @@ if (!keys) keys = l_queue_new(); + if (!beacons) + beacons = l_queue_new(); + key = l_new(struct net_key, 1); memcpy(key->flooding, flooding, 16); key->ref_cnt++; - result = mesh_crypto_k2(flooding, p, sizeof(p), &key->nid, key->encrypt, - key->privacy); + key->mpb_refresh = NET_MPB_REFRESH_DEFAULT; + result = mesh_crypto_k2(flooding, p, sizeof(p), &key->nid, key->enc_key, + key->prv_key); + if (!result) + goto fail; + + result = mesh_crypto_k3(flooding, key->net_id); if (!result) goto fail; - result = mesh_crypto_k3(flooding, key->network); + result = mesh_crypto_nkbk(flooding, key->snb_key); if (!result) goto fail; - result = mesh_crypto_nkbk(flooding, key->beacon); + result = mesh_crypto_nkpk(flooding, key->pvt_key); if (!result) goto fail; @@ -146,7 +170,7 @@ l_put_be16(fn_cnt, p + 7); result = mesh_crypto_k2(key->flooding, p, sizeof(p), &frnd_key->nid, - frnd_key->encrypt, frnd_key->privacy); + frnd_key->enc_key, frnd_key->prv_key); if (!result) { l_free(frnd_key); @@ -167,7 +191,7 @@ if (key && key->ref_cnt) { if (--key->ref_cnt == 0) { - l_timeout_remove(key->snb.timeout); + l_timeout_remove(key->observe.timeout); l_queue_remove(keys, key); l_free(key); } @@ -206,7 +230,7 @@ result = mesh_crypto_packet_decode(cache_pkt, cache_len, false, cache_plain, cache_iv_index, - key->encrypt, key->privacy); + key->enc_key, key->prv_key); if (result) { cache_id = key->id; @@ -254,8 +278,8 @@ if (!key) return false; - result = mesh_crypto_packet_encode(pkt, len, iv_index, key->encrypt, - key->privacy); + result = mesh_crypto_packet_encode(pkt, len, iv_index, key->enc_key, + key->prv_key); if (!result) return false; @@ -265,9 +289,9 @@ return result; } -uint32_t net_key_network_id(const uint8_t network[8]) +uint32_t net_key_network_id(const uint8_t net_id[8]) { - struct net_key *key = l_queue_find(keys, match_network, network); + struct net_key *key = l_queue_find(keys, match_network, net_id); if (!key) return 0; @@ -275,6 +299,55 @@ return key->id; } +struct auth_check { + const uint8_t *data; + uint32_t id; + uint32_t ivi; + bool ivu; + bool kr; +}; + +static void check_auth(void *a, void *b) +{ + struct net_key *key = a; + struct auth_check *auth = b; + uint8_t out[5]; + + + /* Stop checking if already found */ + if (auth->id) + return; + + if (mesh_crypto_aes_ccm_decrypt(auth->data + 1, key->pvt_key, NULL, 0, + auth->data + 14, 13, + out, NULL, 8)) { + auth->id = key->id; + auth->ivi = l_get_be32(out + 1); + auth->ivu = !!(out[0] & 0x02); + auth->kr = !!(out[0] & 0x01); + } +} + +static uint32_t private_beacon_check(const void *beacon, uint32_t *ivi, + bool *ivu, bool *kr) +{ + struct auth_check auth = { + .data = beacon, + .id = 0, + }; + + auth.id = 0; + l_queue_foreach(keys, check_auth, &auth); + + if (auth.id) { + *ivi = auth.ivi; + *ivu = auth.ivu; + *kr = auth.kr; + } + + return auth.id; +} + bool net_key_snb_check(uint32_t id, uint32_t iv_index, bool kr, bool ivu, uint64_t cmac) { @@ -285,7 +358,7 @@ return false; /* Any behavioral changes must pass CMAC test */ - if (!mesh_crypto_beacon_cmac(key->beacon, key->network, iv_index, kr, + if (!mesh_crypto_beacon_cmac(key->snb_key, key->net_id, iv_index, kr, ivu, &cmac_check)) { l_error("mesh_crypto_beacon_cmac failed"); return false; @@ -300,39 +373,142 @@ return true; } -bool net_key_snb_compose(uint32_t id, uint32_t iv_index, bool kr, bool ivu, - uint8_t *snb) +static bool mpb_compose(struct net_key *key, uint32_t ivi, bool kr, bool ivu) +{ + uint8_t b_data[5 + 8]; + uint8_t random[13]; + + if (!key) + return false; + + b_data[0] = 0; + l_put_be32(ivi, b_data + 1); + + if (kr) + b_data[0] |= KEY_REFRESH; + + if (ivu) + b_data[0] |= IV_INDEX_UPDATE; + + l_getrandom(random, sizeof(random)); + if (!mesh_crypto_aes_ccm_encrypt(random, key->pvt_key, NULL, 0, + b_data, 5, b_data, NULL, 8)) + return false; + + key->mpb[0] = MESH_AD_TYPE_BEACON; + key->mpb[1] = BEACON_TYPE_MPB; + memcpy(key->mpb + 2, random, 13); + memcpy(key->mpb + 15, b_data, 13); + + return true; +} + +static bool snb_compose(struct net_key *key, uint32_t ivi, bool kr, bool ivu) { - struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); uint64_t cmac; if (!key) return false; /* Any behavioral changes must pass CMAC test */ - if (!mesh_crypto_beacon_cmac(key->beacon, key->network, iv_index, kr, + if (!mesh_crypto_beacon_cmac(key->snb_key, key->net_id, ivi, kr, ivu, &cmac)) { l_error("mesh_crypto_beacon_cmac failed"); return false; } - snb[0] = MESH_AD_TYPE_BEACON; - snb[1] = BEACON_TYPE_SNB; - snb[2] = 0; + key->snb[0] = MESH_AD_TYPE_BEACON; + key->snb[1] = BEACON_TYPE_SNB; + key->snb[2] = 0; if (kr) - snb[2] |= KEY_REFRESH; + key->snb[2] |= KEY_REFRESH; if (ivu) - snb[2] |= IV_INDEX_UPDATE; + key->snb[2] |= IV_INDEX_UPDATE; - memcpy(snb + 3, key->network, 8); - l_put_be32(iv_index, snb + 11); - l_put_be64(cmac, snb + 15); + memcpy(key->snb + 3, key->net_id, 8); + l_put_be32(ivi, key->snb + 11); + l_put_be64(cmac, key->snb + 15); return true; } +static bool match_beacon(const void *a, const void *b) +{ + const struct beacon_rx *cached = a; + const uint8_t *incoming = b; + + if (incoming[0] == BEACON_TYPE_MPB) + return !memcmp(cached->data, incoming, 27); + + if (incoming[0] == BEACON_TYPE_SNB) + return !memcmp(cached->data, incoming, 22); + + return false; +} + +uint32_t net_key_beacon(const uint8_t *data, uint16_t len, uint32_t *ivi, + bool *ivu, bool *kr) +{ + struct net_key *key; + struct beacon_rx *beacon; + uint32_t b_id, b_ivi; + bool b_ivu, b_kr; + + if (data[1] == BEACON_TYPE_SNB && len != 23) + return 0; + + if (data[1] == BEACON_TYPE_MPB && len != 28) + return 0; + + beacon = l_queue_remove_if(beacons, match_beacon, data + 1); + + if (beacon) + goto accept; + + /* Validate beacon data */ + if (data[1] == BEACON_TYPE_SNB) { + key = l_queue_find(keys, match_network, data + 3); + + if (!key) + return 0; + + b_id = key->id; + b_ivu = !!(data[2] & 0x02); + b_kr = !!(data[2] & 0x01); + b_ivi = l_get_be32(data + 11); + + if (!net_key_snb_check(b_id, b_ivi, b_kr, b_ivu, + l_get_be64(data + 15))) + return 0; + + } else if (data[1] == BEACON_TYPE_MPB) { + b_id = private_beacon_check(data + 1, &b_ivi, &b_ivu, &b_kr); + + if (!b_id) + return 0; + + } else + return 0; + + beacon = l_new(struct beacon_rx, 1); + memcpy(beacon->data, data + 1, len - 1); + beacon->id = b_id; + beacon->ivi = b_ivi; + beacon->ivu = b_ivu; + beacon->kr = b_kr; + +accept: + *ivi = beacon->ivi; + *ivu = beacon->ivu; + *kr = beacon->kr; + + l_queue_push_head(beacons, beacon); + + return beacon->id; +} + static void send_network_beacon(struct net_key *key) { struct mesh_io_send_info info = { @@ -343,10 +519,26 @@ .u.gen.max_delay = DEFAULT_MAX_DELAY }; - mesh_io_send(NULL, &info, key->snb.beacon, sizeof(key->snb.beacon)); + if (key->mpb_enables) { + /* If Interval steps == 0, refresh key every time */ + if (!key->mpb_refresh || !key->mpb || !key->mpb[0]) + net_key_beacon_refresh(key->id, key->ivi, key->kr, + key->ivu, true); + + mesh_io_send(NULL, &info, key->mpb, 28); + } + + if (key->snb_enables) { + if (!key->snb || !key->snb[0]) { + net_key_beacon_refresh(key->id, key->ivi, key->kr, + key->ivu, true); + } + + mesh_io_send(NULL, &info, key->snb, 23); + } } -static void snb_timeout(struct l_timeout *timeout, void *user_data) +static void beacon_timeout(struct l_timeout *timeout, void *user_data) { struct net_key *key = user_data; uint32_t interval, scale_factor; @@ -355,29 +547,29 @@ send_network_beacon(key); /* Count our own beacons towards the vicinity total */ - key->snb.observed++; + key->observe.seen++; - if (!key->snb.half_period) { + if (!key->observe.half_period) { l_debug("beacon %d for %d nodes, period %d, obs %d, exp %d", - key->id, - key->beacon_enables, - key->snb.observe_period, - key->snb.observed, - key->snb.expected); + key->id, + key->snb_enables + key->mpb_enables, + key->observe.period, + key->observe.seen, + key->observe.expected); - interval = (key->snb.observe_period * key->snb.observed) - / key->snb.expected; + interval = (key->observe.period * key->observe.seen) + / key->observe.expected; /* Limit Increases and Decreases by 10 seconds Up and * 20 seconds down each step, to avoid going nearly silent * in highly populated environments. */ - if (interval - 10 > key->snb.observe_period) - interval = key->snb.observe_period + 10; - else if (interval + 20 < key->snb.observe_period) - interval = key->snb.observe_period - 20; + if (interval - 10 > key->observe.period) + interval = key->observe.period + 10; + else if (interval + 20 < key->observe.period) + interval = key->observe.period - 20; /* Beaconing must be no *slower* than once every 10 minutes, * and no *faster* than once every 10 seconds, per spec. @@ -388,26 +580,26 @@ else if (interval > BEACON_INTERVAL_MAX * 2) interval = BEACON_INTERVAL_MAX * 2; - key->snb.observe_period = interval; - key->snb.observed = 0; + key->observe.period = interval; + key->observe.seen = 0; /* To prevent "over slowing" of the beaconing frequency, * require more significant "over observing" the slower * our own beaconing frequency. */ - key->snb.expected = interval / 10; + key->observe.expected = interval / 10; scale_factor = interval / 60; - key->snb.expected += scale_factor * 3; + key->observe.expected += scale_factor * 3; } - interval = key->snb.observe_period / 2; - key->snb.half_period = !key->snb.half_period; + interval = key->observe.period / 2; + key->observe.half_period = !key->observe.half_period; - if (key->beacon_enables) + if (key->mpb_enables || key->snb_enables) l_timeout_modify(timeout, interval); else { l_timeout_remove(timeout); - key->snb.timeout = NULL; + key->observe.timeout = NULL; } } @@ -416,8 +608,8 @@ struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (key) { - key->snb.observed++; - key->snb.ts = get_timestamp_secs(); + key->observe.seen++; + key->observe.ts = get_timestamp_secs(); } } @@ -426,97 +618,181 @@ struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); if (key) - return key->snb.ts; + return key->observe.ts; return 0; } -void net_key_beacon_enable(uint32_t id) +bool net_key_beacon_refresh(uint32_t id, uint32_t ivi, bool kr, bool ivu, + bool force) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); - bool enabled; + bool refresh = force; uint32_t rand_ms; if (!key) - return; + return false; - enabled = !!key->beacon_enables; - key->beacon_enables++; + if (key->snb_enables && !key->snb) { + key->snb = l_new(uint8_t, 23); + refresh = true; + } - /* If already Enabled, do nothing */ - if (enabled) - return; + if (key->mpb_enables && !key->mpb) { + key->mpb = l_new(uint8_t, 28); + refresh = true; + } - /* Randomize first timeout to avoid bursts of beacons */ - l_getrandom(&rand_ms, sizeof(rand_ms)); - rand_ms %= (BEACON_INTERVAL_MIN * 1000); - rand_ms++; + if (key->ivi != ivi || key->ivu != ivu || key->kr != kr) + refresh = true; - /* Enable Periodic Beaconing on this key */ - key->snb.observe_period = BEACON_INTERVAL_MIN * 2; - key->snb.expected = 2; - key->snb.observed = 0; - key->snb.half_period = true; - l_timeout_remove(key->snb.timeout); - key->snb.timeout = l_timeout_create_ms(rand_ms, snb_timeout, key, NULL); -} + if (!refresh) + return true; -bool net_key_beacon_refresh(uint32_t id, uint32_t iv_index, bool kr, bool ivu) -{ - struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); - uint8_t beacon[23]; - uint32_t rand_ms; + if (key->mpb) { + if (!mpb_compose(key, ivi, kr, ivu)) + return false; - if (!key) - return false; + print_packet("Set MPB to", key->mpb, 28); + } - if (!net_key_snb_compose(id, iv_index, kr, ivu, beacon)) - return false; + if (key->snb) { + if (!snb_compose(key, ivi, kr, ivu)) + return false; - if (memcmp(key->snb.beacon, beacon, sizeof(beacon))) - memcpy(key->snb.beacon, beacon, sizeof(beacon)); - else - return false; + print_packet("Set SNB to", key->snb, 23); + } - l_debug("Setting SNB: IVI: %8.8x, IVU: %d, KR: %d", iv_index, ivu, kr); - print_packet("Set SNB Beacon to", beacon, sizeof(beacon)); + l_debug("Set Beacon: IVI: %8.8x, IVU: %d, KR: %d", ivi, ivu, kr); + + key->ivi = ivi; + key->ivu = ivu; + key->kr = kr; /* Propagate changes to all local nodes */ - net_local_beacon(id, beacon); + net_local_beacon(id, ivi, ivu, kr); /* Send one new SNB soon, after all nodes have seen it */ l_getrandom(&rand_ms, sizeof(rand_ms)); rand_ms %= 1000; - key->snb.expected++; + key->observe.expected++; - if (key->snb.timeout) - l_timeout_modify_ms(key->snb.timeout, 500 + rand_ms); + if (key->observe.timeout) + l_timeout_modify_ms(key->observe.timeout, 500 + rand_ms); else - key->snb.timeout = l_timeout_create_ms(500 + rand_ms, - snb_timeout, key, NULL); + key->observe.timeout = l_timeout_create_ms(500 + rand_ms, + beacon_timeout, key, NULL); return true; } -void net_key_beacon_disable(uint32_t id) +static void mpb_timeout(struct l_timeout *timeout, void *user_data) +{ + struct net_key *key = user_data; + + if (key->mpb_refresh) { + l_debug("Refresh in %d seconds", key->mpb_refresh * 10); + l_timeout_modify(timeout, key->mpb_refresh * 10); + } + + net_key_beacon_refresh(key->id, key->ivi, key->kr, key->ivu, true); +} + +void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + bool enabled; + uint32_t rand_ms; + + if (!key) + return; + + enabled = !!key->snb_enables || !!key->mpb_enables; + + if (mpb) { + key->mpb_enables++; + key->mpb_refresh = refresh_count; + l_timeout_remove(key->mpb_to); + if (refresh_count) + key->mpb_to = l_timeout_create(refresh_count * 10, + mpb_timeout, key, NULL); + else + key->mpb_to = NULL; + } else + key->snb_enables++; + + /* If already Enabled, do nothing */ + if (enabled) + return; + + /* Randomize first timeout to avoid bursts of beacons */ + l_getrandom(&rand_ms, sizeof(rand_ms)); + rand_ms %= (BEACON_INTERVAL_MIN * 1000); + rand_ms++; + + /* Enable Periodic Beaconing on this key */ + key->observe.period = BEACON_INTERVAL_MIN * 2; + key->observe.expected = 2; + key->observe.seen = 0; + key->observe.half_period = true; + l_timeout_remove(key->observe.timeout); + key->observe.timeout = l_timeout_create_ms(rand_ms, beacon_timeout, + key, NULL); +} + +void net_key_beacon_disable(uint32_t id, bool mpb) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); - if (!key || !key->beacon_enables) + if (!key) return; - key->beacon_enables--; + if (mpb) { + if (!key->mpb_enables) + return; + + key->mpb_enables--; + + if (!key->mpb_enables) { + l_free(key->mpb); + key->mpb = NULL; + l_timeout_remove(key->mpb_to); + key->mpb_to = NULL; + } + } else { + if (!key->snb_enables) + return; + + key->snb_enables--; + + if (!key->snb_enables) { + l_free(key->snb); + key->snb = NULL; + } + } - if (key->beacon_enables) + if (key->snb_enables || key->mpb_enables) return; /* Disable periodic Beaconing on this key */ - l_timeout_remove(key->snb.timeout); - key->snb.timeout = NULL; + l_timeout_remove(key->observe.timeout); + key->observe.timeout = NULL; +} + +static void free_key(void *data) +{ + struct net_key *key = data; + + l_timeout_remove(key->mpb_to); + l_free(key->snb); + l_free(key->mpb); + l_free(key); } void net_key_cleanup(void) { - l_queue_destroy(keys, l_free); + l_queue_destroy(keys, free_key); keys = NULL; + l_queue_destroy(beacons, l_free); + beacons = NULL; } diff -Nru bluez-5.66/mesh/net-keys.h bluez-5.68/mesh/net-keys.h --- bluez-5.66/mesh/net-keys.h 2021-10-13 18:38:34.000000000 +0000 +++ bluez-5.68/mesh/net-keys.h 2023-06-30 08:10:20.000000000 +0000 @@ -9,8 +9,10 @@ */ #define BEACON_TYPE_SNB 0x01 +#define BEACON_TYPE_MPB 0x02 #define KEY_REFRESH 0x01 #define IV_INDEX_UPDATE 0x02 +#define NET_MPB_REFRESH_DEFAULT 60 void net_key_cleanup(void); bool net_key_confirm(uint32_t id, const uint8_t flooding[16]); @@ -23,12 +25,15 @@ uint8_t **plain, size_t *plain_len); bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len); uint32_t net_key_network_id(const uint8_t network[8]); +uint32_t net_key_beacon(const uint8_t *data, uint16_t len, uint32_t *ivi, + bool *ivu, bool *kr); bool net_key_snb_check(uint32_t id, uint32_t iv_index, bool kr, bool ivu, uint64_t cmac); bool net_key_snb_compose(uint32_t id, uint32_t iv_index, bool kr, bool ivu, uint8_t *snb); void net_key_beacon_seen(uint32_t id); -void net_key_beacon_enable(uint32_t id); -bool net_key_beacon_refresh(uint32_t id, uint32_t iv_index, bool kr, bool ivu); -void net_key_beacon_disable(uint32_t id); +bool net_key_beacon_refresh(uint32_t id, uint32_t iv_index, bool kr, bool ivu, + bool force); +void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count); +void net_key_beacon_disable(uint32_t id, bool mpb); uint32_t net_key_beacon_last_seen(uint32_t id); diff -Nru bluez-5.66/mesh/node.c bluez-5.68/mesh/node.c --- bluez-5.66/mesh/node.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/node.c 2023-06-30 08:10:20.000000000 +0000 @@ -27,9 +27,12 @@ #include "mesh/appkey.h" #include "mesh/mesh-config.h" #include "mesh/provision.h" +#include "mesh/prov.h" #include "mesh/keyring.h" #include "mesh/model.h" #include "mesh/cfgmod.h" +#include "mesh/remprv.h" +#include "mesh/prv-beacon.h" #include "mesh/util.h" #include "mesh/error.h" #include "mesh/dbus.h" @@ -98,6 +101,8 @@ uint8_t proxy; uint8_t friend; uint8_t beacon; + uint8_t mpb; + uint8_t mpb_period; }; struct node_import { @@ -204,6 +209,8 @@ { node->lpn = MESH_MODE_UNSUPPORTED; node->proxy = MESH_MODE_UNSUPPORTED; + node->mpb = MESH_MODE_DISABLED; + node->mpb_period = NET_MPB_REFRESH_DEFAULT; node->friend = (mesh_friendship_supported()) ? MESH_MODE_DISABLED : MESH_MODE_UNSUPPORTED; node->beacon = (mesh_beacon_enabled()) ? MESH_MODE_ENABLED : @@ -340,6 +347,7 @@ struct mesh_config_node *db_node) { const struct l_queue_entry *entry; + struct node_element *ele; entry = l_queue_get_entries(db_node->elements); @@ -347,6 +355,20 @@ if (!add_element_from_storage(node, entry->data)) return false; + ele = l_queue_find(node->elements, match_element_idx, + L_UINT_TO_PTR(PRIMARY_ELE_IDX)); + if (!ele) + return false; + + /* Add configuration server model on the primary element */ + mesh_model_add(node, ele->models, CONFIG_SRV_MODEL, NULL); + + /* Add remote provisioning models on the primary element */ + mesh_model_add(node, ele->models, REM_PROV_SRV_MODEL, NULL); + + if (node->provisioner) + mesh_model_add(node, ele->models, REM_PROV_CLI_MODEL, NULL); + return true; } @@ -392,7 +414,7 @@ return rpl_init(node->storage_dir); } -static void update_net_settings(struct mesh_node *node) +static void init_net_settings(struct mesh_node *node) { struct mesh_net *net = node->net; @@ -403,7 +425,9 @@ mesh_net_set_relay_mode(net, node->relay.mode == MESH_MODE_ENABLED, node->relay.cnt, node->relay.interval); - mesh_net_set_beacon_mode(net, node->beacon == MESH_MODE_ENABLED); + mesh_net_set_snb_mode(net, node->beacon == MESH_MODE_ENABLED); + mesh_net_set_mpb_mode(net, node->mpb == MESH_MODE_ENABLED, + node->mpb_period, true); } static bool init_from_storage(struct mesh_config_node *db_node, @@ -431,6 +455,8 @@ node->relay.cnt = db_node->modes.relay.cnt; node->relay.interval = db_node->modes.relay.interval; node->beacon = db_node->modes.beacon; + node->mpb = db_node->modes.mpb; + node->mpb_period = db_node->modes.mpb_period; l_debug("relay %2.2x, proxy %2.2x, lpn %2.2x, friend %2.2x", node->relay.mode, node->proxy, node->lpn, node->friend); @@ -484,11 +510,18 @@ mesh_net_set_seq_num(node->net, node->seq_number); mesh_net_set_default_ttl(node->net, node->ttl); - update_net_settings(node); + init_net_settings(node); /* Initialize configuration server model */ cfgmod_server_init(node, PRIMARY_ELE_IDX); + /* Initialize remote provisioning models */ + remote_prov_server_init(node, PRIMARY_ELE_IDX); + remote_prov_client_init(node, PRIMARY_ELE_IDX); + + /* Initialize Private Beacon server model */ + prv_beacon_server_init(node, PRIMARY_ELE_IDX); + node->cfg = cfg; return true; @@ -550,12 +583,78 @@ return node->primary; } +bool node_refresh(struct mesh_node *node, bool hard, void *prov_info) +{ + struct mesh_prov_node_info *info = prov_info; + bool res = true; + + if (!node || !info) + return false; + + if (!IS_UNICAST(info->unicast)) + return false; + + /* Changing Unicast addresses requires a hard node reset */ + if (!hard && info->unicast != node->primary) + return false; + + /* + * Hard refresh results in immediate use of new Device Key. + * Soft refresh saves new device key as Candidate until we + * successfully receive new incoming message on that key. + */ + if (hard) { + if (!mesh_config_write_device_key(node->cfg, info->device_key)) + return false; + + memcpy(node->dev_key, info->device_key, sizeof(node->dev_key)); + + } else if (!mesh_config_write_candidate(node->cfg, info->device_key)) + return false; + + /* Replace Primary Unicast address if it has changed */ + if (node->primary != info->unicast) { + res = mesh_config_write_unicast(node->cfg, info->unicast); + if (res) { + node->primary = info->unicast; + node->num_ele = info->num_ele; + mesh_net_register_unicast(node->net, node->primary, + node->num_ele); + } + } + + /* Replace Page 0 with Page 128 if it exists */ + if (res) { + if (node_replace_comp(node, 0, 128)) + return true; + } + + return res; +} + const uint8_t *node_get_device_key(struct mesh_node *node) { if (!node) return NULL; - else - return node->dev_key; + + return node->dev_key; +} + +bool node_get_device_key_candidate(struct mesh_node *node, uint8_t *key) +{ + if (!node) + return false; + + return mesh_config_read_candidate(node->cfg, key); +} + +void node_finalize_candidate(struct mesh_node *node) +{ + if (!node) + return; + + if (mesh_config_read_candidate(node->cfg, node->dev_key)) + mesh_config_finalize_candidate(node->cfg); } void node_set_token(struct mesh_node *node, uint8_t token[8]) @@ -744,7 +843,7 @@ if (res) { node->beacon = beacon; - mesh_net_set_beacon_mode(node->net, enable); + mesh_net_set_snb_mode(node->net, enable); } return res; @@ -758,6 +857,36 @@ return node->beacon; } +bool node_mpb_mode_set(struct mesh_node *node, bool enable, uint8_t period) +{ + bool res; + uint8_t beacon; + + if (!node) + return false; + + beacon = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; + res = mesh_config_write_mpb(node->cfg, beacon, period); + + if (res) { + node->mpb = beacon; + node->mpb_period = period; + mesh_net_set_mpb_mode(node->net, enable, period, false); + } + + return res; +} + +uint8_t node_mpb_mode_get(struct mesh_node *node, uint8_t *period) +{ + if (!node) + return MESH_MODE_DISABLED; + + *period = node->mpb_period; + + return node->mpb; +} + bool node_friend_mode_set(struct mesh_node *node, bool enable) { bool res; @@ -785,7 +914,7 @@ return node->friend; } -static uint16_t generate_node_comp(struct mesh_node *node, uint8_t *buf, +static uint16_t node_generate_comp(struct mesh_node *node, uint8_t *buf, uint16_t sz) { uint16_t n, features, num_ele = 0; @@ -870,6 +999,8 @@ db_node->modes.relay.cnt = node->relay.cnt; db_node->modes.relay.interval = node->relay.interval; db_node->modes.beacon = node->beacon; + db_node->modes.mpb = node->mpb; + db_node->modes.mpb_period = node->mpb_period; db_node->ttl = node->ttl; db_node->seq_number = node->seq_number; @@ -895,6 +1026,21 @@ } +static void free_db_storage(struct mesh_config_node *db_node) +{ + const struct l_queue_entry *entry; + + /* Free temporarily allocated resources */ + entry = l_queue_get_entries(db_node->elements); + for (; entry; entry = entry->next) { + struct mesh_config_element *db_ele = entry->data; + + l_queue_destroy(db_ele->models, l_free); + } + + l_queue_destroy(db_node->elements, l_free); +} + static bool create_node_config(struct mesh_node *node, const uint8_t uuid[16]) { struct mesh_config_node db_node; @@ -922,7 +1068,22 @@ return node->cfg != NULL; } -static bool set_node_comp(struct mesh_node *node, uint8_t page_num, +static void node_del_comp(struct mesh_node *node, uint8_t page_num) +{ + struct mesh_config_comp_page *page; + + if (!node) + return; + + page = l_queue_remove_if(node->pages, match_page, + L_UINT_TO_PTR(page_num)); + + l_free(page); + + mesh_config_comp_page_del(node->cfg, page_num); +} + +static bool node_set_comp(struct mesh_node *node, uint8_t page_num, const uint8_t *data, uint16_t len) { struct mesh_config_comp_page *page; @@ -944,16 +1105,6 @@ return mesh_config_comp_page_add(node->cfg, page_num, page->data, len); } -static bool create_node_comp(struct mesh_node *node) -{ - uint16_t len; - uint8_t comp[MAX_MSG_LEN - 2]; - - len = generate_node_comp(node, comp, sizeof(comp)); - - return set_node_comp(node, 0, comp, len); -} - const uint8_t *node_get_comp(struct mesh_node *node, uint8_t page_num, uint16_t *len) { @@ -975,6 +1126,7 @@ bool node_replace_comp(struct mesh_node *node, uint8_t retire, uint8_t with) { struct mesh_config_comp_page *old_page, *keep; + bool status; if (!node) return false; @@ -989,9 +1141,13 @@ l_free(old_page); keep->page_num = retire; - mesh_config_comp_page_mv(node->cfg, with, retire); + status = mesh_config_comp_page_add(node->cfg, keep->page_num, + keep->data, keep->len); - return true; + if (with != retire) + mesh_config_comp_page_del(node->cfg, with); + + return status; } static void attach_io(void *a, void *b) @@ -1067,9 +1223,16 @@ while (l_dbus_message_iter_next_entry(&mods, &m_id, &var)) { uint32_t id = SET_ID(SIG_VENDOR, m_id); - /* Allow Config Server Model only on the primary element */ - if (ele->idx != PRIMARY_ELE_IDX && id == CONFIG_SRV_MODEL) - return false; + /* + * Allow Config Server & Private Beacon Models only on + * the primary element + */ + if (ele->idx != PRIMARY_ELE_IDX) { + if (id == CONFIG_SRV_MODEL) + return false; + if (id == PRV_BEACON_SRV_MODEL) + return false; + } if (!mesh_model_add(node, ele->models, id, &var)) return false; @@ -1170,8 +1333,14 @@ * daemon. If the model is present in the application properties, * the operation below will be a "no-op". */ - if (ele->idx == PRIMARY_ELE_IDX) + if (ele->idx == PRIMARY_ELE_IDX) { mesh_model_add(node, ele->models, CONFIG_SRV_MODEL, NULL); + mesh_model_add(node, ele->models, PRV_BEACON_SRV_MODEL, NULL); + mesh_model_add(node, ele->models, REM_PROV_SRV_MODEL, NULL); + if (node->provisioner) + mesh_model_add(node, ele->models, REM_PROV_CLI_MODEL, + NULL); + } return true; fail: @@ -1232,6 +1401,15 @@ return true; } +static void save_pages(void *data, void *user_data) +{ + struct mesh_config_comp_page *page = data; + struct mesh_node *node = user_data; + + mesh_config_comp_page_add(node->cfg, page->page_num, page->data, + page->len); +} + static bool add_local_node(struct mesh_node *node, uint16_t unicast, bool kr, bool ivu, uint32_t iv_idx, uint8_t dev_key[16], uint16_t net_key_idx, uint8_t net_key[16]) @@ -1275,10 +1453,17 @@ return false; } - update_net_settings(node); + l_queue_foreach(node->pages, save_pages, node); - /* Initialize configuration server model */ + init_net_settings(node); + + /* Initialize internal server models */ cfgmod_server_init(node, PRIMARY_ELE_IDX); + remote_prov_server_init(node, PRIMARY_ELE_IDX); + remote_prov_client_init(node, PRIMARY_ELE_IDX); + + /* Initialize Private Beacon server model */ + prv_beacon_server_init(node, PRIMARY_ELE_IDX); node->busy = true; @@ -1326,39 +1511,59 @@ static bool check_req_node(struct managed_obj_request *req) { + struct mesh_node *node; const int offset = 8; uint16_t node_len, len; uint8_t comp[MAX_MSG_LEN - 2]; const uint8_t *node_comp; - len = generate_node_comp(req->node, comp, sizeof(comp)); + if (req->type != REQUEST_TYPE_ATTACH) { + node = req->node; - if (len < MIN_COMP_SIZE) - return false; + if (!create_node_config(node, node->uuid)) + return false; + } else + node = req->attach; - node_comp = node_get_comp(req->attach, 0, &node_len); + node_comp = node_get_comp(node, 0, &node_len); + len = node_generate_comp(req->node, comp, sizeof(comp)); - /* If no page 0 exists, create it and accept */ - if (!node_len || !node_comp) - return set_node_comp(req->attach, 0, comp, len); + /* If no page 0 exists, then current composition as valid */ + if (req->type != REQUEST_TYPE_ATTACH || !node_len) + goto page_zero_valid; - /* Test Element/Model part of composition and reject if changed */ + /* + * If composition has materially changed, save new composition + * in page 128 until next NPPI procedure. But we do allow + * for CID, PID, VID and/or CRPL to freely change without + * requiring a NPPI procedure. + */ if (node_len != len || memcmp(&node_comp[offset], &comp[offset], node_len - offset)) - return false; + return node_set_comp(node, 128, comp, len); - /* If comp has changed, but not Element/Models, resave and accept */ - else if (memcmp(node_comp, comp, node_len)) - return set_node_comp(req->attach, 0, comp, len); +page_zero_valid: + /* If page 0 represents current App, ensure page 128 doesn't exist */ + node_del_comp(node, 128); - /* Nothing has changed */ - return true; + if (len == node_len && !memcmp(node_comp, comp, len)) + return true; + + return node_set_comp(node, 0, comp, len); +} + +static bool is_zero(const void *a, const void *b) +{ + const struct node_element *element = a; + + return !element->idx; } static bool attach_req_node(struct mesh_node *attach, struct mesh_node *node) { const struct l_queue_entry *attach_entry; const struct l_queue_entry *node_entry; + bool comp_changed = false; attach->obj_path = node->obj_path; node->obj_path = NULL; @@ -1368,6 +1573,34 @@ return false; } + if (attach->num_ele != node->num_ele) { + struct mesh_config_node db_node; + struct node_element *old_ele, *new_ele; + + convert_node_to_storage(node, &db_node); + + /* + * If composition has materially changed, we need to discard + * everything we knew about elements in the old application, + * and start from what they are telling us now. + */ + old_ele = l_queue_remove_if(attach->elements, is_zero, NULL); + new_ele = l_queue_remove_if(node->elements, is_zero, NULL); + element_free(new_ele); + + l_queue_destroy(attach->elements, element_free); + attach->elements = node->elements; + attach->num_ele = node->num_ele; + + /* Restore primary elements */ + l_queue_push_head(attach->elements, old_ele); + + comp_changed = true; + + mesh_config_reset(attach->cfg, &db_node); + free_db_storage(&db_node); + } + attach_entry = l_queue_get_entries(attach->elements); node_entry = l_queue_get_entries(node->elements); @@ -1384,6 +1617,10 @@ attach_entry = attach_entry->next; node_entry = node_entry->next; + + /* Only need the Primary element during Composition change */ + if (comp_changed) + break; } mesh_agent_remove(attach->agent); @@ -1399,8 +1636,12 @@ node->owner = NULL; update_composition(node, attach); + update_model_options(node, attach); + if (comp_changed) + node->elements = NULL; + node_remove(node); return true; @@ -1499,16 +1740,7 @@ node->num_ele = num_ele; - if (req->type != REQUEST_TYPE_ATTACH) { - /* Generate node configuration for a brand new node */ - if (!create_node_config(node, node->uuid)) - goto fail; - - /* Create node composition */ - if (!create_node_comp(node)) - goto fail; - } else if (!check_req_node(req)) - /* Check the integrity of the node composition */ + if (!check_req_node(req)) goto fail; switch (req->type) { diff -Nru bluez-5.66/mesh/node.h bluez-5.68/mesh/node.h --- bluez-5.66/mesh/node.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/mesh/node.h 2023-06-30 08:10:20.000000000 +0000 @@ -38,6 +38,8 @@ void node_set_token(struct mesh_node *node, uint8_t token[8]); const uint8_t *node_get_token(struct mesh_node *node); const uint8_t *node_get_device_key(struct mesh_node *node); +bool node_get_device_key_candidate(struct mesh_node *node, uint8_t *key); +void node_finalize_candidate(struct mesh_node *node); void node_set_num_elements(struct mesh_node *node, uint8_t num_ele); uint8_t node_get_num_elements(struct mesh_node *node); uint8_t node_default_ttl_get(struct mesh_node *node); @@ -61,6 +63,8 @@ bool node_proxy_mode_set(struct mesh_node *node, bool enable); uint8_t node_proxy_mode_get(struct mesh_node *node); bool node_beacon_mode_set(struct mesh_node *node, bool enable); +bool node_mpb_mode_set(struct mesh_node *node, bool enable, uint8_t period); +uint8_t node_mpb_mode_get(struct mesh_node *node, uint8_t *period); uint8_t node_beacon_mode_get(struct mesh_node *node); bool node_friend_mode_set(struct mesh_node *node, bool enable); uint8_t node_friend_mode_get(struct mesh_node *node); @@ -89,3 +93,4 @@ bool node_load_from_storage(const char *storage_dir); void node_finalize_new_node(struct mesh_node *node, struct mesh_io *io); void node_property_changed(struct mesh_node *node, const char *property); +bool node_refresh(struct mesh_node *node, bool hard, void *prov_info); diff -Nru bluez-5.66/mesh/pb-adv.c bluez-5.68/mesh/pb-adv.c --- bluez-5.66/mesh/pb-adv.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/pb-adv.c 2023-06-30 08:10:20.000000000 +0000 @@ -219,7 +219,7 @@ cb(user_data, 1); } -static void pb_adv_tx(void *user_data, void *data, uint16_t len) +static void pb_adv_tx(void *user_data, const void *data, uint16_t len) { struct pb_adv_session *session = user_data; @@ -478,7 +478,7 @@ bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, - uint8_t uuid[16], void *user_data) + const uint8_t *uuid, void *user_data) { struct pb_adv_session *session, *old_session; diff -Nru bluez-5.66/mesh/pb-adv.h bluez-5.68/mesh/pb-adv.h --- bluez-5.66/mesh/pb-adv.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/mesh/pb-adv.h 2023-06-30 08:10:20.000000000 +0000 @@ -11,5 +11,5 @@ bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, - uint8_t uuid[16], void *user_data); + const uint8_t *uuid, void *user_data); void pb_adv_unreg(void *user_data); diff -Nru bluez-5.66/mesh/prov-acceptor.c bluez-5.68/mesh/prov-acceptor.c --- bluez-5.66/mesh/prov-acceptor.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/mesh/prov-acceptor.c 2023-06-30 08:10:20.000000000 +0000 @@ -22,6 +22,7 @@ #include "mesh/net.h" #include "mesh/prov.h" #include "mesh/provision.h" +#include "mesh/remprv.h" #include "mesh/pb-adv.h" #include "mesh/mesh.h" #include "mesh/agent.h" @@ -169,9 +170,6 @@ prov->transport != transport) return; - if (transport != PB_ADV) - return; - prov->trans_tx = trans_tx; prov->transport = transport; prov->trans_data = trans_data; @@ -425,9 +423,10 @@ return true; } -static void acp_prov_rx(void *user_data, const uint8_t *data, uint16_t len) +static void acp_prov_rx(void *user_data, const void *dptr, uint16_t len) { struct mesh_prov_acceptor *rx_prov = user_data; + const uint8_t *data = dptr; struct mesh_prov_node_info *info; struct prov_fail_msg fail; uint8_t type = *data++; @@ -654,14 +653,19 @@ info->flags = prov->rand_auth_workspace[18]; info->iv_index = l_get_be32(prov->rand_auth_workspace + 19); info->unicast = l_get_be16(prov->rand_auth_workspace + 23); + info->num_ele = prov->conf_inputs.caps.num_ele; + + /* Send prov complete */ + prov->rand_auth_workspace[0] = PROV_COMPLETE; + prov->trans_tx(prov->trans_data, + prov->rand_auth_workspace, 1); result = prov->cmplt(prov->caller_data, PROV_ERR_SUCCESS, info); prov->cmplt = NULL; l_free(info); if (result) { - prov->rand_auth_workspace[0] = PROV_COMPLETE; - prov_send(prov, prov->rand_auth_workspace, 1); + l_debug("PROV_COMPLETE"); goto cleanup; } else { fail.reason = PROV_ERR_UNEXPECTED_ERR; @@ -721,7 +725,7 @@ /* This starts unprovisioned device beacon */ -bool acceptor_start(uint8_t num_ele, uint8_t uuid[16], +bool acceptor_start(uint8_t num_ele, uint8_t *uuid, uint16_t algorithms, uint32_t timeout, struct mesh_agent *agent, mesh_prov_acceptor_complete_func_t complete_cb, @@ -733,8 +737,10 @@ uint8_t len = sizeof(beacon) - sizeof(uint32_t); bool result; - /* Invoked from Join() method in mesh-api.txt, to join a - * remote mesh network. + /* + * Invoked from Join() method in mesh-api.txt, to join a + * remote mesh network. May also be invoked with a NULL + * uuid to perform a Device Key Refresh procedure. */ if (prov) @@ -752,37 +758,50 @@ caps = mesh_agent_get_caps(agent); - /* TODO: Should we sanity check values here or elsewhere? */ prov->conf_inputs.caps.num_ele = num_ele; - prov->conf_inputs.caps.pub_type = caps->pub_type; - prov->conf_inputs.caps.static_type = caps->static_type; - prov->conf_inputs.caps.output_size = caps->output_size; - prov->conf_inputs.caps.input_size = caps->input_size; - - /* Store UINT16 values in Over-the-Air order, in packed structure - * for crypto inputs - */ l_put_be16(algorithms, &prov->conf_inputs.caps.algorithms); - l_put_be16(caps->output_action, &prov->conf_inputs.caps.output_action); - l_put_be16(caps->input_action, &prov->conf_inputs.caps.input_action); - /* Compose Unprovisioned Beacon */ - memcpy(beacon + 2, uuid, 16); - l_put_be16(caps->oob_info, beacon + 18); - if (caps->oob_info & OOB_INFO_URI_HASH){ - l_put_be32(caps->uri_hash, beacon + 20); - len += sizeof(uint32_t); - } - - /* Infinitely Beacon until Canceled, or Provisioning Starts */ - result = mesh_send_pkt(0, 500, beacon, len); - - if (!result) - goto error_fail; - - /* Always register for PB-ADV */ - result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, - acp_prov_ack, uuid, prov); + if (caps) { + /* TODO: Should we sanity check values here or elsewhere? */ + prov->conf_inputs.caps.pub_type = caps->pub_type; + prov->conf_inputs.caps.static_type = caps->static_type; + prov->conf_inputs.caps.output_size = caps->output_size; + prov->conf_inputs.caps.input_size = caps->input_size; + + /* Store UINT16 values in Over-the-Air order, in packed + * structure for crypto inputs + */ + l_put_be16(caps->output_action, + &prov->conf_inputs.caps.output_action); + l_put_be16(caps->input_action, + &prov->conf_inputs.caps.input_action); + + /* Populate Caps fields of beacon */ + l_put_be16(caps->oob_info, beacon + 18); + if (caps->oob_info & OOB_INFO_URI_HASH) { + l_put_be32(caps->uri_hash, beacon + 20); + len += sizeof(uint32_t); + } + } + + if (uuid) { + /* Compose Unprovisioned Beacon */ + memcpy(beacon + 2, uuid, 16); + + /* Infinitely Beacon until Canceled, or Provisioning Starts */ + result = mesh_send_pkt(0, 500, beacon, len); + + if (!result) + goto error_fail; + + /* Always register for PB-ADV */ + result = pb_adv_reg(false, acp_prov_open, acp_prov_close, + acp_prov_rx, acp_prov_ack, uuid, prov); + } else { + /* Run Device Key Refresh Procedure */ + result = register_nppi_acceptor(acp_prov_open, acp_prov_close, + acp_prov_rx, acp_prov_ack, prov); + } if (result) return true; diff -Nru bluez-5.66/mesh/prov.h bluez-5.68/mesh/prov.h --- bluez-5.66/mesh/prov.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/mesh/prov.h 2023-06-30 08:10:20.000000000 +0000 @@ -39,14 +39,14 @@ struct mesh_prov; -typedef void (*prov_trans_tx_t)(void *trans_data, void *data, uint16_t len); +typedef void (*prov_trans_tx_t)(void *tx_data, const void *data, uint16_t len); typedef void (*mesh_prov_open_func_t)(void *user_data, prov_trans_tx_t trans_tx, void *trans_data, uint8_t trans_type); typedef void (*mesh_prov_close_func_t)(void *user_data, uint8_t reason); typedef void (*mesh_prov_send_func_t)(bool success, struct mesh_prov *prov); typedef void (*mesh_prov_ack_func_t)(void *user_data, uint8_t msg_num); -typedef void (*mesh_prov_receive_func_t)(void *user_data, const uint8_t *data, +typedef void (*mesh_prov_receive_func_t)(void *user_data, const void *data, uint16_t size); diff -Nru bluez-5.66/mesh/prov-initiator.c bluez-5.68/mesh/prov-initiator.c --- bluez-5.66/mesh/prov-initiator.c 2021-06-13 19:56:36.000000000 +0000 +++ bluez-5.68/mesh/prov-initiator.c 2023-06-30 08:10:20.000000000 +0000 @@ -21,10 +21,12 @@ #include "mesh/crypto.h" #include "mesh/net.h" #include "mesh/node.h" +#include "mesh/model.h" #include "mesh/keyring.h" #include "mesh/prov.h" #include "mesh/provision.h" #include "mesh/pb-adv.h" +#include "mesh/remprv.h" #include "mesh/mesh.h" #include "mesh/agent.h" #include "mesh/error.h" @@ -82,12 +84,16 @@ struct l_timeout *timeout; uint32_t to_secs; enum int_state state; - enum trans_type transport; uint16_t net_idx; + uint16_t svr_idx; uint16_t unicast; + uint16_t server; + uint8_t transport; uint8_t material; uint8_t expected; int8_t previous; + uint8_t out_num; + uint8_t rpr_state; struct conf_input conf_inputs; uint8_t calc_key[16]; uint8_t salt[16]; @@ -100,14 +106,23 @@ uint8_t uuid[16]; }; +struct scan_req { + mesh_prov_initiator_scan_result_t scan_result; + struct mesh_node *node; + int count; +}; + static struct mesh_prov_initiator *prov = NULL; +static struct l_queue *scans; static void initiator_free(void) { - if (prov) + if (prov) { l_timeout_remove(prov->timeout); - mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); + if (!prov->server) + mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); + } pb_adv_unreg(prov); @@ -119,6 +134,15 @@ { struct mesh_prov_initiator *prov = user_data; struct mesh_prov_node_info info; + uint8_t msg[4]; + int n; + + if (prov->server) { + n = mesh_model_opcode_set(OP_REM_PROV_LINK_CLOSE, msg); + msg[n++] = reason == PROV_ERR_SUCCESS ? 0x00 : 0x02; + mesh_model_send(prov->node, 0, prov->server, APP_IDX_DEV_REMOTE, + prov->svr_idx, DEFAULT_TTL, true, n, msg); + } if (reason != PROV_ERR_SUCCESS) { prov->complete_cb(prov->caller_data, reason, NULL); @@ -626,9 +650,10 @@ } } -static void int_prov_rx(void *user_data, const uint8_t *data, uint16_t len) +static void int_prov_rx(void *user_data, const void *dptr, uint16_t len) { struct mesh_prov_initiator *rx_prov = user_data; + const uint8_t *data = dptr; uint8_t *out; uint8_t type = *data++; uint8_t fail_code[2]; @@ -651,7 +676,7 @@ if (type >= L_ARRAY_SIZE(expected_pdu_size) || len != expected_pdu_size[type]) { l_error("Expected PDU size %d, Got %d (type: %2.2x)", - len, expected_pdu_size[type], type); + expected_pdu_size[type], len, type); fail_code[1] = PROV_ERR_INVALID_FORMAT; goto failure; } @@ -773,7 +798,12 @@ goto failure; } - if (!prov->data_req_cb(prov->caller_data, + if (prov->transport == PB_NPPI_00 || + prov->transport == PB_NPPI_02) { + /* No App data needed */ + initiator_prov_data(prov->svr_idx, prov->server, + prov->caller_data); + } else if (!prov->data_req_cb(prov->caller_data, prov->conf_inputs.caps.num_ele)) { l_error("Provisioning Failed-Data Get"); fail_code[1] = PROV_ERR_CANT_ASSIGN_ADDR; @@ -851,6 +881,8 @@ static void initiator_open_cb(void *user_data, int err) { + uint8_t msg[20]; + int n; bool result; if (!prov) @@ -859,18 +891,30 @@ if (err != MESH_ERROR_NONE) goto fail; - /* Always register for PB-ADV */ - result = pb_adv_reg(true, int_prov_open, int_prov_close, int_prov_rx, - int_prov_ack, prov->uuid, prov); + if (prov->server) { + n = mesh_model_opcode_set(OP_REM_PROV_LINK_OPEN, msg); + + if (prov->transport <= PB_NPPI_02) { + msg[n++] = prov->transport; + } else { + memcpy(msg + n, prov->uuid, 16); + n += 16; + } + + result = mesh_model_send(prov->node, 0, prov->server, + APP_IDX_DEV_REMOTE, prov->svr_idx, + DEFAULT_TTL, true, n, msg); + } else { + /* Always register for PB-ADV */ + result = pb_adv_reg(true, int_prov_open, int_prov_close, + int_prov_rx, int_prov_ack, prov->uuid, prov); + } if (!result) { err = MESH_ERROR_FAILED; goto fail; } - if (!prov) - return; - prov->start_cb(prov->caller_data, MESH_ERROR_NONE); return; fail: @@ -878,10 +922,20 @@ initiator_free(); } -bool initiator_start(enum trans_type transport, - uint8_t uuid[16], - uint16_t max_ele, - uint32_t timeout, /* in seconds from mesh.conf */ +static void initiate_to(struct l_timeout *timeout, void *user_data) +{ + struct mesh_prov_initiator *rx_prov = user_data; + + if (rx_prov != prov) { + l_timeout_remove(timeout); + return; + } + + int_prov_close(user_data, PROV_ERR_TIMEOUT); +} + +bool initiator_start(uint8_t transport, uint16_t server, uint16_t svr_idx, + uint8_t uuid[16], uint16_t max_ele, uint32_t timeout, struct mesh_agent *agent, mesh_prov_initiator_start_func_t start_cb, mesh_prov_initiator_data_req_func_t data_req_cb, @@ -904,6 +958,10 @@ prov->data_req_cb = data_req_cb; prov->caller_data = caller_data; prov->previous = -1; + prov->server = server; + prov->svr_idx = svr_idx; + prov->transport = transport; + prov->timeout = l_timeout_create(timeout, initiate_to, prov, NULL); memcpy(prov->uuid, uuid, 16); mesh_agent_refresh(prov->agent, initiator_open_cb, prov); @@ -915,3 +973,182 @@ { initiator_free(); } + +static void rpr_tx(void *user_data, const void *data, uint16_t len) +{ + struct mesh_prov_initiator *prov = user_data; + uint8_t msg[72]; + int n; + + n = mesh_model_opcode_set(OP_REM_PROV_PDU_SEND, msg); + msg[n++] = ++prov->out_num; + memcpy(msg + n, data, len); + l_debug("Send OB %2.2x, with packet type %d", msg[n], prov->out_num); + n += len; + + prov->rpr_state = PB_REMOTE_STATE_OB_PKT_TX; + mesh_model_send(prov->node, 0, prov->server, APP_IDX_DEV_REMOTE, + prov->svr_idx, DEFAULT_TTL, true, n, msg); +} + +static bool match_req_node(const void *a, const void *b) +{ + const struct scan_req *req = a; + const struct mesh_node *node = b; + + return req->node == node; +} + +static bool remprv_cli_pkt(uint16_t src, uint16_t unicast, uint16_t app_idx, + uint16_t net_idx, const uint8_t *data, + uint16_t size, const void *user_data) +{ + struct mesh_node *node = (struct mesh_node *) user_data; + const uint8_t *pkt = data; + struct scan_req *req; + uint32_t opcode; + uint16_t n; + + if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { + size -= n; + pkt += n; + } else + return false; + + if (opcode < OP_REM_PROV_SCAN_CAP_GET || + opcode > OP_REM_PROV_PDU_REPORT) + return false; + + if (app_idx != APP_IDX_DEV_REMOTE && app_idx != APP_IDX_DEV_LOCAL) + return true; + + /* Local Dev key only allowed for Loop-backs */ + if (app_idx == APP_IDX_DEV_LOCAL && unicast != src) + return true; + + if (prov && (prov->server != src || prov->node != node)) + return true; + + n = 0; + + switch (opcode) { + default: + return false; + + /* Provisioning Opcodes */ + case OP_REM_PROV_LINK_STATUS: + if (size != 2 || !prov) + break; + + if (pkt[0] == PB_REM_ERR_SUCCESS) + prov->rpr_state = pkt[1]; + + break; + + case OP_REM_PROV_LINK_REPORT: + if (size != 2 || !prov) + return true; + + if (pkt[0] != PB_REM_ERR_SUCCESS) { + if (pkt[0] == PB_REM_ERR_CLOSED_BY_DEVICE || + pkt[0] == PB_REM_ERR_CLOSED_BY_SERVER) + int_prov_close(prov, pkt[1]); + + break; + } + + + if (prov->rpr_state == PB_REMOTE_STATE_LINK_OPENING) + int_prov_open(prov, rpr_tx, prov, prov->transport); + else if (prov->rpr_state == PB_REMOTE_STATE_LINK_CLOSING) { + prov->rpr_state = PB_REMOTE_STATE_IDLE; + int_prov_close(prov, pkt[1]); + break; + } + + prov->rpr_state = pkt[1]; + + break; + + case OP_REM_PROV_PDU_REPORT: + int_prov_rx(prov, pkt + 1, size - 1); + break; + + case OP_REM_PROV_PDU_OB_REPORT: + if (size != 1 || !prov) + break; + + l_debug("Got Ack for OB %d", pkt[0]); + if (prov->rpr_state == PB_REMOTE_STATE_OB_PKT_TX && + pkt[0] == prov->out_num) + int_prov_ack(prov, pkt[0]); + + break; + + /* Scan Opcodes */ + case OP_REM_PROV_SCAN_CAP_STATUS: + case OP_REM_PROV_SCAN_STATUS: + break; + + case OP_REM_PROV_SCAN_REPORT: + case OP_REM_PROV_EXT_SCAN_REPORT: + req = l_queue_find(scans, match_req_node, node); + if (req) { + req->scan_result(node, src, + opcode == OP_REM_PROV_EXT_SCAN_REPORT, + pkt, size); + } + } + + return true; +} + +void initiator_scan_reg(mesh_prov_initiator_scan_result_t scan_result, + void *user_data) +{ + struct scan_req *req; + + if (!scans) + scans = l_queue_new(); + + req = l_queue_find(scans, match_req_node, user_data); + if (!req) { + req = l_new(struct scan_req, 1); + l_queue_push_head(scans, req); + } + + req->scan_result = scan_result; + req->node = user_data; + req->count++; +} + +void initiator_scan_unreg(void *user_data) +{ + struct scan_req *req; + + req = l_queue_find(scans, match_req_node, user_data); + if (req) { + req->count--; + if (!req->count) { + l_queue_remove(scans, req); + l_free(req); + } + } +} + +static void remprv_cli_unregister(void *user_data) +{ +} + +static const struct mesh_model_ops ops = { + .unregister = remprv_cli_unregister, + .recv = remprv_cli_pkt, + .bind = NULL, + .sub = NULL, + .pub = NULL +}; + +void remote_prov_client_init(struct mesh_node *node, uint8_t ele_idx) +{ + mesh_model_register(node, ele_idx, REM_PROV_CLI_MODEL, &ops, node); +} diff -Nru bluez-5.66/mesh/provision.h bluez-5.68/mesh/provision.h --- bluez-5.66/mesh/provision.h 2021-06-13 19:56:36.000000000 +0000 +++ bluez-5.68/mesh/provision.h 2023-06-30 08:10:20.000000000 +0000 @@ -70,10 +70,11 @@ #define OOB_INFO_URI_HASH 0x0002 /* PB_REMOTE not supported from unprovisioned state */ -enum trans_type { - PB_ADV = 0, - PB_GATT, -}; +#define PB_NPPI_00 0x00 +#define PB_NPPI_01 0x01 +#define PB_NPPI_02 0x02 +#define PB_ADV 0x03 /* Internal only, and may be reassigned */ +#define PB_GATT 0x04 /* Internal only, and may be reassigned */ #define PROV_FLAG_KR 0x01 #define PROV_FLAG_IVU 0x02 @@ -101,15 +102,21 @@ uint8_t status, struct mesh_prov_node_info *info); +typedef void (*mesh_prov_initiator_scan_result_t)(void *user_data, + uint16_t server, bool extended, + const uint8_t *data, uint16_t len); + /* This starts unprovisioned device beacon */ -bool acceptor_start(uint8_t num_ele, uint8_t uuid[16], +bool acceptor_start(uint8_t num_ele, uint8_t *uuid, uint16_t algorithms, uint32_t timeout, struct mesh_agent *agent, mesh_prov_acceptor_complete_func_t complete_cb, void *caller_data); void acceptor_cancel(void *user_data); -bool initiator_start(enum trans_type transport, +bool initiator_start(uint8_t transport, + uint16_t server, + uint16_t svr_idx, uint8_t uuid[16], uint16_t max_ele, uint32_t timeout, /* in seconds from mesh.conf */ @@ -120,3 +127,7 @@ void *node, void *caller_data); void initiator_prov_data(uint16_t net_idx, uint16_t primary, void *caller_data); void initiator_cancel(void *caller_data); + +void initiator_scan_reg(mesh_prov_initiator_scan_result_t scan_result, + void *user_data); +void initiator_scan_unreg(void *caller_data); diff -Nru bluez-5.66/mesh/prv-beacon.h bluez-5.68/mesh/prv-beacon.h --- bluez-5.66/mesh/prv-beacon.h 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/mesh/prv-beacon.h 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,36 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2023 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. + * + */ + +struct mesh_node; + +#define PRV_BEACON_SRV_MODEL SET_ID(SIG_VENDOR, 0x0008) +#define PRV_BEACON_CLI_MODEL SET_ID(SIG_VENDOR, 0x0009) + +/* Private Beacon opcodes */ +#define OP_PRIVATE_BEACON_GET 0x8060 +#define OP_PRIVATE_BEACON_SET 0x8061 +#define OP_PRIVATE_BEACON_STATUS 0x8062 +#define OP_PRIVATE_GATT_PROXY_GET 0x8063 +#define OP_PRIVATE_GATT_PROXY_SET 0x8064 +#define OP_PRIVATE_GATT_PROXY_STATUS 0x8065 +#define OP_PRIVATE_NODE_ID_GET 0x8066 +#define OP_PRIVATE_NODE_ID_SET 0x8067 +#define OP_PRIVATE_NODE_ID_STATUS 0x8068 + +void prv_beacon_server_init(struct mesh_node *node, uint8_t ele_idx); diff -Nru bluez-5.66/mesh/prvbeac-server.c bluez-5.68/mesh/prvbeac-server.c --- bluez-5.66/mesh/prvbeac-server.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/mesh/prvbeac-server.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,128 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2023 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "mesh/mesh-defs.h" +#include "mesh/node.h" +#include "mesh/net.h" +#include "mesh/appkey.h" +#include "mesh/model.h" +#include "mesh/mesh-config.h" +#include "mesh/prv-beacon.h" + +#define NOT_SUPPORTED 0x02 + +static bool prvbec_srv_pkt(uint16_t src, uint16_t dst, uint16_t app_idx, + uint16_t net_idx, const uint8_t *data, + uint16_t size, const void *user_data) +{ + struct mesh_node *node = (struct mesh_node *) user_data; + const uint8_t *pkt = data; + uint32_t opcode; + uint8_t msg[5]; + uint16_t n; + uint8_t period; + + if (app_idx != APP_IDX_DEV_LOCAL) + return false; + + if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { + size -= n; + pkt += n; + } else + return false; + + l_debug("PRV-BEAC-SRV-opcode 0x%x size %u idx %3.3x", opcode, size, + net_idx); + + n = 0; + + switch (opcode) { + default: + return false; + + case OP_PRIVATE_BEACON_SET: + if (size == 1) + node_mpb_mode_get(node, &period); + else if (size == 2) + period = pkt[1]; + else + return true; + + if (pkt[0] > 1) + return true; + + node_mpb_mode_set(node, !!pkt[0], period); + + /* fall through */ + + case OP_PRIVATE_BEACON_GET: + n = mesh_model_opcode_set(OP_PRIVATE_BEACON_STATUS, msg); + + msg[n++] = node_mpb_mode_get(node, &period); + msg[n++] = period; + + l_debug("Get/Set Private Beacon (%d)", msg[n-2]); + break; + + case OP_PRIVATE_GATT_PROXY_SET: + /* fall through */ + case OP_PRIVATE_GATT_PROXY_GET: + n = mesh_model_opcode_set(OP_PRIVATE_GATT_PROXY_STATUS, msg); + msg[n++] = NOT_SUPPORTED; + break; + + case OP_PRIVATE_NODE_ID_SET: + /* fall through */ + case OP_PRIVATE_NODE_ID_GET: + n = mesh_model_opcode_set(OP_PRIVATE_NODE_ID_STATUS, msg); + msg[n++] = NOT_SUPPORTED; + break; + } + + if (n) + mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, + DEFAULT_TTL, false, n, msg); + + return true; +} + +static void prvbec_srv_unregister(void *user_data) +{ +} + +static const struct mesh_model_ops ops = { + .unregister = prvbec_srv_unregister, + .recv = prvbec_srv_pkt, + .bind = NULL, + .sub = NULL, + .pub = NULL +}; + +void prv_beacon_server_init(struct mesh_node *node, uint8_t ele_idx) +{ + l_debug("%2.2x", ele_idx); + mesh_model_register(node, ele_idx, PRV_BEACON_SRV_MODEL, &ops, node); +} diff -Nru bluez-5.66/mesh/remprv.h bluez-5.68/mesh/remprv.h --- bluez-5.66/mesh/remprv.h 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/mesh/remprv.h 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,78 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2023 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. + * + */ + +#define REM_PROV_SRV_MODEL SET_ID(SIG_VENDOR, 0x0004) +#define REM_PROV_CLI_MODEL SET_ID(SIG_VENDOR, 0x0005) + +#define PB_REMOTE_MAX_SCAN_QUEUE_SIZE 5 + +#define PB_REMOTE_STATE_IDLE 0x00 +#define PB_REMOTE_STATE_LINK_OPENING 0x01 +#define PB_REMOTE_STATE_LINK_ACTIVE 0x02 +#define PB_REMOTE_STATE_OB_PKT_TX 0x03 +#define PB_REMOTE_STATE_LINK_CLOSING 0x04 + +#define PB_REMOTE_TYPE_LOCAL 0x01 +#define PB_REMOTE_TYPE_ADV 0x02 +#define PB_REMOTE_TYPE_GATT 0x04 + +#define PB_REMOTE_SCAN_TYPE_NONE 0x00 +#define PB_REMOTE_SCAN_TYPE_UNLIMITED 0x01 +#define PB_REMOTE_SCAN_TYPE_LIMITED 0x02 +#define PB_REMOTE_SCAN_TYPE_DETAILED 0x03 + +/* Remote Provisioning Opcode List */ +#define OP_REM_PROV_SCAN_CAP_GET 0x804F +#define OP_REM_PROV_SCAN_CAP_STATUS 0x8050 +#define OP_REM_PROV_SCAN_GET 0x8051 +#define OP_REM_PROV_SCAN_START 0x8052 +#define OP_REM_PROV_SCAN_STOP 0x8053 +#define OP_REM_PROV_SCAN_STATUS 0x8054 +#define OP_REM_PROV_SCAN_REPORT 0x8055 +#define OP_REM_PROV_EXT_SCAN_START 0x8056 +#define OP_REM_PROV_EXT_SCAN_REPORT 0x8057 +#define OP_REM_PROV_LINK_GET 0x8058 +#define OP_REM_PROV_LINK_OPEN 0x8059 +#define OP_REM_PROV_LINK_CLOSE 0x805A +#define OP_REM_PROV_LINK_STATUS 0x805B +#define OP_REM_PROV_LINK_REPORT 0x805C +#define OP_REM_PROV_PDU_SEND 0x805D +#define OP_REM_PROV_PDU_OB_REPORT 0x805E +#define OP_REM_PROV_PDU_REPORT 0x805F + +/* Remote Provisioning Errors */ +#define PB_REM_ERR_SUCCESS 0x00 +#define PB_REM_ERR_SCANNING_CANNOT_START 0x01 +#define PB_REM_ERR_INVALID_STATE 0x02 +#define PB_REM_ERR_LIMITED_RESOURCES 0x03 +#define PB_REM_ERR_CANNOT_OPEN 0x04 +#define PB_REM_ERR_OPEN_FAILED 0x05 +#define PB_REM_ERR_CLOSED_BY_DEVICE 0x06 +#define PB_REM_ERR_CLOSED_BY_SERVER 0x07 +#define PB_REM_ERR_CLOSED_BY_CLIENT 0x08 +#define PB_REM_ERR_CLOSED_CANNOT_RX_PDU 0x09 +#define PB_REM_ERR_CLOSED_CANNOT_TX_PDU 0x0A + +void remote_prov_server_init(struct mesh_node *node, uint8_t ele_idx); +void remote_prov_client_init(struct mesh_node *node, uint8_t ele_idx); +bool register_nppi_acceptor(mesh_prov_open_func_t open_cb, + mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, + mesh_prov_ack_func_t ack_cb, + void *user_data); diff -Nru bluez-5.66/mesh/remprv-server.c bluez-5.68/mesh/remprv-server.c --- bluez-5.66/mesh/remprv-server.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/mesh/remprv-server.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,919 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2023 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "src/shared/ad.h" + +#include "mesh/mesh-defs.h" +#include "mesh/mesh-io.h" +#include "mesh/util.h" +#include "mesh/node.h" +#include "mesh/net.h" +#include "mesh/appkey.h" +#include "mesh/model.h" +#include "mesh/prov.h" +#include "mesh/provision.h" +#include "mesh/pb-adv.h" +#include "mesh/remprv.h" + +#define EXT_LIST_SIZE 60 + +#define RPR_DEV_KEY 0x00 +#define RPR_ADDR 0x01 +#define RPR_COMP 0x02 +#define RPR_ADV 0xFF /* Internal use only*/ + +struct rem_scan_data { + struct mesh_node *node; + struct l_timeout *timeout; + uint8_t *list; + uint16_t client; + uint16_t oob_info; + uint16_t net_idx; + uint8_t state; + uint8_t scanned_limit; + uint8_t addr[6]; + uint8_t uuid[16]; + uint8_t to_secs; + uint8_t rxed_ads; + uint8_t ext_cnt; + bool fltr; + uint8_t ext[0]; +}; + +static struct rem_scan_data *rpb_scan; + +struct rem_prov_data { + struct mesh_node *node; + struct l_timeout *timeout; + void *trans_data; + uint16_t client; + uint16_t net_idx; + uint8_t svr_pdu_num; + uint8_t cli_pdu_num; + uint8_t state; + uint8_t nppi_proc; + union { + struct { + mesh_prov_open_func_t open_cb; + mesh_prov_close_func_t close_cb; + mesh_prov_receive_func_t rx_cb; + mesh_prov_ack_func_t ack_cb; + struct mesh_prov_node_info info; + } nppi; + struct { + uint8_t uuid[17]; + prov_trans_tx_t tx; + } adv; + } u; +}; + +static struct rem_prov_data *rpb_prov; + +static const uint8_t prvb[2] = {BT_AD_MESH_BEACON, 0x00}; +static const uint8_t pkt_filter = BT_AD_MESH_PROV; +static const char *name = "Test Name"; + +static const uint8_t zero[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static void srv_open(void *user_data, prov_trans_tx_t adv_tx, + void *trans_data, uint8_t nppi_proc) +{ + struct rem_prov_data *prov = user_data; + uint8_t msg[5]; + int n; + + if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_LINK_OPENING) + return; + + l_debug("Remote Link open confirmed"); + prov->u.adv.tx = adv_tx; + prov->trans_data = trans_data; + prov->state = PB_REMOTE_STATE_LINK_ACTIVE; + + n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg); + msg[n++] = PB_REM_ERR_SUCCESS; + msg[n++] = prov->state; + + mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, + prov->net_idx, DEFAULT_TTL, true, n, msg); +} + +static void srv_rx(void *user_data, const void *dptr, uint16_t len) +{ + struct rem_prov_data *prov = user_data; + const uint8_t *data = dptr; + uint8_t msg[69]; + int n; + + if (prov != rpb_prov || prov->state < PB_REMOTE_STATE_LINK_ACTIVE || + len > 65) + return; + + l_debug("Remote PB IB-PDU"); + + prov->svr_pdu_num++; + n = mesh_model_opcode_set(OP_REM_PROV_PDU_REPORT, msg); + msg[n++] = prov->svr_pdu_num; + memcpy(msg + n, data, len); + n += len; + + mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, + prov->net_idx, DEFAULT_TTL, true, n, msg); +} + +static void srv_ack(void *user_data, uint8_t msg_num) +{ + struct rem_prov_data *prov = user_data; + uint8_t msg[4]; + int n; + + if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_OB_PKT_TX) + return; + + l_debug("Remote PB ACK"); + + prov->state = PB_REMOTE_STATE_LINK_ACTIVE; + n = mesh_model_opcode_set(OP_REM_PROV_PDU_OB_REPORT, msg); + msg[n++] = prov->cli_pdu_num; + + mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, + prov->net_idx, DEFAULT_TTL, true, n, msg); +} + +static void srv_close(void *user_data, uint8_t reason) +{ + struct rem_prov_data *prov = user_data; + uint8_t msg[4]; + int n; + + if (prov != rpb_prov || prov->state < PB_REMOTE_STATE_LINK_ACTIVE) + return; + + l_debug("Remote PB Close"); + + prov->state = PB_REMOTE_STATE_LINK_CLOSING; + n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg); + msg[n++] = prov->state; + msg[n++] = reason; + + mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, + prov->net_idx, DEFAULT_TTL, true, n, msg); +} + +static void send_prov_status(struct rem_prov_data *prov, uint8_t status) +{ + uint16_t n; + uint8_t msg[5]; + bool segmented = prov->state == PB_REMOTE_STATE_LINK_CLOSING ? + true : false; + + n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg); + msg[n++] = status; + msg[n++] = prov->state; + + l_info("RPB-Link Status(%d): dst %4.4x", prov->state, prov->client); + + mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, + prov->net_idx, DEFAULT_TTL, segmented, n, msg); +} + +static void remprv_prov_cancel(struct l_timeout *timeout, + void *user_data) +{ + struct rem_prov_data *prov = user_data; + + if (prov != rpb_prov) + return; + + l_timeout_remove(prov->timeout); + l_free(prov); + rpb_prov = NULL; +} + +static void deregister_ext_ad_type(uint8_t ad_type) +{ + uint8_t short_ad; + + switch (ad_type) { + case BT_AD_MESH_BEACON: + case BT_AD_MESH_DATA: + case BT_AD_MESH_PROV: + case BT_AD_UUID16_SOME: + case BT_AD_UUID32_SOME: + case BT_AD_UUID128_SOME: + case BT_AD_NAME_SHORT: + return; + + case BT_AD_UUID16_ALL: + case BT_AD_UUID32_ALL: + case BT_AD_UUID128_ALL: + case BT_AD_NAME_COMPLETE: + /* Automatically get short versions */ + short_ad = ad_type - 1; + mesh_io_deregister_recv_cb(NULL, &short_ad, 1); + + /* fall through */ + default: + mesh_io_deregister_recv_cb(NULL, &ad_type, 1); + break; + } +} + +static void remprv_scan_cancel(struct l_timeout *timeout, + void *user_data) +{ + struct rem_scan_data *scan = user_data; + uint8_t msg[22 + EXT_LIST_SIZE]; + uint16_t i, n; + + if (!scan || scan != rpb_scan) + return; + + for (n = 0; n < scan->ext_cnt; n++) + deregister_ext_ad_type(scan->ext[n]); + + if (scan->timeout == timeout) { + /* Return Extended Results */ + if (scan->ext_cnt) { + /* Return Extended Result */ + n = mesh_model_opcode_set( + OP_REM_PROV_EXT_SCAN_REPORT, msg); + msg[n++] = PB_REM_ERR_SUCCESS; + memcpy(msg + n, scan->uuid, 16); + n += 16; + + if (scan->oob_info) { + l_put_le16(0, msg + n); + n += 2; + } + + i = 0; + while (scan->list[i]) { + msg[n++] = scan->list[i]; + memcpy(msg + n, &scan->list[i + 1], + scan->list[i]); + n += scan->list[i]; + i += scan->list[i] + 1; + } + } + } + + l_timeout_remove(scan->timeout); + l_free(scan->list); + l_free(scan); + rpb_scan = NULL; +} + +static void scan_pkt(void *user_data, struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len) +{ + struct rem_scan_data *scan = user_data; + uint8_t msg[22 + EXT_LIST_SIZE]; + uint8_t addr[6]; + uint16_t i, n; + int8_t rssi; + uint8_t filled = 0; + bool report = false; + + if (scan != rpb_scan) + return; + + if (info) { + rssi = info->rssi; + memcpy(addr, info->addr, 6); + } else { + rssi = 0; + memset(addr, 0, 6); + } + + if (scan->ext_cnt) + goto extended_scan; + + /* RX Unprovisioned Beacon */ + if (data[0] != BT_AD_MESH_BEACON || data[1] || + (len != 18 && len != 20 && len != 24)) + return; + + data += 2; + len -= 2; + + for (n = 0; !report && n < scan->scanned_limit; n++) { + if (!memcmp(&scan->list[n * 17 + 1], data, 16)) { + + /* Repeat UUID, check RSSI */ + if ((int8_t) scan->list[n * 17] < rssi) { + report = true; + scan->list[n * 17] = (uint8_t) rssi; + } + + } else if (!memcmp(&scan->list[n * 17 + 1], zero, 16)) { + + /* Found Empty slot */ + report = true; + scan->list[n * 17] = (uint8_t) rssi; + memcpy(&scan->list[n * 17 + 1], data, 16); + } + + filled++; + } + + if (!report) + return; + + n = mesh_model_opcode_set(OP_REM_PROV_SCAN_REPORT, msg); + msg[n++] = (uint8_t) rssi; + memcpy(msg + n, data, len); + n += len; + + /* Always return oob_info, even if it wasn't in beacon */ + if (len == 16) { + l_put_le16(0, msg + n); + n += 2; + } + + goto send_report; + +extended_scan: + if (data[0] == BT_AD_MESH_BEACON && !data[1]) { + if (len != 18 && len != 20 && len != 24) + return; + + /* Check UUID */ + if (memcmp(data + 2, scan->uuid, 16)) + return; + + /* Zero AD list if prior data RXed from different bd_addr */ + if (memcmp(scan->addr, addr, 6)) { + scan->list[0] = 0; + scan->rxed_ads = 0; + } + + memcpy(scan->addr, addr, 6); + scan->fltr = true; + + if (len >= 20) + scan->oob_info = l_get_le16(data + 18); + + if (scan->rxed_ads != scan->ext_cnt) + return; + + + } else if (data[0] != BT_AD_MESH_BEACON) { + if (!scan->fltr || !memcmp(scan->addr, addr, 6)) { + i = 0; + while (scan->list[i]) { + /* check if seen */ + if (scan->list[i + 1] == data[0]) + return; + + i += scan->list[i] + 1; + } + + /* Overflow Protection */ + if (i + len + 1 > EXT_LIST_SIZE) + return; + + scan->list[i] = len; + scan->list[i + len + 1] = 0; + memcpy(scan->list + i + 1, data, len); + scan->rxed_ads++; + } + + if (scan->rxed_ads != scan->ext_cnt) + return; + + } else + return; + + n = mesh_model_opcode_set(OP_REM_PROV_EXT_SCAN_REPORT, msg); + msg[n++] = PB_REM_ERR_SUCCESS; + memcpy(msg + n, scan->uuid, 16); + n += 16; + l_put_le16(scan->oob_info, msg + n); + n += 2; + + i = 0; + while (scan->list[i]) { + msg[n++] = scan->list[i]; + memcpy(msg + n, &scan->list[i + 1], scan->list[i]); + n += scan->list[i]; + i += scan->list[i]; + } + +send_report: + print_packet("App Tx", msg, n); + mesh_model_send(scan->node, 0, scan->client, APP_IDX_DEV_LOCAL, + scan->net_idx, DEFAULT_TTL, true, n, msg); + + /* Clean-up if we are done reporting*/ + if (filled == scan->scanned_limit || scan->ext_cnt) + remprv_scan_cancel(NULL, scan); +} + +static bool register_ext_ad_type(uint8_t ad_type, struct rem_scan_data *scan) +{ + uint8_t short_ad; + + switch (ad_type) { + case BT_AD_MESH_PROV: + case BT_AD_UUID16_SOME: + case BT_AD_UUID32_SOME: + case BT_AD_UUID128_SOME: + case BT_AD_NAME_SHORT: + /* Illegal Requests */ + return false; + + case BT_AD_UUID16_ALL: + case BT_AD_UUID32_ALL: + case BT_AD_UUID128_ALL: + case BT_AD_NAME_COMPLETE: + /* Automatically get short versions */ + short_ad = ad_type - 1; + mesh_io_register_recv_cb(NULL, &short_ad, 1, scan_pkt, scan); + + /* fall through */ + default: + mesh_io_register_recv_cb(NULL, &ad_type, 1, scan_pkt, scan); + + /* fall through */ + + case BT_AD_MESH_BEACON: + /* Ignored/auto request */ + break; + } + + return true; +} + +static void link_active(void *user_data) +{ + struct rem_prov_data *prov = user_data; + uint8_t msg[5]; + int n; + + if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_LINK_OPENING) + return; + + l_debug("Remote Link open confirmed"); + prov->state = PB_REMOTE_STATE_LINK_ACTIVE; + + n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg); + msg[n++] = PB_REM_ERR_SUCCESS; + msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE; + + mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL, + prov->net_idx, DEFAULT_TTL, true, n, msg); +} + +bool register_nppi_acceptor(mesh_prov_open_func_t open_cb, + mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, + mesh_prov_ack_func_t ack_cb, + void *user_data) +{ + struct rem_prov_data *prov = rpb_prov; + + if (!prov || prov->nppi_proc == RPR_ADV) + return false; + + prov->u.nppi.open_cb = open_cb; + prov->u.nppi.close_cb = close_cb; + prov->u.nppi.rx_cb = rx_cb; + prov->u.nppi.ack_cb = ack_cb; + prov->trans_data = user_data; + + open_cb(user_data, srv_rx, prov, prov->nppi_proc); + + l_idle_oneshot(link_active, prov, NULL); + + return true; +} + +static bool nppi_cmplt(void *user_data, uint8_t status, + struct mesh_prov_node_info *info) +{ + struct rem_prov_data *prov = user_data; + + if (prov != rpb_prov) + return false; + + /* Save new info to apply on Link Close */ + prov->u.nppi.info = *info; + return true; +} + +static bool start_dev_key_refresh(struct mesh_node *node, uint8_t nppi_proc, + struct rem_prov_data *prov) +{ + uint8_t num_ele = node_get_num_elements(node); + + prov->nppi_proc = nppi_proc; + return acceptor_start(num_ele, NULL, 0x0001, 60, NULL, nppi_cmplt, + prov); +} + +static bool remprv_srv_pkt(uint16_t src, uint16_t unicast, uint16_t app_idx, + uint16_t net_idx, const uint8_t *data, + uint16_t size, const void *user_data) +{ + struct rem_prov_data *prov = rpb_prov; + struct rem_scan_data *scan = rpb_scan; + struct mesh_node *node = (struct mesh_node *) user_data; + const uint8_t *pkt = data; + bool segmented = false; + uint32_t opcode; + uint8_t msg[69]; + uint8_t old_state, status; + uint16_t n; + + if (app_idx != APP_IDX_DEV_LOCAL) + return false; + + if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { + size -= n; + pkt += n; + } else + return false; + + n = 0; + + switch (opcode) { + default: + return false; + + case OP_REM_PROV_SCAN_CAP_GET: + if (size != 0) + return true; + + /* Compose Scan Info Status */ + n = mesh_model_opcode_set(OP_REM_PROV_SCAN_CAP_STATUS, msg); + msg[n++] = PB_REMOTE_MAX_SCAN_QUEUE_SIZE; + msg[n++] = 1; /* Active Scanning Supported */ + break; + + case OP_REM_PROV_EXT_SCAN_START: + if (!size || !pkt[0]) + return true; + + /* Size check the message */ + if (pkt[0] + 18 == size) { + /* Range check the Timeout */ + if (!pkt[size - 1] || pkt[size - 1] > 5) + return true; + } else if (pkt[0] + 1 != size) + return true; + + /* Get local device extended info */ + if (pkt[0] + 18 != size) { + n = mesh_model_opcode_set( + OP_REM_PROV_EXT_SCAN_REPORT, msg); + msg[n++] = PB_REM_ERR_SUCCESS; + memcpy(msg + n, node_uuid_get(node), 16); + n += 16; + l_put_le16(0, msg + n); + n += 2; + size--; + pkt++; + + while (size--) { + if (*pkt++ == BT_AD_NAME_COMPLETE) { + msg[n] = strlen(name) + 1; + if (msg[n] > sizeof(msg) - n - 1) + msg[n] = sizeof(msg) - n - 1; + n++; + msg[n++] = BT_AD_NAME_COMPLETE; + memcpy(&msg[n], name, msg[n - 2] - 1); + n += msg[n - 2] - 1; + goto send_pkt; + } + } + + /* Send internal report */ + l_debug("Send internal extended info %d", n); + goto send_pkt; + } + + status = PB_REM_ERR_SUCCESS; + if (scan) { + if (scan->client != src || scan->node != node || + scan->ext_cnt != pkt[0]) + status = PB_REM_ERR_SCANNING_CANNOT_START; + else if (memcmp(scan->ext, pkt + 1, pkt[0])) + status = PB_REM_ERR_SCANNING_CANNOT_START; + else if (memcmp(scan->uuid, pkt + 2, 16)) + status = PB_REM_ERR_SCANNING_CANNOT_START; + } + + if (status != PB_REM_ERR_SUCCESS) { + n = mesh_model_opcode_set(OP_REM_PROV_EXT_SCAN_REPORT, + msg); + msg[n++] = status; + memset(msg + n, 0, 16); + n += 16; + segmented = true; + break; + } + + /* Ignore extended requests while already scanning */ + if (scan) + return true; + + scan = (void *) l_new(uint8_t, + sizeof(struct rem_scan_data) + pkt[0]); + + /* Validate and register Extended AD types */ + for (n = 0; n < pkt[0]; n++) { + if (!register_ext_ad_type(pkt[1 + n], scan)) { + /* Invalid AD type detected -- Undo */ + while (n--) + deregister_ext_ad_type(pkt[1 + n]); + + l_free(scan); + return true; + } + } + + rpb_scan = scan; + scan->client = src; + scan->net_idx = net_idx; + memcpy(scan->uuid, pkt + size - 17, 16); + scan->ext_cnt = pkt[0]; + memcpy(scan->ext, pkt + 1, pkt[0]); + scan->list = l_malloc(EXT_LIST_SIZE); + scan->list[0] = 0; + + mesh_io_register_recv_cb(NULL, prvb, sizeof(prvb), + scan_pkt, scan); + + scan->timeout = l_timeout_create(pkt[size-1], + remprv_scan_cancel, scan, NULL); + return true; + + case OP_REM_PROV_SCAN_START: + if (size != 2 && size != 18) + return true; + + /* Reject Timeout of Zero */ + if (!pkt[1]) + return true; + + status = PB_REM_ERR_SUCCESS; + if (scan) { + if (scan->ext_cnt || scan->client != src || + scan->node != node) + status = PB_REM_ERR_SCANNING_CANNOT_START; + else if (!!(scan->fltr) != !!(size != 18)) + status = PB_REM_ERR_SCANNING_CANNOT_START; + else if (scan->fltr && memcmp(scan->uuid, pkt + 2, 16)) + status = PB_REM_ERR_SCANNING_CANNOT_START; + } + + if (status != PB_REM_ERR_SUCCESS) { + n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STATUS, msg); + msg[n++] = status; + msg[n++] = scan ? scan->state : 0; + msg[n++] = scan ? scan->scanned_limit : + PB_REMOTE_MAX_SCAN_QUEUE_SIZE; + msg[n++] = scan ? scan->to_secs : 0; + break; + } + + if (!scan) + scan = l_new(struct rem_scan_data, 1); + + rpb_scan = scan; + + if (size == 18) { + memcpy(scan->uuid, pkt + 2, 16); + scan->fltr = true; + scan->state = 0x02; /* Limited */ + } else { + memset(scan->uuid, 0, 16); + scan->fltr = false; + scan->state = 0x01; /* Unlimited */ + } + + scan->client = src; + scan->net_idx = net_idx; + scan->node = node; + + if (!scan->list) + scan->list = l_new(uint8_t, + 23 * PB_REMOTE_MAX_SCAN_QUEUE_SIZE); + + mesh_io_register_recv_cb(NULL, prvb, 2, scan_pkt, scan); + + scan->to_secs = pkt[1]; + + if (pkt[0]) + scan->scanned_limit = pkt[0]; + else + scan->scanned_limit = PB_REMOTE_MAX_SCAN_QUEUE_SIZE; + + scan->timeout = l_timeout_create(pkt[1], + remprv_scan_cancel, scan, NULL); + + /* fall through */ + + case OP_REM_PROV_SCAN_GET: + /* Compose Scan Status */ + n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STATUS, msg); + msg[n++] = PB_REM_ERR_SUCCESS; + msg[n++] = scan ? scan->state : 0; + msg[n++] = scan ? scan->scanned_limit : + PB_REMOTE_MAX_SCAN_QUEUE_SIZE; + msg[n++] = scan ? scan->to_secs : 0; + break; + + case OP_REM_PROV_SCAN_STOP: + if (size != 0 || !scan) + return true; + + remprv_scan_cancel(NULL, scan); + return true; + + case OP_REM_PROV_LINK_GET: + if (size != 0 || !prov) + return true; + + send_prov_status(prov, PB_REM_ERR_SUCCESS); + return true; + + case OP_REM_PROV_LINK_OPEN: + /* Sanity check args */ + if (size != 16 && size != 17 && size != 1) + return true; + + if (size == 17 && (pkt[16] == 0 || pkt[16] > 0x3c)) + return true; + + if (size == 1 && pkt[0] > 0x02) + return true; + + if (prov) { + if (prov->client != src || prov->node != node || + (size == 1 && prov->nppi_proc != pkt[0]) || + (size >= 16 && (prov->nppi_proc != RPR_ADV || + memcmp(prov->u.adv.uuid, pkt, 16)))) { + + /* Send Reject (in progress) */ + send_prov_status(prov, PB_REM_ERR_CANNOT_OPEN); + n = mesh_model_opcode_set( + OP_REM_PROV_LINK_STATUS, msg); + msg[n++] = PB_REM_ERR_CANNOT_OPEN; + msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE; + break; + } + + /* Send redundant Success */ + send_prov_status(prov, PB_REM_ERR_SUCCESS); + return true; + } + + if (scan && scan->client != src && scan->node != node) { + n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg); + msg[n++] = PB_REM_ERR_CANNOT_OPEN; + msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE; + break; + } + + print_packet("Remote Prov Link Open", pkt, size); + + remprv_scan_cancel(NULL, scan); + + rpb_prov = prov = l_new(struct rem_prov_data, 1); + prov->client = src; + prov->net_idx = net_idx; + prov->node = node; + prov->state = PB_REMOTE_STATE_LINK_OPENING; + + if (size == 1) { + status = start_dev_key_refresh(node, pkt[0], prov); + + } else { + if (size == 17) + prov->timeout = l_timeout_create(pkt[16], + remprv_prov_cancel, prov, NULL); + + + prov->nppi_proc = RPR_ADV; + memcpy(prov->u.adv.uuid, pkt, 16); + status = pb_adv_reg(true, srv_open, srv_close, srv_rx, + srv_ack, pkt, prov); + } + + if (status) + send_prov_status(prov, PB_REM_ERR_SUCCESS); + else { + n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg); + msg[n++] = PB_REM_ERR_CANNOT_OPEN; + msg[n++] = PB_REMOTE_STATE_IDLE; + remprv_prov_cancel(NULL, prov); + } + + return true; + + case OP_REM_PROV_LINK_CLOSE: + if (size != 1) + return true; + + if (!prov || prov->node != node || prov->client != src) + return true; + + old_state = prov->state; + prov->state = PB_REMOTE_STATE_LINK_CLOSING; + mesh_io_send_cancel(NULL, &pkt_filter, sizeof(pkt_filter)); + send_prov_status(prov, PB_REM_ERR_SUCCESS); + if (pkt[0] == 0x02 && + old_state >= PB_REMOTE_STATE_LINK_ACTIVE) { + msg[0] = PROV_FAILED; + msg[1] = PROV_ERR_CANT_ASSIGN_ADDR; + if (prov->nppi_proc == RPR_ADV) + prov->u.adv.tx(prov->trans_data, msg, 2); + else + prov->u.nppi.rx_cb(prov->trans_data, msg, 2); + } + + if (prov->nppi_proc == RPR_ADV) + pb_adv_unreg(prov); + + else if (prov->nppi_proc <= RPR_COMP) { + /* Hard or Soft refresh of local node, based on NPPI */ + node_refresh(prov->node, (prov->nppi_proc == RPR_ADDR), + &prov->u.nppi.info); + } + + remprv_prov_cancel(NULL, prov); + + return true; + + case OP_REM_PROV_PDU_SEND: + if (!prov || prov->node != node || prov->client != src) + return true; + + if (size < 2) + return true; + + + prov->cli_pdu_num = *pkt++; + size--; + prov->state = PB_REMOTE_STATE_OB_PKT_TX; + + if (prov->nppi_proc == RPR_ADV) + prov->u.adv.tx(prov->trans_data, pkt, size); + else { + srv_ack(prov, prov->cli_pdu_num); + prov->u.nppi.rx_cb(prov->trans_data, pkt, size); + } + + return true; + } + +send_pkt: + l_info("PB-SVR: src %4.4x dst %4.4x", unicast, src); + print_packet("App Tx", msg, n); + mesh_model_send(node, 0, src, APP_IDX_DEV_LOCAL, + net_idx, DEFAULT_TTL, segmented, n, msg); + + return true; +} + +static void remprv_srv_unregister(void *user_data) +{ +} + +static const struct mesh_model_ops ops = { + .unregister = remprv_srv_unregister, + .recv = remprv_srv_pkt, + .bind = NULL, + .sub = NULL, + .pub = NULL +}; + +void remote_prov_server_init(struct mesh_node *node, uint8_t ele_idx) +{ + mesh_model_register(node, ele_idx, REM_PROV_SRV_MODEL, &ops, node); +} diff -Nru bluez-5.66/monitor/att.c bluez-5.68/monitor/att.c --- bluez-5.66/monitor/att.c 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/monitor/att.c 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -42,6 +43,7 @@ #include "display.h" #include "l2cap.h" #include "att.h" +#include "keys.h" struct att_read { struct gatt_db_attribute *attr; @@ -106,27 +108,118 @@ return read->chan == frame->chan; } -static void print_data_list(const char *label, uint8_t length, - const struct l2cap_frame *frame) +static struct att_read *att_get_read(const struct l2cap_frame *frame) { struct packet_conn_data *conn; struct att_conn_data *data; + + conn = packet_get_conn_data(frame->handle); + if (!conn) + return NULL; + + data = conn->data; + if (!data) + return NULL; + + return queue_remove_if(data->reads, match_read_frame, (void *)frame); +} + +static void print_value(struct gatt_db_attribute *attr) +{ + uint16_t handle; + struct gatt_db_attribute *val; + const bt_uuid_t *uuid; + bt_uuid_t chrc = { + .type = BT_UUID16, + .value.u16 = 0x2803, + }; + char label[27]; + + uuid = gatt_db_attribute_get_type(attr); + if (!uuid) + return; + + /* Skip in case of characteristic declaration since it already prints + * the value handle and properties. + */ + if (!bt_uuid_cmp(uuid, &chrc)) + return; + + val = gatt_db_attribute_get_value(attr); + if (!val || val == attr) + return; + + uuid = gatt_db_attribute_get_type(val); + if (!uuid) + return; + + handle = gatt_db_attribute_get_handle(val); + if (!handle) + return; + + switch (uuid->type) { + case BT_UUID16: + sprintf(label, "Value Handle: 0x%4.4x Type", handle); + print_field("%s: %s (0x%4.4x)", label, + bt_uuid16_to_str(uuid->value.u16), + uuid->value.u16); + return; + case BT_UUID128: + sprintf(label, "Value Handle: 0x%4.4x Type", handle); + print_uuid(label, &uuid->value.u128, 16); + return; + case BT_UUID_UNSPEC: + case BT_UUID32: + break; + } +} + +static void print_attribute(struct gatt_db_attribute *attr) +{ + uint16_t handle; + const bt_uuid_t *uuid; + char label[21]; + + handle = gatt_db_attribute_get_handle(attr); + if (!handle) + goto done; + + uuid = gatt_db_attribute_get_type(attr); + if (!uuid) + goto done; + + switch (uuid->type) { + case BT_UUID16: + sprintf(label, "Handle: 0x%4.4x Type", handle); + print_field("%s: %s (0x%4.4x)", label, + bt_uuid16_to_str(uuid->value.u16), + uuid->value.u16); + print_value(attr); + return; + case BT_UUID128: + sprintf(label, "Handle: 0x%4.4x Type", handle); + print_uuid(label, &uuid->value.u128, 16); + print_value(attr); + return; + case BT_UUID_UNSPEC: + case BT_UUID32: + break; + } + +done: + print_field("Handle: 0x%4.4x", handle); +} + +static void print_data_list(const char *label, uint8_t length, + const struct l2cap_frame *frame) +{ struct att_read *read; uint8_t count; if (length == 0) return; - conn = packet_get_conn_data(frame->handle); - if (conn) { - data = conn->data; - if (data) - read = queue_remove_if(data->reads, match_read_frame, - (void *)frame); - else - read = NULL; - } else - read = NULL; + read = att_get_read(frame); count = frame->size / length; @@ -271,6 +364,13 @@ pdu->request); print_field("Handle: 0x%4.4x", le16_to_cpu(pdu->handle)); print_field("Error: %s (0x%2.2x)", str, pdu->error); + + /* Read/Read By Type/Read By Group Type may create a read object which + * needs to be dequeued and freed in case the operation fails. + */ + if (pdu->request == 0x08 || pdu->request == 0x0a || + pdu->request == 0x10) + free(att_get_read(frame)); } static const struct bitfield_data chrc_prop_table[] = { @@ -285,10 +385,202 @@ { } }; +static void att_conn_data_free(void *data) +{ + struct att_conn_data *att_data = data; + + gatt_db_unref(att_data->rdb); + gatt_db_unref(att_data->ldb); + queue_destroy(att_data->reads, free); + free(att_data); +} + +static struct att_conn_data *att_get_conn_data(struct packet_conn_data *conn) +{ + struct att_conn_data *data; + + if (!conn) + return NULL; + + data = conn->data; + + if (data) + return data; + + data = new0(struct att_conn_data, 1); + data->rdb = gatt_db_new(); + data->ldb = gatt_db_new(); + conn->data = data; + conn->destroy = att_conn_data_free; + + return data; +} + +static void gatt_load_db(struct gatt_db *db, const char *filename, + struct timespec *mtim) +{ + struct stat st; + + if (lstat(filename, &st)) + return; + + if (!gatt_db_isempty(db)) { + /* Check if file has been modified since last time */ + if (st.st_mtim.tv_sec == mtim->tv_sec && + st.st_mtim.tv_nsec == mtim->tv_nsec) + return; + /* Clear db before reloading */ + gatt_db_clear(db); + } + + *mtim = st.st_mtim; + + btd_settings_gatt_db_load(db, filename); +} + +static void load_gatt_db(struct packet_conn_data *conn) +{ + struct att_conn_data *data = att_get_conn_data(conn); + char filename[PATH_MAX]; + char local[18]; + char peer[18]; + uint8_t id[6], id_type; + + ba2str((bdaddr_t *)conn->src, local); + + if (keys_resolve_identity(conn->dst, id, &id_type)) + ba2str((bdaddr_t *)id, peer); + else + ba2str((bdaddr_t *)conn->dst, peer); + + create_filename(filename, PATH_MAX, "/%s/attributes", local); + gatt_load_db(data->ldb, filename, &data->ldb_mtim); + + create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); + gatt_load_db(data->rdb, filename, &data->rdb_mtim); +} + +static struct gatt_db *get_db(const struct l2cap_frame *frame, bool rsp) +{ + struct packet_conn_data *conn; + struct att_conn_data *data; + struct gatt_db *db; + + conn = packet_get_conn_data(frame->handle); + if (!conn) + return NULL; + + /* Try loading local and remote gatt_db if not loaded yet */ + load_gatt_db(conn); + + data = conn->data; + if (!data) + return NULL; + + if (frame->in) { + if (rsp) + db = data->rdb; + else + db = data->ldb; + } else { + if (rsp) + db = data->ldb; + else + db = data->rdb; + } + + return db; +} + +static struct gatt_db_attribute *insert_chrc(const struct l2cap_frame *frame, + uint16_t handle, + bt_uuid_t *uuid, uint8_t prop, + bool rsp) +{ + struct gatt_db *db; + + db = get_db(frame, rsp); + if (!db) + return NULL; + + return gatt_db_insert_characteristic(db, handle, uuid, 0, prop, NULL, + NULL, NULL); +} + +static int bt_uuid_from_data(bt_uuid_t *uuid, const void *data, uint16_t size) +{ + uint128_t u128; + + if (!uuid) + return -EINVAL; + + switch (size) { + case 2: + return bt_uuid16_create(uuid, get_le16(data)); + case 4: + return bt_uuid32_create(uuid, get_le32(data)); + case 16: + memcpy(u128.data, data, sizeof(u128.data)); + return bt_uuid128_create(uuid, u128); + } + + return -EINVAL; +} + +static bool svc_read(const struct l2cap_frame *frame, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid) +{ + if (!l2cap_frame_get_le16((void *)frame, start)) + return false; + + if (!l2cap_frame_get_le16((void *)frame, end)) + return false; + + return !bt_uuid_from_data(uuid, frame->data, frame->size); +} + +static struct gatt_db_attribute *insert_svc(const struct l2cap_frame *frame, + uint16_t handle, + bt_uuid_t *uuid, bool primary, + bool rsp, uint16_t num_handles) +{ + struct gatt_db *db; + + db = get_db(frame, rsp); + if (!db) + return NULL; + + return gatt_db_insert_service(db, handle, uuid, primary, num_handles); +} + +static void pri_svc_read(const struct l2cap_frame *frame) +{ + uint16_t start, end; + bt_uuid_t uuid; + + if (!svc_read(frame, &start, &end, &uuid)) + return; + + insert_svc(frame, start, &uuid, true, true, end - start + 1); +} + +static void sec_svc_read(const struct l2cap_frame *frame) +{ + uint16_t start, end; + bt_uuid_t uuid; + + if (!svc_read(frame, &start, &end, &uuid)) + return; + + insert_svc(frame, start, &uuid, true, false, end - start + 1); +} + static void print_chrc(const struct l2cap_frame *frame) { uint8_t prop; uint8_t mask; + uint16_t handle; + bt_uuid_t uuid; if (!l2cap_frame_get_u8((void *)frame, &prop)) { print_text(COLOR_ERROR, "Property: invalid size"); @@ -302,10 +594,16 @@ print_text(COLOR_WHITE_BG, " Unknown fields (0x%2.2x)", mask); - if (!l2cap_frame_print_le16((void *)frame, " Value Handle")) + if (!l2cap_frame_get_le16((void *)frame, &handle)) { + print_text(COLOR_ERROR, " Value Handle: invalid size"); return; + } + print_field(" Value Handle: 0x%4.4x", handle); print_uuid(" Value UUID", frame->data, frame->size); + bt_uuid_from_data(&uuid, frame->data, frame->size); + + insert_chrc(frame, handle, &uuid, prop, true); } static void chrc_read(const struct l2cap_frame *frame) @@ -845,31 +1143,31 @@ print_field(" Sampling Frequency: 22.05 Khz (0x04)"); break; case 0x05: - print_field(" Sampling Frequency: 24 Khz (0x04)"); + print_field(" Sampling Frequency: 24 Khz (0x05)"); break; case 0x06: - print_field(" Sampling Frequency: 32 Khz (0x04)"); + print_field(" Sampling Frequency: 32 Khz (0x06)"); break; case 0x07: - print_field(" Sampling Frequency: 44.1 Khz (0x04)"); + print_field(" Sampling Frequency: 44.1 Khz (0x07)"); break; case 0x08: - print_field(" Sampling Frequency: 48 Khz (0x04)"); + print_field(" Sampling Frequency: 48 Khz (0x08)"); break; case 0x09: - print_field(" Sampling Frequency: 88.2 Khz (0x04)"); + print_field(" Sampling Frequency: 88.2 Khz (0x09)"); break; case 0x0a: - print_field(" Sampling Frequency: 96 Khz (0x04)"); + print_field(" Sampling Frequency: 96 Khz (0x0a)"); break; case 0x0b: - print_field(" Sampling Frequency: 176.4 Khz (0x04)"); + print_field(" Sampling Frequency: 176.4 Khz (0x0b)"); break; case 0x0c: - print_field(" Sampling Frequency: 192 Khz (0x04)"); + print_field(" Sampling Frequency: 192 Khz (0x0c)"); break; case 0x0d: - print_field(" Sampling Frequency: 384 Khz (0x04)"); + print_field(" Sampling Frequency: 384 Khz (0x0d)"); break; default: print_field(" Sampling Frequency: RFU (0x%2.2x)", value); @@ -1688,23 +1986,102 @@ print_pac_context(frame); } -static void print_vcs_state(const struct l2cap_frame *frame) +static void csip_rank_read(const struct l2cap_frame *frame) { - uint8_t vol_set, mute, chng_ctr; + uint8_t rank; - if (!l2cap_frame_get_u8((void *)frame, &vol_set)) { - print_text(COLOR_ERROR, "Volume Settings: invalid size"); + if (!l2cap_frame_get_u8((void *)frame, &rank)) { + print_text(COLOR_ERROR, "Rank: invalid size"); goto done; } - print_field(" Volume Setting: %u", vol_set); - if (!l2cap_frame_get_u8((void *)frame, &mute)) { - print_text(COLOR_ERROR, "Mute Filed: invalid size"); - goto done; - } + print_field(" Rank: 0x%02x", rank); - switch (mute) { - case 0x00: +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void csip_lock_read(const struct l2cap_frame *frame) +{ + uint8_t lock; + + if (!l2cap_frame_get_u8((void *)frame, &lock)) { + print_text(COLOR_ERROR, "Lock: invalid size"); + goto done; + } + + switch (lock) { + case 0x01: + print_field(" Unlocked (0x%02x)", lock); + break; + case 0x02: + print_field(" Locked (0x%02x)", lock); + break; + default: + print_field(" RFU (0x%02x)", lock); + break; + } + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void print_csip_size(const struct l2cap_frame *frame) +{ + uint8_t size; + + if (!l2cap_frame_get_u8((void *)frame, &size)) { + print_text(COLOR_ERROR, "Size: invalid size"); + goto done; + } + print_field(" Size: 0x%02x", size); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void csip_size_read(const struct l2cap_frame *frame) +{ + print_csip_size(frame); +} + +static void csip_size_notify(const struct l2cap_frame *frame) +{ + print_csip_size(frame); +} + +static void csip_sirk_read(const struct l2cap_frame *frame) +{ + if (frame->size) + print_hex_field(" SIRK", frame->data, frame->size); +} + +static void csip_sirk_notify(const struct l2cap_frame *frame) +{ + if (frame->size) + print_hex_field(" SIRK", frame->data, frame->size); +} + +static void print_vcs_state(const struct l2cap_frame *frame) +{ + uint8_t vol_set, mute, chng_ctr; + + if (!l2cap_frame_get_u8((void *)frame, &vol_set)) { + print_text(COLOR_ERROR, "Volume Settings: invalid size"); + goto done; + } + print_field(" Volume Setting: %u", vol_set); + + if (!l2cap_frame_get_u8((void *)frame, &mute)) { + print_text(COLOR_ERROR, "Mute Filed: invalid size"); + goto done; + } + + switch (mute) { + case 0x00: print_field(" Not Muted: %u", mute); break; case 0x01: @@ -2337,400 +2714,728 @@ print_content_control_id(frame); } -#define GATT_HANDLER(_uuid, _read, _write, _notify) \ -{ \ - .uuid = { \ - .type = BT_UUID16, \ - .value.u16 = _uuid, \ - }, \ - .read = _read, \ - .write = _write, \ - .notify = _notify \ -} +static const struct pa_sync_state_decoder { + uint8_t code; + char *value; +} pa_sync_state_decoders[] = { + { 0x00, "Not synchronized to PA" }, + { 0x01, "SyncInfo Request" }, + { 0x02, "Synchronized to PA" }, + { 0x03, "Failed to synchronize to PA" }, + { 0x04, "No PAST" }, +}; -struct gatt_handler { - bt_uuid_t uuid; - void (*read)(const struct l2cap_frame *frame); - void (*write)(const struct l2cap_frame *frame); - void (*notify)(const struct l2cap_frame *frame); -} gatt_handlers[] = { - GATT_HANDLER(0x2803, chrc_read, NULL, NULL), - GATT_HANDLER(0x2902, ccc_read, ccc_write, NULL), - GATT_HANDLER(0x2bc4, ase_read, NULL, ase_notify), - GATT_HANDLER(0x2bc5, ase_read, NULL, ase_notify), - GATT_HANDLER(0x2bc6, NULL, ase_cp_write, ase_cp_notify), - GATT_HANDLER(0x2bc9, pac_read, NULL, pac_notify), - GATT_HANDLER(0x2bca, pac_loc_read, NULL, pac_loc_notify), - GATT_HANDLER(0x2bcb, pac_read, NULL, pac_notify), - GATT_HANDLER(0x2bcc, pac_loc_read, NULL, pac_loc_notify), - GATT_HANDLER(0x2bcd, pac_context_read, NULL, pac_context_notify), - GATT_HANDLER(0x2bce, pac_context_read, NULL, pac_context_notify), - GATT_HANDLER(0x2b7d, vol_state_read, NULL, vol_state_notify), - GATT_HANDLER(0x2b7e, NULL, vol_cp_write, NULL), - GATT_HANDLER(0x2b7f, vol_flag_read, NULL, vol_flag_notify), - GATT_HANDLER(0x2b93, mp_name_read, NULL, mp_name_notify), - GATT_HANDLER(0x2b96, NULL, NULL, track_changed_notify), - GATT_HANDLER(0x2b97, track_title_read, NULL, track_title_notify), - GATT_HANDLER(0x2b98, track_duration_read, NULL, track_duration_notify), - GATT_HANDLER(0x2b99, track_position_read, track_position_write, - track_position_notify), - GATT_HANDLER(0x2b9a, playback_speed_read, playback_speed_write, - playback_speed_notify), - GATT_HANDLER(0x2b9b, seeking_speed_read, NULL, seeking_speed_notify), - GATT_HANDLER(0x2ba1, playing_order_read, playing_order_write, - playing_order_notify), - GATT_HANDLER(0x2ba2, playing_orders_supported_read, NULL, NULL), - GATT_HANDLER(0x2ba3, media_state_read, NULL, media_state_notify), - GATT_HANDLER(0x2ba4, NULL, media_cp_write, media_cp_notify), - GATT_HANDLER(0x2ba5, media_cp_op_supported_read, NULL, - media_cp_op_supported_notify), - GATT_HANDLER(0x2bba, content_control_id_read, NULL, NULL), +static const struct cp_pa_sync_state_decoder { + uint8_t code; + char *value; +} cp_pa_sync_state_decoders[] = { + { 0x00, "Do not synchronize to PA" }, + { 0x01, "Synchronize to PA - PAST available" }, + { 0x02, "Synchronize to PA - PAST not available" }, }; -static struct gatt_handler *get_handler_uuid(const bt_uuid_t *uuid) +static const struct big_enc_decoder { + uint8_t code; + char *value; +} big_enc_decoders[] = { + { 0x00, "Not encrypted" }, + { 0x01, "Broadcast_Code required" }, + { 0x02, "Decrypting" }, + { 0x03, "Bad_Code (incorrect encryption key)" }, +}; + +static bool print_subgroup_lv(const struct l2cap_frame *frame, + const char *label, struct packet_ltv_decoder *decoder, + size_t decoder_len) { - size_t i; + struct bt_hci_lv_data *lv; - for (i = 0; i < ARRAY_SIZE(gatt_handlers); i++) { - struct gatt_handler *handler = &gatt_handlers[i]; + lv = l2cap_frame_pull((void *)frame, frame, sizeof(*lv)); + if (!lv) { + print_text(COLOR_ERROR, "%s: invalid size", label); + return false; + } - if (!bt_uuid_cmp(&handler->uuid, uuid)) - return handler; + if (!l2cap_frame_pull((void *)frame, frame, lv->len)) { + print_text(COLOR_ERROR, "%s: invalid size", label); + return false; } - return NULL; + packet_print_ltv(label, lv->data, lv->len, decoder, decoder_len); + + return true; } -static struct gatt_handler *get_handler(struct gatt_db_attribute *attr) +static bool print_subgroup_metadata(const char *label, + const struct l2cap_frame *frame) { - return get_handler_uuid(gatt_db_attribute_get_type(attr)); + return print_subgroup_lv(frame, label, NULL, 0); } -static void att_exchange_mtu_req(const struct l2cap_frame *frame) +static void print_bcast_recv_state(const struct l2cap_frame *frame) { - const struct bt_l2cap_att_exchange_mtu_req *pdu = frame->data; + uint8_t i; + uint8_t id; + uint8_t addr_type; + uint8_t *addr; + uint8_t sid; + uint32_t bid; + uint8_t pa_sync_state; + uint8_t enc; + uint8_t *bad_code; + uint8_t num_subgroups = 0; + uint32_t bis_sync_state; - print_field("Client RX MTU: %d", le16_to_cpu(pdu->mtu)); -} + if (frame->size == 0) { + print_field(" Empty characteristic"); + goto done; + } -static void att_exchange_mtu_rsp(const struct l2cap_frame *frame) -{ - const struct bt_l2cap_att_exchange_mtu_rsp *pdu = frame->data; + if (!l2cap_frame_get_u8((void *)frame, &id)) { + print_text(COLOR_ERROR, "Source_ID: invalid size"); + goto done; + } - print_field("Server RX MTU: %d", le16_to_cpu(pdu->mtu)); -} + print_field(" Source_ID: %u", id); -static void att_find_info_req(const struct l2cap_frame *frame) -{ - print_handle_range("Handle range", frame->data); -} + if (!l2cap_frame_get_u8((void *)frame, &addr_type)) { + print_text(COLOR_ERROR, "Source_Address_Type: invalid size"); + goto done; + } -static const char *att_format_str(uint8_t format) -{ - switch (format) { - case 0x01: - return "UUID-16"; - case 0x02: - return "UUID-128"; - default: - return "unknown"; + print_field(" Source_Address_Type: %u", addr_type); + + addr = l2cap_frame_pull((void *)frame, frame, sizeof(bdaddr_t)); + if (!addr) { + print_text(COLOR_ERROR, "Source_Address: invalid size"); + goto done; } -} -static uint16_t print_info_data_16(const void *data, uint16_t len) -{ - while (len >= 4) { - print_field("Handle: 0x%4.4x", get_le16(data)); - print_uuid("UUID", data + 2, 2); - data += 4; - len -= 4; + print_field(" Source_Address: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + addr[5], addr[4], + addr[3], addr[2], + addr[1], addr[0]); + + if (!l2cap_frame_get_u8((void *)frame, &sid)) { + print_text(COLOR_ERROR, "Source_Adv_SID: invalid size"); + goto done; } - return len; -} + print_field(" Source_Adv_SID: %u", sid); -static uint16_t print_info_data_128(const void *data, uint16_t len) -{ - while (len >= 18) { - print_field("Handle: 0x%4.4x", get_le16(data)); - print_uuid("UUID", data + 2, 16); - data += 18; - len -= 18; + if (!l2cap_frame_get_le24((void *)frame, &bid)) { + print_text(COLOR_ERROR, "Broadcast_ID: invalid size"); + goto done; } - return len; -} + print_field(" Broadcast_ID: 0x%06x", bid); -static void att_find_info_rsp(const struct l2cap_frame *frame) -{ - const uint8_t *format = frame->data; - uint16_t len; + if (!l2cap_frame_get_u8((void *)frame, &pa_sync_state)) { + print_text(COLOR_ERROR, "PA_Sync_State: invalid size"); + goto done; + } - print_field("Format: %s (0x%2.2x)", att_format_str(*format), *format); + for (i = 0; i < ARRAY_SIZE(pa_sync_state_decoders); i++) { + const struct pa_sync_state_decoder *decoder; - if (*format == 0x01) - len = print_info_data_16(frame->data + 1, frame->size - 1); - else if (*format == 0x02) - len = print_info_data_128(frame->data + 1, frame->size - 1); - else - len = frame->size - 1; + decoder = &pa_sync_state_decoders[i]; - packet_hexdump(frame->data + (frame->size - len), len); -} + if (decoder->code == pa_sync_state) { + print_field(" PA_Sync_State: %s", decoder->value); + break; + } + } -static void att_find_by_type_val_req(const struct l2cap_frame *frame) -{ - uint16_t type; + if (i == ARRAY_SIZE(pa_sync_state_decoders)) + print_field(" PA_Sync_State: %s", "Invalid value"); - print_handle_range("Handle range", frame->data); + if (!l2cap_frame_get_u8((void *)frame, &enc)) { + print_text(COLOR_ERROR, "BIG_Encryption: invalid size"); + goto done; + } - type = get_le16(frame->data + 4); - print_attribute_info(type, frame->data + 6, frame->size - 6); -} + for (i = 0; i < ARRAY_SIZE(big_enc_decoders); i++) { + const struct big_enc_decoder *decoder; -static void att_find_by_type_val_rsp(const struct l2cap_frame *frame) -{ - const uint8_t *ptr = frame->data; - uint16_t len = frame->size; + decoder = &big_enc_decoders[i]; - while (len >= 4) { - print_handle_range("Handle range", ptr); - ptr += 4; - len -= 4; + if (decoder->code == enc) { + print_field(" BIG_Encryption: %s", decoder->value); + break; + } } - packet_hexdump(ptr, len); -} + if (i == ARRAY_SIZE(big_enc_decoders)) + print_field(" BIG_Encryption: %s", "Invalid value"); -static int bt_uuid_from_data(bt_uuid_t *uuid, const void *data, uint16_t size) -{ - uint128_t u128; + if (enc == 0x03) { + bad_code = l2cap_frame_pull((void *)frame, frame, 16); + if (!bad_code) { + print_text(COLOR_ERROR, "Bad_Code: invalid size"); + goto done; + } - switch (size) { - case 2: - return bt_uuid16_create(uuid, get_le16(data)); - case 4: - return bt_uuid32_create(uuid, get_le32(data)); - case 16: - memcpy(u128.data, data, sizeof(u128.data)); - return bt_uuid128_create(uuid, u128); + print_hex_field(" Bad_Code", bad_code, 16); } - return -EINVAL; -} + if (!l2cap_frame_get_u8((void *)frame, &num_subgroups)) { + print_text(COLOR_ERROR, "Num_Subgroups: invalid size"); + goto done; + } -static void att_conn_data_free(void *data) -{ - struct att_conn_data *att_data = data; + print_field(" Num_Subgroups: %u", num_subgroups); - gatt_db_unref(att_data->rdb); - gatt_db_unref(att_data->ldb); - queue_destroy(att_data->reads, free); - free(att_data); -} + for (i = 0; i < num_subgroups; i++) { + print_field(" Subgroup #%u:", i); -static struct att_conn_data *att_get_conn_data(struct packet_conn_data *conn) -{ - struct att_conn_data *data = conn->data; + if (!l2cap_frame_get_le32((void *)frame, &bis_sync_state)) { + print_text(COLOR_ERROR, "BIS_Sync State: invalid size"); + goto done; + } - if (data) - return data; + print_field(" BIS_Sync State: 0x%8.8x", bis_sync_state); - data = new0(struct att_conn_data, 1); - data->rdb = gatt_db_new(); - data->ldb = gatt_db_new(); - conn->data = data; - conn->destroy = att_conn_data_free; + if (!print_subgroup_metadata(" Metadata", frame)) + goto done; + } - return data; +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); } -static void att_read_type_req(const struct l2cap_frame *frame) +static void bcast_recv_state_read(const struct l2cap_frame *frame) { - bt_uuid_t uuid; - struct packet_conn_data *conn; - struct att_conn_data *data; - struct att_read *read; - struct gatt_handler *handler; + print_bcast_recv_state(frame); +} - print_handle_range("Handle range", frame->data); - print_uuid("Attribute type", frame->data + 4, frame->size - 4); +static void bcast_recv_state_notify(const struct l2cap_frame *frame) +{ + print_bcast_recv_state(frame); +} - if (bt_uuid_from_data(&uuid, frame->data + 4, frame->size - 4)) +#define BCAST_AUDIO_SCAN_CP_CMD(_op, _desc, _func) \ +[_op] = { \ + .desc = _desc, \ + .func = _func, \ +} + +static void bcast_audio_scan_cp_add_src_cmd(const struct l2cap_frame *frame) +{ + uint8_t i; + uint8_t addr_type; + uint8_t *addr; + uint8_t sid; + uint32_t bid; + uint8_t pa_sync_state; + uint16_t pa_interval; + uint8_t num_subgroups = 0; + uint32_t bis_sync_state; + + if (!l2cap_frame_get_u8((void *)frame, &addr_type)) { + print_text(COLOR_ERROR, "Source_Address_Type: invalid size"); return; + } - handler = get_handler_uuid(&uuid); - if (!handler || !handler->read) + print_field(" Source_Address_Type: %u", addr_type); + + addr = l2cap_frame_pull((void *)frame, frame, sizeof(bdaddr_t)); + if (!addr) { + print_text(COLOR_ERROR, "Source_Address: invalid size"); return; + } - conn = packet_get_conn_data(frame->handle); - data = att_get_conn_data(conn); + print_field(" Source_Address: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + addr[5], addr[4], + addr[3], addr[2], + addr[1], addr[0]); - if (!data->reads) - data->reads = queue_new(); + if (!l2cap_frame_get_u8((void *)frame, &sid)) { + print_text(COLOR_ERROR, "Source_Adv_SID: invalid size"); + return; + } - read = new0(struct att_read, 1); - read->in = frame->in; - read->chan = frame->chan; - read->func = handler->read; + print_field(" Source_Adv_SID: %u", sid); - queue_push_tail(data->reads, read); + if (!l2cap_frame_get_le24((void *)frame, &bid)) { + print_text(COLOR_ERROR, "Broadcast_ID: invalid size"); + return; + } + + print_field(" Broadcast_ID: 0x%06x", bid); + + if (!l2cap_frame_get_u8((void *)frame, &pa_sync_state)) { + print_text(COLOR_ERROR, "PA_Sync_State: invalid size"); + return; + } + + for (i = 0; i < ARRAY_SIZE(cp_pa_sync_state_decoders); i++) { + const struct cp_pa_sync_state_decoder *decoder; + + decoder = &cp_pa_sync_state_decoders[i]; + + if (decoder->code == pa_sync_state) { + print_field(" PA_Sync_State: %s", decoder->value); + break; + } + } + + if (i == ARRAY_SIZE(cp_pa_sync_state_decoders)) + print_field(" PA_Sync_State: %s", "Invalid value"); + + if (!l2cap_frame_get_le16((void *)frame, &pa_interval)) { + print_text(COLOR_ERROR, "PA_Interval: invalid size"); + return; + } + + print_field(" PA_Interval: 0x%04x", pa_interval); + + if (!l2cap_frame_get_u8((void *)frame, &num_subgroups)) { + print_text(COLOR_ERROR, "Num_Subgroups: invalid size"); + return; + } + + print_field(" Num_Subgroups: %u", num_subgroups); + + for (i = 0; i < num_subgroups; i++) { + print_field(" Subgroup #%u:", i); + + if (!l2cap_frame_get_le32((void *)frame, &bis_sync_state)) { + print_text(COLOR_ERROR, "BIS_Sync State: invalid size"); + return; + } + + print_field(" BIS_Sync State: 0x%8.8x", bis_sync_state); + + if (!print_subgroup_metadata(" Metadata", frame)) + return; + } } -static void att_read_type_rsp(const struct l2cap_frame *frame) +static void bcast_audio_scan_cp_mod_src_cmd(const struct l2cap_frame *frame) { - uint8_t len; + uint8_t i; + uint8_t id; + uint8_t pa_sync_state; + uint16_t pa_interval; + uint8_t num_subgroups = 0; + uint32_t bis_sync_state; - if (!l2cap_frame_get_u8((void *)frame, &len)) { - print_text(COLOR_ERROR, "invalid size"); + if (!l2cap_frame_get_u8((void *)frame, &id)) { + print_text(COLOR_ERROR, "Source_ID: invalid size"); return; } - print_field("Attribute data length: %d", len); - print_data_list("Attribute data list", len, frame); + print_field(" Source_ID: %u", id); + + if (!l2cap_frame_get_u8((void *)frame, &pa_sync_state)) { + print_text(COLOR_ERROR, "PA_Sync_State: invalid size"); + return; + } + + for (i = 0; i < ARRAY_SIZE(cp_pa_sync_state_decoders); i++) { + const struct cp_pa_sync_state_decoder *decoder; + + decoder = &cp_pa_sync_state_decoders[i]; + + if (decoder->code == pa_sync_state) { + print_field(" PA_Sync_State: %s", decoder->value); + break; + } + } + + if (i == ARRAY_SIZE(cp_pa_sync_state_decoders)) + print_field(" PA_Sync_State: %s", "Invalid value"); + + if (!l2cap_frame_get_le16((void *)frame, &pa_interval)) { + print_text(COLOR_ERROR, "PA_Interval: invalid size"); + return; + } + + print_field(" PA_Interval: 0x%04x", pa_interval); + + if (!l2cap_frame_get_u8((void *)frame, &num_subgroups)) { + print_text(COLOR_ERROR, "Num_Subgroups: invalid size"); + return; + } + + print_field(" Num_Subgroups: %u", num_subgroups); + + for (i = 0; i < num_subgroups; i++) { + print_field(" Subgroup #%u:", i); + + if (!l2cap_frame_get_le32((void *)frame, &bis_sync_state)) { + print_text(COLOR_ERROR, "BIS_Sync State: invalid size"); + return; + } + + print_field(" BIS_Sync State: 0x%8.8x", bis_sync_state); + + if (!print_subgroup_metadata(" Metadata", frame)) + return; + } } -static void gatt_load_db(struct gatt_db *db, const char *filename, - struct timespec *mtim) +static void bcast_audio_scan_cp_set_bcode_cmd(const struct l2cap_frame *frame) { - struct stat st; + uint8_t id; + uint8_t *bcast_code; - if (lstat(filename, &st)) + if (!l2cap_frame_get_u8((void *)frame, &id)) { + print_text(COLOR_ERROR, "Source_ID: invalid size"); return; + } - if (!gatt_db_isempty(db)) { - /* Check if file has been modified since last time */ - if (st.st_mtim.tv_sec == mtim->tv_sec && - st.st_mtim.tv_nsec == mtim->tv_nsec) - return; - /* Clear db before reloading */ - gatt_db_clear(db); + print_field(" Source_ID: %u", id); + + bcast_code = l2cap_frame_pull((void *)frame, frame, 16); + if (!bcast_code) { + print_text(COLOR_ERROR, "Broadcast_Code: invalid size"); + return; } - *mtim = st.st_mtim; + print_hex_field(" Broadcast_Code", bcast_code, 16); - btd_settings_gatt_db_load(db, filename); } -static void load_gatt_db(struct packet_conn_data *conn) +static void bcast_audio_scan_cp_remove_src_cmd(const struct l2cap_frame *frame) { - struct att_conn_data *data = att_get_conn_data(conn); - char filename[PATH_MAX]; - char local[18]; - char peer[18]; + uint8_t id; - ba2str((bdaddr_t *)conn->src, local); - ba2str((bdaddr_t *)conn->dst, peer); + if (!l2cap_frame_get_u8((void *)frame, &id)) { + print_text(COLOR_ERROR, "Source_ID: invalid size"); + return; + } - create_filename(filename, PATH_MAX, "/%s/attributes", local); - gatt_load_db(data->ldb, filename, &data->ldb_mtim); + print_field(" Source_ID: %u", id); +} - create_filename(filename, PATH_MAX, "/%s/cache/%s", local, peer); - gatt_load_db(data->rdb, filename, &data->rdb_mtim); +struct bcast_audio_scan_cp_cmd { + const char *desc; + void (*func)(const struct l2cap_frame *frame); +} bcast_audio_scan_cp_cmd_table[] = { + /* Opcode = 0x00 (Remote Scan Stopped) */ + BCAST_AUDIO_SCAN_CP_CMD(0x00, "Remote Scan Stopped", NULL), + /* Opcode = 0x01 (Remote Scan Started) */ + BCAST_AUDIO_SCAN_CP_CMD(0x01, "Remote Scan Started", NULL), + /* Opcode = 0x02 (Add Source) */ + BCAST_AUDIO_SCAN_CP_CMD(0x02, "Add Source", + bcast_audio_scan_cp_add_src_cmd), + /* Opcode = 0x03 (Modify Source) */ + BCAST_AUDIO_SCAN_CP_CMD(0x03, "Modify Source", + bcast_audio_scan_cp_mod_src_cmd), + /* Opcode = 0x04 (Set Broadcast_Code) */ + BCAST_AUDIO_SCAN_CP_CMD(0x04, "Set Broadcast_Code", + bcast_audio_scan_cp_set_bcode_cmd), + /* Opcode = 0x05 (Remove Source) */ + BCAST_AUDIO_SCAN_CP_CMD(0x05, "Remove Source", + bcast_audio_scan_cp_remove_src_cmd), +}; + +static struct bcast_audio_scan_cp_cmd *bcast_audio_scan_cp_get_cmd(uint8_t op) +{ + if (op > ARRAY_SIZE(bcast_audio_scan_cp_cmd_table)) + return NULL; + + return &bcast_audio_scan_cp_cmd_table[op]; } -static struct gatt_db_attribute *get_attribute(const struct l2cap_frame *frame, - uint16_t handle, bool rsp) +static void print_bcast_audio_scan_cp_cmd(const struct l2cap_frame *frame) { - struct packet_conn_data *conn; - struct att_conn_data *data; - struct gatt_db *db; + uint8_t op; + struct bcast_audio_scan_cp_cmd *cmd; - conn = packet_get_conn_data(frame->handle); - if (!conn) + if (!l2cap_frame_get_u8((void *)frame, &op)) { + print_text(COLOR_ERROR, "Opcode: invalid size"); + goto done; + } + + cmd = bcast_audio_scan_cp_get_cmd(op); + if (!cmd) { + print_field(" Opcode: Reserved (0x%2.2x)", op); + goto done; + } + + print_field(" Opcode: %s (0x%2.2x)", cmd->desc, op); + if (cmd->func) + cmd->func(frame); + +done: + if (frame->size) + print_hex_field(" Data", frame->data, frame->size); +} + +static void bcast_audio_scan_cp_write(const struct l2cap_frame *frame) +{ + print_bcast_audio_scan_cp_cmd(frame); +} + +#define GATT_HANDLER(_uuid, _read, _write, _notify) \ +{ \ + .uuid = { \ + .type = BT_UUID16, \ + .value.u16 = _uuid, \ + }, \ + .read = _read, \ + .write = _write, \ + .notify = _notify \ +} + +struct gatt_handler { + bt_uuid_t uuid; + void (*read)(const struct l2cap_frame *frame); + void (*write)(const struct l2cap_frame *frame); + void (*notify)(const struct l2cap_frame *frame); +} gatt_handlers[] = { + GATT_HANDLER(0x2800, pri_svc_read, NULL, NULL), + GATT_HANDLER(0x2801, sec_svc_read, NULL, NULL), + GATT_HANDLER(0x2803, chrc_read, NULL, NULL), + GATT_HANDLER(0x2902, ccc_read, ccc_write, NULL), + GATT_HANDLER(0x2bc4, ase_read, NULL, ase_notify), + GATT_HANDLER(0x2bc5, ase_read, NULL, ase_notify), + GATT_HANDLER(0x2bc6, NULL, ase_cp_write, ase_cp_notify), + GATT_HANDLER(0x2bc9, pac_read, NULL, pac_notify), + GATT_HANDLER(0x2bca, pac_loc_read, NULL, pac_loc_notify), + GATT_HANDLER(0x2bcb, pac_read, NULL, pac_notify), + GATT_HANDLER(0x2bcc, pac_loc_read, NULL, pac_loc_notify), + GATT_HANDLER(0x2bcd, pac_context_read, NULL, pac_context_notify), + GATT_HANDLER(0x2bce, pac_context_read, NULL, pac_context_notify), + GATT_HANDLER(0x2b7d, vol_state_read, NULL, vol_state_notify), + GATT_HANDLER(0x2b7e, NULL, vol_cp_write, NULL), + GATT_HANDLER(0x2b7f, vol_flag_read, NULL, vol_flag_notify), + + GATT_HANDLER(0x2b84, csip_sirk_read, NULL, csip_sirk_notify), + GATT_HANDLER(0x2b85, csip_size_read, NULL, csip_size_notify), + GATT_HANDLER(0x2b86, csip_lock_read, NULL, NULL), + GATT_HANDLER(0x2b87, csip_rank_read, NULL, NULL), + + GATT_HANDLER(0x2b93, mp_name_read, NULL, mp_name_notify), + GATT_HANDLER(0x2b96, NULL, NULL, track_changed_notify), + GATT_HANDLER(0x2b97, track_title_read, NULL, track_title_notify), + GATT_HANDLER(0x2b98, track_duration_read, NULL, track_duration_notify), + GATT_HANDLER(0x2b99, track_position_read, track_position_write, + track_position_notify), + GATT_HANDLER(0x2b9a, playback_speed_read, playback_speed_write, + playback_speed_notify), + GATT_HANDLER(0x2b9b, seeking_speed_read, NULL, seeking_speed_notify), + GATT_HANDLER(0x2ba1, playing_order_read, playing_order_write, + playing_order_notify), + GATT_HANDLER(0x2ba2, playing_orders_supported_read, NULL, NULL), + GATT_HANDLER(0x2ba3, media_state_read, NULL, media_state_notify), + GATT_HANDLER(0x2ba4, NULL, media_cp_write, media_cp_notify), + GATT_HANDLER(0x2ba5, media_cp_op_supported_read, NULL, + media_cp_op_supported_notify), + GATT_HANDLER(0x2bba, content_control_id_read, NULL, NULL), + + GATT_HANDLER(0x2bc7, NULL, bcast_audio_scan_cp_write, NULL), + GATT_HANDLER(0x2bc8, bcast_recv_state_read, NULL, + bcast_recv_state_notify), +}; + +static struct gatt_handler *get_handler_uuid(const bt_uuid_t *uuid) +{ + size_t i; + + if (!uuid) return NULL; - /* Try loading local and remote gatt_db if not loaded yet */ - load_gatt_db(conn); + for (i = 0; i < ARRAY_SIZE(gatt_handlers); i++) { + struct gatt_handler *handler = &gatt_handlers[i]; - data = conn->data; - if (!data) + if (!bt_uuid_cmp(&handler->uuid, uuid)) + return handler; + } + + return NULL; +} + +static struct gatt_handler *get_handler(struct gatt_db_attribute *attr) +{ + return get_handler_uuid(gatt_db_attribute_get_type(attr)); +} + +static void att_exchange_mtu_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_exchange_mtu_req *pdu = frame->data; + + print_field("Client RX MTU: %d", le16_to_cpu(pdu->mtu)); +} + +static void att_exchange_mtu_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_exchange_mtu_rsp *pdu = frame->data; + + print_field("Server RX MTU: %d", le16_to_cpu(pdu->mtu)); +} + +static void att_find_info_req(const struct l2cap_frame *frame) +{ + print_handle_range("Handle range", frame->data); +} + +static const char *att_format_str(uint8_t format) +{ + switch (format) { + case 0x01: + return "UUID-16"; + case 0x02: + return "UUID-128"; + default: + return "unknown"; + } +} + +static struct gatt_db_attribute *insert_desc(const struct l2cap_frame *frame, + uint16_t handle, + bt_uuid_t *uuid, bool rsp) +{ + struct gatt_db *db; + + db = get_db(frame, rsp); + if (!db) return NULL; - if (frame->in) { - if (rsp) - db = data->rdb; - else - db = data->ldb; - } else { - if (rsp) - db = data->ldb; - else - db = data->rdb; + return gatt_db_insert_descriptor(db, handle, uuid, 0, NULL, NULL, NULL); +} + +static void att_find_info_rsp_16(const struct l2cap_frame *frame) +{ + while (frame->size >= 4) { + uint16_t handle; + uint16_t u16; + bt_uuid_t uuid; + + if (!l2cap_frame_get_le16((void *)frame, &handle)) { + print_text(COLOR_ERROR, " Handle: invalid size"); + return; + } + + if (!l2cap_frame_get_le16((void *)frame, &u16)) { + print_text(COLOR_ERROR, " UUID: invalid size"); + return; + } + + print_field("Handle: 0x%4.4x", handle); + print_uuid("UUID", &u16, 2); + + bt_uuid16_create(&uuid, u16); + + insert_desc(frame, handle, &uuid, true); } +} - return gatt_db_get_attribute(db, handle); +static void att_find_info_rsp_128(const struct l2cap_frame *frame) +{ + while (frame->size >= 18) { + uint16_t handle; + bt_uuid_t uuid; + + if (!l2cap_frame_get_le16((void *)frame, &handle)) { + print_text(COLOR_ERROR, " Handle: invalid size"); + return; + } + + if (frame->size < 16) { + print_text(COLOR_ERROR, " UUID: invalid size"); + return; + } + + print_field("Handle: 0x%4.4x", handle); + print_uuid("UUID", frame->data, 16); + + bt_uuid_from_data(&uuid, frame->data, 16); + + if (!l2cap_frame_pull((void *)frame, frame, 16)) + return; + + insert_desc(frame, handle, &uuid, true); + } } -static void print_attribute(struct gatt_db_attribute *attr) +static void att_find_info_rsp(const struct l2cap_frame *frame) { - uint16_t handle = gatt_db_attribute_get_handle(attr); - const bt_uuid_t *uuid; - char label[21]; + uint8_t format; - uuid = gatt_db_attribute_get_type(attr); - if (!uuid) + if (!l2cap_frame_get_u8((void *)frame, &format)) { + print_text(COLOR_ERROR, " Format: invalid size"); goto done; + } - switch (uuid->type) { - case BT_UUID16: - sprintf(label, "Handle: 0x%4.4x Type", handle); - print_field("%s: %s (0x%4.4x)", label, - bt_uuid16_to_str(uuid->value.u16), - uuid->value.u16); - return; - case BT_UUID128: - sprintf(label, "Handle: 0x%4.4x Type", handle); - print_uuid(label, &uuid->value.u128, 16); - return; - case BT_UUID_UNSPEC: - case BT_UUID32: + print_field("Format: %s (0x%2.2x)", att_format_str(format), format); + + switch (format) { + case 0x01: + att_find_info_rsp_16(frame); + break; + case 0x02: + att_find_info_rsp_128(frame); break; } done: - print_field("Handle: 0x%4.4x", handle); + if (frame->size) + packet_hexdump(frame->data, frame->size); } -static void print_handle(const struct l2cap_frame *frame, uint16_t handle, - bool rsp) +static void att_find_by_type_val_req(const struct l2cap_frame *frame) { - struct gatt_db_attribute *attr; + uint16_t type; - attr = get_attribute(frame, handle, rsp); - if (!attr) { - print_field("Handle: 0x%4.4x", handle); - return; + print_handle_range("Handle range", frame->data); + + type = get_le16(frame->data + 4); + print_attribute_info(type, frame->data + 6, frame->size - 6); +} + +static void att_find_by_type_val_rsp(const struct l2cap_frame *frame) +{ + const uint8_t *ptr = frame->data; + uint16_t len = frame->size; + + while (len >= 4) { + print_handle_range("Handle range", ptr); + ptr += 4; + len -= 4; } - print_attribute(attr); + packet_hexdump(ptr, len); } -static void att_read_req(const struct l2cap_frame *frame) +static struct gatt_db_attribute *get_attribute(const struct l2cap_frame *frame, + uint16_t handle, bool rsp) +{ + struct gatt_db *db; + + db = get_db(frame, rsp); + if (!db) + return NULL; + + return gatt_db_get_attribute(db, handle); +} + +static void queue_read(const struct l2cap_frame *frame, bt_uuid_t *uuid, + uint16_t handle) { - const struct bt_l2cap_att_read_req *pdu = frame->data; - uint16_t handle; struct packet_conn_data *conn; struct att_conn_data *data; struct att_read *read; - struct gatt_db_attribute *attr; + struct gatt_db_attribute *attr = NULL; struct gatt_handler *handler; - l2cap_frame_pull((void *)frame, frame, sizeof(*pdu)); - - handle = le16_to_cpu(pdu->handle); - print_handle(frame, handle, false); - - attr = get_attribute(frame, handle, false); - if (!attr) - return; + if (handle) { + attr = get_attribute(frame, handle, false); + if (!attr) + return; + } - handler = get_handler(attr); + handler = attr ? get_handler(attr) : get_handler_uuid(uuid); if (!handler || !handler->read) return; conn = packet_get_conn_data(frame->handle); - data = conn->data; + data = att_get_conn_data(conn); + if (!data) + return; if (!data->reads) data->reads = queue_new(); @@ -2744,25 +3449,69 @@ queue_push_tail(data->reads, read); } -static void att_read_rsp(const struct l2cap_frame *frame) +static void att_read_type_req(const struct l2cap_frame *frame) { - struct packet_conn_data *conn; - struct att_conn_data *data; - struct att_read *read; + bt_uuid_t uuid; - print_hex_field("Value", frame->data, frame->size); + print_handle_range("Handle range", frame->data); + print_uuid("Attribute type", frame->data + 4, frame->size - 4); - conn = packet_get_conn_data(frame->handle); - if (!conn) + if (bt_uuid_from_data(&uuid, frame->data + 4, frame->size - 4)) return; - data = conn->data; + queue_read(frame, &uuid, 0x0000); +} + +static void att_read_type_rsp(const struct l2cap_frame *frame) +{ + uint8_t len; + + if (!l2cap_frame_get_u8((void *)frame, &len)) { + print_text(COLOR_ERROR, "invalid size"); + return; + } + + print_field("Attribute data length: %d", len); + print_data_list("Attribute data list", len, frame); +} + +static void print_handle(const struct l2cap_frame *frame, uint16_t handle, + bool rsp) +{ + struct gatt_db_attribute *attr; + + attr = get_attribute(frame, handle, rsp); + if (!attr) { + print_field("Handle: 0x%4.4x", handle); + return; + } + + print_attribute(attr); +} + +static void att_read_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_read_req *pdu = frame->data; + uint16_t handle; + + l2cap_frame_pull((void *)frame, frame, sizeof(*pdu)); - read = queue_remove_if(data->reads, match_read_frame, (void *)frame); + handle = le16_to_cpu(pdu->handle); + print_handle(frame, handle, false); + + queue_read(frame, NULL, handle); +} + +static void att_read_rsp(const struct l2cap_frame *frame) +{ + struct att_read *read; + + read = att_get_read(frame); if (!read) return; print_attribute(read->attr); + print_hex_field("Value", frame->data, frame->size); read->func(frame); @@ -2792,40 +3541,60 @@ static void att_read_group_type_req(const struct l2cap_frame *frame) { + bt_uuid_t uuid; + print_handle_range("Handle range", frame->data); print_uuid("Attribute group type", frame->data + 4, frame->size - 4); + + if (bt_uuid_from_data(&uuid, frame->data + 4, frame->size - 4)) + return; + + queue_read(frame, &uuid, 0x0000); } static void print_group_list(const char *label, uint8_t length, - const void *data, uint16_t size) + const struct l2cap_frame *frame) { + struct att_read *read; uint8_t count; if (length == 0) return; - count = size / length; + read = att_get_read(frame); + + count = frame->size / length; print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies"); - while (size >= length) { - print_handle_range("Handle range", data); - print_uuid("UUID", data + 4, length - 4); + while (frame->size >= length) { + print_handle_range("Handle range", frame->data); + print_uuid("UUID", frame->data + 4, length - 4); - data += length; - size -= length; + if (read) { + struct l2cap_frame f; + + l2cap_frame_clone_size(&f, frame, length); + + read->func(&f); + } + + if (!l2cap_frame_pull((void *)frame, frame, length)) + break; } - packet_hexdump(data, size); + packet_hexdump(frame->data, frame->size); + free(read); } static void att_read_group_type_rsp(const struct l2cap_frame *frame) { const struct bt_l2cap_att_read_group_type_rsp *pdu = frame->data; + l2cap_frame_pull((void *)frame, frame, sizeof(*pdu)); + print_field("Attribute data length: %d", pdu->length); - print_group_list("Attribute group list", pdu->length, - frame->data + 1, frame->size - 1); + print_group_list("Attribute group list", pdu->length, frame); } static void print_write(const struct l2cap_frame *frame, uint16_t handle, @@ -2835,19 +3604,20 @@ struct gatt_handler *handler; print_handle(frame, handle, false); - print_hex_field(" Data", frame->data, frame->size); if (len > frame->size) { print_text(COLOR_ERROR, "invalid size"); return; } + print_hex_field(" Data", frame->data, len); + attr = get_attribute(frame, handle, false); if (!attr) return; handler = get_handler(attr); - if (!handler) + if (!handler || !handler->write) return; handler->write(frame); @@ -3006,7 +3776,6 @@ } print_write(frame, handle, frame->size - 12); - print_hex_field(" Data", frame->data, frame->size - 12); print_hex_field(" Signature", frame->data + frame->size - 12, 12); } diff -Nru bluez-5.66/monitor/broadcom.c bluez-5.68/monitor/broadcom.c --- bluez-5.66/monitor/broadcom.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/monitor/broadcom.c 2023-06-30 08:10:20.000000000 +0000 @@ -706,7 +706,8 @@ } } -static void lm_diag_evt(uint16_t index, const void *data, uint8_t size) +static void lm_diag_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { broadcom_lm_diag(data, 63); } diff -Nru bluez-5.66/monitor/bt.h bluez-5.68/monitor/bt.h --- bluez-5.66/monitor/bt.h 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/monitor/bt.h 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -1787,6 +1788,12 @@ #define BT_HCI_LOCAL_CODEC_LE_CIS BIT(2) #define BT_HCI_LOCAL_CODEC_LE_BIS BIT(3) +struct bt_hci_vnd_codec_v2 { + uint16_t cid; + uint16_t vid; + uint8_t transport; +} __attribute__ ((packed)); + struct bt_hci_vnd_codec { uint8_t id; uint16_t cid; @@ -3706,14 +3713,17 @@ #define BT_HCI_ERR_AUTH_FAILURE 0x05 #define BT_HCI_ERR_PIN_OR_KEY_MISSING 0x06 #define BT_HCI_ERR_MEM_CAPACITY_EXCEEDED 0x07 +#define BT_HCI_ERR_CONN_ALREADY_EXISTS 0x0b #define BT_HCI_ERR_COMMAND_DISALLOWED 0x0c #define BT_HCI_ERR_UNSUPPORTED_FEATURE 0x11 #define BT_HCI_ERR_INVALID_PARAMETERS 0x12 +#define BT_HCI_ERR_LOCAL_HOST_TERM 0x16 #define BT_HCI_ERR_UNSPECIFIED_ERROR 0x1f #define BT_HCI_ERR_ADV_TIMEOUT 0x3c #define BT_HCI_ERR_CONN_FAILED_TO_ESTABLISH 0x3e #define BT_HCI_ERR_UNKNOWN_ADVERTISING_ID 0x42 #define BT_HCI_ERR_CANCELLED 0x44 +#define BT_HCI_ERR_ENC_MODE_NOT_ACCEPTABLE 0x25 struct bt_l2cap_hdr { uint16_t len; diff -Nru bluez-5.66/monitor/intel.c bluez-5.68/monitor/intel.c --- bluez-5.66/monitor/intel.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/monitor/intel.c 2023-06-30 08:10:20.000000000 +0000 @@ -793,11 +793,13 @@ return NULL; } -static void startup_evt(uint16_t index, const void *data, uint8_t size) +static void startup_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { } -static void fatal_exception_evt(uint16_t index, const void *data, uint8_t size) +static void fatal_exception_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint16_t line = get_le16(data); uint8_t module = get_u8(data + 2); @@ -808,7 +810,8 @@ print_field("Reason: 0x%2.2x", reason); } -static void bootup_evt(uint16_t index, const void *data, uint8_t size) +static void bootup_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t zero = get_u8(data); uint8_t num_packets = get_u8(data + 1); @@ -911,7 +914,8 @@ print_field("DDC status: %s (0x%2.2x)", str, ddc_status); } -static void default_bd_data_evt(uint16_t index, const void *data, uint8_t size) +static void default_bd_data_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t mem_status = get_u8(data); const char *str; @@ -928,8 +932,8 @@ print_field("Memory status: %s (0x%2.2x)", str, mem_status); } -static void secure_send_commands_result_evt(uint16_t index, const void *data, - uint8_t size) +static void secure_send_commands_result_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t result = get_u8(data); uint16_t opcode = get_le16(data + 1); @@ -973,7 +977,8 @@ print_status(status); } -static void debug_exception_evt(uint16_t index, const void *data, uint8_t size) +static void debug_exception_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint16_t line = get_le16(data); uint8_t module = get_u8(data + 2); @@ -984,8 +989,8 @@ print_field("Reason: 0x%2.2x", reason); } -static void le_link_established_evt(uint16_t index, const void *data, - uint8_t size) +static void le_link_established_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint16_t handle = get_le16(data); uint32_t access_addr = get_le32(data + 10); @@ -999,7 +1004,8 @@ packet_hexdump(data + 14, size - 14); } -static void scan_status_evt(uint16_t index, const void *data, uint8_t size) +static void scan_status_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t enable = get_u8(data); @@ -1014,15 +1020,16 @@ } -static void act_deact_traces_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void act_deact_traces_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t status = get_u8(data); print_status(status); } -static void lmp_pdu_trace_evt(uint16_t index, const void *data, uint8_t size) +static void lmp_pdu_trace_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t type, len, id; uint16_t handle, count; @@ -1116,16 +1123,16 @@ } } -static void write_bd_data_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void write_bd_data_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t status = get_u8(data); print_status(status); } -static void sco_rejected_via_lmp_evt(uint16_t index, const void *data, - uint8_t size) +static void sco_rejected_via_lmp_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t reason = get_u8(data + 6); @@ -1133,8 +1140,8 @@ packet_print_error("Reason", reason); } -static void ptt_switch_notification_evt(uint16_t index, const void *data, - uint8_t size) +static void ptt_switch_notification_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint16_t handle = get_le16(data); uint8_t table = get_u8(data + 2); @@ -1157,7 +1164,8 @@ print_field("Packet type table: %s (0x%2.2x)", str, table); } -static void system_exception_evt(uint16_t index, const void *data, uint8_t size) +static void system_exception_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t type = get_u8(data); const char *str; @@ -1257,11 +1265,9 @@ str = "Disconnection Event"; break; case 0x05: - str = "Audio Link Quality Report Type"; - break; - case 0x06: - str = "Stats for BR/EDR Link Type"; + str = "Performance Stats"; break; + default: print_text(COLOR_UNKNOWN_EXT_EVENT, "Unknown extended telemetry event type (0x%2.2x)", @@ -1287,6 +1293,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Rx HEC errors (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1294,6 +1304,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Rx CRC errors (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1301,6 +1315,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Packets from host (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1309,6 +1327,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Tx packets (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1317,6 +1339,10 @@ char *subevent_str; uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + switch (tlv->subevent_id) { case 0x4f: subevent_str = "Tx packets 0 retries"; @@ -1346,6 +1372,10 @@ char *packet_type_str; uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + switch (tlv->subevent_id) { case 0x54: packet_type_str = "DH1"; @@ -1387,6 +1417,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Rx packets (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1395,7 +1429,11 @@ { uint32_t num = get_le32(tlv->value); - print_field("ACL link throughput (KBps) (0x%2.2x): %d", + /* Skip if 0 */ + if (!num) + return; + + print_field("ACL link throughput (bps) (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1403,7 +1441,11 @@ { uint32_t num = get_le32(tlv->value); - print_field("ACL max packet latency (ms) (0x%2.2x): %d", + /* Skip if 0 */ + if (!num) + return; + + print_field("ACL max packet latency (us) (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1411,10 +1453,55 @@ { uint32_t num = get_le32(tlv->value); - print_field("ACL avg packet latency (ms) (0x%2.2x): %d", + /* Skip if 0 */ + if (!num) + return; + + print_field("ACL avg packet latency (us) (0x%2.2x): %d", tlv->subevent_id, num); } +static void ext_acl_evt_rssi_moving_avg(const struct intel_tlv *tlv) +{ + uint32_t num = get_le16(tlv->value); + + /* Skip if 0 */ + if (!num) + return; + + print_field("ACL RX RSSI moving avg (0x%2.2x): %d", + tlv->subevent_id, num); +} + +static void ext_acl_evt_bad_cnt(const char *prefix, const struct intel_tlv *tlv) +{ + uint32_t c_1m = get_le32(tlv->value); + uint32_t c_2m = get_le32(tlv->value + 4); + uint32_t c_3m = get_le32(tlv->value + 8); + + /* Skip if all 0 */ + if (!c_1m && !c_2m && !c_3m) + return; + + print_field("%s (0x%2.2x): 1M %d 2M %d 3M %d", + prefix, tlv->subevent_id, c_1m, c_2m, c_3m); +} + +static void ext_acl_evt_snr_bad_cnt(const struct intel_tlv *tlv) +{ + ext_acl_evt_bad_cnt("ACL RX SNR Bad Margin Counter", tlv); +} + +static void ext_acl_evt_rx_rssi_bad_cnt(const struct intel_tlv *tlv) +{ + ext_acl_evt_bad_cnt("ACL RX RSSI Bad Counter", tlv); +} + +static void ext_acl_evt_tx_rssi_bad_cnt(const struct intel_tlv *tlv) +{ + ext_acl_evt_bad_cnt("ACL TX RSSI Bad Counter", tlv); +} + static void ext_sco_evt_conn_handle(const struct intel_tlv *tlv) { uint16_t conn_handle = get_le16(tlv->value); @@ -1427,6 +1514,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Packets from host (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1434,6 +1525,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Tx packets (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1441,6 +1536,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Rx payload lost (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1449,6 +1548,10 @@ uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Tx payload lost (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1508,6 +1611,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Late samples inserted based on CDC (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1516,6 +1623,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Samples dropped (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1523,6 +1634,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("Mute samples sent at initial connection (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1531,6 +1646,10 @@ { uint32_t num = get_le32(tlv->value); + /* Skip if 0 */ + if (!num) + return; + print_field("PLC injection data (0x%2.2x): %d", tlv->subevent_id, num); } @@ -1565,6 +1684,10 @@ { 0x5e, 4, ext_acl_evt_link_throughput }, { 0x5f, 4, ext_acl_evt_max_packet_latency }, { 0x60, 4, ext_acl_evt_avg_packet_latency }, + { 0x61, 2, ext_acl_evt_rssi_moving_avg }, + { 0x62, 12, ext_acl_evt_snr_bad_cnt }, + { 0x63, 12, ext_acl_evt_rx_rssi_bad_cnt }, + { 0x64, 12, ext_acl_evt_tx_rssi_bad_cnt }, /* SCO/eSCO audio link quality subevents */ { 0x6a, 2, ext_sco_evt_conn_handle }, @@ -1606,7 +1729,8 @@ print_text(COLOR_UNKNOWN_EXT_EVENT, "Unknown extended subevent 0x%2.2x", tlv->subevent_id); - return NULL; + packet_hexdump(tlv->value, tlv->length); + return next_tlv; } if (tlv->length != subevent->length) { @@ -1625,7 +1749,8 @@ return next_tlv; } -static void intel_vendor_ext_evt(uint16_t index, const void *data, uint8_t size) +static void intel_vendor_ext_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { /* The data pointer points to a number of tlv.*/ const struct intel_tlv *tlv = data; diff -Nru bluez-5.66/monitor/keys.c bluez-5.68/monitor/keys.c --- bluez-5.66/monitor/keys.c 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/monitor/keys.c 2023-06-30 08:10:20.000000000 +0000 @@ -112,3 +112,29 @@ return false; } + +static bool match_key(const void *data, const void *match_data) +{ + const struct irk_data *irk = data; + const uint8_t *key = match_data; + + return !memcmp(irk->key, key, 16); +} + +bool keys_add_identity(const uint8_t addr[6], uint8_t addr_type, + const uint8_t key[16]) +{ + struct irk_data *irk; + + irk = queue_find(irk_list, match_key, key); + if (!irk) { + irk = new0(struct irk_data, 1); + memcpy(irk->key, key, 16); + queue_push_tail(irk_list, irk); + } + + memcpy(irk->addr, addr, 6); + irk->addr_type = addr_type; + + return true; +} diff -Nru bluez-5.66/monitor/keys.h bluez-5.68/monitor/keys.h --- bluez-5.66/monitor/keys.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/monitor/keys.h 2023-06-30 08:10:20.000000000 +0000 @@ -20,3 +20,5 @@ bool keys_resolve_identity(const uint8_t addr[6], uint8_t ident[6], uint8_t *ident_type); +bool keys_add_identity(const uint8_t addr[6], uint8_t addr_type, + const uint8_t key[16]); diff -Nru bluez-5.66/monitor/l2cap.c bluez-5.68/monitor/l2cap.c --- bluez-5.66/monitor/l2cap.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/monitor/l2cap.c 2023-06-30 08:10:20.000000000 +0000 @@ -23,6 +23,7 @@ #include "lib/uuid.h" #include "src/shared/util.h" +#include "src/shared/queue.h" #include "bt.h" #include "packet.h" #include "display.h" @@ -99,6 +100,9 @@ uint8_t ext_ctrl; uint8_t seq_num; uint16_t sdu; + struct timeval tx_min; + struct timeval tx_max; + struct timeval tx_med; }; static struct chan_data chan_list[MAX_CHAN]; @@ -1538,6 +1542,23 @@ { }, }; +static void l2cap_queue_frame(struct l2cap_frame *frame) +{ + struct packet_conn_data *conn; + struct l2cap_frame *tx; + + conn = packet_get_conn_data(frame->handle); + if (!conn) + return; + + if (!conn->chan_q) + conn->chan_q = queue_new(); + + tx = new0(struct l2cap_frame, 1); + memcpy(tx, frame, sizeof(*frame)); + queue_push_tail(conn->chan_q, tx); +} + void l2cap_frame_init(struct l2cap_frame *frame, uint16_t index, bool in, uint16_t handle, uint8_t ident, uint16_t cid, uint16_t psm, @@ -1554,6 +1575,9 @@ frame->psm = psm ? psm : get_psm(frame); frame->mode = get_mode(frame); frame->seq_num = psm ? 1 : get_seq_num(frame); + + if (!in) + l2cap_queue_frame(frame); } static void bredr_sig_packet(uint16_t index, bool in, uint16_t handle, @@ -2773,3 +2797,55 @@ return; } } + +#define TV_MSEC(_tv) (long long)((_tv)->tv_sec * 1000 + (_tv)->tv_usec / 1000) + +void l2cap_dequeue_frame(struct timeval *delta, struct packet_conn_data *conn) +{ + struct l2cap_frame *frame; + struct chan_data *chan; + + frame = queue_pop_head(conn->chan_q); + if (!frame) + return; + + chan = get_chan(frame); + if (!chan) + return; + + if ((!timerisset(&chan->tx_min) || timercmp(delta, &chan->tx_min, <)) + && delta->tv_sec >= 0 && delta->tv_usec >= 0) + chan->tx_min = *delta; + + if (!timerisset(&chan->tx_max) || timercmp(delta, &chan->tx_max, >)) + chan->tx_max = *delta; + + if (timerisset(&chan->tx_med)) { + struct timeval tmp; + + timeradd(&chan->tx_med, delta, &tmp); + + tmp.tv_sec /= 2; + tmp.tv_usec /= 2; + if (tmp.tv_sec % 2) { + tmp.tv_usec += 500000; + if (tmp.tv_usec >= 1000000) { + tmp.tv_sec++; + tmp.tv_usec -= 1000000; + } + } + + chan->tx_med = tmp; + } else + chan->tx_med = *delta; + + print_field("Channel: %d [PSM %d mode %s (0x%02x)] {chan %d}", + frame->cid, frame->psm, mode2str(frame->mode), + frame->mode, frame->chan); + + print_field("Channel Latency: %lld msec (%lld-%lld msec ~%lld msec)", + TV_MSEC(delta), TV_MSEC(&chan->tx_min), + TV_MSEC(&chan->tx_max), TV_MSEC(&chan->tx_med)); + + free(frame); +} diff -Nru bluez-5.66/monitor/l2cap.h bluez-5.68/monitor/l2cap.h --- bluez-5.66/monitor/l2cap.h 2022-11-10 20:24:02.000000000 +0000 +++ bluez-5.68/monitor/l2cap.h 2023-06-30 08:10:20.000000000 +0000 @@ -355,3 +355,5 @@ const void *data, uint16_t size); void rfcomm_packet(const struct l2cap_frame *frame); + +void l2cap_dequeue_frame(struct timeval *delta, struct packet_conn_data *conn); diff -Nru bluez-5.66/monitor/main.c bluez-5.68/monitor/main.c --- bluez-5.66/monitor/main.c 2021-04-02 08:59:48.000000000 +0000 +++ bluez-5.68/monitor/main.c 2023-06-30 08:10:20.000000000 +0000 @@ -62,6 +62,7 @@ "\t-T, --date Show time and date information\n" "\t-S, --sco Dump SCO traffic\n" "\t-A, --a2dp Dump A2DP stream traffic\n" + "\t-I, --iso Dump ISO traffic\n" "\t-E, --ellisys [ip] Send Ellisys HCI Injection\n" "\t-P, --no-pager Disable pager usage\n" "\t-J --jlink ,[],[],[]\n" @@ -89,6 +90,7 @@ { "date", no_argument, NULL, 'T' }, { "sco", no_argument, NULL, 'S' }, { "a2dp", no_argument, NULL, 'A' }, + { "iso", no_argument, NULL, 'I' }, { "ellisys", required_argument, NULL, 'E' }, { "no-pager", no_argument, NULL, 'P' }, { "jlink", required_argument, NULL, 'J' }, @@ -126,8 +128,8 @@ struct sockaddr_un addr; opt = getopt_long(argc, argv, - "r:w:a:s:p:i:d:B:V:MNtTSAE:PJ:R:C:c:vh", - main_options, NULL); + "r:w:a:s:p:i:d:B:V:MNtTSAIE:PJ:R:C:c:vh", + main_options, NULL); if (opt < 0) break; @@ -197,6 +199,9 @@ case 'A': filter_mask |= PACKET_FILTER_SHOW_A2DP_STREAM; break; + case 'I': + filter_mask |= PACKET_FILTER_SHOW_ISO_DATA; + break; case 'E': ellisys_server = optarg; ellisys_port = 24352; diff -Nru bluez-5.66/monitor/msft.c bluez-5.68/monitor/msft.c --- bluez-5.66/monitor/msft.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/monitor/msft.c 2023-06-30 08:10:20.000000000 +0000 @@ -299,7 +299,8 @@ return &vendor_ocf_entry; } -static void msft_evt(uint16_t index, const void *data, uint8_t size) +static void msft_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { packet_hexdump(data, size); } diff -Nru bluez-5.66/monitor/packet.c bluez-5.68/monitor/packet.c --- bluez-5.66/monitor/packet.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/monitor/packet.c 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2011-2014 Intel Corporation * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -33,18 +34,19 @@ #include "src/shared/util.h" #include "src/shared/btsnoop.h" +#include "src/shared/queue.h" #include "display.h" #include "bt.h" #include "ll.h" #include "hwdb.h" #include "keys.h" +#include "packet.h" #include "l2cap.h" #include "control.h" #include "vendor.h" #include "msft.h" #include "intel.h" #include "broadcom.h" -#include "packet.h" #define COLOR_CHANNEL_LABEL COLOR_WHITE #define COLOR_FRAME_LABEL COLOR_WHITE @@ -65,6 +67,7 @@ #define COLOR_HCI_EVENT_UNKNOWN COLOR_WHITE_BG #define COLOR_HCI_ACLDATA COLOR_CYAN #define COLOR_HCI_SCODATA COLOR_YELLOW +#define COLOR_HCI_ISODATA COLOR_YELLOW #define COLOR_UNKNOWN_ERROR COLOR_WHITE_BG #define COLOR_UNKNOWN_FEATURE_BIT COLOR_WHITE_BG @@ -200,11 +203,15 @@ int i; for (i = 0; i < MAX_CONN; i++) { - if (conn_list[i].handle == handle) { - if (conn_list[i].destroy) - conn_list[i].destroy(conn_list[i].data); + struct packet_conn_data *conn = &conn_list[i]; - memset(&conn_list[i], 0, sizeof(conn_list[i])); + if (conn->handle == handle) { + if (conn->destroy) + conn->destroy(conn->data); + + queue_destroy(conn->tx_q, free); + queue_destroy(conn->chan_q, free); + memset(conn, 0, sizeof(*conn)); break; } } @@ -308,13 +315,33 @@ memcpy(index_list[index_current].msft_evt_prefix, prefix, len); } +static void cred_pid(struct ucred *cred, char *str, size_t len) +{ + char *path = alloca(24); + char line[128]; + FILE *fp; + + snprintf(path, 23, "/proc/%u/comm", cred->pid); + + fp = fopen(path, "re"); + if (fp) { + if (fgets(line, sizeof(line), fp)) { + line[strcspn(line, "\r\n")] = '\0'; + snprintf(str, len, "%s[%u]", line, cred->pid); + } else + snprintf(str, len, "[%u]", cred->pid); + fclose(fp); + } else + snprintf(str, len, "[%u]", cred->pid); +} + static void print_packet(struct timeval *tv, struct ucred *cred, char ident, uint16_t index, const char *channel, const char *color, const char *label, const char *text, const char *extra) { int col = num_columns(); - char line[256], ts_str[96]; + char line[256], ts_str[96], pid_str[140]; int n, ts_len = 0, ts_pos = 0, len = 0, pos = 0; static size_t last_frame; @@ -411,7 +438,13 @@ pos += n; } - n = sprintf(line + pos, "%c %s", ident, label ? label : ""); + if (cred && cred->pid) { + cred_pid(cred, pid_str, sizeof(pid_str)); + n = sprintf(line + pos, "%s: %c %s", pid_str, ident, + label ? label : ""); + } else + n = sprintf(line + pos, "%c %s", ident, label ? label : ""); + if (n > 0) { pos += n; len += n; @@ -3123,6 +3156,7 @@ #define BT_EIR_MESH_PROV 0x29 #define BT_EIR_MESH_DATA 0x2a #define BT_EIR_MESH_BEACON 0x2b +#define BT_EIR_CSIP_RSI 0x2e #define BT_EIR_3D_INFO_DATA 0x3d #define BT_EIR_MANUFACTURER_DATA 0xff @@ -4017,6 +4051,14 @@ print_mesh_beacon(data, data_len); break; + case BT_EIR_CSIP_RSI: + if (data_len < 6) + break; + print_addr("Resolvable Set Identifier", data, 0xff); + print_field(" Hash: 0x%6x", get_le24(data)); + print_field(" Random: 0x%6x", get_le24(data + 3)); + break; + case BT_EIR_MANUFACTURER_DATA: if (data_len < 2) break; @@ -6384,6 +6426,20 @@ print_hex_field("", data, size); } +static void print_vnd_codecs_v2(const void *data, int i) +{ + const struct bt_hci_vnd_codec_v2 *codec = data; + uint8_t mask; + + packet_print_company(" Company ID", le16_to_cpu(codec->cid)); + print_field(" Vendor Codec ID: 0x%4.4x", le16_to_cpu(codec->vid)); + print_field(" Logical Transport Type: 0x%02x", codec->transport); + mask = print_bitfield(4, codec->transport, codec_transport_table); + if (mask) + print_text(COLOR_UNKNOWN_SERVICE_CLASS, + " Unknown transport (0x%2.2x)", mask); +} + static void read_local_codecs_rsp_v2(uint16_t index, const void *data, uint8_t size) { @@ -6417,7 +6473,18 @@ num_vnd_codecs = rsp->codec[rsp->num_codecs].id; + size -= 1; + print_field("Number of vendor codecs: %d", num_vnd_codecs); + + if (size < num_vnd_codecs * sizeof(*rsp->codec)) { + print_field("Invalid number of vendor codecs."); + return; + } + + print_list(&rsp->codec[rsp->num_codecs] + 1, size, num_vnd_codecs, + sizeof(struct bt_hci_vnd_codec_v2), + print_vnd_codecs_v2); } static void print_path_direction(const char *prefix, uint8_t dir) @@ -6520,11 +6587,9 @@ static void print_usec_interval(const char *prefix, const uint8_t interval[3]) { - uint32_t u24 = 0; + uint32_t value = get_le24(interval); - memcpy(&u24, interval, 3); - print_field("%s: %u us (0x%6.6x)", prefix, le32_to_cpu(u24), - le32_to_cpu(u24)); + print_field("%s: %u us (0x%6.6x)", prefix, value, value); } static void read_local_ctrl_delay_rsp(uint16_t index, const void *data, @@ -8774,7 +8839,7 @@ print_field("BIG Handle: 0x%2.2x", cmd->handle); print_field("BIG Sync Handle: 0x%4.4x", le16_to_cpu(cmd->sync_handle)); print_field("Encryption: %s (0x%2.2x)", - cmd->encryption ? "Unencrypted" : "Encrypted", + cmd->encryption ? "Encrypted" : "Unencrypted", cmd->encryption); print_hex_field("Broadcast Code", cmd->bcode, 16); print_field("Maximum Number Subevents: 0x%2.2x", cmd->mse); @@ -9933,14 +9998,16 @@ return NULL; } -static void inquiry_complete_evt(uint16_t index, const void *data, uint8_t size) +static void inquiry_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_inquiry_complete *evt = data; print_status(evt->status); } -static void inquiry_result_evt(uint16_t index, const void *data, uint8_t size) +static void inquiry_result_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_inquiry_result *evt = data; @@ -9956,7 +10023,8 @@ packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); } -static void conn_complete_evt(uint16_t index, const void *data, uint8_t size) +static void conn_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_conn_complete *evt = data; @@ -9971,7 +10039,8 @@ (void *)evt->bdaddr, BDADDR_BREDR); } -static void conn_request_evt(uint16_t index, const void *data, uint8_t size) +static void conn_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_conn_request *evt = data; @@ -9980,8 +10049,8 @@ print_link_type(evt->link_type); } -static void disconnect_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void disconnect_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_disconnect_complete *evt = data; @@ -9993,7 +10062,8 @@ release_handle(le16_to_cpu(evt->handle)); } -static void auth_complete_evt(uint16_t index, const void *data, uint8_t size) +static void auth_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_auth_complete *evt = data; @@ -10001,8 +10071,8 @@ print_handle(evt->handle); } -static void remote_name_request_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void remote_name_request_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_remote_name_request_complete *evt = data; @@ -10011,7 +10081,8 @@ print_name(evt->name); } -static void encrypt_change_evt(uint16_t index, const void *data, uint8_t size) +static void encrypt_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_encrypt_change *evt = data; @@ -10020,8 +10091,9 @@ print_encr_mode_change(evt->encr_mode, evt->handle); } -static void change_conn_link_key_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void change_conn_link_key_complete_evt(struct timeval *tv, + uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_change_conn_link_key_complete *evt = data; @@ -10029,8 +10101,8 @@ print_handle(evt->handle); } -static void link_key_type_changed_evt(uint16_t index, const void *data, - uint8_t size) +static void link_key_type_changed_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_link_key_type_changed *evt = data; @@ -10039,8 +10111,8 @@ print_key_flag(evt->key_flag); } -static void remote_features_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void remote_features_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_remote_features_complete *evt = data; @@ -10049,8 +10121,8 @@ print_features(0, evt->features, 0x00); } -static void remote_version_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void remote_version_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_remote_version_complete *evt = data; @@ -10066,8 +10138,8 @@ } } -static void qos_setup_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void qos_setup_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_qos_setup_complete *evt = data; @@ -10083,7 +10155,8 @@ print_field("Delay variation: %d", le32_to_cpu(evt->delay_variation)); } -static void cmd_complete_evt(uint16_t index, const void *data, uint8_t size) +static void cmd_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_cmd_complete *evt = data; uint16_t opcode = le16_to_cpu(evt->opcode); @@ -10179,7 +10252,8 @@ opcode_data->rsp_func(index, data + 3, size - 3); } -static void cmd_status_evt(uint16_t index, const void *data, uint8_t size) +static void cmd_status_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_cmd_status *evt = data; uint16_t opcode = le16_to_cpu(evt->opcode); @@ -10231,21 +10305,24 @@ print_status(evt->status); } -static void hardware_error_evt(uint16_t index, const void *data, uint8_t size) +static void hardware_error_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_hardware_error *evt = data; print_field("Code: 0x%2.2x", evt->code); } -static void flush_occurred_evt(uint16_t index, const void *data, uint8_t size) +static void flush_occurred_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_flush_occurred *evt = data; print_handle(evt->handle); } -static void role_change_evt(uint16_t index, const void *data, uint8_t size) +static void role_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_role_change *evt = data; @@ -10254,20 +10331,93 @@ print_role(evt->role); } -static void num_completed_packets_evt(uint16_t index, const void *data, - uint8_t size) +#define TV_MSEC(_tv) (long long)(_tv.tv_sec * 1000 + _tv.tv_usec / 1000) + +static void packet_dequeue_tx(struct timeval *tv, uint16_t handle) +{ + struct packet_conn_data *conn; + struct timeval *tx; + struct timeval delta; + + conn = packet_get_conn_data(handle); + if (!conn) + return; + + tx = queue_pop_head(conn->tx_q); + if (!tx) + return; + + timersub(tv, tx, &delta); + + if ((!timerisset(&conn->tx_min) || timercmp(&delta, &conn->tx_min, <)) + && delta.tv_sec >= 0 && delta.tv_usec >= 0) + conn->tx_min = delta; + + if (!timerisset(&conn->tx_max) || timercmp(&delta, &conn->tx_max, >)) + conn->tx_max = delta; + + if (timerisset(&conn->tx_med)) { + struct timeval tmp; + + timeradd(&conn->tx_med, &delta, &tmp); + + tmp.tv_sec /= 2; + tmp.tv_usec /= 2; + if (tmp.tv_sec % 2) { + tmp.tv_usec += 500000; + if (tmp.tv_usec >= 1000000) { + tmp.tv_sec++; + tmp.tv_usec -= 1000000; + } + } + + conn->tx_med = tmp; + } else + conn->tx_med = delta; + + print_field("Latency: %lld msec (%lld-%lld msec ~%lld msec)", + TV_MSEC(delta), TV_MSEC(conn->tx_min), + TV_MSEC(conn->tx_max), TV_MSEC(conn->tx_med)); + + l2cap_dequeue_frame(&delta, conn); + + free(tx); +} + +static void num_completed_packets_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { + struct iovec iov = { (void *)data, size}; const struct bt_hci_evt_num_completed_packets *evt = data; + int i; + + iov_pull(&iov, 1); print_field("Num handles: %d", evt->num_handles); - print_handle(evt->handle); - print_field("Count: %d", le16_to_cpu(evt->count)); - if (size > sizeof(*evt)) - packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); + for (i = 0; i < evt->num_handles; i++) { + uint16_t handle; + uint16_t count; + + if (!util_iov_pull_le16(&iov, &handle)) + break; + + print_handle_native(handle); + + if (!util_iov_pull_le16(&iov, &count)) + break; + + print_field("Count: %d", le16_to_cpu(evt->count)); + + packet_dequeue_tx(tv, handle); + } + + if (iov.iov_len) + packet_hexdump(iov.iov_base, iov.iov_len); } -static void mode_change_evt(uint16_t index, const void *data, uint8_t size) +static void mode_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_mode_change *evt = data; @@ -10277,7 +10427,8 @@ print_interval(evt->interval); } -static void return_link_keys_evt(uint16_t index, const void *data, uint8_t size) +static void return_link_keys_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_return_link_keys *evt = data; uint8_t i; @@ -10290,21 +10441,24 @@ } } -static void pin_code_request_evt(uint16_t index, const void *data, uint8_t size) +static void pin_code_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_pin_code_request *evt = data; print_bdaddr(evt->bdaddr); } -static void link_key_request_evt(uint16_t index, const void *data, uint8_t size) +static void link_key_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_link_key_request *evt = data; print_bdaddr(evt->bdaddr); } -static void link_key_notify_evt(uint16_t index, const void *data, uint8_t size) +static void link_key_notify_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_link_key_notify *evt = data; @@ -10313,20 +10467,22 @@ print_key_type(evt->key_type); } -static void loopback_command_evt(uint16_t index, const void *data, uint8_t size) +static void loopback_command_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { packet_hexdump(data, size); } -static void data_buffer_overflow_evt(uint16_t index, const void *data, - uint8_t size) +static void data_buffer_overflow_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_data_buffer_overflow *evt = data; print_link_type(evt->link_type); } -static void max_slots_change_evt(uint16_t index, const void *data, uint8_t size) +static void max_slots_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_max_slots_change *evt = data; @@ -10334,8 +10490,8 @@ print_field("Max slots: %d", evt->max_slots); } -static void clock_offset_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void clock_offset_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_clock_offset_complete *evt = data; @@ -10344,8 +10500,8 @@ print_clock_offset(evt->clock_offset); } -static void conn_pkt_type_changed_evt(uint16_t index, const void *data, - uint8_t size) +static void conn_pkt_type_changed_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_conn_pkt_type_changed *evt = data; @@ -10354,15 +10510,16 @@ print_pkt_type(evt->pkt_type); } -static void qos_violation_evt(uint16_t index, const void *data, uint8_t size) +static void qos_violation_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_qos_violation *evt = data; print_handle(evt->handle); } -static void pscan_mode_change_evt(uint16_t index, const void *data, - uint8_t size) +static void pscan_mode_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_pscan_mode_change *evt = data; @@ -10370,8 +10527,8 @@ print_pscan_mode(evt->pscan_mode); } -static void pscan_rep_mode_change_evt(uint16_t index, const void *data, - uint8_t size) +static void pscan_rep_mode_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_pscan_rep_mode_change *evt = data; @@ -10379,8 +10536,8 @@ print_pscan_rep_mode(evt->pscan_rep_mode); } -static void flow_spec_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void flow_spec_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_flow_spec_complete *evt = data; @@ -10398,8 +10555,8 @@ print_field("Access latency: %d", le32_to_cpu(evt->access_latency)); } -static void inquiry_result_with_rssi_evt(uint16_t index, const void *data, - uint8_t size) +static void inquiry_result_with_rssi_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_inquiry_result_with_rssi *evt = data; @@ -10415,8 +10572,8 @@ packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); } -static void remote_ext_features_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void remote_ext_features_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_remote_ext_features_complete *evt = data; @@ -10426,8 +10583,8 @@ print_features(evt->page, evt->features, 0x00); } -static void sync_conn_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void sync_conn_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_sync_conn_complete *evt = data; @@ -10442,8 +10599,8 @@ print_air_mode(evt->air_mode); } -static void sync_conn_changed_evt(uint16_t index, const void *data, - uint8_t size) +static void sync_conn_changed_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_sync_conn_changed *evt = data; @@ -10455,7 +10612,8 @@ print_field("TX packet length: %d", le16_to_cpu(evt->tx_pkt_len)); } -static void sniff_subrating_evt(uint16_t index, const void *data, uint8_t size) +static void sniff_subrating_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_sniff_subrating *evt = data; @@ -10467,8 +10625,8 @@ print_slot_625("Min local timeout", evt->min_local_timeout); } -static void ext_inquiry_result_evt(uint16_t index, const void *data, - uint8_t size) +static void ext_inquiry_result_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_ext_inquiry_result *evt = data; @@ -10482,8 +10640,8 @@ print_eir(evt->data, sizeof(evt->data), false); } -static void encrypt_key_refresh_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void encrypt_key_refresh_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_encrypt_key_refresh_complete *evt = data; @@ -10491,16 +10649,16 @@ print_handle(evt->handle); } -static void io_capability_request_evt(uint16_t index, const void *data, - uint8_t size) +static void io_capability_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_io_capability_request *evt = data; print_bdaddr(evt->bdaddr); } -static void io_capability_response_evt(uint16_t index, const void *data, - uint8_t size) +static void io_capability_response_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_io_capability_response *evt = data; @@ -10510,8 +10668,8 @@ print_authentication(evt->authentication); } -static void user_confirm_request_evt(uint16_t index, const void *data, - uint8_t size) +static void user_confirm_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_user_confirm_request *evt = data; @@ -10519,24 +10677,24 @@ print_passkey(evt->passkey); } -static void user_passkey_request_evt(uint16_t index, const void *data, - uint8_t size) +static void user_passkey_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_user_passkey_request *evt = data; print_bdaddr(evt->bdaddr); } -static void remote_oob_data_request_evt(uint16_t index, const void *data, - uint8_t size) +static void remote_oob_data_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_remote_oob_data_request *evt = data; print_bdaddr(evt->bdaddr); } -static void simple_pairing_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void simple_pairing_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_simple_pairing_complete *evt = data; @@ -10544,8 +10702,8 @@ print_bdaddr(evt->bdaddr); } -static void link_supv_timeout_changed_evt(uint16_t index, const void *data, - uint8_t size) +static void link_supv_timeout_changed_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_link_supv_timeout_changed *evt = data; @@ -10553,16 +10711,16 @@ print_timeout(evt->timeout); } -static void enhanced_flush_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void enhanced_flush_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_enhanced_flush_complete *evt = data; print_handle(evt->handle); } -static void user_passkey_notify_evt(uint16_t index, const void *data, - uint8_t size) +static void user_passkey_notify_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_user_passkey_notify *evt = data; @@ -10570,7 +10728,8 @@ print_passkey(evt->passkey); } -static void keypress_notify_evt(uint16_t index, const void *data, uint8_t size) +static void keypress_notify_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_keypress_notify *evt = data; const char *str; @@ -10601,8 +10760,8 @@ print_field("Notification type: %s (0x%2.2x)", str, evt->type); } -static void remote_host_features_notify_evt(uint16_t index, const void *data, - uint8_t size) +static void remote_host_features_notify_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_remote_host_features_notify *evt = data; @@ -10610,8 +10769,8 @@ print_features(1, evt->features, 0x00); } -static void phy_link_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void phy_link_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_phy_link_complete *evt = data; @@ -10619,15 +10778,16 @@ print_phy_handle(evt->phy_handle); } -static void channel_selected_evt(uint16_t index, const void *data, uint8_t size) +static void channel_selected_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_channel_selected *evt = data; print_phy_handle(evt->phy_handle); } -static void disconn_phy_link_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void disconn_phy_link_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_disconn_phy_link_complete *evt = data; @@ -10636,8 +10796,8 @@ print_reason(evt->reason); } -static void phy_link_loss_early_warning_evt(uint16_t index, const void *data, - uint8_t size) +static void phy_link_loss_early_warning_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_phy_link_loss_early_warning *evt = data; const char *str; @@ -10668,16 +10828,16 @@ print_field("Reason: %s (0x%2.2x)", str, evt->reason); } -static void phy_link_recovery_evt(uint16_t index, const void *data, - uint8_t size) +static void phy_link_recovery_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_phy_link_recovery *evt = data; print_phy_handle(evt->phy_handle); } -static void logic_link_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void logic_link_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_logic_link_complete *evt = data; @@ -10687,8 +10847,8 @@ print_field("TX flow spec: 0x%2.2x", evt->flow_spec); } -static void disconn_logic_link_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void disconn_logic_link_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_disconn_logic_link_complete *evt = data; @@ -10697,8 +10857,8 @@ print_reason(evt->reason); } -static void flow_spec_modify_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void flow_spec_modify_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_flow_spec_modify_complete *evt = data; @@ -10706,8 +10866,8 @@ print_handle(evt->handle); } -static void num_completed_data_blocks_evt(uint16_t index, const void *data, - uint8_t size) +static void num_completed_data_blocks_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_num_completed_data_blocks *evt = data; @@ -10722,8 +10882,8 @@ packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); } -static void short_range_mode_change_evt(uint16_t index, const void *data, - uint8_t size) +static void short_range_mode_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_short_range_mode_change *evt = data; @@ -10732,8 +10892,8 @@ print_enable("Short range mode", evt->mode); } -static void amp_status_change_evt(uint16_t index, const void *data, - uint8_t size) +static void amp_status_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_amp_status_change *evt = data; @@ -10741,8 +10901,8 @@ print_amp_status(evt->amp_status); } -static void triggered_clock_capture_evt(uint16_t index, const void *data, - uint8_t size) +static void triggered_clock_capture_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_triggered_clock_capture *evt = data; @@ -10752,16 +10912,16 @@ print_clock_offset(evt->clock_offset); } -static void sync_train_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void sync_train_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_sync_train_complete *evt = data; print_status(evt->status); } -static void sync_train_received_evt(uint16_t index, const void *data, - uint8_t size) +static void sync_train_received_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_sync_train_received *evt = data; @@ -10776,8 +10936,8 @@ print_field("Service Data: 0x%2.2x", evt->service_data); } -static void peripheral_broadcast_receive_evt(uint16_t index, const void *data, - uint8_t size) +static void peripheral_broadcast_receive_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_peripheral_broadcast_receive *evt = data; @@ -10799,8 +10959,8 @@ packet_hexdump(data + 18, size - 18); } -static void peripheral_broadcast_timeout_evt(uint16_t index, const void *data, - uint8_t size) +static void peripheral_broadcast_timeout_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_peripheral_broadcast_timeout *evt = data; @@ -10808,8 +10968,8 @@ print_lt_addr(evt->lt_addr); } -static void truncated_page_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void truncated_page_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_truncated_page_complete *evt = data; @@ -10817,21 +10977,22 @@ print_bdaddr(evt->bdaddr); } -static void peripheral_page_response_timeout_evt(uint16_t index, - const void *data, uint8_t size) +static void peripheral_page_response_timeout_evt(struct timeval *tv, + uint16_t index, const void *data, + uint8_t size) { } -static void channel_map_change_evt(uint16_t index, const void *data, - uint8_t size) +static void channel_map_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_channel_map_change *evt = data; print_channel_map(evt->map); } -static void inquiry_response_notify_evt(uint16_t index, const void *data, - uint8_t size) +static void inquiry_response_notify_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_inquiry_response_notify *evt = data; @@ -10839,15 +11000,16 @@ print_rssi(evt->rssi); } -static void auth_payload_timeout_expired_evt(uint16_t index, const void *data, - uint8_t size) +static void auth_payload_timeout_expired_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_auth_payload_timeout_expired *evt = data; print_handle(evt->handle); } -static void le_conn_complete_evt(uint16_t index, const void *data, uint8_t size) +static void le_conn_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_conn_complete *evt = data; @@ -10868,7 +11030,8 @@ (void *)evt->peer_addr, evt->peer_addr_type); } -static void le_adv_report_evt(uint16_t index, const void *data, uint8_t size) +static void le_adv_report_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_adv_report *evt = data; uint8_t evt_len; @@ -10896,8 +11059,8 @@ } } -static void le_conn_update_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void le_conn_update_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_conn_update_complete *evt = data; @@ -10910,8 +11073,8 @@ le16_to_cpu(evt->supv_timeout)); } -static void le_remote_features_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void le_remote_features_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_remote_features_complete *evt = data; @@ -10920,8 +11083,8 @@ print_features(0, evt->features, 0x01); } -static void le_long_term_key_request_evt(uint16_t index, const void *data, - uint8_t size) +static void le_long_term_key_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_long_term_key_request *evt = data; @@ -10930,8 +11093,8 @@ print_encrypted_diversifier(evt->ediv); } -static void le_conn_param_request_evt(uint16_t index, const void *data, - uint8_t size) +static void le_conn_param_request_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_conn_param_request *evt = data; @@ -10944,8 +11107,8 @@ le16_to_cpu(evt->supv_timeout)); } -static void le_data_length_change_evt(uint16_t index, const void *data, - uint8_t size) +static void le_data_length_change_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_data_length_change *evt = data; @@ -10956,8 +11119,8 @@ print_field("Max RX time: %d", le16_to_cpu(evt->max_rx_time)); } -static void le_read_local_pk256_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void le_read_local_pk256_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_read_local_pk256_complete *evt = data; @@ -10965,8 +11128,8 @@ print_pk256("Local P-256 public key", evt->local_pk256); } -static void le_generate_dhkey_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void le_generate_dhkey_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_generate_dhkey_complete *evt = data; @@ -10974,8 +11137,8 @@ print_dhkey(evt->dhkey); } -static void le_enhanced_conn_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void le_enhanced_conn_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_enhanced_conn_complete *evt = data; @@ -10998,8 +11161,8 @@ (void *)evt->peer_addr, evt->peer_addr_type); } -static void le_direct_adv_report_evt(uint16_t index, const void *data, - uint8_t size) +static void le_direct_adv_report_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_direct_adv_report *evt = data; @@ -11016,8 +11179,8 @@ packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); } -static void le_phy_update_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void le_phy_update_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_phy_update_complete *evt = data; @@ -11120,8 +11283,8 @@ print_field(" Legacy PDU Type: %s (0x%4.4x)", str, flags); } -static void le_ext_adv_report_evt(uint16_t index, const void *data, - uint8_t size) +static void le_ext_adv_report_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_ext_adv_report *evt = data; const struct bt_hci_le_ext_adv_report *report; @@ -11208,7 +11371,8 @@ } } -static void le_pa_sync(uint16_t index, const void *data, uint8_t size) +static void le_pa_sync_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_per_sync_established *evt = data; @@ -11226,7 +11390,8 @@ print_field("Advertiser clock accuracy: 0x%2.2x", evt->clock_accuracy); } -static void le_pa_report_evt(uint16_t index, const void *data, uint8_t size) +static void le_pa_report_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_le_pa_report *evt = data; const char *color_on; @@ -11288,14 +11453,16 @@ packet_hexdump(evt->data, evt->data_len); } -static void le_pa_sync_lost(uint16_t index, const void *data, uint8_t size) +static void le_pa_sync_lost_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_per_sync_lost *evt = data; print_field("Sync handle: %d", evt->handle); } -static void le_adv_set_term_evt(uint16_t index, const void *data, uint8_t size) +static void le_adv_set_term_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_adv_set_term *evt = data; @@ -11306,8 +11473,8 @@ evt->num_evts); } -static void le_scan_req_received_evt(uint16_t index, const void *data, - uint8_t size) +static void le_scan_req_received_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_scan_req_received *evt = data; @@ -11317,8 +11484,8 @@ evt->scanner_addr_type); } -static void le_chan_select_alg_evt(uint16_t index, const void *data, - uint8_t size) +static void le_chan_select_alg_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_chan_select_alg *evt = data; const char *str; @@ -11340,8 +11507,8 @@ print_field("Algorithm: %s (0x%2.2x)", str, evt->algorithm); } -static void le_cte_request_failed_evt(uint16_t index, const void *data, - uint8_t size) +static void le_cte_request_failed_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_cte_request_failed *evt = data; @@ -11349,8 +11516,8 @@ print_field("Connection handle: %d", evt->handle); } -static void le_pa_sync_trans_rec_evt(uint16_t index, const void *data, - uint8_t size) +static void le_pa_sync_trans_rec_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_pa_sync_trans_rec *evt = data; @@ -11368,8 +11535,8 @@ print_clock_accuracy(evt->clock_accuracy); } -static void le_cis_established_evt(uint16_t index, const void *data, - uint8_t size) +static void le_cis_established_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_cis_established *evt = data; @@ -11388,10 +11555,11 @@ print_field("Peripheral to Central Flush Timeout: %u", evt->p_ft); print_field("Central to Peripheral MTU: %u", le16_to_cpu(evt->c_mtu)); print_field("Peripheral to Central MTU: %u", le16_to_cpu(evt->p_mtu)); - print_field("ISO Interval: %u", le16_to_cpu(evt->interval)); + print_slot_125("ISO Interval", evt->interval); } -static void le_req_cis_evt(uint16_t index, const void *data, uint8_t size) +static void le_req_cis_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_cis_req *evt = data; @@ -11408,7 +11576,8 @@ print_field("Connection Handle #%d: %d", i, handle); } -static void le_big_complete_evt(uint16_t index, const void *data, uint8_t size) +static void le_big_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_big_complete *evt = data; @@ -11427,7 +11596,8 @@ sizeof(*evt->bis_handle), print_bis_handle); } -static void le_big_terminate_evt(uint16_t index, const void *data, uint8_t size) +static void le_big_terminate_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_big_terminate *evt = data; @@ -11435,8 +11605,8 @@ print_reason(evt->reason); } -static void le_big_sync_estabilished_evt(uint16_t index, const void *data, - uint8_t size) +static void le_big_sync_estabilished_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_big_sync_estabilished *evt = data; @@ -11453,7 +11623,8 @@ print_bis_handle); } -static void le_big_sync_lost_evt(uint16_t index, const void *data, uint8_t size) +static void le_big_sync_lost_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_big_sync_lost *evt = data; @@ -11461,8 +11632,8 @@ print_reason(evt->reason); } -static void le_req_sca_complete_evt(uint16_t index, const void *data, - uint8_t size) +static void le_req_sca_complete_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_req_peer_sca_complete *evt = data; @@ -11471,7 +11642,8 @@ print_sca(evt->sca); } -static void le_big_info_evt(uint16_t index, const void *data, uint8_t size) +static void le_big_info_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { const struct bt_hci_evt_le_big_info_adv_report *evt = data; @@ -11493,12 +11665,13 @@ struct subevent_data { uint8_t subevent; const char *str; - void (*func) (uint16_t index, const void *data, uint8_t size); + void (*func) (struct timeval *tv, uint16_t index, const void *data, + uint8_t size); uint8_t size; bool fixed; }; -static void print_subevent(uint16_t index, +static void print_subevent(struct timeval *tv, uint16_t index, const struct subevent_data *subevent_data, const void *data, uint8_t size) { @@ -11531,7 +11704,7 @@ } } - subevent_data->func(index, data, size); + subevent_data->func(tv, index, data, size); } static const struct subevent_data le_meta_event_table[] = { @@ -11562,11 +11735,11 @@ { 0x0d, "LE Extended Advertising Report", le_ext_adv_report_evt, 1, false}, { 0x0e, "LE Periodic Advertising Sync Established", - le_pa_sync, 15, true }, + le_pa_sync_evt, 15, true }, { 0x0f, "LE Periodic Advertising Report", le_pa_report_evt, 7, false}, { 0x10, "LE Periodic Advertising Sync Lost", - le_pa_sync_lost, 2, true}, + le_pa_sync_lost_evt, 2, true}, { 0x11, "LE Scan Timeout" }, { 0x12, "LE Advertising Set Terminated", le_adv_set_term_evt, 5, true}, @@ -11616,7 +11789,8 @@ { } }; -static void le_meta_event_evt(uint16_t index, const void *data, uint8_t size) +static void le_meta_event_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { uint8_t subevent = *((const uint8_t *) data); struct subevent_data unknown; @@ -11636,10 +11810,11 @@ } } - print_subevent(index, subevent_data, data + 1, size - 1); + print_subevent(tv, index, subevent_data, data + 1, size - 1); } -static void vendor_evt(uint16_t index, const void *data, uint8_t size) +static void vendor_evt(struct timeval *tv, uint16_t index, + const void *data, uint8_t size) { struct subevent_data vendor_data; char vendor_str[150]; @@ -11660,7 +11835,7 @@ vendor_data.size = vnd->evt_size; vendor_data.fixed = vnd->evt_fixed; - print_subevent(index, &vendor_data, data + consumed_size, + print_subevent(tv, index, &vendor_data, data + consumed_size, size - consumed_size); } else { uint16_t manufacturer; @@ -11677,7 +11852,8 @@ struct event_data { uint8_t event; const char *str; - void (*func) (uint16_t index, const void *data, uint8_t size); + void (*func) (struct timeval *tv, uint16_t index, const void *data, + uint8_t size); uint8_t size; bool fixed; }; @@ -11910,7 +12086,7 @@ struct monitor_l2cap_hdr { uint16_t cid; uint16_t psm; -}; +} __attribute__((packed)); static void packet_decode(struct timeval *tv, struct ucred *cred, char dir, uint16_t index, const char *color, @@ -11929,7 +12105,8 @@ NULL); /* Discard last byte since it just a filler */ - l2cap_frame(index, dir == '>', 0, hdr->cid, hdr->psm, + l2cap_frame(index, dir == '>', 0, + le16_to_cpu(hdr->cid), le16_to_cpu(hdr->psm), data + sizeof(*hdr), size - (sizeof(*hdr) + 1)); } @@ -11938,7 +12115,6 @@ const char *ident, const void *data, uint16_t size) { - char pid_str[140]; const char *label; const char *color; @@ -11964,26 +12140,7 @@ } if (cred) { - char *path = alloca(24); - char line[128]; - FILE *fp; - - snprintf(path, 23, "/proc/%u/comm", cred->pid); - - fp = fopen(path, "re"); - if (fp) { - if (fgets(line, sizeof(line), fp)) { - line[strcspn(line, "\r\n")] = '\0'; - snprintf(pid_str, sizeof(pid_str), "%s[%u]", - line, cred->pid); - } else - snprintf(pid_str, sizeof(pid_str), "%u", - cred->pid); - fclose(fp); - } else - snprintf(pid_str, sizeof(pid_str), "%u", cred->pid); - - label = pid_str; + label = NULL; } else { if (ident) label = ident; @@ -11993,8 +12150,8 @@ if (ident && (ident[0] == '<' || ident[0] == '>')) { packet_decode(tv, cred, ident[0], index, color, - label == ident ? &ident[2] : label, - data, size); + label == ident ? &ident[2] : label, + data, size); return; } @@ -12189,7 +12346,24 @@ } } - event_data->func(index, data, hdr->plen); + event_data->func(tv, index, data, hdr->plen); +} + +static void packet_queue_tx(struct timeval *tv, uint16_t handle) +{ + struct packet_conn_data *conn; + struct timeval *tx; + + conn = packet_get_conn_data(handle); + if (!conn) + return; + + if (!conn->tx_q) + conn->tx_q = queue_new(); + + tx = new0(struct timeval, 1); + memcpy(tx, tv, sizeof(*tv)); + queue_push_tail(conn->tx_q, tx); } void packet_hci_acldata(struct timeval *tv, struct ucred *cred, uint16_t index, @@ -12229,6 +12403,9 @@ in ? "ACL Data RX" : "ACL Data TX", handle_str, extra_str); + if (!in) + packet_queue_tx(tv, acl_handle(handle)); + if (size != dlen) { print_text(COLOR_ERROR, "invalid packet size (%d != %d)", size, dlen); @@ -12278,6 +12455,9 @@ in ? "SCO Data RX" : "SCO Data TX", handle_str, extra_str); + if (!in) + packet_queue_tx(tv, acl_handle(handle)); + if (size != hdr->dlen) { print_text(COLOR_ERROR, "invalid packet size (%d != %d)", size, hdr->dlen); @@ -12321,10 +12501,13 @@ sprintf(handle_str, "Handle %d", acl_handle(handle)); sprintf(extra_str, "flags 0x%2.2x dlen %d", flags, hdr->dlen); - print_packet(tv, cred, in ? '>' : '<', index, NULL, COLOR_HCI_SCODATA, + print_packet(tv, cred, in ? '>' : '<', index, NULL, COLOR_HCI_ISODATA, in ? "ISO Data RX" : "ISO Data TX", handle_str, extra_str); + if (!in) + packet_queue_tx(tv, acl_handle(handle)); + if (size != hdr->dlen) { print_text(COLOR_ERROR, "invalid packet size (%d != %d)", size, hdr->dlen); @@ -12332,7 +12515,7 @@ return; } - if (filter_mask & PACKET_FILTER_SHOW_SCO_DATA) + if (filter_mask & PACKET_FILTER_SHOW_ISO_DATA) packet_hexdump(data, size); } @@ -12615,7 +12798,11 @@ { 15, "Static Address" }, { 16, "PHY Configuration" }, { 17, "Wideband Speech" }, - { } + { 18, "CIS Central" }, + { 19, "CIS Peripheral" }, + { 20, "ISO Broadcaster" }, + { 21, "Sync Receiver" }, + {} }; static void mgmt_print_settings(const char *label, uint32_t settings) @@ -12669,6 +12856,8 @@ { 1, "Legacy Pairing" }, { 2, "Not Connectable" }, { 3, "Connection Locally Initiated" }, + { 4, "Name Request Failed" }, + { 5, "Scan Response" }, { } }; @@ -12834,6 +13023,7 @@ mgmt_print_address(data, address_type); print_hex_field("Key", data + 7, 16); + keys_add_identity(data, address_type, data + 7); } static void mgmt_print_signature_resolving_key(const void *data) diff -Nru bluez-5.66/monitor/packet.h bluez-5.68/monitor/packet.h --- bluez-5.66/monitor/packet.h 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/monitor/packet.h 2023-06-30 08:10:20.000000000 +0000 @@ -22,6 +22,7 @@ #define PACKET_FILTER_SHOW_SCO_DATA (1 << 5) #define PACKET_FILTER_SHOW_A2DP_STREAM (1 << 6) #define PACKET_FILTER_SHOW_MGMT_SOCKET (1 << 7) +#define PACKET_FILTER_SHOW_ISO_DATA (1 << 8) struct packet_conn_data { uint16_t index; @@ -30,6 +31,11 @@ uint8_t type; uint8_t dst[6]; uint8_t dst_type; + struct queue *tx_q; + struct queue *chan_q; + struct timeval tx_min; + struct timeval tx_max; + struct timeval tx_med; void *data; void (*destroy)(void *data); }; diff -Nru bluez-5.66/monitor/vendor.h bluez-5.68/monitor/vendor.h --- bluez-5.66/monitor/vendor.h 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/monitor/vendor.h 2023-06-30 08:10:20.000000000 +0000 @@ -25,7 +25,8 @@ struct vendor_evt { uint8_t evt; const char *str; - void (*evt_func) (uint16_t index, const void *data, uint8_t size); + void (*evt_func) (struct timeval *tv, uint16_t index, + const void *data, uint8_t size); uint8_t evt_size; bool evt_fixed; }; diff -Nru bluez-5.66/obexd/src/manager.c bluez-5.68/obexd/src/manager.c --- bluez-5.66/obexd/src/manager.c 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/obexd/src/manager.c 2023-06-30 08:10:20.000000000 +0000 @@ -38,6 +38,7 @@ #define TRANSFER_INTERFACE OBEXD_SERVICE ".Transfer1" #define SESSION_INTERFACE OBEXD_SERVICE ".Session1" #define AGENT_INTERFACE OBEXD_SERVICE ".Agent1" +#define OBEX_ERROR_REJECT "org.bluez.obex.Error.Rejected" #define TIMEOUT 60*1000 /* Timeout for user response (miliseconds) */ @@ -45,6 +46,7 @@ char *bus_name; char *path; gboolean auth_pending; + gboolean auth_reject; char *new_name; char *new_folder; unsigned int watch_id; @@ -630,6 +632,8 @@ if (dbus_error_has_name(&derr, DBUS_ERROR_NO_REPLY)) agent_cancel(); + else if (dbus_error_has_name(&derr, OBEX_ERROR_REJECT)) + agent->auth_reject = TRUE; dbus_error_free(&derr); dbus_message_unref(reply); @@ -646,7 +650,10 @@ agent->new_name = g_strdup(name); agent->new_folder = NULL; } else { - agent->new_name = g_strdup(slash + 1); + if (strlen(slash) == 1) + agent->new_name = NULL; + else + agent->new_name = g_strdup(slash + 1); agent->new_folder = g_strndup(name, slash - name); } } @@ -694,6 +701,7 @@ dbus_message_unref(msg); agent->auth_pending = TRUE; + agent->auth_reject = FALSE; got_reply = FALSE; /* Catches errors before authorization response comes */ @@ -716,7 +724,7 @@ dbus_pending_call_unref(call); - if (!agent || !agent->new_name) + if (!agent || agent->auth_reject) return -EPERM; *new_folder = agent->new_folder; diff -Nru bluez-5.66/plugins/admin.c bluez-5.68/plugins/admin.c --- bluez-5.66/plugins/admin.c 2022-01-05 21:53:58.000000000 +0000 +++ bluez-5.68/plugins/admin.c 2023-06-30 08:10:20.000000000 +0000 @@ -618,13 +618,12 @@ .resume = NULL, .remove = admin_policy_remove, .device_resolved = admin_policy_device_added, - .device_removed = admin_policy_device_removed + .device_removed = admin_policy_device_removed, + .experimental = true, }; static int admin_init(void) { - DBG(""); - dbus_conn = btd_get_dbus_connection(); return btd_register_adapter_driver(&admin_policy_driver); @@ -632,8 +631,6 @@ static void admin_exit(void) { - DBG(""); - btd_unregister_adapter_driver(&admin_policy_driver); } diff -Nru bluez-5.66/plugins/hostname.c bluez-5.68/plugins/hostname.c --- bluez-5.66/plugins/hostname.c 2021-06-13 19:56:37.000000000 +0000 +++ bluez-5.68/plugins/hostname.c 2023-06-30 08:10:20.000000000 +0000 @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include "lib/bluetooth.h" #include "lib/sdp.h" @@ -44,8 +46,10 @@ static uint8_t major_class = MAJOR_CLASS_MISCELLANEOUS; static uint8_t minor_class = MINOR_CLASS_UNCATEGORIZED; -static char *pretty_hostname = NULL; -static char *static_hostname = NULL; +static char *pretty_hostname = NULL; +static char *static_hostname = NULL; +static char *transient_hostname = NULL; +static guint hostname_id = 0; /* * Fallback to static hostname only if empty pretty hostname was already @@ -60,6 +64,10 @@ if (static_hostname && g_str_equal(static_hostname, "") == FALSE) return static_hostname; + + if (transient_hostname && + g_str_equal(transient_hostname, "") == FALSE) + return transient_hostname; } return NULL; @@ -128,7 +136,7 @@ dbus_message_iter_get_basic(iter, &str); - DBG("pretty hostname: %s", str); + DBG("pretty hostname: '%s'", str); g_free(pretty_hostname); pretty_hostname = g_strdup(str); @@ -146,7 +154,7 @@ dbus_message_iter_get_basic(iter, &str); - DBG("static hostname: %s", str); + DBG("static hostname: '%s'", str); g_free(static_hostname); static_hostname = g_strdup(str); @@ -165,7 +173,7 @@ dbus_message_iter_get_basic(iter, &str); - DBG("chassis: %s", str); + DBG("chassis: '%s'", str); for (i = 0; chassis_table[i].chassis; i++) { if (strcmp(chassis_table[i].chassis, str)) @@ -181,6 +189,32 @@ } } +static void read_transient_hostname(void) +{ + struct utsname u; + + if (uname(&u) != 0) { + g_free(transient_hostname); + transient_hostname = NULL; + DBG("failed to read transient hostname"); + return; + } + + g_free(transient_hostname); + transient_hostname = g_strdup(u.nodename); + + DBG("read transient hostname: '%s'", transient_hostname); +} + +static gboolean hostname_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + DBG("transient hostname changed"); + read_transient_hostname(); + adapter_foreach(update_class, NULL); + return TRUE; +} + static int hostname_probe(struct btd_adapter *adapter) { DBG(""); @@ -261,9 +295,11 @@ static int hostname_init(void) { DBusConnection *conn = btd_get_dbus_connection(); + int fd; int err; read_dmi_fallback(); + read_transient_hostname(); hostname_client = g_dbus_client_new(conn, "org.freedesktop.hostname1", "/org/freedesktop/hostname1"); @@ -289,6 +325,17 @@ hostname_client = NULL; } + fd = open("/proc/sys/kernel/hostname", O_RDONLY); + if (fd < 0) { + error("open(/proc/sys/kernel/hostname): %s (%d)", + strerror(errno), errno); + } else { + GIOChannel *io = g_io_channel_unix_new(fd); + + hostname_id = g_io_add_watch(io, G_IO_ERR, hostname_cb, NULL); + g_io_channel_unref(io); + } + return err; } @@ -306,8 +353,14 @@ hostname_client = NULL; } + if (hostname_id != 0) { + g_source_remove(hostname_id); + hostname_id = 0; + } + g_free(pretty_hostname); g_free(static_hostname); + g_free(transient_hostname); } BLUETOOTH_PLUGIN_DEFINE(hostname, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, diff -Nru bluez-5.66/profiles/audio/avrcp.c bluez-5.68/profiles/audio/avrcp.c --- bluez-5.66/profiles/audio/avrcp.c 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/profiles/audio/avrcp.c 2023-06-30 08:10:20.000000000 +0000 @@ -3901,6 +3901,12 @@ case AVRCP_EVENT_UIDS_CHANGED: avrcp_uids_changed(session, pdu); break; + default: + if (event > AVRCP_EVENT_LAST) { + warn("Unsupported event: %u", event); + return FALSE; + } + break; } session->registered_events |= (1 << event); diff -Nru bluez-5.66/profiles/audio/bap.c bluez-5.68/profiles/audio/bap.c --- bluez-5.66/profiles/audio/bap.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/profiles/audio/bap.c 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. + * Copyright 2023 NXP * * */ @@ -34,6 +35,7 @@ #include "lib/sdp.h" #include "lib/uuid.h" +#include "src/btd.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" @@ -53,6 +55,7 @@ #include "src/log.h" #include "src/error.h" +#define ISO_SOCKET_UUID "6fbaf188-05e0-496a-9885-d6ddfdb4e03e" #define PACS_UUID_STR "00001850-0000-1000-8000-00805f9b34fb" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" @@ -74,6 +77,7 @@ struct bap_data { struct btd_device *device; + struct btd_adapter *adapter; struct btd_service *service; struct bt_bap *bap; unsigned int ready_id; @@ -81,12 +85,25 @@ unsigned int pac_id; struct queue *srcs; struct queue *snks; + struct queue *bcast; struct queue *streams; GIOChannel *listen_io; + int selecting; + void *user_data; }; static struct queue *sessions; +static bool bap_data_set_user_data(struct bap_data *data, void *user_data) +{ + if (!data) + return false; + + data->user_data = user_data; + + return true; +} + static void bap_debug(const char *str, void *user_data) { DBG_IDX(0xffff, "%s", str); @@ -119,7 +136,7 @@ queue_destroy(data->streams, NULL); bt_bap_ready_unregister(data->bap, data->ready_id); bt_bap_state_unregister(data->bap, data->state_id); - bt_bap_pac_unregister(data->pac_id); + bt_bap_pac_unregister(data->bap, data->pac_id); bt_bap_unref(data->bap); free(data); } @@ -165,8 +182,10 @@ if (queue_find(ep->data->snks, NULL, ep)) uuid = PAC_SINK_UUID; - else + else if (queue_find(ep->data->srcs, NULL, ep)) uuid = PAC_SOURCE_UUID; + else + uuid = BAA_SERVICE_UUID; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); @@ -212,7 +231,10 @@ struct bap_ep *ep = data; const char *path; - path = device_get_path(ep->data->device); + if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) + path = adapter_get_path(ep->data->adapter); + else + path = device_get_path(ep->data->device); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); @@ -251,7 +273,11 @@ struct iovec **metadata, struct bt_bap_qos *qos) { const char *key; + struct bt_bap_io_qos io_qos; + uint8_t framing = 0; + bool broadcast = false; + memset(&io_qos, 0, sizeof(io_qos)); while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var; @@ -280,17 +306,27 @@ if (var != DBUS_TYPE_BYTE) goto fail; - dbus_message_iter_get_basic(&value, &qos->cig_id); + dbus_message_iter_get_basic(&value, &qos->ucast.cig_id); + } else if (!strcasecmp(key, "BIG")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->bcast.big); } else if (!strcasecmp(key, "CIS")) { if (var != DBUS_TYPE_BYTE) goto fail; - dbus_message_iter_get_basic(&value, &qos->cis_id); + dbus_message_iter_get_basic(&value, &qos->ucast.cis_id); + } else if (!strcasecmp(key, "BIS")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, &qos->bcast.bis); } else if (!strcasecmp(key, "Interval")) { if (var != DBUS_TYPE_UINT32) goto fail; - dbus_message_iter_get_basic(&value, &qos->interval); + dbus_message_iter_get_basic(&value, &io_qos.interval); } else if (!strcasecmp(key, "Framing")) { dbus_bool_t val; @@ -299,7 +335,7 @@ dbus_message_iter_get_basic(&value, &val); - qos->framing = val; + framing = val; } else if (!strcasecmp(key, "PHY")) { const char *str; @@ -309,42 +345,105 @@ dbus_message_iter_get_basic(&value, &str); if (!strcasecmp(str, "1M")) - qos->phy = 0x01; + io_qos.phy = 0x01; else if (!strcasecmp(str, "2M")) - qos->phy = 0x02; + io_qos.phy = 0x02; else goto fail; } else if (!strcasecmp(key, "SDU")) { if (var != DBUS_TYPE_UINT16) goto fail; - dbus_message_iter_get_basic(&value, &qos->sdu); + dbus_message_iter_get_basic(&value, &io_qos.sdu); } else if (!strcasecmp(key, "Retransmissions")) { if (var != DBUS_TYPE_BYTE) goto fail; - dbus_message_iter_get_basic(&value, &qos->rtn); + dbus_message_iter_get_basic(&value, &io_qos.rtn); } else if (!strcasecmp(key, "Latency")) { if (var != DBUS_TYPE_UINT16) goto fail; - dbus_message_iter_get_basic(&value, &qos->latency); + dbus_message_iter_get_basic(&value, &io_qos.latency); } else if (!strcasecmp(key, "Delay")) { if (var != DBUS_TYPE_UINT32) goto fail; - dbus_message_iter_get_basic(&value, &qos->delay); + dbus_message_iter_get_basic(&value, &qos->ucast.delay); } else if (!strcasecmp(key, "TargetLatency")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, - &qos->target_latency); + &qos->ucast.target_latency); + } else if (!strcasecmp(key, "Encryption")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.encryption); + broadcast = true; + } else if (!strcasecmp(key, "Options")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.options); + } else if (!strcasecmp(key, "Skip")) { + if (var != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.skip); + } else if (!strcasecmp(key, "SyncTimeout")) { + if (var != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.sync_timeout); + } else if (!strcasecmp(key, "SyncCteType")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.sync_cte_type); + + } else if (!strcasecmp(key, "SyncInterval")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.sync_interval); + } else if (!strcasecmp(key, "MSE")) { + if (var != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.mse); + } else if (!strcasecmp(key, "Timeout")) { + if (var != DBUS_TYPE_UINT16) + goto fail; + + dbus_message_iter_get_basic(&value, + &qos->bcast.timeout); + } else if (!strcasecmp(key, "BroadcastCode")) { + if (var != DBUS_TYPE_ARRAY) + goto fail; + parse_array(&value, &qos->bcast.bcode); } dbus_message_iter_next(props); } + if (broadcast) { + memcpy(&qos->bcast.io_qos, &io_qos, sizeof(io_qos)); + qos->bcast.framing = framing; + + } else { + memcpy(&qos->ucast.io_qos, &io_qos, sizeof(io_qos)); + qos->ucast.framing = framing; + } + return 0; fail: @@ -366,6 +465,8 @@ DBG("stream %p code 0x%02x reason 0x%02x", stream, code, reason); + ep->id = 0; + if (!ep->msg) return; @@ -451,9 +552,15 @@ bt_bap_stream_io_connecting(ep->stream, -1); } - /* Mark CIG and CIS to be auto assigned */ - ep->qos.cig_id = BT_ISO_QOS_CIG_UNSET; - ep->qos.cis_id = BT_ISO_QOS_CIS_UNSET; + if (bt_bap_pac_get_type(ep->lpac) == BT_BAP_BCAST_SOURCE) { + /* Mark CIG and CIS to be auto assigned */ + ep->qos.bcast.big = BT_ISO_QOS_BIG_UNSET; + ep->qos.bcast.bis = BT_ISO_QOS_BIS_UNSET; + } else { + /* Mark CIG and CIS to be auto assigned */ + ep->qos.ucast.cig_id = BT_ISO_QOS_CIG_UNSET; + ep->qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET; + } if (parse_properties(&props, &ep->caps, &ep->metadata, &ep->qos) < 0) { DBG("Unable to parse properties"); @@ -463,15 +570,13 @@ /* TODO: Check if stream capabilities match add support for Latency * and PHY. */ - if (ep->stream) - ep->id = bt_bap_stream_config(ep->stream, &ep->qos, ep->caps, - config_cb, ep); - else - ep->stream = bt_bap_config(ep->data->bap, ep->lpac, ep->rpac, - &ep->qos, ep->caps, - config_cb, ep); + if (!ep->stream) + ep->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, + ep->rpac, &ep->qos, ep->caps); - if (!ep->stream) { + ep->id = bt_bap_stream_config(ep->stream, &ep->qos, ep->caps, + config_cb, ep); + if (!ep->id) { DBG("Unable to config stream"); free(ep->caps); ep->caps = NULL; @@ -479,7 +584,19 @@ } bt_bap_stream_set_user_data(ep->stream, ep->path); - ep->msg = dbus_message_ref(msg); + + if (ep->metadata && ep->metadata->iov_len) + bt_bap_stream_metadata(ep->stream, ep->metadata, NULL, NULL); + + switch (bt_bap_stream_get_type(ep->stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + ep->msg = dbus_message_ref(msg); + break; + case BT_BAP_STREAM_TYPE_BCAST: + /* No message sent over the air for broadcast */ + ep->id = 0; + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } return NULL; } @@ -501,11 +618,87 @@ bap_io_close(ep); - free(ep->caps); + util_iov_free(ep->caps, 1); + util_iov_free(ep->metadata, 1); + if (bt_bap_stream_get_type(ep->stream) == BT_BAP_STREAM_TYPE_BCAST) + util_iov_free(ep->qos.bcast.bcode, 1); free(ep->path); free(ep); } +struct match_ep { + struct bt_bap_pac *lpac; + struct bt_bap_pac *rpac; +}; + +static bool match_ep(const void *data, const void *user_data) +{ + const struct bap_ep *ep = data; + const struct match_ep *match = user_data; + + if (ep->lpac != match->lpac) + return false; + + return ep->rpac == match->rpac; +} + +static struct bap_ep *ep_register_bcast(struct bap_data *data, + struct bt_bap_pac *lpac, + struct bt_bap_pac *rpac) +{ + struct btd_adapter *adapter = data->user_data; + struct bap_ep *ep; + struct queue *queue; + int i, err; + const char *suffix; + struct match_ep match = { lpac, rpac }; + + switch (bt_bap_pac_get_type(rpac)) { + case BT_BAP_BCAST_SOURCE: + queue = data->bcast; + i = queue_length(data->bcast); + suffix = "bcast"; + break; + default: + return NULL; + } + + ep = queue_find(queue, match_ep, &match); + if (ep) + return ep; + + ep = new0(struct bap_ep, 1); + ep->data = data; + ep->lpac = lpac; + ep->rpac = rpac; + + err = asprintf(&ep->path, "%s/pac_%s%d", adapter_get_path(adapter), + suffix, i); + if (err < 0) { + error("Could not allocate path for remote pac %s/pac%d", + adapter_get_path(adapter), i); + free(ep); + return NULL; + } + + if (g_dbus_register_interface(btd_get_dbus_connection(), + ep->path, MEDIA_ENDPOINT_INTERFACE, + ep_methods, NULL, ep_properties, + ep, ep_free) == FALSE) { + error("Could not register remote ep %s", ep->path); + ep_free(ep); + return NULL; + } + + bt_bap_pac_set_user_data(rpac, ep->path); + + DBG("ep %p lpac %p rpac %p path %s", ep, ep->lpac, ep->rpac, ep->path); + + queue_push_tail(queue, ep); + + return ep; +} + static struct bap_ep *ep_register(struct btd_service *service, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac) @@ -516,6 +709,7 @@ struct queue *queue; int i, err; const char *suffix; + struct match_ep match = { lpac, rpac }; switch (bt_bap_pac_get_type(rpac)) { case BT_BAP_SINK: @@ -532,6 +726,10 @@ return NULL; } + ep = queue_find(queue, match_ep, &match); + if (ep) + return ep; + ep = new0(struct bap_ep, 1); ep->data = data; ep->lpac = lpac; @@ -564,6 +762,35 @@ return ep; } +static void bap_config(void *data, void *user_data) +{ + struct bap_ep *ep = data; + + DBG("ep %p caps %p metadata %p", ep, ep->caps, ep->metadata); + + if (!ep->caps) + return; + + /* TODO: Check if stream capabilities match add support for Latency + * and PHY. + */ + if (!ep->stream) + ep->stream = bt_bap_stream_new(ep->data->bap, ep->lpac, + ep->rpac, &ep->qos, ep->caps); + + ep->id = bt_bap_stream_config(ep->stream, &ep->qos, ep->caps, + config_cb, ep); + if (!ep->id) { + DBG("Unable to config stream"); + util_iov_free(ep->caps, 1); + ep->caps = NULL; + util_iov_free(ep->metadata, 1); + ep->metadata = NULL; + } + + bt_bap_stream_set_user_data(ep->stream, ep->path); +} + static void select_cb(struct bt_bap_pac *pac, int err, struct iovec *caps, struct iovec *metadata, struct bt_bap_qos *qos, void *user_data) @@ -572,31 +799,28 @@ if (err) { error("err %d", err); - return; + ep->data->selecting--; + goto done; + } + + ep->caps = util_iov_dup(caps, 1); + + if (metadata && metadata->iov_base && metadata->iov_len) { + ep->metadata = util_iov_dup(metadata, 1); + bt_bap_stream_metadata(ep->stream, ep->metadata, NULL, NULL); } - ep->caps = caps; - ep->metadata = metadata; ep->qos = *qos; - /* TODO: Check if stream capabilities match add support for Latency - * and PHY. - */ - if (ep->stream) - ep->id = bt_bap_stream_config(ep->stream, &ep->qos, ep->caps, - config_cb, ep); - else - ep->stream = bt_bap_config(ep->data->bap, ep->lpac, ep->rpac, - &ep->qos, ep->caps, - config_cb, ep); + DBG("selecting %d", ep->data->selecting); + ep->data->selecting--; - if (!ep->stream) { - DBG("Unable to config stream"); - free(ep->caps); - ep->caps = NULL; - } +done: + if (ep->data->selecting) + return; - bt_bap_stream_set_user_data(ep->stream, ep->path); + queue_foreach(ep->data->srcs, bap_config, NULL); + queue_foreach(ep->data->snks, bap_config, NULL); } static bool pac_found(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, @@ -614,8 +838,26 @@ } /* TODO: Cache LRU? */ - if (btd_service_is_initiator(service)) - bt_bap_select(lpac, rpac, select_cb, ep); + if (btd_service_is_initiator(service)) { + if (!bt_bap_select(lpac, rpac, select_cb, ep)) + ep->data->selecting++; + } + + return true; +} + +static bool pac_found_bcast(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + void *user_data) +{ + struct bap_ep *ep; + + DBG("lpac %p rpac %p", lpac, rpac); + + ep = ep_register_bcast(user_data, lpac, rpac); + if (!ep) { + error("Unable to register endpoint for pac %p", rpac); + return true; + } return true; } @@ -643,11 +885,18 @@ { struct bap_ep *ep; - ep = queue_find(data->snks, match_ep_by_stream, stream); - if (ep) - return ep; + switch (bt_bap_stream_get_type(stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + ep = queue_find(data->snks, match_ep_by_stream, stream); + if (ep) + return ep; + + return queue_find(data->srcs, match_ep_by_stream, stream); + case BT_BAP_STREAM_TYPE_BCAST: + return queue_find(data->bcast, match_ep_by_stream, stream); + } - return queue_find(data->srcs, match_ep_by_stream, stream); + return NULL; } static void iso_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) @@ -679,11 +928,11 @@ if (!qos) return; - io->interval = qos->interval; - io->latency = qos->latency; - io->sdu = qos->sdu; - io->phy = qos->phy; - io->rtn = qos->rtn; + io->interval = qos->ucast.io_qos.interval; + io->latency = qos->ucast.io_qos.latency; + io->sdu = qos->ucast.io_qos.sdu; + io->phy = qos->ucast.io_qos.phy; + io->rtn = qos->ucast.io_qos.rtn; } static bool match_stream_qos(const void *data, const void *user_data) @@ -694,10 +943,10 @@ qos = bt_bap_stream_get_qos((void *)stream); - if (iso_qos->cig != qos->cig_id) + if (iso_qos->ucast.cig != qos->ucast.cig_id) return false; - return iso_qos->cis == qos->cis_id; + return iso_qos->ucast.cis == qos->ucast.cis_id; } static void iso_confirm_cb(GIOChannel *io, void *user_data) @@ -719,7 +968,7 @@ } DBG("ISO: incoming connect from %s (CIG 0x%02x CIS 0x%02x)", - address, qos.cig, qos.cis); + address, qos.ucast.cig, qos.ucast.cis); stream = queue_remove_if(data->streams, match_stream_qos, &qos); if (!stream) { @@ -886,6 +1135,61 @@ bt_bap_stream_io_connecting(stream, g_io_channel_unix_get_fd(io)); } +static void bap_connect_io_broadcast(struct bap_data *data, struct bap_ep *ep, + struct bt_bap_stream *stream, + struct bt_iso_qos *qos) +{ + struct btd_adapter *adapter = data->user_data; + GIOChannel *io = NULL; + GError *err = NULL; + bdaddr_t dst_addr = {0}; + char addr[18]; + struct bt_iso_base base; + + /* If IO already set and we are in the creation step, + * skip creating it again + */ + if (bt_bap_stream_get_io(stream)) + return; + + if (ep->io_id) { + g_source_remove(ep->io_id); + ep->io_id = 0; + } + base.base_len = ep->caps->iov_len; + + memset(base.base, 0, 248); + memcpy(base.base, ep->caps->iov_base, base.base_len); + DBG("ep %p stream %p ", ep, stream); + ba2str(btd_adapter_get_address(adapter), addr); + + io = bt_io_connect(bap_connect_io_cb, ep, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_DEST_BDADDR, + &dst_addr, + BT_IO_OPT_DEST_TYPE, + BDADDR_LE_PUBLIC, + BT_IO_OPT_MODE, BT_IO_MODE_ISO, + BT_IO_OPT_QOS, qos, + BT_IO_OPT_BASE, &base, + BT_IO_OPT_DEFER_TIMEOUT, false, + BT_IO_OPT_INVALID); + + if (!io) { + error("%s", err->message); + g_error_free(err); + return; + } + + ep->io_id = g_io_add_watch(io, G_IO_HUP | G_IO_ERR | G_IO_NVAL, + bap_io_disconnected, ep); + + ep->io = io; + + bt_bap_stream_io_connecting(stream, g_io_channel_unix_get_fd(io)); +} + static void bap_listen_io(struct bap_data *data, struct bt_bap_stream *stream, struct bt_iso_qos *qos) { @@ -918,31 +1222,26 @@ data->listen_io = io; } -static void bap_create_io(struct bap_data *data, struct bap_ep *ep, +static void bap_create_ucast_io(struct bap_data *data, struct bap_ep *ep, struct bt_bap_stream *stream, int defer) { struct bt_bap_qos *qos[2] = {}; struct bt_iso_qos iso_qos; - DBG("ep %p stream %p defer %s", ep, stream, defer ? "true" : "false"); - - if (!data->streams) - data->streams = queue_new(); - - if (!queue_find(data->streams, NULL, stream)) - queue_push_tail(data->streams, stream); - if (!bt_bap_stream_io_get_qos(stream, &qos[0], &qos[1])) { error("bt_bap_stream_get_qos_links: failed"); return; } memset(&iso_qos, 0, sizeof(iso_qos)); - iso_qos.cig = qos[0] ? qos[0]->cig_id : qos[1]->cig_id; - iso_qos.cis = qos[0] ? qos[0]->cis_id : qos[1]->cis_id; - bap_iso_qos(qos[0], &iso_qos.in); - bap_iso_qos(qos[1], &iso_qos.out); + iso_qos.ucast.cig = qos[0] ? qos[0]->ucast.cig_id : + qos[1]->ucast.cig_id; + iso_qos.ucast.cis = qos[0] ? qos[0]->ucast.cis_id : + qos[1]->ucast.cis_id; + + bap_iso_qos(qos[0], &iso_qos.ucast.in); + bap_iso_qos(qos[1], &iso_qos.ucast.out); if (ep) bap_connect_io(data, ep, stream, &iso_qos, defer); @@ -950,6 +1249,60 @@ bap_listen_io(data, stream, &iso_qos); } +static void bap_create_bcast_io(struct bap_data *data, struct bap_ep *ep, + struct bt_bap_stream *stream, int defer) +{ + struct bt_iso_qos iso_qos; + + memset(&iso_qos, 0, sizeof(iso_qos)); + + if (!defer) + goto done; + + iso_qos.bcast.big = ep->qos.bcast.big; + iso_qos.bcast.bis = ep->qos.bcast.bis; + iso_qos.bcast.sync_interval = ep->qos.bcast.sync_interval; + iso_qos.bcast.packing = ep->qos.bcast.packing; + iso_qos.bcast.framing = ep->qos.bcast.framing; + iso_qos.bcast.encryption = ep->qos.bcast.encryption; + if (ep->qos.bcast.bcode) + memcpy(iso_qos.bcast.bcode, ep->qos.bcast.bcode->iov_base, 16); + iso_qos.bcast.options = ep->qos.bcast.options; + iso_qos.bcast.skip = ep->qos.bcast.skip; + iso_qos.bcast.sync_timeout = ep->qos.bcast.sync_timeout; + iso_qos.bcast.sync_cte_type = ep->qos.bcast.sync_cte_type; + iso_qos.bcast.mse = ep->qos.bcast.mse; + iso_qos.bcast.timeout = ep->qos.bcast.timeout; + memcpy(&iso_qos.bcast.out, &ep->qos.bcast.io_qos, + sizeof(struct bt_iso_io_qos)); +done: + if (ep) + bap_connect_io_broadcast(data, ep, stream, &iso_qos); + else + bap_listen_io(data, stream, &iso_qos); +} + +static void bap_create_io(struct bap_data *data, struct bap_ep *ep, + struct bt_bap_stream *stream, int defer) +{ + DBG("ep %p stream %p defer %s", ep, stream, defer ? "true" : "false"); + + if (!data->streams) + data->streams = queue_new(); + + if (!queue_find(data->streams, NULL, stream)) + queue_push_tail(data->streams, stream); + + switch (bt_bap_stream_get_type(stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + bap_create_ucast_io(data, ep, stream, defer); + break; + case BT_BAP_STREAM_TYPE_BCAST: + bap_create_bcast_io(data, ep, stream, defer); + break; + } +} + static void bap_state(struct bt_bap_stream *stream, uint8_t old_state, uint8_t new_state, void *user_data) { @@ -968,9 +1321,10 @@ switch (new_state) { case BT_BAP_STREAM_STATE_IDLE: /* Release stream if idle */ - if (ep) + if (ep) { bap_io_close(ep); - else + ep->stream = NULL; + } else queue_remove(data->streams, stream); break; case BT_BAP_STREAM_STATE_CONFIG: @@ -983,12 +1337,16 @@ } - /* Wait QoS response to respond */ - ep->id = bt_bap_stream_qos(stream, &ep->qos, qos_cb, - ep); - if (!ep->id) { - error("Failed to Configure QoS"); - bt_bap_stream_release(stream, NULL, NULL); + if (bt_bap_stream_get_type(stream) == + BT_BAP_STREAM_TYPE_UCAST) { + /* Wait QoS response to respond */ + ep->id = bt_bap_stream_qos(stream, &ep->qos, + qos_cb, ep); + if (!ep->id) { + error("Failed to Configure QoS"); + bt_bap_stream_release(stream, + NULL, NULL); + } } } break; @@ -999,6 +1357,13 @@ if (ep) bap_create_io(data, ep, stream, false); break; + case BT_BAP_STREAM_STATE_STREAMING: + if (bt_bap_stream_get_type(stream) == + BT_BAP_STREAM_TYPE_BCAST) { + if (ep) + bap_create_io(data, ep, stream, false); + } + break; } } @@ -1018,12 +1383,24 @@ bt_bap_foreach_pac(data->bap, BT_BAP_SINK, pac_found, service); } -static bool ep_match_rpac(const void *data, const void *match_data) +static void pac_added_broadcast(struct bt_bap_pac *pac, void *user_data) +{ + struct bap_data *data = user_data; + + if (bt_bap_pac_get_type(pac) == BT_BAP_BCAST_SOURCE) { + DBG("pac %p", pac); + + bt_bap_foreach_pac(data->bap, BT_BAP_BCAST_SOURCE, + pac_found_bcast, data); + } +} + +static bool ep_match_pac(const void *data, const void *match_data) { const struct bap_ep *ep = data; const struct bt_bap_pac *pac = match_data; - return ep->rpac == pac; + return ep->rpac == pac || ep->lpac == pac; } static void pac_removed(struct bt_bap_pac *pac, void *user_data) @@ -1051,7 +1428,39 @@ return; } - ep = queue_remove_if(queue, ep_match_rpac, pac); + ep = queue_remove_if(queue, ep_match_pac, pac); + if (!ep) + return; + + ep_unregister(ep); +} + +static void pac_removed_broadcast(struct bt_bap_pac *pac, void *user_data) +{ + struct btd_service *service = user_data; + struct bap_data *data; + struct queue *queue; + struct bap_ep *ep; + + DBG("pac %p", pac); + + data = btd_service_get_user_data(service); + + switch (bt_bap_pac_get_type(pac)) { + case BT_BAP_SINK: + queue = data->srcs; + break; + case BT_BAP_SOURCE: + queue = data->snks; + break; + case BT_BAP_BCAST_SOURCE: + queue = data->bcast; + break; + default: + return; + } + + ep = queue_remove_if(queue, ep_match_pac, pac); if (!ep) return; @@ -1066,6 +1475,7 @@ data->device = device; data->srcs = queue_new(); data->snks = queue_new(); + data->bcast = queue_new(); return data; } @@ -1098,6 +1508,28 @@ return bdata->bap == bap; } +static bool match_data_bap_data(const void *data, const void *match_data) +{ + const struct bap_data *bdata = data; + const struct btd_adapter *adapter = match_data; + + return bdata->user_data == adapter; +} + +static bool io_get_qos(GIOChannel *io, struct bt_iso_qos *qos) +{ + GError *err = NULL; + bool ret; + + ret = bt_io_get(io, &err, BT_IO_OPT_QOS, qos, BT_IO_OPT_INVALID); + if (!ret) { + error("%s", err->message); + g_error_free(err); + } + + return ret; +} + static void bap_connecting(struct bt_bap_stream *stream, bool state, int fd, void *user_data) { @@ -1122,26 +1554,43 @@ g_io_channel_set_close_on_unref(io, FALSE); - /* Attempt to get CIG/CIS if they have not been set */ - if (ep->qos.cig_id == BT_ISO_QOS_CIG_UNSET || - ep->qos.cis_id == BT_ISO_QOS_CIS_UNSET) { - struct bt_iso_qos qos; - GError *err = NULL; - - if (!bt_io_get(io, &err, BT_IO_OPT_QOS, &qos, - BT_IO_OPT_INVALID)) { - error("%s", err->message); - g_error_free(err); - g_io_channel_unref(io); - return; + switch (bt_bap_stream_get_type(ep->stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + /* Attempt to get CIG/CIS if they have not been set */ + if (ep->qos.ucast.cig_id == BT_ISO_QOS_CIG_UNSET || + ep->qos.ucast.cis_id == BT_ISO_QOS_CIS_UNSET) { + struct bt_iso_qos qos; + + if (!io_get_qos(io, &qos)) { + g_io_channel_unref(io); + return; + } + + ep->qos.ucast.cig_id = qos.ucast.cig; + ep->qos.ucast.cis_id = qos.ucast.cis; } - ep->qos.cig_id = qos.cig; - ep->qos.cis_id = qos.cis; - } + DBG("stream %p fd %d: CIG 0x%02x CIS 0x%02x", stream, fd, + ep->qos.ucast.cig_id, ep->qos.ucast.cis_id); + break; + case BT_BAP_STREAM_TYPE_BCAST: + /* Attempt to get BIG/BIS if they have not been set */ + if (ep->qos.bcast.big == BT_ISO_QOS_BIG_UNSET || + ep->qos.bcast.bis == BT_ISO_QOS_BIS_UNSET) { + struct bt_iso_qos qos; + + if (!io_get_qos(io, &qos)) { + g_io_channel_unref(io); + return; + } + + ep->qos.bcast.big = qos.bcast.big; + ep->qos.bcast.bis = qos.bcast.bis; + } - DBG("stream %p fd %d: CIG 0x%02x CIS 0x%02x", stream, fd, - ep->qos.cig_id, ep->qos.cis_id); + DBG("stream %p fd %d: BIG 0x%02x BIS 0x%02x", stream, fd, + ep->qos.bcast.big, ep->qos.bcast.bis); + } } static void bap_attached(struct bt_bap *bap, void *user_data) @@ -1235,8 +1684,8 @@ NULL); data->state_id = bt_bap_state_register(data->bap, bap_state, bap_connecting, data, NULL); - data->pac_id = bt_bap_pac_register(pac_added, pac_removed, service, - NULL); + data->pac_id = bt_bap_pac_register(data->bap, pac_added, pac_removed, + service, NULL); bt_bap_set_user_data(data->bap, service); @@ -1289,6 +1738,67 @@ return 0; } +static int bap_adapter_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct btd_gatt_database *database = btd_adapter_get_database(adapter); + struct bap_data *data; + char addr[18]; + + ba2str(btd_adapter_get_address(adapter), addr); + DBG("%s", addr); + + if (!btd_kernel_experimental_enabled(ISO_SOCKET_UUID)) { + error("BAP requires ISO Socket which is not enabled"); + return -ENOTSUP; + } + + data = bap_data_new(NULL); + data->adapter = adapter; + + data->bap = bt_bap_new(btd_gatt_database_get_db(database), + btd_gatt_database_get_db(database)); + if (!data->bap) { + error("Unable to create BAP instance"); + free(data); + return -EINVAL; + } + + bap_data_add(data); + + if (!bt_bap_attach_broadcast(data->bap)) { + error("BAP unable to attach"); + return -EINVAL; + } + + data->state_id = bt_bap_state_register(data->bap, bap_state, + bap_connecting, data, NULL); + data->pac_id = bt_bap_pac_register(data->bap, pac_added_broadcast, + pac_removed_broadcast, data, NULL); + + bt_bap_set_user_data(data->bap, adapter); + bap_data_set_user_data(data, adapter); + return 0; +} + +static void bap_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct bap_data *data = queue_find(sessions, match_data_bap_data, + adapter); + char addr[18]; + + ba2str(btd_adapter_get_address(adapter), addr); + DBG("%s", addr); + + if (!data) { + error("BAP service not handled by profile"); + return; + } + + bap_data_remove(data); +} + static struct btd_profile bap_profile = { .name = "bap", .priority = BTD_PROFILE_PRIORITY_MEDIUM, @@ -1297,18 +1807,22 @@ .device_remove = bap_remove, .accept = bap_accept, .disconnect = bap_disconnect, + .adapter_probe = bap_adapter_probe, + .adapter_remove = bap_adapter_remove, + .auto_connect = false, + .experimental = true, }; static unsigned int bap_id = 0; static int bap_init(void) { - if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { - warn("D-Bus experimental not enabled"); - return -ENOTSUP; - } + int err; + + err = btd_profile_register(&bap_profile); + if (err) + return err; - btd_profile_register(&bap_profile); bap_id = bt_bap_register(bap_attached, bap_detached, NULL); return 0; @@ -1316,10 +1830,8 @@ static void bap_exit(void) { - if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) { - btd_profile_unregister(&bap_profile); - bt_bap_unregister(bap_id); - } + btd_profile_unregister(&bap_profile); + bt_bap_unregister(bap_id); } BLUETOOTH_PLUGIN_DEFINE(bap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, diff -Nru bluez-5.66/profiles/audio/bass.c bluez-5.68/profiles/audio/bass.c --- bluez-5.66/profiles/audio/bass.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/profiles/audio/bass.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright 2023 NXP + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/dbus-common.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/gatt-server.h" +#include "src/shared/bass.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/gatt-database.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" +#include "src/error.h" + +#define BASS_UUID_STR "0000184f-0000-1000-8000-00805f9b34fb" + +struct bass_data { + struct btd_device *device; + struct btd_service *service; + struct bt_bass *bass; +}; + +static struct queue *sessions; + +static void bass_debug(const char *str, void *user_data) +{ + DBG_IDX(0xffff, "%s", str); +} + +static struct bass_data *bass_data_new(struct btd_device *device) +{ + struct bass_data *data; + + data = new0(struct bass_data, 1); + data->device = device; + + return data; +} + +static void bass_data_add(struct bass_data *data) +{ + DBG("data %p", data); + + if (queue_find(sessions, NULL, data)) { + error("data %p already added", data); + return; + } + + bt_bass_set_debug(data->bass, bass_debug, NULL, NULL); + + if (!sessions) + sessions = queue_new(); + + queue_push_tail(sessions, data); + + if (data->service) + btd_service_set_user_data(data->service, data); +} + +static bool match_data(const void *data, const void *match_data) +{ + const struct bass_data *bdata = data; + const struct bt_bass *bass = match_data; + + return bdata->bass == bass; +} + +static void bass_data_free(struct bass_data *data) +{ + if (data->service) { + btd_service_set_user_data(data->service, NULL); + bt_bass_set_user_data(data->bass, NULL); + } + + bt_bass_unref(data->bass); + free(data); +} + +static void bass_data_remove(struct bass_data *data) +{ + DBG("data %p", data); + + if (!queue_remove(sessions, data)) + return; + + bass_data_free(data); + + if (queue_isempty(sessions)) { + queue_destroy(sessions, NULL); + sessions = NULL; + } +} + +static void bass_detached(struct bt_bass *bass, void *user_data) +{ + struct bass_data *data; + + DBG("%p", bass); + + data = queue_find(sessions, match_data, bass); + if (!data) { + error("Unable to find bass session"); + return; + } + + /* If there is a service it means there is BASS thus we can keep + * instance allocated. + */ + if (data->service) + return; + + bass_data_remove(data); +} + +static void bass_attached(struct bt_bass *bass, void *user_data) +{ + struct bass_data *data; + struct bt_att *att; + struct btd_device *device; + + DBG("%p", bass); + + data = queue_find(sessions, match_data, bass); + if (data) + return; + + att = bt_bass_get_att(bass); + if (!att) + return; + + device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); + if (!device) { + error("Unable to find device"); + return; + } + + data = bass_data_new(device); + data->bass = bass; + + bass_data_add(data); +} + +static int bass_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct btd_adapter *adapter = device_get_adapter(device); + struct btd_gatt_database *database = btd_adapter_get_database(adapter); + struct bass_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + /* Ignore, if we were probed for this device already */ + if (data) { + error("Profile probed twice for the same device!"); + return -EINVAL; + } + + data = bass_data_new(device); + data->service = service; + + data->bass = bt_bass_new(btd_gatt_database_get_db(database), + btd_device_get_gatt_db(device)); + if (!data->bass) { + error("Unable to create BASS instance"); + free(data); + return -EINVAL; + } + + bass_data_add(data); + bt_bass_set_user_data(data->bass, service); + + return 0; +} + +static void bass_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct bass_data *data; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + data = btd_service_get_user_data(service); + if (!data) { + error("BASS service not handled by profile"); + return; + } + + bass_data_remove(data); +} +static int bass_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + struct bass_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + if (!data) { + error("BASS service not handled by profile"); + return -EINVAL; + } + + if (!bt_bass_attach(data->bass, client)) { + error("BASS unable to attach"); + return -EINVAL; + } + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static int bass_disconnect(struct btd_service *service) +{ + struct bass_data *data = btd_service_get_user_data(service); + struct btd_device *device = btd_service_get_device(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + bt_bass_detach(data->bass); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static struct btd_profile bass_service = { + .name = "bass", + .priority = BTD_PROFILE_PRIORITY_MEDIUM, + .remote_uuid = BASS_UUID_STR, + .device_probe = bass_probe, + .device_remove = bass_remove, + .accept = bass_accept, + .disconnect = bass_disconnect, + .experimental = true, +}; + +static unsigned int bass_id; + +static int bass_init(void) +{ + int err; + + err = btd_profile_register(&bass_service); + if (err) + return err; + + bass_id = bt_bass_register(bass_attached, bass_detached, NULL); + + return 0; +} + +static void bass_exit(void) +{ + btd_profile_unregister(&bass_service); + bt_bass_unregister(bass_id); +} + +BLUETOOTH_PLUGIN_DEFINE(bass, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + bass_init, bass_exit) diff -Nru bluez-5.66/profiles/audio/csip.c bluez-5.68/profiles/audio/csip.c --- bluez-5.66/profiles/audio/csip.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/profiles/audio/csip.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/dbus-common.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/gatt-server.h" +#include "src/shared/csip.h" + +#include "btio/btio.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/gatt-database.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" +#include "src/error.h" +#include "src/btd.h" + +#define CSIS_UUID_STR "00001846-0000-1000-8000-00805f9b34fb" + +struct csip_data { + struct btd_device *device; + struct btd_service *service; + struct bt_csip *csip; + unsigned int ready_id; +}; + +static struct queue *sessions; + +static void csip_debug(const char *str, void *user_data) +{ + DBG_IDX(0xffff, "%s", str); +} + +static struct csip_data *csip_data_new(struct btd_device *device) +{ + struct csip_data *data; + + data = new0(struct csip_data, 1); + data->device = device; + + return data; +} + +static bool csip_ltk_read(struct bt_csip *csip, uint8_t k[16], void *user_data) +{ + /* TODO: Retrieve LTK using device object */ + return false; +} + +static void csip_data_add(struct csip_data *data) +{ + DBG("data %p", data); + + if (queue_find(sessions, NULL, data)) { + error("data %p already added", data); + return; + } + + bt_csip_set_debug(data->csip, csip_debug, NULL, NULL); + + bt_csip_set_sirk(data->csip, btd_opts.csis.encrypt, btd_opts.csis.sirk, + btd_opts.csis.size, btd_opts.csis.rank, + csip_ltk_read, data); + + if (!sessions) + sessions = queue_new(); + + queue_push_tail(sessions, data); + + if (data->service) + btd_service_set_user_data(data->service, data); +} + +static int csip_disconnect(struct btd_service *service) +{ + struct csip_data *data = btd_service_get_user_data(service); + + bt_csip_detach(data->csip); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static bool match_data(const void *data, const void *match_data) +{ + const struct csip_data *vdata = data; + const struct bt_csip *csip = match_data; + + return vdata->csip == csip; +} + +static void csip_data_free(struct csip_data *data) +{ + if (data->service) { + btd_service_set_user_data(data->service, NULL); + bt_csip_set_user_data(data->csip, NULL); + } + + bt_csip_ready_unregister(data->csip, data->ready_id); + bt_csip_unref(data->csip); + free(data); +} + +static void csip_data_remove(struct csip_data *data) +{ + DBG("data %p", data); + + if (!queue_remove(sessions, data)) + return; + + csip_data_free(data); + + if (queue_isempty(sessions)) { + queue_destroy(sessions, NULL); + sessions = NULL; + } +} + +static void csip_detached(struct bt_csip *csip, void *user_data) +{ + struct csip_data *data; + + DBG("%p", csip); + + data = queue_find(sessions, match_data, csip); + if (!data) { + error("Unable to find csip session"); + return; + } + + /* If there is a service it means there is CSIS thus we can keep + * instance allocated. + */ + if (data->service) + return; + + csip_data_remove(data); +} + +static void csip_attached(struct bt_csip *csip, void *user_data) +{ + struct csip_data *data; + struct bt_att *att; + struct btd_device *device; + + DBG("%p", csip); + + data = queue_find(sessions, match_data, csip); + if (data) + return; + + att = bt_csip_get_att(csip); + if (!att) + return; + + device = btd_adapter_find_device_by_fd(bt_att_get_fd(att)); + if (!device) { + error("Unable to find device"); + return; + } + + data = csip_data_new(device); + data->csip = csip; + + csip_data_add(data); + +} + +static int csip_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct btd_gatt_database *database = btd_adapter_get_database(adapter); + + DBG("CSIP path %s", adapter_get_path(adapter)); + + bt_csip_add_db(btd_gatt_database_get_db(database)); + + return 0; +} + +static void csip_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + DBG("CSIP remove Adapter"); +} + +static int csip_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + struct csip_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + if (!data) { + error("CSIP service not handled by profile"); + return -EINVAL; + } + + if (!bt_csip_attach(data->csip, client)) { + error("CSIP unable to attach"); + return -EINVAL; + } + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static void csip_ready(struct bt_csip *csip, void *user_data) +{ + struct btd_service *service = user_data; + struct btd_device *device = btd_service_get_device(service); + uint8_t type, size, rank; + uint8_t k[16]; + + DBG("csip %p", csip); + + if (!bt_csip_get_sirk(csip, &type, k, &size, &rank)) { + error("Unable to read SIRK"); + return; + } + + btd_device_add_set(device, type == BT_CSIP_SIRK_ENCRYPT ? true : false, + k, size, rank); +} + +static int csip_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct btd_adapter *adapter = device_get_adapter(device); + struct btd_gatt_database *database = btd_adapter_get_database(adapter); + struct csip_data *data = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + /* Ignore, if we were probed for this device already */ + if (data) { + error("Profile probed twice for the same device!"); + return -EINVAL; + } + + data = csip_data_new(device); + data->service = service; + + data->csip = bt_csip_new(btd_gatt_database_get_db(database), + btd_device_get_gatt_db(device)); + if (!data->csip) { + error("Unable to create CSIP instance"); + free(data); + return -EINVAL; + } + + csip_data_add(data); + + data->ready_id = bt_csip_ready_register(data->csip, csip_ready, service, + NULL); + + bt_csip_set_user_data(data->csip, service); + + return 0; +} + +static void csip_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct csip_data *data; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("%s", addr); + + data = btd_service_get_user_data(service); + if (!data) { + error("CSIP service not handled by profile"); + return; + } + + csip_data_remove(data); +} + +static struct btd_profile csip_profile = { + .name = "csip", + .priority = BTD_PROFILE_PRIORITY_MEDIUM, + .remote_uuid = CSIS_UUID_STR, + + .device_probe = csip_probe, + .device_remove = csip_remove, + + .accept = csip_accept, + .disconnect = csip_disconnect, + + .adapter_probe = csip_server_probe, + .adapter_remove = csip_server_remove, + + .experimental = true, +}; + +static unsigned int csip_id; + +static int csip_init(void) +{ + int err; + + err = btd_profile_register(&csip_profile); + if (err) + return err; + + csip_id = bt_csip_register(csip_attached, csip_detached, NULL); + + return 0; +} + +static void csip_exit(void) +{ + btd_profile_unregister(&csip_profile); + bt_csip_unregister(csip_id); +} + +BLUETOOTH_PLUGIN_DEFINE(csip, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + csip_init, csip_exit) diff -Nru bluez-5.66/profiles/audio/mcp.c bluez-5.68/profiles/audio/mcp.c --- bluez-5.66/profiles/audio/mcp.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/profiles/audio/mcp.c 2023-06-30 08:10:20.000000000 +0000 @@ -403,27 +403,18 @@ .adapter_probe = media_control_server_probe, .adapter_remove = media_control_server_remove, + + .experimental = true, }; static int mcp_init(void) { - DBG(""); - - if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { - warn("D-Bus experimental not enabled"); - return -ENOTSUP; - } - - btd_profile_register(&mcp_profile); - return 0; + return btd_profile_register(&mcp_profile); } static void mcp_exit(void) { - DBG(""); - - if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) - btd_profile_unregister(&mcp_profile); + btd_profile_unregister(&mcp_profile); } BLUETOOTH_PLUGIN_DEFINE(mcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, diff -Nru bluez-5.66/profiles/audio/media.c bluez-5.68/profiles/audio/media.c --- bluez-5.66/profiles/audio/media.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/profiles/audio/media.c 2023-06-30 08:10:20.000000000 +0000 @@ -6,7 +6,7 @@ * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. - * + * Copyright 2023 NXP * */ @@ -23,6 +23,7 @@ #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/uuid.h" +#include "lib/mgmt.h" #include "gdbus/gdbus.h" @@ -86,15 +87,18 @@ struct media_endpoint { struct a2dp_sep *sep; struct bt_bap_pac *pac; - void *stream; char *sender; /* Endpoint DBus bus id */ char *path; /* Endpoint object path */ char *uuid; /* Endpoint property UUID */ uint8_t codec; /* Endpoint codec */ + uint16_t cid; /* Endpoint company ID */ + uint16_t vid; /* Endpoint vendor codec ID */ bool delay_reporting;/* Endpoint delay_reporting */ struct bt_bap_pac_qos qos; /* Endpoint qos */ uint8_t *capabilities; /* Endpoint property capabilities */ size_t size; /* Endpoint capabilities size */ + uint8_t *metadata; /* Endpoint property metadata */ + size_t metadata_size; /* Endpoint metadata size */ guint hs_watch; guint ag_watch; guint watch; @@ -178,6 +182,7 @@ g_dbus_remove_watch(btd_get_dbus_connection(), endpoint->watch); g_free(endpoint->capabilities); + g_free(endpoint->metadata); g_free(endpoint->sender); g_free(endpoint->path); g_free(endpoint->uuid); @@ -725,28 +730,28 @@ void *user_data; }; -static int parse_array(DBusMessageIter *iter, struct iovec **iov) +static int parse_array(DBusMessageIter *iter, struct iovec *iov) { DBusMessageIter array; if (!iov) return 0; - if (!(*iov)) - *iov = new0(struct iovec, 1); - dbus_message_iter_recurse(iter, &array); - dbus_message_iter_get_fixed_array(&array, &(*iov)->iov_base, - (int *)&(*iov)->iov_len); + dbus_message_iter_get_fixed_array(&array, &iov->iov_base, + (int *)&iov->iov_len); return 0; } -static int parse_select_properties(DBusMessageIter *props, struct iovec **caps, - struct iovec **metadata, +static int parse_select_properties(DBusMessageIter *props, struct iovec *caps, + struct iovec *metadata, struct bt_bap_qos *qos) { const char *key; + struct bt_bap_io_qos io_qos; + uint8_t framing = 0; + memset(&io_qos, 0, sizeof(io_qos)); while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter value, entry; int var; @@ -775,17 +780,17 @@ if (var != DBUS_TYPE_BYTE) goto fail; - dbus_message_iter_get_basic(&value, &qos->cig_id); + dbus_message_iter_get_basic(&value, &qos->ucast.cig_id); } else if (!strcasecmp(key, "CIS")) { if (var != DBUS_TYPE_BYTE) goto fail; - dbus_message_iter_get_basic(&value, &qos->cis_id); + dbus_message_iter_get_basic(&value, &qos->ucast.cis_id); } else if (!strcasecmp(key, "Interval")) { if (var != DBUS_TYPE_UINT32) goto fail; - dbus_message_iter_get_basic(&value, &qos->interval); + dbus_message_iter_get_basic(&value, &io_qos.interval); } else if (!strcasecmp(key, "Framing")) { dbus_bool_t val; @@ -794,7 +799,7 @@ dbus_message_iter_get_basic(&value, &val); - qos->framing = val; + framing = val; } else if (!strcasecmp(key, "PHY")) { const char *str; @@ -804,52 +809,49 @@ dbus_message_iter_get_basic(&value, &str); if (!strcasecmp(str, "1M")) - qos->phy = 0x01; + io_qos.phy = 0x01; else if (!strcasecmp(str, "2M")) - qos->phy = 0x02; + io_qos.phy = 0x02; else goto fail; } else if (!strcasecmp(key, "SDU")) { if (var != DBUS_TYPE_UINT16) goto fail; - dbus_message_iter_get_basic(&value, &qos->sdu); + dbus_message_iter_get_basic(&value, &io_qos.sdu); } else if (!strcasecmp(key, "Retransmissions")) { if (var != DBUS_TYPE_BYTE) goto fail; - dbus_message_iter_get_basic(&value, &qos->rtn); + dbus_message_iter_get_basic(&value, &io_qos.rtn); } else if (!strcasecmp(key, "Latency")) { if (var != DBUS_TYPE_UINT16) goto fail; - dbus_message_iter_get_basic(&value, &qos->latency); + dbus_message_iter_get_basic(&value, &io_qos.latency); } else if (!strcasecmp(key, "Delay")) { if (var != DBUS_TYPE_UINT32) goto fail; - dbus_message_iter_get_basic(&value, &qos->delay); + dbus_message_iter_get_basic(&value, &qos->ucast.delay); } else if (!strcasecmp(key, "TargetLatency")) { if (var != DBUS_TYPE_BYTE) goto fail; dbus_message_iter_get_basic(&value, - &qos->target_latency); + &qos->ucast.target_latency); } dbus_message_iter_next(props); } + memcpy(&qos->ucast.io_qos, &io_qos, sizeof(io_qos)); + qos->ucast.framing = framing; return 0; fail: DBG("Failed parsing %s", key); - if (*caps) { - free(*caps); - *caps = NULL; - } - return -EINVAL; } @@ -859,7 +861,7 @@ struct pac_select_data *data = user_data; DBusMessageIter *iter = ret; int err; - struct iovec *caps = NULL, *metadata = NULL; + struct iovec caps, meta; struct bt_bap_qos qos; if (!ret) { @@ -878,15 +880,18 @@ memset(&qos, 0, sizeof(qos)); /* Mark CIG and CIS to be auto assigned */ - qos.cig_id = BT_ISO_QOS_CIG_UNSET; - qos.cis_id = BT_ISO_QOS_CIS_UNSET; + qos.ucast.cig_id = BT_ISO_QOS_CIG_UNSET; + qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET; + + memset(&caps, 0, sizeof(caps)); + memset(&meta, 0, sizeof(meta)); - err = parse_select_properties(iter, &caps, &metadata, &qos); + err = parse_select_properties(iter, &caps, &meta, &qos); if (err < 0) DBG("Unable to parse properties"); done: - data->cb(data->pac, err, caps, metadata, &qos, data->user_data); + data->cb(data->pac, err, &caps, &meta, &qos, data->user_data); } static int pac_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, @@ -901,6 +906,7 @@ DBusMessage *msg; DBusMessageIter iter, dict; const char *key = "Capabilities"; + uint32_t loc; bt_bap_pac_get_codec(rpac, NULL, &caps, &metadata); if (!caps) @@ -932,6 +938,11 @@ DBUS_TYPE_BYTE, &caps->iov_base, caps->iov_len); + loc = bt_bap_pac_get_locations(rpac); + if (loc) + g_dbus_dict_append_entry(&dict, "Location", DBUS_TYPE_UINT32, + &loc); + if (metadata) { key = "Metadata"; g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key, @@ -978,23 +989,20 @@ static int transport_cmp(gconstpointer data, gconstpointer user_data) { const struct media_transport *transport = data; - const char *path = user_data; - if (g_str_has_prefix(media_transport_get_path((void *)transport), path)) + if (media_transport_get_stream((void *)transport) == user_data) return 0; return -1; } static struct media_transport *find_transport(struct media_endpoint *endpoint, - const char *path) + void *stream) { GSList *match; - if (!path) - return NULL; - - match = g_slist_find_custom(endpoint->transports, path, transport_cmp); + match = g_slist_find_custom(endpoint->transports, stream, + transport_cmp); if (match == NULL) return NULL; @@ -1006,13 +1014,64 @@ { struct pac_config_data *data = user_data; gboolean *ret_value = ret; + struct media_transport *transport; - if (ret_value) - endpoint->stream = data->stream; + /* If transport was cleared, configuration was cancelled */ + transport = find_transport(endpoint, data->stream); + if (!transport) + return; data->cb(data->stream, ret_value ? 0 : -EINVAL); } +static struct media_transport *pac_ucast_config(struct bt_bap_stream *stream, + struct iovec *cfg, + struct media_endpoint *endpoint) +{ + struct bt_bap *bap = bt_bap_stream_get_session(stream); + struct btd_service *service = bt_bap_get_user_data(bap); + struct btd_device *device; + const char *path; + + if (service) + device = btd_service_get_device(service); + else { + struct bt_att *att = bt_bap_get_att(bap); + int fd = bt_att_get_fd(att); + + device = btd_adapter_find_device_by_fd(fd); + } + + if (!device) { + error("Unable to find device"); + return NULL; + } + + path = bt_bap_stream_get_user_data(stream); + + return media_transport_create(device, path, cfg->iov_base, cfg->iov_len, + endpoint, stream); +} + +static struct media_transport *pac_bcast_config(struct bt_bap_stream *stream, + struct iovec *cfg, + struct media_endpoint *endpoint) +{ + struct bt_bap *bap = bt_bap_stream_get_session(stream); + struct btd_adapter *adapter = bt_bap_get_user_data(bap); + const char *path; + + if (!adapter) { + error("Unable to find adapter"); + return NULL; + } + + path = bt_bap_stream_get_user_data(stream); + + return media_transport_create(NULL, path, cfg->iov_base, cfg->iov_len, + endpoint, stream); +} + static int pac_config(struct bt_bap_stream *stream, struct iovec *cfg, struct bt_bap_qos *qos, bt_bap_pac_config_t cb, void *user_data) @@ -1025,38 +1084,24 @@ DBusMessageIter iter; const char *path; - path = bt_bap_stream_get_user_data(stream); - - DBG("endpoint %p path %s", endpoint, path); + DBG("endpoint %p stream %p", endpoint, stream); - transport = find_transport(endpoint, path); + transport = find_transport(endpoint, stream); if (!transport) { - struct bt_bap *bap = bt_bap_stream_get_session(stream); - struct btd_service *service = bt_bap_get_user_data(bap); - struct btd_device *device; - - if (service) - device = btd_service_get_device(service); - else { - struct bt_att *att = bt_bap_get_att(bap); - int fd = bt_att_get_fd(att); - - device = btd_adapter_find_device_by_fd(fd); - } - - if (!device) { - error("Unable to find device"); - return -EINVAL; + switch (bt_bap_stream_get_type(stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + transport = pac_ucast_config(stream, cfg, endpoint); + break; + case BT_BAP_STREAM_TYPE_BCAST: + transport = pac_bcast_config(stream, cfg, endpoint); + break; } - transport = media_transport_create(device, path, cfg->iov_base, - cfg->iov_len, endpoint, - stream); if (!transport) return -EINVAL; - path = media_transport_get_path(transport); - bt_bap_stream_set_user_data(stream, (void *)path); + endpoint->transports = g_slist_append(endpoint->transports, + transport); } msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, @@ -1064,7 +1109,7 @@ "SetConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); - media_transport_destroy(transport); + endpoint_remove_transport(endpoint, transport); return FALSE; } @@ -1073,8 +1118,6 @@ data->cb = cb; data->user_data = user_data; - endpoint->transports = g_slist_append(endpoint->transports, transport); - dbus_message_iter_init_append(msg, &iter); path = media_transport_get_path(transport); @@ -1089,11 +1132,13 @@ static void pac_clear(struct bt_bap_stream *stream, void *user_data) { struct media_endpoint *endpoint = user_data; + struct media_transport *transport; - endpoint->stream = NULL; + DBG("endpoint %p stream %p", endpoint, stream); - while (endpoint->transports != NULL) - clear_configuration(endpoint, endpoint->transports->data); + transport = find_transport(endpoint, stream); + if (transport) + clear_configuration(endpoint, transport); } static struct bt_bap_pac_ops pac_ops = { @@ -1113,6 +1158,7 @@ struct btd_gatt_database *database; struct gatt_db *db; struct iovec data; + struct iovec *metadata = NULL; char *name; if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { @@ -1133,23 +1179,36 @@ return false; } + if (!bap_print_cc(endpoint->metadata, endpoint->metadata_size, + bap_debug, NULL)) { + error("Unable to parse endpoint metadata"); + return false; + } + db = btd_gatt_database_get_db(database); data.iov_base = endpoint->capabilities; data.iov_len = endpoint->size; - /* TODO: Add support for metadata */ - if (asprintf(&name, "%s:%s", endpoint->sender, endpoint->path) < 0) { error("Could not allocate name for pac %s:%s", endpoint->sender, endpoint->path); return false; } - endpoint->pac = bt_bap_add_pac(db, name, type, endpoint->codec, - &endpoint->qos, &data, NULL); + /* TODO: Add support for metadata */ + if (endpoint->metadata_size) { + metadata = g_new0(struct iovec, 1); + metadata->iov_base = endpoint->metadata; + metadata->iov_len = endpoint->metadata_size; + } + + endpoint->pac = bt_bap_add_vendor_pac(db, name, type, endpoint->codec, + endpoint->cid, endpoint->vid, &endpoint->qos, + &data, metadata); if (!endpoint->pac) { error("Unable to create PAC"); + free(metadata); return false; } @@ -1158,6 +1217,7 @@ DBG("PAC %s registered", name); free(name); + free(metadata); return true; } @@ -1172,6 +1232,12 @@ return endpoint_init_pac(endpoint, BT_BAP_SOURCE, err); } +static bool endpoint_init_broadcast_source(struct media_endpoint *endpoint, + int *err) +{ + return endpoint_init_pac(endpoint, BT_BAP_BCAST_SOURCE, err); +} + static bool endpoint_properties_exists(const char *uuid, struct btd_device *dev, void *user_data) @@ -1254,8 +1320,11 @@ return true; } -static bool endpoint_supported(struct btd_adapter *adapter) +static bool a2dp_endpoint_supported(struct btd_adapter *adapter) { + if (!btd_adapter_has_settings(adapter, MGMT_SETTING_BREDR)) + return false; + return true; } @@ -1264,6 +1333,21 @@ if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) return false; + if (!btd_adapter_has_settings(adapter, MGMT_SETTING_CIS_CENTRAL | + MGMT_SETTING_CIS_PERIPHERAL)) + return false; + + return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL; +} + +static bool experimental_broadcaster_ep_supported(struct btd_adapter *adapter) +{ + if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) + return false; + + if (!btd_adapter_has_settings(adapter, MGMT_SETTING_ISO_BROADCASTER)) + return false; + return g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL; } @@ -1272,12 +1356,16 @@ bool (*func)(struct media_endpoint *endpoint, int *err); bool (*supported)(struct btd_adapter *adapter); } init_table[] = { - { A2DP_SOURCE_UUID, endpoint_init_a2dp_source, endpoint_supported }, - { A2DP_SINK_UUID, endpoint_init_a2dp_sink, endpoint_supported }, + { A2DP_SOURCE_UUID, endpoint_init_a2dp_source, + a2dp_endpoint_supported }, + { A2DP_SINK_UUID, endpoint_init_a2dp_sink, + a2dp_endpoint_supported }, { PAC_SINK_UUID, endpoint_init_pac_sink, experimental_endpoint_supported }, { PAC_SOURCE_UUID, endpoint_init_pac_source, experimental_endpoint_supported }, + { BAA_SERVICE_UUID, endpoint_init_broadcast_source, + experimental_broadcaster_ep_supported }, }; static struct media_endpoint * @@ -1287,9 +1375,13 @@ const char *uuid, gboolean delay_reporting, uint8_t codec, + uint16_t cid, + uint16_t vid, struct bt_bap_pac_qos *qos, uint8_t *capabilities, int size, + uint8_t *metadata, + int metadata_size, int *err) { struct media_endpoint *endpoint; @@ -1302,6 +1394,8 @@ endpoint->path = g_strdup(path); endpoint->uuid = g_strdup(uuid); endpoint->codec = codec; + endpoint->cid = cid; + endpoint->vid = vid; endpoint->delay_reporting = delay_reporting; if (qos) @@ -1313,6 +1407,12 @@ endpoint->size = size; } + if (metadata_size > 0) { + endpoint->metadata = g_new(uint8_t, metadata_size); + memcpy(endpoint->metadata, metadata, metadata_size); + endpoint->metadata_size = metadata_size; + } + endpoint->adapter = adapter; for (i = 0; i < ARRAY_SIZE(init_table); i++) { @@ -1352,13 +1452,21 @@ return endpoint; } +struct vendor { + uint16_t cid; + uint16_t vid; +} __packed; + static int parse_properties(DBusMessageIter *props, const char **uuid, gboolean *delay_reporting, uint8_t *codec, + uint16_t *cid, uint16_t *vid, struct bt_bap_pac_qos *qos, - uint8_t **capabilities, int *size) + uint8_t **capabilities, int *size, + uint8_t **metadata, int *metadata_size) { gboolean has_uuid = FALSE; gboolean has_codec = FALSE; + struct vendor vendor; while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { const char *key; @@ -1382,6 +1490,12 @@ return -EINVAL; dbus_message_iter_get_basic(&value, codec); has_codec = TRUE; + } else if (strcasecmp(key, "Vendor") == 0) { + if (var != DBUS_TYPE_UINT32) + return -EINVAL; + dbus_message_iter_get_basic(&value, &vendor); + *cid = vendor.cid; + *vid = vendor.vid; } else if (strcasecmp(key, "DelayReporting") == 0) { if (var != DBUS_TYPE_BOOLEAN) return -EINVAL; @@ -1395,6 +1509,15 @@ dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, capabilities, size); + } else if (strcasecmp(key, "Metadata") == 0) { + DBusMessageIter array; + + if (var != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, metadata, + metadata_size); } else if (strcasecmp(key, "Framing") == 0) { if (var != DBUS_TYPE_BYTE) return -EINVAL; @@ -1418,11 +1541,11 @@ } else if (strcasecmp(key, "PreferredMinimumDelay") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; - dbus_message_iter_get_basic(&value, &qos->pd_min); + dbus_message_iter_get_basic(&value, &qos->ppd_min); } else if (strcasecmp(key, "PreferredMaximumDelay") == 0) { if (var != DBUS_TYPE_UINT16) return -EINVAL; - dbus_message_iter_get_basic(&value, &qos->pd_max); + dbus_message_iter_get_basic(&value, &qos->ppd_max); } dbus_message_iter_next(props); @@ -1439,9 +1562,13 @@ const char *sender, *path, *uuid; gboolean delay_reporting = FALSE; uint8_t codec = 0; + uint16_t cid = 0; + uint16_t vid = 0; struct bt_bap_pac_qos qos = {}; uint8_t *capabilities = NULL; + uint8_t *metadata = NULL; int size = 0; + int metadata_size = 0; int err; sender = dbus_message_get_sender(msg); @@ -1458,12 +1585,14 @@ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); - if (parse_properties(&props, &uuid, &delay_reporting, &codec, &qos, - &capabilities, &size) < 0) + if (parse_properties(&props, &uuid, &delay_reporting, &codec, &cid, + &vid, &qos, &capabilities, &size, &metadata, + &metadata_size) < 0) return btd_error_invalid_args(msg); if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, - codec, &qos, capabilities, size, + codec, cid, vid, &qos, capabilities, + size, metadata, metadata_size, &err) == NULL) { if (err == -EPROTONOSUPPORT) return btd_error_not_supported(msg); @@ -2490,9 +2619,12 @@ const char *uuid; gboolean delay_reporting = FALSE; uint8_t codec; + struct vendor vendor; struct bt_bap_pac_qos qos; uint8_t *capabilities = NULL; int size = 0; + uint8_t *metadata = NULL; + int metadata_size = 0; DBusMessageIter iter, array; struct media_endpoint *endpoint; @@ -2519,6 +2651,15 @@ dbus_message_iter_get_basic(&iter, &codec); + memset(&vendor, 0, sizeof(vendor)); + + if (g_dbus_proxy_get_property(proxy, "Vendor", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) + goto fail; + + dbus_message_iter_get_basic(&iter, &vendor); + } + /* DelayReporting and Capabilities are considered optional */ if (g_dbus_proxy_get_property(proxy, "DelayReporting", &iter)) { if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) @@ -2535,6 +2676,15 @@ dbus_message_iter_get_fixed_array(&array, &capabilities, &size); } + if (g_dbus_proxy_get_property(proxy, "Metadata", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&iter, &array); + dbus_message_iter_get_fixed_array(&array, &metadata, + &metadata_size); + } + /* Parse QoS preferences */ memset(&qos, 0, sizeof(qos)); if (g_dbus_proxy_get_property(proxy, "Framing", &iter)) { @@ -2587,8 +2737,11 @@ } endpoint = media_endpoint_create(app->adapter, app->sender, path, uuid, - delay_reporting, codec, &qos, - capabilities, size, &app->err); + delay_reporting, codec, + vendor.cid, vendor.vid, &qos, + capabilities, size, + metadata, metadata_size, + &app->err); if (!endpoint) { error("Unable to register endpoint %s:%s: %s", app->sender, path, strerror(-app->err)); @@ -2726,9 +2879,6 @@ goto reply; } - queue_foreach(app->proxies, app_register_endpoint, app); - queue_foreach(app->proxies, app_register_player, app); - if (app->err) { if (app->err == -EPROTONOSUPPORT) reply = btd_error_not_supported(app->reg); @@ -2772,6 +2922,10 @@ path = g_dbus_proxy_get_path(proxy); DBG("Proxy added: %s, iface: %s", path, iface); + + app_register_endpoint(proxy, app); + app_register_player(proxy, app); + } static bool match_endpoint_by_path(const void *a, const void *b) @@ -3085,3 +3239,9 @@ { return endpoint->codec; } + +struct btd_adapter *media_endpoint_get_btd_adapter( + struct media_endpoint *endpoint) +{ + return endpoint->adapter->btd_adapter; +} diff -Nru bluez-5.66/profiles/audio/media.h bluez-5.68/profiles/audio/media.h --- bluez-5.66/profiles/audio/media.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/profiles/audio/media.h 2023-06-30 08:10:20.000000000 +0000 @@ -20,5 +20,7 @@ struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint); const char *media_endpoint_get_uuid(struct media_endpoint *endpoint); uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint); +struct btd_adapter *media_endpoint_get_btd_adapter( + struct media_endpoint *endpoint); int8_t media_player_get_device_volume(struct btd_device *device); diff -Nru bluez-5.66/profiles/audio/transport.c bluez-5.68/profiles/audio/transport.c --- bluez-5.66/profiles/audio/transport.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/profiles/audio/transport.c 2023-06-30 22:14:43.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright 2023 NXP * * */ @@ -84,18 +85,13 @@ struct bt_bap_stream *stream; unsigned int state_id; bool linked; - uint32_t interval; - uint8_t framing; - uint8_t phy; - uint16_t sdu; - uint8_t rtn; - uint16_t latency; - uint32_t delay; + struct bt_bap_qos qos; }; struct media_transport { char *path; /* Transport object path */ struct btd_device *device; /* Transport device */ + struct btd_adapter *adapter; /* Transport adapter bcast*/ const char *remote_endpoint; /* Transport remote SEP */ struct media_endpoint *endpoint; /* Transport endpoint */ struct media_owner *owner; /* Transport owner */ @@ -116,6 +112,8 @@ guint id); void (*set_state) (struct media_transport *transport, transport_state_t state); + void *(*get_stream) + (struct media_transport *transport); GDestroyNotify destroy; void *data; }; @@ -529,12 +527,19 @@ owner->pending = req; } +static void *get_stream_bap(struct media_transport *transport) +{ + struct bap_transport *bap = transport->data; + + return bap->stream; +} + static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_transport *transport = data; struct media_owner *owner; - struct media_request *req; + struct media_request *req = NULL; guint id; if (transport->owner != NULL) @@ -544,15 +549,25 @@ return btd_error_not_authorized(msg); owner = media_owner_create(msg); + + if (!strcmp(media_endpoint_get_uuid(transport->endpoint), + BAA_SERVICE_UUID)) { + req = media_request_create(msg, 0x00); + media_owner_add(owner, req); + media_transport_set_owner(transport, owner); + } + id = transport->resume(transport, owner); if (id == 0) { media_owner_free(owner); return btd_error_not_authorized(msg); } - req = media_request_create(msg, id); - media_owner_add(owner, req); - media_transport_set_owner(transport, owner); + if (!req) { + req = media_request_create(msg, id); + media_owner_add(owner, req); + media_transport_set_owner(transport, owner); + } return NULL; } @@ -631,7 +646,12 @@ DBusMessageIter *iter, void *data) { struct media_transport *transport = data; - const char *path = device_get_path(transport->device); + const char *path; + + if (transport->device) + path = device_get_path(transport->device); + else + path = adapter_get_path(transport->adapter); dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); @@ -738,15 +758,22 @@ uint16_t arg; int8_t volume; bool notify; + int err; - if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) - goto error; + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Expected UINT16"); + return; + } dbus_message_iter_get_basic(iter, &arg); - if (arg > INT8_MAX) - goto error; - - g_dbus_pending_property_success(id); + if (arg > INT8_MAX) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Volume must not be larger than 127"); + return; + } volume = (int8_t)arg; if (a2dp->volume == volume) @@ -761,12 +788,17 @@ "Volume"); } - avrcp_set_volume(transport->device, volume, notify); - return; + err = avrcp_set_volume(transport->device, volume, notify); + if (err) { + error("avrcp_set_volume returned %s (%d)", strerror(-err), err); + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".Failed", + "Internal error %s (%d)", + strerror(-err), err); + return; + } -error: - g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", - "Invalid arguments in method call"); + g_dbus_pending_property_success(id); } static gboolean endpoint_exists(const GDBusPropertyTable *property, void *data) @@ -815,13 +847,46 @@ { } }; +static gboolean qos_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + return bap->qos.ucast.io_qos.phy != 0x00; +} + +static gboolean get_cig(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, + &bap->qos.ucast.cig_id); + + return TRUE; +} + +static gboolean get_cis(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, + &bap->qos.ucast.cis_id); + + return TRUE; +} + static gboolean get_interval(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; - dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &bap->interval); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, + &bap->qos.ucast.io_qos.interval); return TRUE; } @@ -831,20 +896,33 @@ { struct media_transport *transport = data; struct bap_transport *bap = transport->data; - dbus_bool_t val = bap->framing; + dbus_bool_t val = bap->qos.ucast.framing; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } +static gboolean get_phy(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct bap_transport *bap = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, + &bap->qos.ucast.io_qos.phy); + + return TRUE; +} + static gboolean get_sdu(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct media_transport *transport = data; struct bap_transport *bap = transport->data; - dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &bap->sdu); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, + &bap->qos.ucast.io_qos.sdu); return TRUE; } @@ -855,7 +933,8 @@ struct media_transport *transport = data; struct bap_transport *bap = transport->data; - dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &bap->rtn); + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, + &bap->qos.ucast.io_qos.rtn); return TRUE; } @@ -866,7 +945,8 @@ struct media_transport *transport = data; struct bap_transport *bap = transport->data; - dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &bap->latency); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, + &bap->qos.ucast.io_qos.latency); return TRUE; } @@ -877,7 +957,8 @@ struct media_transport *transport = data; struct bap_transport *bap = transport->data; - dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &bap->delay); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, + &bap->qos.ucast.delay); return TRUE; } @@ -964,12 +1045,15 @@ { "Codec", "y", get_codec }, { "Configuration", "ay", get_configuration }, { "State", "s", get_state }, - { "Interval", "u", get_interval }, - { "Framing", "b", get_framing }, - { "SDU", "q", get_sdu }, - { "Retransmissions", "y", get_retransmissions }, - { "Latency", "q", get_latency }, - { "Delay", "u", get_delay }, + { "CIG", "y", get_cig, NULL, qos_exists }, + { "CIS", "y", get_cis, NULL, qos_exists }, + { "Interval", "u", get_interval, NULL, qos_exists }, + { "Framing", "b", get_framing, NULL, qos_exists }, + { "PHY", "y", get_phy, NULL, qos_exists }, + { "SDU", "q", get_sdu, NULL, qos_exists }, + { "Retransmissions", "y", get_retransmissions, NULL, qos_exists }, + { "Latency", "q", get_latency, NULL, qos_exists }, + { "Delay", "u", get_delay, NULL, qos_exists }, { "Endpoint", "o", get_endpoint, NULL, endpoint_exists }, { "Location", "u", get_location }, { "Metadata", "ay", get_metadata }, @@ -1181,10 +1265,52 @@ DBG("stream %p linked %s", bap->stream, bap->linked ? "true" : "false"); } +static void bap_update_qos(const struct media_transport *transport) +{ + struct bap_transport *bap = transport->data; + struct bt_bap_qos *qos; + + qos = bt_bap_stream_get_qos(bap->stream); + + if (!memcmp(qos, &bap->qos, sizeof(struct bt_bap_qos))) + return; + + bap->qos = *qos; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "CIG"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "CIS"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "Interval"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "Framing"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "PHY"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "SDU"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "Retransmissions"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "Latency"); + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + "Delay"); +} + static guint resume_bap(struct media_transport *transport, struct media_owner *owner) { struct bap_transport *bap = transport->data; + struct iovec *meta; guint id; if (!bap->stream) @@ -1202,7 +1328,8 @@ return g_idle_add(resume_complete, transport); } - id = bt_bap_stream_enable(bap->stream, bap->linked, NULL, + meta = bt_bap_stream_get_metadata(bap->stream); + id = bt_bap_stream_enable(bap->stream, bap->linked, meta, bap_enable_complete, owner); if (!id) return 0; @@ -1323,6 +1450,7 @@ if (owner && owner->pending) return; bap_update_links(transport); + bap_update_qos(transport); transport_update_playing(transport, FALSE); return; case BT_BAP_STREAM_STATE_DISABLING: @@ -1397,12 +1525,7 @@ bap = new0(struct bap_transport, 1); bap->stream = stream; - bap->interval = qos->interval; - bap->framing = qos->framing; - bap->phy = qos->phy; - bap->rtn = qos->rtn; - bap->latency = qos->latency; - bap->delay = qos->delay; + bap->qos = *qos; bap->state_id = bt_bap_state_register(bt_bap_stream_get_session(stream), bap_state_changed, bap_connecting, @@ -1413,6 +1536,7 @@ transport->suspend = suspend_bap; transport->cancel = cancel_bap; transport->set_state = set_state_bap; + transport->get_stream = get_stream_bap; transport->destroy = free_bap; return 0; @@ -1431,15 +1555,26 @@ const GDBusPropertyTable *properties; transport = g_new0(struct media_transport, 1); - transport->device = device; + if (device) + transport->device = device; + else + transport->adapter = media_endpoint_get_btd_adapter(endpoint); + transport->endpoint = endpoint; transport->configuration = g_new(uint8_t, size); memcpy(transport->configuration, configuration, size); transport->size = size; transport->remote_endpoint = remote_endpoint; - transport->path = g_strdup_printf("%s/fd%d", - remote_endpoint ? remote_endpoint : - device_get_path(device), fd++); + + if (device) + transport->path = g_strdup_printf("%s/fd%d", + remote_endpoint ? remote_endpoint : + device_get_path(device), fd++); + else + transport->path = g_strdup_printf("%s/fd%d", + remote_endpoint ? remote_endpoint : + adapter_get_path(transport->adapter), + fd++); transport->fd = -1; uuid = media_endpoint_get_uuid(endpoint); @@ -1452,7 +1587,8 @@ goto fail; properties = a2dp_properties; } else if (!strcasecmp(uuid, PAC_SINK_UUID) || - !strcasecmp(uuid, PAC_SOURCE_UUID)) { + !strcasecmp(uuid, PAC_SOURCE_UUID) || + !strcasecmp(uuid, BAA_SERVICE_UUID)) { if (media_transport_init_bap(transport, stream) < 0) goto fail; properties = bap_properties; @@ -1481,6 +1617,14 @@ return transport->path; } +void *media_transport_get_stream(struct media_transport *transport) +{ + if (transport->get_stream) + return transport->get_stream(transport); + + return NULL; +} + void media_transport_update_delay(struct media_transport *transport, uint16_t delay) { diff -Nru bluez-5.66/profiles/audio/transport.h bluez-5.68/profiles/audio/transport.h --- bluez-5.66/profiles/audio/transport.h 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/profiles/audio/transport.h 2023-06-30 08:10:20.000000000 +0000 @@ -19,6 +19,7 @@ void media_transport_destroy(struct media_transport *transport); const char *media_transport_get_path(struct media_transport *transport); +void *media_transport_get_stream(struct media_transport *transport); struct btd_device *media_transport_get_dev(struct media_transport *transport); int8_t media_transport_get_volume(struct media_transport *transport); void media_transport_update_delay(struct media_transport *transport, diff -Nru bluez-5.66/profiles/audio/vcp.c bluez-5.68/profiles/audio/vcp.c --- bluez-5.66/profiles/audio/vcp.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/profiles/audio/vcp.c 2023-06-30 08:10:20.000000000 +0000 @@ -289,18 +289,20 @@ .adapter_probe = vcp_server_probe, .adapter_remove = vcp_server_remove, + + .experimental = true, }; static unsigned int vcp_id = 0; static int vcp_init(void) { - if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { - warn("D-Bus experimental not enabled"); - return -ENOTSUP; - } + int err; + + err = btd_profile_register(&vcp_profile); + if (err) + return err; - btd_profile_register(&vcp_profile); vcp_id = bt_vcp_register(vcp_attached, vcp_detached, NULL); return 0; @@ -308,10 +310,8 @@ static void vcp_exit(void) { - if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) { - btd_profile_unregister(&vcp_profile); - bt_vcp_unregister(vcp_id); - } + btd_profile_unregister(&vcp_profile); + bt_vcp_unregister(vcp_id); } BLUETOOTH_PLUGIN_DEFINE(vcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, diff -Nru bluez-5.66/profiles/input/hog-lib.c bluez-5.68/profiles/input/hog-lib.c --- bluez-5.66/profiles/input/hog-lib.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/profiles/input/hog-lib.c 2023-06-30 08:10:20.000000000 +0000 @@ -374,6 +374,15 @@ error("bt_uhid_send: %s (%d)", strerror(-err), -err); } +static void report_notify_destroy(void *user_data) +{ + struct report *report = user_data; + + DBG(""); + + report->notifyid = 0; +} + static void report_ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen, gpointer user_data) { @@ -393,7 +402,13 @@ report->notifyid = g_attrib_register(hog->attrib, ATT_OP_HANDLE_NOTIFY, report->value_handle, - report_value_cb, report, NULL); + report_value_cb, report, + report_notify_destroy); + if (!report->notifyid) { + error("Unable to register report notification: handle 0x%04x", + report->value_handle); + goto remove; + } DBG("Report characteristic descriptor written: notifications enabled"); @@ -1798,7 +1813,11 @@ r->notifyid = g_attrib_register(hog->attrib, ATT_OP_HANDLE_NOTIFY, r->value_handle, - report_value_cb, r, NULL); + report_value_cb, r, + report_notify_destroy); + if (!r->notifyid) + error("Unable to register report notification: " + "handle 0x%04x", r->value_handle); } return true; diff -Nru bluez-5.66/src/adapter.c bluez-5.68/src/adapter.c --- bluez-5.66/src/adapter.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/adapter.c 2023-06-30 08:10:20.000000000 +0000 @@ -2192,6 +2192,7 @@ bool empty_uuid = false; bool has_regular_discovery = false; bool has_filtered_discovery = false; + uint8_t adapter_scan_type = get_scan_type(adapter); for (l = adapter->discovery_list; l != NULL; l = g_slist_next(l)) { struct discovery_client *client = l->data; @@ -2202,6 +2203,20 @@ continue; } + /* + * Detect empty filter with only discoverable + * (which does not require a kernel filter) set. + */ + if (item->uuids == NULL && + item->pathloss == DISTANCE_VAL_INVALID && + item->rssi == DISTANCE_VAL_INVALID && + item->type == adapter_scan_type && + item->duplicate == false && + item->pattern == NULL) { + has_regular_discovery = true; + continue; + } + has_filtered_discovery = true; *transport |= item->type; @@ -2251,7 +2266,7 @@ * It there is both regular and filtered scan running, then * clear whole fitler to report all devices. */ - *transport = get_scan_type(adapter); + *transport = adapter_scan_type; *rssi = HCI_RSSI_INVALID; g_slist_free(*uuids); *uuids = NULL; @@ -4387,8 +4402,8 @@ if (dev) { device_set_paired(dev, info->bdaddr_type); device_set_bonded(dev, info->bdaddr_type); - device_set_ltk_enc_size(dev, info->enc_size); - device_set_ltk_enc_size(dev, info->enc_size); + device_set_ltk(dev, info->val, info->central, + info->enc_size); } } @@ -7073,12 +7088,10 @@ return discoverable; } -void btd_adapter_update_found_device(struct btd_adapter *adapter, +void btd_adapter_device_found(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, int8_t rssi, - bool confirm, bool legacy, - bool not_connectable, - bool name_resolve_failed, + uint32_t flags, const uint8_t *data, uint8_t data_len, bool monitoring) { @@ -7087,9 +7100,20 @@ struct eir_data eir_data; bool name_known, discoverable; char addr[18]; + bool confirm; + bool legacy; + bool not_connectable; + bool name_resolve_failed; + bool scan_rsp; bool duplicate = false; struct queue *matched_monitors = NULL; + confirm = (flags & MGMT_DEV_FOUND_CONFIRM_NAME); + legacy = (flags & MGMT_DEV_FOUND_LEGACY_PAIRING); + not_connectable = (flags & MGMT_DEV_FOUND_NOT_CONNECTABLE); + name_resolve_failed = (flags & MGMT_DEV_FOUND_NAME_REQUEST_FAILED); + scan_rsp = (flags & MGMT_DEV_FOUND_SCAN_RSP); + if (!btd_adv_monitor_offload_enabled(adapter->adv_monitor_manager) || (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 22))) { @@ -7122,7 +7146,15 @@ dev = btd_adapter_find_device(adapter, bdaddr, bdaddr_type); if (!dev) { - if (!discoverable && !monitoring) { + /* In case of being just a scan response don't attempt to create + * the device. + */ + if (scan_rsp) { + eir_data_free(&eir_data); + return; + } + + if (!discoverable && !monitoring && !eir_data.rsi) { eir_data_free(&eir_data); return; } @@ -7169,7 +7201,7 @@ /* If there is no matched Adv monitors, don't continue if not * discoverable or if active discovery filter don't match. */ - if (!monitoring && (!discoverable || + if (!eir_data.rsi && !monitoring && (!discoverable || (adapter->filtered_discovery && !is_filter_match( adapter->discovery_list, &eir_data, rssi)))) { eir_data_free(&eir_data); @@ -7302,10 +7334,6 @@ const uint8_t *eir; uint16_t eir_len; uint32_t flags; - bool confirm_name; - bool legacy; - bool not_connectable; - bool name_resolve_failed; char addr[18]; if (length < sizeof(*ev)) { @@ -7327,22 +7355,15 @@ else eir = ev->eir; - flags = btohl(ev->flags); + flags = le32_to_cpu(ev->flags); ba2str(&ev->addr.bdaddr, addr); DBG("hci%u addr %s, rssi %d flags 0x%04x eir_len %u", index, addr, ev->rssi, flags, eir_len); - confirm_name = (flags & MGMT_DEV_FOUND_CONFIRM_NAME); - legacy = (flags & MGMT_DEV_FOUND_LEGACY_PAIRING); - not_connectable = (flags & MGMT_DEV_FOUND_NOT_CONNECTABLE); - name_resolve_failed = (flags & MGMT_DEV_FOUND_NAME_REQUEST_FAILED); - - btd_adapter_update_found_device(adapter, &ev->addr.bdaddr, - ev->addr.type, ev->rssi, confirm_name, - legacy, not_connectable, - name_resolve_failed, eir, eir_len, - false); + btd_adapter_device_found(adapter, &ev->addr.bdaddr, + ev->addr.type, ev->rssi, flags, + eir, eir_len, false); } struct agent *adapter_get_agent(struct btd_adapter *adapter) @@ -7432,6 +7453,12 @@ int btd_register_adapter_driver(struct btd_adapter_driver *driver) { + if (driver->experimental && !(g_dbus_get_flags() & + G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { + DBG("D-Bus experimental not enabled"); + return -ENOTSUP; + } + adapter_drivers = g_slist_append(adapter_drivers, driver); if (driver->probe == NULL) @@ -8657,7 +8684,7 @@ device_set_bonded(device, addr->type); } - device_set_ltk_enc_size(device, ev->key.enc_size); + device_set_ltk(device, ev->key.val, ev->key.central, ev->key.enc_size); bonding_complete(adapter, &addr->bdaddr, addr->type, 0); } @@ -10146,7 +10173,8 @@ } if (missing_settings & MGMT_SETTING_SECURE_CONN) - set_mode(adapter, MGMT_OP_SET_SECURE_CONN, 0x01); + set_mode(adapter, MGMT_OP_SET_SECURE_CONN, + btd_opts.secure_conn); if (adapter->supported_settings & MGMT_SETTING_PRIVACY) set_privacy(adapter, btd_opts.privacy); @@ -10711,6 +10739,14 @@ return false; } +bool btd_adapter_has_settings(struct btd_adapter *adapter, uint32_t settings) +{ + if (!adapter) + return false; + + return (adapter->current_settings & settings) ? true : false; +} + bool btd_has_kernel_features(uint32_t features) { return (kernel_features & features) ? true : false; diff -Nru bluez-5.66/src/adapter.h bluez-5.68/src/adapter.h --- bluez-5.66/src/adapter.h 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/adapter.h 2023-06-30 08:10:20.000000000 +0000 @@ -88,14 +88,12 @@ const char *path); struct btd_device *btd_adapter_find_device_by_fd(int fd); -void btd_adapter_update_found_device(struct btd_adapter *adapter, +void btd_adapter_device_found(struct btd_adapter *adapter, const bdaddr_t *bdaddr, uint8_t bdaddr_type, int8_t rssi, - bool confirm, bool legacy, - bool not_connectable, - bool name_resolve_failed, + uint32_t flags, const uint8_t *data, uint8_t data_len, - bool monitored); + bool monitoring); const char *adapter_get_path(struct btd_adapter *adapter); const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter); @@ -127,6 +125,11 @@ struct btd_device *device); void (*device_resolved)(struct btd_adapter *adapter, struct btd_device *device); + + /* Indicates the driver is experimental and shall only be registered + * when experimental has been enabled (see: main.conf:Experimental). + */ + bool experimental; }; void device_resolved_drivers(struct btd_adapter *adapter, @@ -256,6 +259,8 @@ bool btd_le_connect_before_pairing(void); +bool btd_adapter_has_settings(struct btd_adapter *adapter, uint32_t settings); + enum experimental_features { EXP_FEAT_DEBUG = 1 << 0, EXP_FEAT_LE_SIMULT_ROLES = 1 << 1, diff -Nru bluez-5.66/src/advertising.c bluez-5.68/src/advertising.c --- bluez-5.66/src/advertising.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/advertising.c 2023-06-30 08:10:20.000000000 +0000 @@ -29,11 +29,13 @@ #include "error.h" #include "log.h" #include "eir.h" +#include "btd.h" #include "src/shared/ad.h" #include "src/shared/mgmt.h" #include "src/shared/queue.h" #include "src/shared/timeout.h" #include "src/shared/util.h" +#include "src/shared/crypto.h" #include "advertising.h" #define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1" @@ -459,13 +461,50 @@ return false; } +static bool set_rsi(struct btd_adv_client *client) +{ + struct bt_crypto *crypto; + uint8_t zero[16] = {}; + struct bt_ad_data rsi = { .type = BT_AD_CSIP_RSI }; + uint8_t data[6]; + bool ret; + + /* Check if a valid SIRK has been set */ + if (!memcmp(btd_opts.csis.sirk, zero, sizeof(zero))) + return false; + + /* Check if RSI needs to be set or data already contains RSI data */ + if (!client || bt_ad_has_data(client->data, &rsi)) + return true; + + crypto = bt_crypto_new(); + if (!crypto) + return false; + + ret = bt_crypto_random_bytes(crypto, data + 3, sizeof(data) - 3); + if (!ret) + goto done; + + ret = bt_crypto_sih(crypto, btd_opts.csis.sirk, data + 3, data); + if (!ret) + goto done; + + ret = bt_ad_add_data(client->data, BT_AD_CSIP_RSI, data, sizeof(data)); + +done: + bt_crypto_unref(crypto); + return ret; +} + static struct adv_include { uint8_t flag; const char *name; + bool (*set)(struct btd_adv_client *client); } includes[] = { { MGMT_ADV_FLAG_TX_POWER, "tx-power" }, { MGMT_ADV_FLAG_APPEARANCE, "appearance" }, { MGMT_ADV_FLAG_LOCAL_NAME, "local-name" }, + { 0 , "rsi", set_rsi }, { }, }; @@ -497,6 +536,11 @@ if (strcmp(str, inc->name)) continue; + if (inc->set && inc->set(client)) { + DBG("Including Feature: %s", str); + continue; + } + if (!(client->manager->supported_flags & inc->flag)) continue; @@ -1644,7 +1688,8 @@ struct adv_include *inc; for (inc = includes; inc && inc->name; inc++) { - if (manager->supported_flags & inc->flag) + if ((inc->set && inc->set(NULL)) || + (manager->supported_flags & inc->flag)) dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &inc->name); } diff -Nru bluez-5.66/src/adv_monitor.c bluez-5.68/src/adv_monitor.c --- bluez-5.66/src/adv_monitor.c 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/src/adv_monitor.c 2023-06-30 08:10:20.000000000 +0000 @@ -1583,10 +1583,6 @@ const uint8_t *ad_data = NULL; uint16_t ad_data_len; uint32_t flags; - bool confirm_name; - bool legacy; - bool not_connectable; - bool name_resolve_failed; char addr[18]; if (length < sizeof(*ev)) { @@ -1605,21 +1601,14 @@ if (ad_data_len > 0) ad_data = ev->ad_data; - flags = btohl(ev->flags); + flags = le32_to_cpu(ev->flags); ba2str(&ev->addr.bdaddr, addr); DBG("hci%u addr %s, rssi %d flags 0x%04x ad_data_len %u", index, addr, ev->rssi, flags, ad_data_len); - confirm_name = (flags & MGMT_DEV_FOUND_CONFIRM_NAME); - legacy = (flags & MGMT_DEV_FOUND_LEGACY_PAIRING); - not_connectable = (flags & MGMT_DEV_FOUND_NOT_CONNECTABLE); - name_resolve_failed = (flags & MGMT_DEV_FOUND_NAME_REQUEST_FAILED); - - btd_adapter_update_found_device(adapter, &ev->addr.bdaddr, - ev->addr.type, ev->rssi, confirm_name, - legacy, not_connectable, - name_resolve_failed, ad_data, + btd_adapter_device_found(adapter, &ev->addr.bdaddr, + ev->addr.type, ev->rssi, flags, ad_data, ad_data_len, true); if (handle) { diff -Nru bluez-5.66/src/battery.c bluez-5.68/src/battery.c --- bluez-5.66/src/battery.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/battery.c 2023-06-30 08:10:20.000000000 +0000 @@ -288,6 +288,10 @@ uint8_t percentage; DBusMessageIter iter; + if (strcmp(g_dbus_proxy_get_interface(proxy), + BATTERY_PROVIDER_INTERFACE) != 0) + return; + if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE) { warn("Battery object %s does not specify device path", path); return; @@ -295,10 +299,6 @@ dbus_message_iter_get_basic(&iter, &export_path); - if (strcmp(g_dbus_proxy_get_interface(proxy), - BATTERY_PROVIDER_INTERFACE) != 0) - return; - device = btd_adapter_find_device_by_path(provider->manager->adapter, export_path); if (!device || device_is_temporary(device)) { @@ -341,15 +341,15 @@ const char *export_path; DBusMessageIter iter; + if (strcmp(g_dbus_proxy_get_interface(proxy), + BATTERY_PROVIDER_INTERFACE) != 0) + return; + if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE) return; dbus_message_iter_get_basic(&iter, &export_path); - if (strcmp(g_dbus_proxy_get_interface(proxy), - BATTERY_PROVIDER_INTERFACE) != 0) - return; - DBG("provided battery removed %s", g_dbus_proxy_get_path(proxy)); battery = find_battery_by_path(export_path); diff -Nru bluez-5.66/src/bluetoothd.8 bluez-5.68/src/bluetoothd.8 --- bluez-5.66/src/bluetoothd.8 2022-11-10 20:41:31.000000000 +0000 +++ bluez-5.68/src/bluetoothd.8 2023-06-30 22:21:09.000000000 +0000 @@ -63,20 +63,20 @@ .TP .B \-d, \-\-debug=::... Sets how much information bluetoothd sends to the log destination (usually -syslog\(aqs "daemon" facility). If the file options are omitted, then +syslog\(aqs \(dqdaemon\(dq facility). If the file options are omitted, then debugging information from all the source files are printed. If file options are present, then only debug prints from that source file are -printed. The option can be a pattern containing "*" and "?" characters. +printed. The option can be a pattern containing \(dq*\(dq and \(dq?\(dq characters. .sp Example: \-\-debug=src/adapter.c:src/agent.c .TP .B \-p, \-\-plugin=,,.. -Load these plugins only. The option can be a pattern containing "*" and -"?" characters. +Load these plugins only. The option can be a pattern containing \(dq*\(dq and +\(dq?\(dq characters. .TP .B \-P, \-\-noplugin=,,.. -Never load these plugins. The option can be a pattern containing "*" and -"?" characters. +Never load these plugins. The option can be a pattern containing \(dq*\(dq and +\(dq?\(dq characters. .UNINDENT .INDENT 0.0 .TP diff -Nru bluez-5.66/src/bluetooth.ver bluez-5.68/src/bluetooth.ver --- bluez-5.66/src/bluetooth.ver 2013-12-10 06:59:06.000000000 +0000 +++ bluez-5.68/src/bluetooth.ver 2023-06-30 08:10:20.000000000 +0000 @@ -7,6 +7,14 @@ debug; baswap; ba2str; + /* Don't break LLVM sanitizers */ + __asan*; + __dfsan*; + __lsan*; + __msan*; + __sanitizer*; + __tsan*; + __ubsan*; local: *; }; diff -Nru bluez-5.66/src/btd.h bluez-5.68/src/btd.h --- bluez-5.66/src/btd.h 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/btd.h 2023-06-30 08:10:20.000000000 +0000 @@ -36,6 +36,12 @@ MPS_MULTIPLE, }; +enum sc_mode_t { + SC_OFF, + SC_ON, + SC_ONLY, +}; + struct btd_br_defaults { uint16_t page_scan_type; uint16_t page_scan_interval; @@ -86,6 +92,13 @@ struct btd_le_defaults le; }; +struct btd_csis { + bool encrypt; + uint8_t sirk[16]; + uint8_t size; + uint8_t rank; +}; + struct btd_avdtp_opts { uint8_t session_mode; uint8_t stream_mode; @@ -98,22 +111,23 @@ struct btd_opts { char *name; uint32_t class; - gboolean pairable; + bool pairable; uint32_t pairto; uint32_t discovto; uint32_t tmpto; uint8_t privacy; bool device_privacy; uint32_t name_request_retry_delay; + uint8_t secure_conn; struct btd_defaults defaults; - gboolean reverse_discovery; - gboolean name_resolv; - gboolean debug_keys; - gboolean fast_conn; - gboolean refresh_discovery; - gboolean experimental; + bool reverse_discovery; + bool name_resolv; + bool debug_keys; + bool fast_conn; + bool refresh_discovery; + bool experimental; struct queue *kernel; uint16_t did_source; @@ -135,6 +149,8 @@ enum jw_repairing_t jw_repairing; struct btd_advmon_opts advmon; + + struct btd_csis csis; }; extern struct btd_opts btd_opts; diff -Nru bluez-5.66/src/device.c bluez-5.68/src/device.c --- bluez-5.66/src/device.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/device.c 2023-06-30 08:10:20.000000000 +0000 @@ -64,6 +64,7 @@ #include "storage.h" #include "eir.h" #include "settings.h" +#include "set.h" #define DISCONNECT_TIMER 2 #define DISCOVERY_TIMER 1 @@ -159,11 +160,25 @@ time_t last_seen; }; +struct ltk_info { + uint8_t key[16]; + bool central; + uint8_t enc_size; +}; + struct csrk_info { uint8_t key[16]; uint32_t counter; }; +struct sirk_info { + struct btd_device_set *set; + uint8_t encrypted; + uint8_t key[16]; + uint8_t size; + uint8_t rank; +}; + enum { WAKE_FLAG_DEFAULT = 0, WAKE_FLAG_ENABLED, @@ -253,7 +268,8 @@ struct csrk_info *local_csrk; struct csrk_info *remote_csrk; - uint8_t ltk_enc_size; + struct ltk_info *ltk; + struct queue *sirks; sdp_list_t *tmp_records; @@ -294,11 +310,18 @@ return &dev->le_state; } -static bool get_initiator(struct btd_device *dev) +bool btd_device_is_initiator(struct btd_device *dev) { - if (dev->le_state.connected) + if (dev->le_state.connected) { + /* Mark as initiator if not set yet and auto-connect flag is + * set and LTK key is for a peripheral. + */ + if (!dev->le_state.initiator && dev->auto_connect && + dev->ltk && !dev->ltk->central) + dev->le_state.initiator = true; + return dev->le_state.initiator; - if (dev->bredr_state.connected) + } else if (dev->bredr_state.connected) return dev->bredr_state.initiator; return dev->att_io ? true : false; @@ -386,6 +409,24 @@ g_key_file_set_integer(key_file, group, "Counter", csrk->counter); } +static void store_sirk(struct sirk_info *sirk, GKeyFile *key_file, + uint8_t index) +{ + char group[28]; + char key[33]; + int i; + + sprintf(group, "SetIdentityResolvingKey#%u", index); + + for (i = 0; i < 16; i++) + sprintf(key + (i * 2), "%2.2X", sirk->key[i]); + + g_key_file_set_boolean(key_file, group, "Encrypted", sirk->encrypted); + g_key_file_set_string(key_file, group, "Key", key); + g_key_file_set_integer(key_file, group, "Size", sirk->size); + g_key_file_set_integer(key_file, group, "Rank", sirk->rank); +} + static gboolean store_device_info_cb(gpointer user_data) { struct btd_device *device = user_data; @@ -483,6 +524,18 @@ if (device->remote_csrk) store_csrk(device->remote_csrk, key_file, "RemoteSignatureKey"); + if (!queue_isempty(device->sirks)) { + const struct queue_entry *entry; + int i; + + for (entry = queue_get_entries(device->sirks), i = 0; entry; + entry = entry->next, i++) { + struct sirk_info *sirk = entry->data; + + store_sirk(sirk, key_file, i); + } + } + str = g_key_file_to_data(key_file, &length, NULL); if (!g_file_set_contents(filename, str, length, &gerr)) { error("Unable set contents for %s: (%s)", filename, @@ -804,8 +857,11 @@ if (device->eir_uuids) g_slist_free_full(device->eir_uuids, g_free); + queue_destroy(device->sirks, free); + g_free(device->local_csrk); g_free(device->remote_csrk); + free(device->ltk); g_free(device->path); g_free(device->alias); free(device->modalias); @@ -1607,6 +1663,67 @@ return device_get_wake_support(device); } +static void append_set(void *data, void *user_data) +{ + struct sirk_info *info = data; + const char *path; + DBusMessageIter *iter = user_data; + DBusMessageIter entry, dict; + + if (!info->set) + return; + + path = btd_set_get_path(info->set); + + dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &path); + + dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + g_dbus_dict_append_entry(&dict, "Rank", DBUS_TYPE_BYTE, &info->rank); + + dbus_message_iter_close_container(&entry, &dict); + dbus_message_iter_close_container(iter, &entry); +} + +static gboolean dev_property_get_set(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_OBJECT_PATH_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &array); + + queue_foreach(device->sirks, append_set, &array); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean dev_property_set_exists(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *device = data; + + return !queue_isempty(device->sirks); +} + static bool disconnect_all(gpointer user_data) { struct btd_device *device = user_data; @@ -1792,10 +1909,97 @@ return device->disconn_timer > 0; } -void device_set_ltk_enc_size(struct btd_device *device, uint8_t enc_size) +static void add_set(void *data, void *user_data) +{ + struct sirk_info *sirk = data; + struct btd_device *device = user_data; + struct btd_device_set *set; + + if (!sirk->encrypted) + return; + + set = btd_set_add_device(device, device->ltk->key, sirk->key, + sirk->size); + if (!set) + return; + + if (sirk->set != set) { + sirk->set = set; + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Sets"); + } +} + +void device_set_ltk(struct btd_device *device, const uint8_t val[16], + bool central, uint8_t enc_size) +{ + if (!device->ltk) + device->ltk = new0(struct ltk_info, 1); + + memcpy(device->ltk->key, val, sizeof(device->ltk->key)); + device->ltk->central = central; + device->ltk->enc_size = enc_size; + bt_att_set_enc_key_size(device->att, enc_size); + + /* Check if there is any set/sirk that needs decryption */ + queue_foreach(device->sirks, add_set, device); +} + +static bool match_sirk(const void *data, const void *match_data) { - device->ltk_enc_size = enc_size; - bt_att_set_enc_key_size(device->att, device->ltk_enc_size); + const struct sirk_info *sirk = data; + const uint8_t *key = match_data; + + return !memcmp(sirk->key, key, sizeof(sirk->key)); +} + +static struct sirk_info *device_add_sirk_info(struct btd_device *device, + bool encrypted, uint8_t key[16], + uint8_t size, uint8_t rank) +{ + struct sirk_info *sirk; + + sirk = queue_find(device->sirks, match_sirk, key); + if (sirk) + return sirk; + + sirk = new0(struct sirk_info, 1); + sirk->encrypted = encrypted; + memcpy(sirk->key, key, sizeof(sirk->key)); + sirk->size = size; + sirk->rank = rank; + + queue_push_tail(device->sirks, sirk); + store_device_info(device); + + return sirk; +} + +bool btd_device_add_set(struct btd_device *device, bool encrypted, + uint8_t key[16], uint8_t size, uint8_t rank) +{ + struct btd_device_set *set; + struct sirk_info *sirk; + + if (encrypted && !device->ltk) + return false; + + sirk = device_add_sirk_info(device, encrypted, key, size, rank); + if (!sirk) + return false; + + set = btd_set_add_device(device, encrypted ? device->ltk->key : NULL, + key, size); + if (!set) + return false; + + if (sirk->set != set) { + sirk->set = set; + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Sets"); + } + + return true; } static void device_set_auto_connect(struct btd_device *device, gboolean enable) @@ -2996,6 +3200,8 @@ { "WakeAllowed", "b", dev_property_get_wake_allowed, dev_property_set_wake_allowed, dev_property_wake_allowed_exist }, + { "Sets", "a{oa{sv}}", dev_property_get_set, NULL, + dev_property_set_exists }, { } }; @@ -3290,6 +3496,63 @@ return NULL; } +static struct sirk_info *load_sirk(GKeyFile *key_file, uint8_t index) +{ + char group[28]; + struct sirk_info *sirk; + char *str; + int i; + + sprintf(group, "SetIdentityResolvingKey#%u", index); + + str = g_key_file_get_string(key_file, group, "Key", NULL); + if (!str) + return NULL; + + sirk = g_new0(struct sirk_info, 1); + + for (i = 0; i < 16; i++) { + if (sscanf(str + (i * 2), "%2hhx", &sirk->key[i]) != 1) + goto fail; + } + + + sirk->encrypted = g_key_file_get_boolean(key_file, group, "Encrypted", + NULL); + sirk->size = g_key_file_get_integer(key_file, group, "Size", NULL); + sirk->rank = g_key_file_get_integer(key_file, group, "Rank", NULL); + g_free(str); + + return sirk; + +fail: + g_free(str); + g_free(sirk); + return NULL; +} + +static void load_sirks(struct btd_device *device, GKeyFile *key_file) +{ + struct sirk_info *sirk; + uint8_t i; + + for (i = 0; i < UINT8_MAX; i++) { + sirk = load_sirk(key_file, i); + if (!sirk) + break; + + queue_push_tail(device->sirks, sirk); + + /* Only add DeviceSet object if sirk does need + * decryption otherwise it has to wait for the LTK in + * order to decrypt. + */ + if (!sirk->encrypted) + btd_set_add_device(device, NULL, sirk->key, + sirk->size); + } +} + static void load_services(struct btd_device *device, char **uuids) { char **uuid; @@ -3430,6 +3693,8 @@ device->local_csrk = load_csrk(key_file, "LocalSignatureKey"); device->remote_csrk = load_csrk(key_file, "RemoteSignatureKey"); + + load_sirks(device, key_file); } g_strfreev(techno); @@ -3711,7 +3976,7 @@ } /* Notify driver about the new connection */ - service_accept(service, get_initiator(device)); + service_accept(service, btd_device_is_initiator(device)); } static void device_add_gatt_services(struct btd_device *device) @@ -3731,7 +3996,7 @@ static void device_accept_gatt_profiles(struct btd_device *device) { GSList *l; - bool initiator = get_initiator(device); + bool initiator = btd_device_is_initiator(device); DBG("initiator %s", initiator ? "true" : "false"); @@ -3945,6 +4210,7 @@ } device->adapter = adapter; + device->sirks = queue_new(); device->temporary = true; device->db_id = gatt_db_register(device->db, gatt_service_added, @@ -4158,7 +4424,10 @@ bacpy(&device->bdaddr, bdaddr); device->bdaddr_type = bdaddr_type; - store_device_info(device); + if (device->temporary) + btd_device_set_temporary(device, false); + else + store_device_info(device); g_dbus_emit_property_changed(dbus_conn, device->path, DEVICE_INTERFACE, "Address"); @@ -5152,12 +5421,14 @@ } bt_gatt_client_set_debug(device->client, gatt_debug, NULL, NULL); + g_attrib_attach_client(device->attrib, device->client); /* - * Notify notify existing service about the new connection so they can - * react to notifications while discovering services + * If we have cache, notify existing service about the new connection + * so they can react to notifications while discovering services */ - device_accept_gatt_profiles(device); + if (!gatt_db_isempty(device->db)) + device_accept_gatt_profiles(device); device->gatt_ready_id = bt_gatt_client_ready_register(device->client, gatt_client_ready_cb, @@ -5205,7 +5476,9 @@ return; } - bt_att_set_enc_key_size(device->att, device->ltk_enc_size); + if (device->ltk) + bt_att_set_enc_key_size(device->att, device->ltk->enc_size); + bt_gatt_server_set_debug(device->server, gatt_debug, NULL, NULL); btd_gatt_database_server_connected(database, device->server); @@ -5420,7 +5693,7 @@ char addr[18]; /* There is one connection attempt going on */ - if (dev->att_io) + if (dev->att_io || dev->att) return -EALREADY; ba2str(&dev->bdaddr, addr); @@ -6758,6 +7031,14 @@ return device; } +static void remove_sirk_info(void *data, void *user_data) +{ + struct sirk_info *info = data; + struct btd_device *device = user_data; + + btd_set_remove_device(info->set, device); +} + void btd_device_unref(struct btd_device *device) { if (__sync_sub_and_fetch(&device->ref_count, 1)) @@ -6768,6 +7049,9 @@ return; } + if (!queue_isempty(device->sirks)) + queue_foreach(device->sirks, remove_sirk_info, device); + DBG("Freeing device %s", device->path); g_dbus_unregister_interface(dbus_conn, device->path, DEVICE_INTERFACE); @@ -6926,3 +7210,9 @@ { return device->volume; } + +void btd_device_foreach_ad(struct btd_device *dev, bt_ad_func_t func, + void *data) +{ + bt_ad_foreach_data(dev->ad, func, data); +} diff -Nru bluez-5.66/src/device.h bluez-5.68/src/device.h --- bluez-5.66/src/device.h 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/device.h 2023-06-30 08:10:20.000000000 +0000 @@ -67,6 +67,7 @@ struct gatt_db *btd_device_get_gatt_db(struct btd_device *device); struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device); struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device); +bool btd_device_is_initiator(struct btd_device *device); void *btd_device_get_attrib(struct btd_device *device); void btd_device_gatt_set_service_changed(struct btd_device *device, uint16_t start, uint16_t end); @@ -128,8 +129,10 @@ bool *remove); void device_request_disconnect(struct btd_device *device, DBusMessage *msg); bool device_is_disconnecting(struct btd_device *device); -void device_set_ltk_enc_size(struct btd_device *device, uint8_t enc_size); - +void device_set_ltk(struct btd_device *device, const uint8_t val[16], + bool central, uint8_t enc_size); +bool btd_device_add_set(struct btd_device *device, bool encrypted, + uint8_t sirk[16], uint8_t size, uint8_t rank); void device_store_svc_chng_ccc(struct btd_device *device, uint8_t bdaddr_type, uint16_t value); void device_load_svc_chng_ccc(struct btd_device *device, uint16_t *ccc_le, @@ -188,3 +191,8 @@ void btd_device_set_volume(struct btd_device *dev, int8_t volume); int8_t btd_device_get_volume(struct btd_device *dev); + +typedef void (*bt_device_ad_func_t)(void *data, void *user_data); + +void btd_device_foreach_ad(struct btd_device *dev, bt_device_ad_func_t func, + void *data); diff -Nru bluez-5.66/src/eir.c bluez-5.68/src/eir.c --- bluez-5.66/src/eir.c 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/src/eir.c 2023-06-30 08:10:20.000000000 +0000 @@ -236,6 +236,9 @@ memcpy(ad->data, data, len); eir->data_list = g_slist_append(eir->data_list, ad); + + if (type == EIR_CSIP_RSI) + eir->rsi = true; } void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len) diff -Nru bluez-5.66/src/eir.h bluez-5.68/src/eir.h --- bluez-5.66/src/eir.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/src/eir.h 2023-06-30 08:10:20.000000000 +0000 @@ -37,6 +37,7 @@ #define EIR_SVC_DATA32 0x20 /* LE: Service data, 32-bit UUID */ #define EIR_SVC_DATA128 0x21 /* LE: Service data, 128-bit UUID */ #define EIR_TRANSPORT_DISCOVERY 0x26 /* Transport Discovery Service */ +#define EIR_CSIP_RSI 0x2e /* Resolvable Set Identifier */ #define EIR_MANUFACTURER_DATA 0xFF /* Manufacturer Specific Data */ /* Flags Descriptions */ @@ -76,6 +77,7 @@ uint32_t class; uint16_t appearance; bool name_complete; + bool rsi; int8_t tx_power; uint8_t *hash; uint8_t *randomizer; diff -Nru bluez-5.66/src/gatt-client.c bluez-5.68/src/gatt-client.c --- bluez-5.66/src/gatt-client.c 2022-01-05 21:53:58.000000000 +0000 +++ bluez-5.68/src/gatt-client.c 2023-06-30 08:10:20.000000000 +0000 @@ -2255,7 +2255,8 @@ char addr[18]; int i; - if (!(client->features & BT_GATT_CHRC_CLI_FEAT_EATT)) + if (!(client->features & BT_GATT_CHRC_CLI_FEAT_EATT) || + !btd_device_is_initiator(dev)) return; if (bt_att_get_channels(att) == btd_opts.gatt_channels) diff -Nru bluez-5.66/src/gatt-database.c bluez-5.68/src/gatt-database.c --- bluez-5.66/src/gatt-database.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/gatt-database.c 2023-06-30 08:10:20.000000000 +0000 @@ -3788,6 +3788,70 @@ return BT_ATT_ERROR_DB_OUT_OF_SYNC; } +static void eatt_confirm_cb(GIOChannel *io, gpointer data) +{ + char address[18]; + uint8_t dst_type; + bdaddr_t src, dst; + GError *gerr = NULL; + struct btd_device *device; + struct bt_gatt_server *server; + struct bt_att *att; + + bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST_TYPE, &dst_type, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + goto drop; + } + + DBG("New incoming EATT connection"); + + /* Confirm the device exists before accepting the connection, if the + * device is using an RPA it could be that the MGMT event has not been + * processed yet which would lead to create a second copy of the same + * device using its identity address. + */ + device = btd_adapter_find_device(adapter_find(&src), &dst, dst_type); + if (!device) { + error("Unable to find device: %s", address); + goto drop; + } + + /* Only allow EATT connection from central */ + if (btd_device_is_initiator(device)) { + warn("EATT connection from peripheral may cause collisions"); + goto drop; + } + + server = btd_device_get_gatt_server(device); + if (!server) { + error("Unable to resolve bt_server"); + goto drop; + } + + att = bt_gatt_server_get_att(server); + if (bt_att_get_channels(att) == btd_opts.gatt_channels) { + DBG("EATT channel limit reached"); + goto drop; + } + + if (!bt_io_accept(io, connect_cb, NULL, NULL, &gerr)) { + error("bt_io_accept: %s", gerr->message); + g_error_free(gerr); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(io, TRUE, NULL); +} + struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter) { struct btd_gatt_database *database; @@ -3824,14 +3888,14 @@ if (btd_opts.gatt_channels == 1) goto bredr; - /* EATT socket */ - database->eatt_io = bt_io_listen(connect_cb, NULL, NULL, NULL, + /* EATT socket, encryption is required */ + database->eatt_io = bt_io_listen(NULL, eatt_confirm_cb, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, addr, BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_PSM, BT_ATT_EATT_PSM, - BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_MTU, btd_opts.gatt_mtu, BT_IO_OPT_INVALID); if (!database->eatt_io) { diff -Nru bluez-5.66/src/main.c bluez-5.68/src/main.c --- bluez-5.66/src/main.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/src/main.c 2023-06-30 08:10:20.000000000 +0000 @@ -43,6 +43,7 @@ #include "shared/mainloop.h" #include "shared/timeout.h" #include "shared/queue.h" +#include "shared/crypto.h" #include "lib/uuid.h" #include "shared/util.h" #include "btd.h" @@ -60,6 +61,9 @@ #define DEFAULT_TEMPORARY_TIMEOUT 30 /* 30 seconds */ #define DEFAULT_NAME_REQUEST_RETRY_DELAY 300 /* 5 minutes */ +/*CSIP Profile - Server */ +#define DEFAULT_SIRK "761FAE703ED681F0C50B34155B6434FB" + #define SHUTDOWN_GRACE_SECONDS 10 struct btd_opts btd_opts; @@ -80,6 +84,7 @@ "MaxControllers" "MultiProfile", "FastConnectable", + "SecureConnections", "Privacy", "JustWorksRepairing", "TemporaryTimeout", @@ -145,6 +150,13 @@ NULL }; +static const char *csip_options[] = { + "SIRK", + "Size", + "Rank", + NULL +}; + static const char *avdtp_options[] = { "SessionMode", "StreamMode", @@ -165,11 +177,55 @@ { "LE", le_options }, { "Policy", policy_options }, { "GATT", gatt_options }, + { "CSIS", csip_options }, { "AVDTP", avdtp_options }, { "AdvMon", advmon_options }, { } }; +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +static int8_t check_sirk_alpha_numeric(char *str) +{ + int8_t val = 0; + char *s = str; + + if (strlen(s) != 32) /* 32 Bytes of Alpha numeric string */ + return 0; + + for ( ; *s; s++) { + if (((*s >= '0') & (*s <= '9')) + || ((*s >= 'a') && (*s <= 'z')) + || ((*s >= 'A') && (*s <= 'Z'))) { + val = 1; + } else { + val = 0; + break; + } + } + + return val; +} + +static size_t hex2bin(const char *hexstr, uint8_t *buf, size_t buflen) +{ + size_t i, len; + + if (!hexstr) + return 0; + + len = MIN((strlen(hexstr) / 2), buflen); + memset(buf, 0, len); + + for (i = 0; i < len; i++) { + if (sscanf(hexstr + (i * 2), "%02hhX", &buf[i]) != 1) + continue; + } + + return len; +} GKeyFile *btd_get_main_conf(void) { @@ -257,7 +313,7 @@ btd_opts.did_version = version; } -static bt_gatt_cache_t parse_gatt_cache(const char *cache) +static bt_gatt_cache_t parse_gatt_cache_str(const char *cache) { if (!strcmp(cache, "always")) { return BT_GATT_CACHE_ALWAYS; @@ -359,6 +415,63 @@ return BT_MODE_DUAL; } +static bool parse_config_string(GKeyFile *config, const char *group, + const char *key, char **val) +{ + GError *err = NULL; + char *tmp; + + tmp = g_key_file_get_string(config, group, key, &err); + if (err) { + if (err->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) + DBG("%s", err->message); + g_error_free(err); + return false; + } + + DBG("%s.%s = %s", group, key, tmp); + + if (val) { + g_free(*val); + *val = tmp; + } + + return true; +} + +static bool parse_config_int(GKeyFile *config, const char *group, + const char *key, int *val, + int min, int max) +{ + int tmp; + char *str = NULL; + char *endptr = NULL; + + if (!parse_config_string(config, group, key, &str)) + return false; + + tmp = strtol(str, &endptr, 0); + if (!endptr || *endptr != '\0') { + error("%s.%s = %s is not integer", group, key, str); + return false; + } + + if (tmp < min) { + warn("%s.%s = %d is out of range (< %d)", group, key, tmp, min); + return false; + } + + if (tmp < max) { + warn("%s.%s = %d is out of range (> %d)", group, key, tmp, max); + return false; + } + + if (val) + *val = tmp; + + return true; +} + struct config_param { const char * const val_name; void * const val; @@ -377,31 +490,15 @@ return; for (i = 0; i < params_len; ++i) { - GError *err = NULL; - char *str; - - str = g_key_file_get_string(config, group, params[i].val_name, - &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - char *endptr = NULL; - int val; - - val = strtol(str, &endptr, 0); - if (!endptr || *endptr != '\0') - continue; - - info("%s=%s(%d)", params[i].val_name, str, val); - - val = MAX(val, params[i].min); - val = MIN(val, params[i].max); + int val; + if (parse_config_int(config, group, params[i].val_name, + &val, params[i].min, params[i].max)) { val = htobl(val); memcpy(params[i].val, &val, params[i].size); - ++btd_opts.defaults.num_entries; } + + ++btd_opts.defaults.num_entries; } } @@ -651,330 +748,411 @@ } } -static void parse_config(GKeyFile *config) +static bool gen_sirk(const char *str) { - GError *err = NULL; - char *str, **strlist; - int val; - gboolean boolean; + struct bt_crypto *crypto; + int ret; - if (!config) - return; + crypto = bt_crypto_new(); + if (!crypto) { + error("Failed to open crypto"); + return false; + } - check_config(config); + ret = bt_crypto_sirk(crypto, str, btd_opts.did_vendor, + btd_opts.did_product, btd_opts.did_version, + btd_opts.did_source, btd_opts.csis.sirk); + if (!ret) + error("Failed to generate SIRK"); - DBG("parsing %s", main_conf_file_path); + bt_crypto_unref(crypto); + return ret; +} - val = g_key_file_get_integer(config, "General", - "DiscoverableTimeout", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("discovto=%d", val); - btd_opts.discovto = val; - } +static bool parse_config_u32(GKeyFile *config, const char *group, + const char *key, uint32_t *val, + uint32_t min, uint32_t max) +{ + int tmp; - boolean = g_key_file_get_boolean(config, "General", - "AlwaysPairable", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("pairable=%s", boolean ? "true" : "false"); - btd_opts.pairable = boolean; - } + if (!parse_config_int(config, group, key, &tmp, 0, UINT32_MAX)) + return false; + + if (val) + *val = tmp; - val = g_key_file_get_integer(config, "General", - "PairableTimeout", &err); + return true; +} + +static bool parse_config_u16(GKeyFile *config, const char *group, + const char *key, uint16_t *val, + uint16_t min, uint16_t max) +{ + int tmp; + + if (!parse_config_int(config, group, key, &tmp, min, max)) + return false; + + if (val) + *val = tmp; + + return true; +} + +static bool parse_config_u8(GKeyFile *config, const char *group, + const char *key, uint8_t *val, + uint8_t min, uint8_t max) +{ + int tmp; + + if (!parse_config_int(config, group, key, &tmp, min, max)) + return false; + + if (val) + *val = tmp; + + return true; +} + +static bool parse_config_bool(GKeyFile *config, const char *group, + const char *key, bool *val) +{ + GError *err = NULL; + gboolean tmp; + + tmp = g_key_file_get_boolean(config, group, key, &err); if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("pairto=%d", val); - btd_opts.pairto = val; + if (err->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) + DBG("%s", err->message); + g_error_free(err); + return false; } - str = g_key_file_get_string(config, "General", "Privacy", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); + DBG("%s.%s = %s", group, key, tmp ? "true" : "false"); + + if (val) + *val = tmp; + + return true; +} + +static void parse_privacy(GKeyFile *config) +{ + char *str = NULL; + + if (!parse_config_string(config, "General", "Privacy", &str)) { btd_opts.privacy = 0x00; btd_opts.device_privacy = true; - } else { - DBG("privacy=%s", str); + return; + } - if (!strcmp(str, "network") || !strcmp(str, "on")) { - btd_opts.privacy = 0x01; - } else if (!strcmp(str, "device")) { - btd_opts.privacy = 0x01; - btd_opts.device_privacy = true; - } else if (!strcmp(str, "limited-network")) { - if (btd_opts.mode != BT_MODE_DUAL) { - DBG("Invalid privacy option: %s", str); - btd_opts.privacy = 0x00; - } - btd_opts.privacy = 0x01; - } else if (!strcmp(str, "limited-device")) { - if (btd_opts.mode != BT_MODE_DUAL) { - DBG("Invalid privacy option: %s", str); - btd_opts.privacy = 0x00; - } - btd_opts.privacy = 0x02; - btd_opts.device_privacy = true; - } else if (!strcmp(str, "off")) { + if (!strcmp(str, "network") || !strcmp(str, "on")) { + btd_opts.privacy = 0x01; + } else if (!strcmp(str, "device")) { + btd_opts.privacy = 0x01; + btd_opts.device_privacy = true; + } else if (!strcmp(str, "limited-network")) { + if (btd_opts.mode != BT_MODE_DUAL) { + DBG("Invalid privacy option: %s", str); btd_opts.privacy = 0x00; - btd_opts.device_privacy = true; - } else { + } + btd_opts.privacy = 0x01; + } else if (!strcmp(str, "limited-device")) { + if (btd_opts.mode != BT_MODE_DUAL) { DBG("Invalid privacy option: %s", str); btd_opts.privacy = 0x00; } - - g_free(str); + btd_opts.privacy = 0x02; + btd_opts.device_privacy = true; + } else if (!strcmp(str, "off")) { + btd_opts.privacy = 0x00; + btd_opts.device_privacy = true; + } else { + DBG("Invalid privacy option: %s", str); + btd_opts.privacy = 0x00; } - str = g_key_file_get_string(config, "General", - "JustWorksRepairing", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); + g_free(str); +} + +static void parse_repairing(GKeyFile *config) +{ + char *str = NULL; + + if (!parse_config_string(config, "General", "JustWorksRepairing", + &str)) { btd_opts.jw_repairing = JW_REPAIRING_NEVER; - } else { - DBG("just_works_repairing=%s", str); - btd_opts.jw_repairing = parse_jw_repairing(str); - g_free(str); + return; } - val = g_key_file_get_integer(config, "General", - "TemporaryTimeout", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("tmpto=%d", val); - btd_opts.tmpto = val; - } + btd_opts.jw_repairing = parse_jw_repairing(str); + g_free(str); +} - str = g_key_file_get_string(config, "General", "Name", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("name=%s", str); - g_free(btd_opts.name); - btd_opts.name = str; - } +static bool parse_config_hex(GKeyFile *config, char *group, + const char *key, uint32_t *val) +{ + char *str = NULL; - str = g_key_file_get_string(config, "General", "Class", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("class=%s", str); - btd_opts.class = strtol(str, NULL, 16); - g_free(str); - } + if (!parse_config_string(config, group, key, &str)) + return false; - str = g_key_file_get_string(config, "General", "DeviceID", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("deviceid=%s", str); - parse_did(str); - g_free(str); - } + if (val) + *val = strtol(str, NULL, 16); - boolean = g_key_file_get_boolean(config, "General", - "ReverseServiceDiscovery", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else - btd_opts.reverse_discovery = boolean; - - boolean = g_key_file_get_boolean(config, "General", - "NameResolving", &err); - if (err) - g_clear_error(&err); - else - btd_opts.name_resolv = boolean; + g_free(str); + return true; +} - boolean = g_key_file_get_boolean(config, "General", - "DebugKeys", &err); - if (err) - g_clear_error(&err); - else - btd_opts.debug_keys = boolean; +static void parse_device_id(GKeyFile *config) +{ + char *str = NULL; - str = g_key_file_get_string(config, "General", "ControllerMode", &err); - if (err) { - g_clear_error(&err); - } else { - DBG("ControllerMode=%s", str); - btd_opts.mode = get_mode(str); - g_free(str); - } + parse_config_string(config, "General", "DeviceID", &str); + if (!str) + return; - val = g_key_file_get_integer(config, "General", "MaxControllers", &err); - if (err) { - g_clear_error(&err); - } else { - DBG("MaxControllers=%d", val); - btd_opts.max_adapters = val; - } + parse_did(str); + g_free(str); +} - str = g_key_file_get_string(config, "General", "MultiProfile", &err); - if (err) { - g_clear_error(&err); - } else { - DBG("MultiProfile=%s", str); +static void parse_ctrl_mode(GKeyFile *config) +{ + char *str = NULL; - if (!strcmp(str, "single")) - btd_opts.mps = MPS_SINGLE; - else if (!strcmp(str, "multiple")) - btd_opts.mps = MPS_MULTIPLE; - else - btd_opts.mps = MPS_OFF; - - g_free(str); - } - - boolean = g_key_file_get_boolean(config, "General", - "FastConnectable", &err); - if (err) - g_clear_error(&err); - else - btd_opts.fast_conn = boolean; + parse_config_string(config, "General", "ControllerMode", &str); + if (!str) + return; - boolean = g_key_file_get_boolean(config, "General", - "RefreshDiscovery", &err); - if (err) - g_clear_error(&err); - else - btd_opts.refresh_discovery = boolean; + btd_opts.mode = get_mode(str); + g_free(str); +} + +static void parse_multi_profile(GKeyFile *config) +{ + char *str = NULL; + + parse_config_string(config, "General", "MultiProfile", &str); + if (!str) + return; - boolean = g_key_file_get_boolean(config, "General", "Experimental", - &err); - if (err) - g_clear_error(&err); + if (!strcmp(str, "single")) + btd_opts.mps = MPS_SINGLE; + else if (!strcmp(str, "multiple")) + btd_opts.mps = MPS_MULTIPLE; else - btd_opts.experimental = boolean; + btd_opts.mps = MPS_OFF; - strlist = g_key_file_get_string_list(config, "General", - "KernelExperimental", - NULL, &err); - if (err) - g_clear_error(&err); - else { + g_free(str); +} + +static gboolean parse_kernel_experimental(const char *key, const char *value, + gpointer user_data, GError **error) +{ + char **strlist; + + if (value && value[0] != '*') { + strlist = g_strsplit(value, ",", -1); btd_parse_kernel_experimental(strlist); g_strfreev(strlist); - } - - val = g_key_file_get_integer(config, "General", - "RemoteNameRequestRetryDelay", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); } else { - DBG("RemoteNameRequestRetryDelay=%d", val); - btd_opts.name_request_retry_delay = val; + if (!btd_opts.kernel) + btd_opts.kernel = queue_new(); + queue_push_head(btd_opts.kernel, strdup("*")); } - str = g_key_file_get_string(config, "GATT", "Cache", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - btd_opts.gatt_cache = parse_gatt_cache(str); - g_free(str); - } + return TRUE; +} - val = g_key_file_get_integer(config, "GATT", "KeySize", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("KeySize=%d", val); +static void parse_kernel_exp(GKeyFile *config) +{ + char *str = NULL; - if (val >=7 && val <= 16) - btd_opts.key_size = val; - } + if (!parse_config_string(config, "General", "KernelExperimental", + &str)) + return; - val = g_key_file_get_integer(config, "GATT", "ExchangeMTU", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - /* Ensure the mtu is within a valid range. */ - val = MIN(val, BT_ATT_MAX_LE_MTU); - val = MAX(val, BT_ATT_DEFAULT_LE_MTU); - DBG("ExchangeMTU=%d", val); - btd_opts.gatt_mtu = val; - } + parse_kernel_experimental(NULL, str, NULL, NULL); - val = g_key_file_get_integer(config, "GATT", "Channels", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("Channels=%d", val); - /* Ensure the channels is within a valid range. */ - val = MIN(val, 5); - val = MAX(val, 1); - btd_opts.gatt_channels = val; - } + g_free(str); +} - str = g_key_file_get_string(config, "AVDTP", "SessionMode", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("SessionMode=%s", str); +static void parse_secure_conns(GKeyFile *config) +{ + char *str = NULL; - if (!strcmp(str, "basic")) - btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; - else if (!strcmp(str, "ertm")) - btd_opts.avdtp.session_mode = BT_IO_MODE_ERTM; - else { - DBG("Invalid mode option: %s", str); - btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; - } - g_free(str); - } + if (!parse_config_string(config, "General", "SecureConnections", + &str)) + return; - str = g_key_file_get_string(config, "AVDTP", "StreamMode", &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - DBG("StreamMode=%s", str); + if (!strcmp(str, "off")) + btd_opts.secure_conn = SC_OFF; + else if (!strcmp(str, "on")) + btd_opts.secure_conn = SC_ON; + else if (!strcmp(str, "only")) + btd_opts.secure_conn = SC_ONLY; + + g_free(str); +} + +static void parse_general(GKeyFile *config) +{ + parse_config_string(config, "General", "Name", &btd_opts.name); + parse_config_hex(config, "General", "Class", &btd_opts.class); + parse_config_u32(config, "General", "DiscoverableTimeout", + &btd_opts.discovto, + 0, UINT32_MAX); + parse_config_bool(config, "General", "AlwaysPairable", + &btd_opts.pairable); + parse_config_u32(config, "General", "PairableTimeout", + &btd_opts.pairto, + 0, UINT32_MAX); + parse_device_id(config); + parse_config_bool(config, "General", "ReverseServiceDiscovery", + &btd_opts.reverse_discovery); + parse_config_bool(config, "General", "NameResolving", + &btd_opts.name_resolv); + parse_config_bool(config, "General", "DebugKeys", + &btd_opts.debug_keys); + parse_ctrl_mode(config); + parse_config_u16(config, "General", "MaxControllers", + &btd_opts.max_adapters, + 0, UINT16_MAX); + parse_multi_profile(config); + parse_config_bool(config, "General", "FastConnectable", + &btd_opts.fast_conn); + parse_privacy(config); + parse_repairing(config); + parse_config_u32(config, "General", "TemporaryTimeout", + &btd_opts.tmpto, + 0, UINT32_MAX); + parse_config_bool(config, "General", "RefreshDiscovery", + &btd_opts.refresh_discovery); + parse_secure_conns(config); + parse_config_bool(config, "General", "Experimental", + &btd_opts.experimental); + parse_kernel_exp(config); + parse_config_u32(config, "General", "RemoteNameRequestRetryDelay", + &btd_opts.name_request_retry_delay, + 0, UINT32_MAX); +} - if (!strcmp(str, "basic")) - btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; - else if (!strcmp(str, "streaming")) - btd_opts.avdtp.stream_mode = BT_IO_MODE_STREAMING; - else { - DBG("Invalid mode option: %s", str); - btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; - } - g_free(str); +static void parse_gatt_cache(GKeyFile *config) +{ + char *str = NULL; + + parse_config_string(config, "GATT", "Cache", &str); + if (!str) + return; + + btd_opts.gatt_cache = parse_gatt_cache_str(str); + g_free(str); +} + +static void parse_gatt(GKeyFile *config) +{ + parse_gatt_cache(config); + parse_config_u8(config, "GATT", "KeySize", &btd_opts.key_size, 7, 16); + parse_config_u16(config, "GATT", "ExchangeMTU", &btd_opts.gatt_mtu, + BT_ATT_DEFAULT_LE_MTU, BT_ATT_MAX_LE_MTU); + parse_config_u8(config, "GATT", "Channels", &btd_opts.gatt_channels, + 1, 5); +} + +static void parse_csis_sirk(GKeyFile *config) +{ + char *str = NULL; + + if (!parse_config_string(config, "CSIS", "SIRK", &str)) + return; + + if (strlen(str) == 32 && check_sirk_alpha_numeric(str)) + hex2bin(str, btd_opts.csis.sirk, sizeof(btd_opts.csis.sirk)); + else if (!gen_sirk(str)) + DBG("Unable to generate SIRK from string"); + + g_free(str); +} + +static void parse_csis(GKeyFile *config) +{ + parse_csis_sirk(config); + parse_config_bool(config, "CSIS", "Encryption", + &btd_opts.csis.encrypt); + parse_config_u8(config, "CSIS", "Size", &btd_opts.csis.size, + 0, UINT8_MAX); + parse_config_u8(config, "CSIS", "Rank", &btd_opts.csis.size, + 0, UINT8_MAX); +} + +static void parse_avdtp_session_mode(GKeyFile *config) +{ + char *str = NULL; + + if (!parse_config_string(config, "AVDTP", "SessionMode", &str)) + return; + + if (!strcmp(str, "basic")) + btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; + else if (!strcmp(str, "ertm")) + btd_opts.avdtp.session_mode = BT_IO_MODE_ERTM; + else { + DBG("Invalid mode option: %s", str); + btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; } - val = g_key_file_get_integer(config, "AdvMon", "RSSISamplingPeriod", - &err); - if (err) { - DBG("%s", err->message); - g_clear_error(&err); - } else { - val = MIN(val, 0xFF); - val = MAX(val, 0); - DBG("RSSISamplingPeriod=%d", val); - btd_opts.advmon.rssi_sampling_period = val; + g_free(str); +} + +static void parse_avdtp_stream_mode(GKeyFile *config) +{ + char *str = NULL; + + if (!parse_config_string(config, "AVDTP", "StreamMode", &str)) + return; + + if (!strcmp(str, "basic")) + btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; + else if (!strcmp(str, "streaming")) + btd_opts.avdtp.stream_mode = BT_IO_MODE_STREAMING; + else { + DBG("Invalid mode option: %s", str); + btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; } + g_free(str); +} + +static void parse_avdtp(GKeyFile *config) +{ + parse_avdtp_session_mode(config); + parse_avdtp_stream_mode(config); +} + +static void parse_advmon(GKeyFile *config) +{ + parse_config_u8(config, "AdvMon", "RSSISamplingPeriod", + &btd_opts.advmon.rssi_sampling_period, + 0, UINT8_MAX); +} + +static void parse_config(GKeyFile *config) +{ + if (!config) + return; + + check_config(config); + + DBG("parsing %s", main_conf_file_path); + + /* Parse Groups */ + parse_general(config); parse_br_config(config); parse_le_config(config); + parse_gatt(config); + parse_csis(config); + parse_avdtp(config); + parse_advmon(config); } static void init_defaults(void) @@ -993,6 +1171,7 @@ btd_opts.debug_keys = FALSE; btd_opts.refresh_discovery = TRUE; btd_opts.name_request_retry_delay = DEFAULT_NAME_REQUEST_RETRY_DELAY; + btd_opts.secure_conn = SC_ON; btd_opts.defaults.num_entries = 0; btd_opts.defaults.br.page_scan_type = 0xFFFF; @@ -1009,7 +1188,7 @@ btd_opts.gatt_cache = BT_GATT_CACHE_ALWAYS; btd_opts.gatt_mtu = BT_ATT_MAX_LE_MTU; - btd_opts.gatt_channels = 3; + btd_opts.gatt_channels = 1; btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC; btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC; @@ -1144,24 +1323,6 @@ return TRUE; } - -static gboolean parse_kernel_experimental(const char *key, const char *value, - gpointer user_data, GError **error) -{ - char **strlist; - - if (value) { - strlist = g_strsplit(value, ",", -1); - btd_parse_kernel_experimental(strlist); - g_strfreev(strlist); - } else { - if (!btd_opts.kernel) - btd_opts.kernel = queue_new(); - queue_push_head(btd_opts.kernel, strdup("*")); - } - - return TRUE; -} static GOptionEntry options[] = { { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, diff -Nru bluez-5.66/src/main.conf bluez-5.68/src/main.conf --- bluez-5.66/src/main.conf 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/src/main.conf 2023-06-30 08:10:20.000000000 +0000 @@ -111,6 +111,17 @@ # profile is connected. Defaults to true. #RefreshDiscovery = true +# Default Secure Connections setting. +# Enables the Secure Connections setting for adapters that support it. It +# provides better crypto algorithms for BT links and also enables CTKD (cross +# transport key derivation) during pairing on any link. +# Possible values: "off", "on", "only" +# - "off": Secure Connections are disabled +# - "on": Secure Connections are enabled when peer device supports them +# - "only": we allow only Secure Connections +# Defaults to "on" +#SecureConnections = on + # Enables D-Bus experimental interfaces # Possible values: true or false #Experimental = false @@ -244,8 +255,33 @@ # Number of ATT channels # Possible values: 1-5 (1 disables EATT) -# Default to 3 -#Channels = 3 +# Default to 1 +#Channels = 1 + +[CSIS] +# SIRK - Set Identification Resolution Key which is common for all the +# sets. They SIRK key is used to identify its sets. This can be any +# 128 bit value or a string value (e.g. product name) which is then hashed. +# Possible Values: +# 16 byte hexadecimal value: 861FAE703ED681F0C50B34155B6434FB +# String value: "My Product Name" +# Defaults to none +#SIRK = + +# SIRK Encryption +# Possible values: +# yes: Encrypt SIRK when read +# no: Do not encrypt SIRK when read. (plaintext) +# Defaults to yes +#Encryption = yes + +# Total no of sets belongs to this Profile +# Defaults to 0 +#Size = 0 + +# Rank for the device +# Defaults to 0 +#Rank = 0 [AVDTP] # AVDTP L2CAP Signalling Channel Mode. diff -Nru bluez-5.66/src/plugin.c bluez-5.68/src/plugin.c --- bluez-5.66/src/plugin.c 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/src/plugin.c 2023-06-30 08:10:20.000000000 +0000 @@ -186,7 +186,7 @@ err = plugin->desc->init(); if (err < 0) { - if (err == -ENOSYS) + if (err == -ENOSYS || err == -ENOTSUP) warn("System does not support %s plugin", plugin->desc->name); else diff -Nru bluez-5.66/src/profile.c bluez-5.68/src/profile.c --- bluez-5.66/src/profile.c 2021-08-22 04:30:44.000000000 +0000 +++ bluez-5.68/src/profile.c 2023-06-30 08:10:20.000000000 +0000 @@ -775,6 +775,12 @@ int btd_profile_register(struct btd_profile *profile) { + if (profile->experimental && !(g_dbus_get_flags() & + G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) { + DBG("D-Bus experimental not enabled"); + return -ENOTSUP; + } + profiles = g_slist_append(profiles, profile); return 0; } diff -Nru bluez-5.66/src/profile.h bluez-5.68/src/profile.h --- bluez-5.66/src/profile.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/src/profile.h 2023-06-30 08:10:20.000000000 +0000 @@ -28,6 +28,11 @@ */ bool external; + /* Indicates the profile is experimental and shall only be registered + * when experimental has been enabled (see: main.conf:Experimental). + */ + bool experimental; + int (*device_probe) (struct btd_service *service); void (*device_remove) (struct btd_service *service); diff -Nru bluez-5.66/src/set.c bluez-5.68/src/set.c --- bluez-5.66/src/set.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/src/set.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2023 Intel Corporation + * + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gdbus/gdbus.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/ad.h" +#include "src/shared/crypto.h" + +#include "log.h" +#include "error.h" +#include "adapter.h" +#include "device.h" +#include "dbus-common.h" +#include "set.h" + +static struct queue *set_list; + +struct btd_device_set { + struct btd_adapter *adapter; + char *path; + uint8_t sirk[16]; + uint8_t size; + bool auto_connect; + struct queue *devices; + struct btd_device *device; +}; + +static DBusMessage *set_disconnect(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + /* TODO */ + return NULL; +} + +static DBusMessage *set_connect(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + /* TODO */ + return NULL; +} + +static const GDBusMethodTable set_methods[] = { + { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Disconnect", NULL, NULL, + set_disconnect) }, + { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Connect", NULL, NULL, + set_connect) }, + {} +}; + +static gboolean get_adapter(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device_set *set = data; + const char *path = adapter_get_path(set->adapter); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static gboolean get_auto_connect(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device_set *set = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, + &set->auto_connect); + + return TRUE; +} + +static void set_auto_connect(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *data) +{ + struct btd_device_set *set = data; + dbus_bool_t b; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &b); + + set->auto_connect = b ? true : false; + + g_dbus_pending_property_success(id); +} + +static void append_device(void *data, void *user_data) +{ + struct btd_device *device = data; + const char *path = device_get_path(device); + DBusMessageIter *entry = user_data; + + dbus_message_iter_append_basic(entry, DBUS_TYPE_OBJECT_PATH, &path); +} + +static gboolean get_devices(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device_set *set = data; + DBusMessageIter entry; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, + &entry); + + queue_foreach(set->devices, append_device, &entry); + + dbus_message_iter_close_container(iter, &entry); + + return TRUE; +} + +static gboolean get_size(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device_set *set = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &set->size); + + return TRUE; +} + +static const GDBusPropertyTable set_properties[] = { + { "Adapter", "o", get_adapter, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "AutoConnect", "b", get_auto_connect, set_auto_connect, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Devices", "ao", get_devices, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Size", "y", get_size, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + {} +}; + +static void set_free(void *data) +{ + struct btd_device_set *set = data; + + queue_destroy(set->devices, NULL); + g_free(set->path); + free(set); +} + +static struct btd_device_set *set_new(struct btd_device *device, + uint8_t sirk[16], uint8_t size) +{ + struct btd_device_set *set; + + set = new0(struct btd_device_set, 1); + set->adapter = device_get_adapter(device); + memcpy(set->sirk, sirk, sizeof(set->sirk)); + set->size = size; + set->auto_connect = true; + set->devices = queue_new(); + queue_push_tail(set->devices, device); + set->path = g_strdup_printf("%s/set_%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x", + adapter_get_path(set->adapter), + sirk[15], sirk[14], sirk[13], sirk[12], + sirk[11], sirk[10], sirk[9], sirk[8], + sirk[7], sirk[6], sirk[5], sirk[4], + sirk[3], sirk[2], sirk[1], sirk[0]); + + DBG("Creating set %s", set->path); + + if (g_dbus_register_interface(btd_get_dbus_connection(), + set->path, BTD_DEVICE_SET_INTERFACE, + set_methods, NULL, + set_properties, set, + set_free) == FALSE) { + error("Unable to register set interface"); + set_free(set); + return NULL; + } + + return set; +} + +static struct btd_device_set *set_find(struct btd_device *device, + uint8_t sirk[16]) +{ + struct btd_adapter *adapter = device_get_adapter(device); + const struct queue_entry *entry; + + for (entry = queue_get_entries(set_list); entry; entry = entry->next) { + struct btd_device_set *set = entry->data; + + if (set->adapter != adapter) + continue; + + if (!memcmp(set->sirk, sirk, sizeof(set->sirk))) + return set; + } + + return NULL; +} + +static void set_connect_next(struct btd_device_set *set) +{ + const struct queue_entry *entry; + + for (entry = queue_get_entries(set->devices); entry; + entry = entry->next) { + struct btd_device *device = entry->data; + + /* Only connect one at time(?) */ + if (!device_connect_le(device)) + return; + } +} + +static void set_add(struct btd_device_set *set, struct btd_device *device) +{ + /* Check if device is already part of the set then skip to connect */ + if (queue_find(set->devices, NULL, device)) + goto done; + + DBG("set %s device %s", set->path, device_get_path(device)); + + queue_push_tail(set->devices, device); + g_dbus_emit_property_changed(btd_get_dbus_connection(), set->path, + BTD_DEVICE_SET_INTERFACE, "Devices"); + +done: + /* Check if set is marked to auto-connect */ + if (btd_device_is_connected(device) && set->auto_connect) + set_connect_next(set); +} + +static void foreach_rsi(void *data, void *user_data) +{ + struct bt_ad_data *ad = data; + struct btd_device_set *set = user_data; + struct bt_crypto *crypto; + uint8_t res[3]; + + if (ad->type != BT_AD_CSIP_RSI || ad->len < 6) + return; + + crypto = bt_crypto_new(); + if (!crypto) + return; + + if (!bt_crypto_sih(crypto, set->sirk, ad->data + 3, res)) { + bt_crypto_unref(crypto); + return; + } + + bt_crypto_unref(crypto); + + if (!memcmp(ad->data, res, sizeof(res))) + device_connect_le(set->device); +} + +static void foreach_device(struct btd_device *device, void *data) +{ + struct btd_device_set *set = data; + + /* Check if device is already part of the set then skip */ + if (queue_find(set->devices, NULL, device)) + return; + + set->device = device; + + btd_device_foreach_ad(device, foreach_rsi, set); +} + +struct btd_device_set *btd_set_add_device(struct btd_device *device, + uint8_t *key, uint8_t sirk[16], + uint8_t size) +{ + struct btd_device_set *set; + + /* In case key has been set it means SIRK is encrypted */ + if (key) { + struct bt_crypto *crypto = bt_crypto_new(); + + if (!crypto) + return NULL; + + /* sef and sdf are symmetric */ + bt_crypto_sef(crypto, key, sirk, sirk); + + bt_crypto_unref(crypto); + } + + /* Check if DeviceSet already exists */ + set = set_find(device, sirk); + if (set) { + set_add(set, device); + /* Check if there are new devices with RSI found */ + goto done; + } + + set = set_new(device, sirk, size); + if (!set) + return NULL; + + if (!set_list) + set_list = queue_new(); + + queue_push_tail(set_list, set); + +done: + /* Attempt to add devices which have matching RSI */ + btd_adapter_for_each_device(device_get_adapter(device), foreach_device, + set); + + return set; +} + +bool btd_set_remove_device(struct btd_device_set *set, + struct btd_device *device) +{ + if (!set || !device) + return false; + + if (!queue_remove_if(set->devices, NULL, device)) + return false; + + if (!queue_isempty(set->devices)) { + g_dbus_emit_property_changed(btd_get_dbus_connection(), + set->path, + BTD_DEVICE_SET_INTERFACE, + "Devices"); + return true; + } + + if (!queue_remove(set_list, set)) + return false; + + /* Unregister if there are no devices left in the set */ + g_dbus_unregister_interface(btd_get_dbus_connection(), set->path, + BTD_DEVICE_SET_INTERFACE); + + return true; +} + +const char *btd_set_get_path(struct btd_device_set *set) +{ + return set->path; +} diff -Nru bluez-5.66/src/set.h bluez-5.68/src/set.h --- bluez-5.66/src/set.h 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/src/set.h 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2023 Intel Corporation + * + * + */ + +#define BTD_DEVICE_SET_INTERFACE "org.bluez.DeviceSet1" + +struct btd_device_set; + +struct btd_device_set *btd_set_add_device(struct btd_device *device, + uint8_t *ltk, uint8_t sirk[16], + uint8_t size); +bool btd_set_remove_device(struct btd_device_set *set, + struct btd_device *device); +const char *btd_set_get_path(struct btd_device_set *set); diff -Nru bluez-5.66/src/shared/ad.c bluez-5.68/src/shared/ad.c --- bluez-5.66/src/shared/ad.c 2021-10-13 18:38:34.000000000 +0000 +++ bluez-5.68/src/shared/ad.c 2023-06-30 08:10:20.000000000 +0000 @@ -1028,6 +1028,9 @@ if (d1->type != d2->type) return false; + if (!d2->len && !d2->data) + return true; + if (d1->len != d2->len) return false; diff -Nru bluez-5.66/src/shared/ad.h bluez-5.68/src/shared/ad.h --- bluez-5.66/src/shared/ad.h 2021-10-13 18:38:34.000000000 +0000 +++ bluez-5.68/src/shared/ad.h 2023-06-30 08:10:20.000000000 +0000 @@ -57,6 +57,7 @@ #define BT_AD_MESH_PROV 0x29 #define BT_AD_MESH_DATA 0x2a #define BT_AD_MESH_BEACON 0x2b +#define BT_AD_CSIP_RSI 0x2e #define BT_AD_3D_INFO_DATA 0x3d #define BT_AD_MANUFACTURER_DATA 0xff diff -Nru bluez-5.66/src/shared/att.c bluez-5.68/src/shared/att.c --- bluez-5.66/src/shared/att.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/src/shared/att.c 2023-06-30 08:10:20.000000000 +0000 @@ -799,8 +799,8 @@ chan->pending_req = NULL; - /* Push operation back to request queue */ - return queue_push_head(att->req_queue, op); + /* Push operation back to channel queue */ + return queue_push_head(chan->queue, op); } static void handle_rsp(struct bt_att_chan *chan, uint8_t opcode, uint8_t *pdu, @@ -1588,6 +1588,14 @@ op->id = att->next_send_id++; + /* Always use fixed channel for BT_ATT_OP_MTU_REQ */ + if (opcode == BT_ATT_OP_MTU_REQ) { + struct bt_att_chan *chan = queue_peek_tail(att->chans); + + result = queue_push_tail(chan->queue, op); + goto done; + } + /* Add the op to the correct queue based on its type */ switch (op->type) { case ATT_OP_TYPE_REQ: @@ -1606,6 +1614,7 @@ break; } +done: if (!result) { free(op->pdu); free(op); diff -Nru bluez-5.66/src/shared/att-types.h bluez-5.68/src/shared/att-types.h --- bluez-5.66/src/shared/att-types.h 2021-10-13 18:38:34.000000000 +0000 +++ bluez-5.68/src/shared/att-types.h 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. + * Copyright 2023 NXP * * */ @@ -101,9 +102,10 @@ /* * Common Profile and Service Error Code descriptions (see Supplement to the * Bluetooth Core Specification, sections 1.2 and 2). The error codes within - * 0xE0-0xFC are reserved for future use. The remaining 3 are defined as the + * 0xE0-0xFB are reserved for future use. The remaining 4 are defined as the * following: */ +#define BT_ERROR_WRITE_REQUEST_REJECTED 0xfc #define BT_ERROR_CCC_IMPROPERLY_CONFIGURED 0xfd #define BT_ERROR_ALREADY_IN_PROGRESS 0xfe #define BT_ERROR_OUT_OF_RANGE 0xff diff -Nru bluez-5.66/src/shared/bap.c bluez-5.68/src/shared/bap.c --- bluez-5.66/src/shared/bap.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/bap.c 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. + * Copyright 2023 NXP * */ @@ -46,7 +47,16 @@ #define BAP_PROCESS_TIMEOUT 10 +#define PACS_SRC_LOCATION 0x00000001 +#define PACS_SNK_LOCATION 0x00000003 + +#define PACS_SRC_CTXT 0x000f +#define PACS_SUPPORTED_SRC_CTXT PACS_SRC_CTXT +#define PACS_SNK_CTXT 0x0fff +#define PACS_SUPPORTED_SNK_CTXT PACS_SNK_CTXT + struct bt_bap_pac_changed { + unsigned int id; bt_bap_pac_func_t added; bt_bap_pac_func_t removed; bt_bap_destroy_func_t destroy; @@ -90,6 +100,12 @@ struct gatt_db_attribute *context_ccc; struct gatt_db_attribute *supported_context; struct gatt_db_attribute *supported_context_ccc; + uint32_t source_loc_value; + uint32_t sink_loc_value; + uint16_t source_context_value; + uint16_t sink_context_value; + uint16_t supported_source_context_value; + uint16_t supported_sink_context_value; }; struct bt_ase { @@ -113,7 +129,8 @@ struct bt_ascs *ascs; struct queue *sinks; struct queue *sources; - struct queue *endpoints; + struct queue *broadcast_sources; + struct queue *broadcast_sinks; }; struct bt_bap_req { @@ -127,17 +144,6 @@ void *user_data; }; -typedef void (*bap_func_t)(struct bt_bap *bap, bool success, uint8_t att_ecode, - const uint8_t *value, uint16_t length, - void *user_data); - -struct bt_bap_pending { - unsigned int id; - struct bt_bap *bap; - bap_func_t func; - void *user_data; -}; - typedef void (*bap_notify_t)(struct bt_bap *bap, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); @@ -156,15 +162,19 @@ struct bt_gatt_client *client; struct bt_att *att; struct bt_bap_req *req; - unsigned int cp_id; + unsigned int cp_id; unsigned int process_id; unsigned int disconn_id; + unsigned int idle_id; + struct queue *reqs; - struct queue *pending; struct queue *notify; struct queue *streams; + struct queue *local_eps; + struct queue *remote_eps; + struct queue *pac_cbs; struct queue *ready_cbs; struct queue *state_cbs; @@ -178,8 +188,6 @@ struct bt_bap_db *bdb; char *name; uint8_t type; - uint32_t locations; - uint16_t contexts; struct bt_bap_codec codec; struct bt_bap_pac_qos qos; struct iovec *data; @@ -189,6 +197,7 @@ }; struct bt_bap_endpoint { + struct bt_bap *bap; struct bt_bap_db *bdb; struct bt_bap_stream *stream; struct gatt_db_attribute *attr; @@ -209,7 +218,6 @@ struct bt_bap_stream { struct bt_bap *bap; struct bt_bap_endpoint *ep; - struct queue *pacs; struct bt_bap_pac *lpac; struct bt_bap_pac *rpac; struct iovec *cc; @@ -250,7 +258,6 @@ /* Contains local bt_bap_db */ static struct queue *bap_db; -static struct queue *pac_cbs; static struct queue *bap_cbs; static struct queue *sessions; @@ -262,94 +269,32 @@ return (bdb->db == db); } -static void *iov_add(struct iovec *iov, size_t len) -{ - void *data; - - data = iov->iov_base + iov->iov_len; - iov->iov_len += len; - - return data; -} - -static void *iov_add_mem(struct iovec *iov, size_t len, const void *d) -{ - void *data; - - data = iov->iov_base + iov->iov_len; - iov->iov_len += len; - - memcpy(data, d, len); - - return data; -} - -static void iov_free(void *data) -{ - struct iovec *iov = data; - - if (!iov) - return; - - free(iov->iov_base); - free(iov); -} - -static void iov_memcpy(struct iovec *iov, void *src, size_t len) -{ - iov->iov_base = realloc(iov->iov_base, len); - iov->iov_len = len; - memcpy(iov->iov_base, src, len); -} - -static int iov_memcmp(struct iovec *iov1, struct iovec *iov2) -{ - if (!iov1) - return 1; - - if (!iov2) - return -1; - - if (iov1->iov_len != iov2->iov_len) - return iov1->iov_len - iov2->iov_len; - - return memcmp(iov1->iov_base, iov2->iov_base, iov1->iov_len); -} - -static struct iovec *iov_dup(struct iovec *iov, size_t len) +static void *iov_append(struct iovec *iov, size_t len, const void *d) { - struct iovec *dup; - size_t i; - - if (!iov) - return NULL; - - dup = new0(struct iovec, len); - - for (i = 0; i < len; i++) - iov_memcpy(&dup[i], iov[i].iov_base, iov[i].iov_len); - - return dup; + iov->iov_base = realloc(iov->iov_base, iov->iov_len + len); + return util_iov_push_mem(iov, len, d); } -unsigned int bt_bap_pac_register(bt_bap_pac_func_t added, +unsigned int bt_bap_pac_register(struct bt_bap *bap, bt_bap_pac_func_t added, bt_bap_pac_func_t removed, void *user_data, bt_bap_destroy_func_t destroy) { struct bt_bap_pac_changed *changed; + static unsigned int id; + + if (!bap) + return 0; changed = new0(struct bt_bap_pac_changed, 1); + changed->id = ++id ? id : ++id; changed->added = added; changed->removed = removed; changed->destroy = destroy; changed->data = user_data; - if (!pac_cbs) - pac_cbs = queue_new(); - - queue_push_tail(pac_cbs, changed); + queue_push_tail(bap->pac_cbs, changed); - return queue_length(pac_cbs); + return changed->id; } static void pac_changed_free(void *data) @@ -362,39 +307,28 @@ free(changed); } -struct match_pac_id { - unsigned int id; - unsigned int index; -}; - -static bool match_index(const void *data, const void *match_data) +static bool match_pac_changed_id(const void *data, const void *match_data) { - struct match_pac_id *match = (void *)match_data; - - match->index++; + const struct bt_bap_pac_changed *changed = data; + unsigned int id = PTR_TO_UINT(match_data); - return match->id == match->index; + return (changed->id == id); } -bool bt_bap_pac_unregister(unsigned int id) +bool bt_bap_pac_unregister(struct bt_bap *bap, unsigned int id) { struct bt_bap_pac_changed *changed; - struct match_pac_id match; - memset(&match, 0, sizeof(match)); - match.id = id; + if (!bap) + return false; - changed = queue_remove_if(pac_cbs, match_index, &match); + changed = queue_remove_if(bap->pac_cbs, match_pac_changed_id, + UINT_TO_PTR(id)); if (!changed) return false; pac_changed_free(changed); - if (queue_isempty(pac_cbs)) { - queue_destroy(pac_cbs, NULL); - pac_cbs = NULL; - } - return true; } @@ -407,27 +341,29 @@ struct bt_pac_metadata *meta; if (!iov->iov_len) { - rsp = iov_add(iov, sizeof(*rsp)); + rsp = util_iov_push(iov, sizeof(*rsp)); rsp->num_pac = 0; } else rsp = iov->iov_base; rsp->num_pac++; - p = iov_add(iov, sizeof(*p)); + p = util_iov_push(iov, sizeof(*p)); p->codec.id = pac->codec.id; + p->codec.cid = cpu_to_le16(pac->codec.cid); + p->codec.vid = cpu_to_le16(pac->codec.vid); if (pac->data) { p->cc_len = pac->data->iov_len; - iov_add_mem(iov, p->cc_len, pac->data->iov_base); + util_iov_push_mem(iov, p->cc_len, pac->data->iov_base); } else p->cc_len = 0; - meta = iov_add(iov, sizeof(*meta)); + meta = util_iov_push(iov, sizeof(*meta)); if (pac->metadata) { meta->len = pac->metadata->iov_len; - iov_add_mem(iov, meta->len, pac->metadata->iov_base); + util_iov_push_mem(iov, meta->len, pac->metadata->iov_base); } else meta->len = 0; } @@ -458,7 +394,8 @@ uint8_t opcode, struct bt_att *att, void *user_data) { - uint32_t value = 0x00000003; + struct bt_pacs *pacs = user_data; + uint32_t value = cpu_to_le32(pacs->sink_loc_value); gatt_db_attribute_read_result(attrib, id, 0, (void *) &value, sizeof(value)); @@ -490,7 +427,8 @@ uint8_t opcode, struct bt_att *att, void *user_data) { - uint32_t value = 0x00000001; + struct bt_pacs *pacs = user_data; + uint32_t value = cpu_to_le32(pacs->source_loc_value); gatt_db_attribute_read_result(attrib, id, 0, (void *) &value, sizeof(value)); @@ -501,9 +439,10 @@ uint8_t opcode, struct bt_att *att, void *user_data) { + struct bt_pacs *pacs = user_data; struct bt_pacs_context ctx = { - .snk = 0x0fff, - .src = 0x000e + .snk = cpu_to_le16(pacs->sink_context_value), + .src = cpu_to_le16(pacs->source_context_value) }; gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx, @@ -515,9 +454,10 @@ uint8_t opcode, struct bt_att *att, void *user_data) { + struct bt_pacs *pacs = user_data; struct bt_pacs_context ctx = { - .snk = 0x0fff, - .src = 0x000e + .snk = cpu_to_le16(pacs->supported_sink_context_value), + .src = cpu_to_le16(pacs->supported_source_context_value) }; gatt_db_attribute_read_result(attrib, id, 0, (void *) &ctx, @@ -534,6 +474,14 @@ pacs = new0(struct bt_pacs, 1); + /* Set default values */ + pacs->sink_loc_value = PACS_SNK_LOCATION; + pacs->source_loc_value = PACS_SRC_LOCATION; + pacs->sink_context_value = PACS_SNK_CTXT; + pacs->source_context_value = PACS_SRC_CTXT; + pacs->supported_sink_context_value = PACS_SUPPORTED_SNK_CTXT; + pacs->supported_source_context_value = PACS_SUPPORTED_SRC_CTXT; + /* Populate DB with PACS attributes */ bt_uuid16_create(&uuid, PACS_UUID); pacs->service = gatt_db_add_service(db, &uuid, true, 19); @@ -685,7 +633,20 @@ return ep; } -static struct bt_bap_endpoint *bap_get_endpoint(struct bt_bap_db *db, +static struct bt_bap_endpoint *bap_endpoint_new_broacast(struct bt_bap_db *bdb) +{ + struct bt_bap_endpoint *ep; + + ep = new0(struct bt_bap_endpoint, 1); + ep->bdb = bdb; + ep->attr = NULL; + ep->dir = BT_BAP_BCAST_SOURCE; + + return ep; +} + +static struct bt_bap_endpoint *bap_get_endpoint(struct queue *endpoints, + struct bt_bap_db *db, struct gatt_db_attribute *attr) { struct bt_bap_endpoint *ep; @@ -693,7 +654,7 @@ if (!db || !attr) return NULL; - ep = queue_find(db->endpoints, bap_endpoint_match, attr); + ep = queue_find(endpoints, bap_endpoint_match, attr); if (ep) return ep; @@ -701,7 +662,31 @@ if (!ep) return NULL; - queue_push_tail(db->endpoints, ep); + queue_push_tail(endpoints, ep); + + return ep; +} + +static struct bt_bap_endpoint *bap_get_endpoint_bcast(struct queue *endpoints, + struct bt_bap_db *db) +{ + struct bt_bap_endpoint *ep; + + if (!db) + return NULL; + /* + * We have support for only one stream so we will have + * only one endpoint. + * TO DO add support for more then one stream + */ + if (queue_length(endpoints) > 0) + return queue_peek_head(endpoints); + + ep = bap_endpoint_new_broacast(db); + if (!ep) + return NULL; + + queue_push_tail(endpoints, ep); return ep; } @@ -714,23 +699,22 @@ return (ep->id == id); } -static struct bt_bap_endpoint *bap_get_endpoint_id(struct bt_bap *bap, - struct bt_bap_db *db, - uint8_t id) +static struct bt_bap_endpoint *bap_get_local_endpoint_id(struct bt_bap *bap, + uint8_t id) { struct bt_bap_endpoint *ep; struct gatt_db_attribute *attr = NULL; size_t i; - if (!bap || !db) + if (!bap) return NULL; - ep = queue_find(db->endpoints, bap_endpoint_match_id, UINT_TO_PTR(id)); + ep = queue_find(bap->local_eps, bap_endpoint_match_id, UINT_TO_PTR(id)); if (ep) return ep; - for (i = 0; i < ARRAY_SIZE(db->ascs->ase); i++) { - struct bt_ase *ase = db->ascs->ase[i]; + for (i = 0; i < ARRAY_SIZE(bap->ldb->ascs->ase); i++) { + struct bt_ase *ase = bap->ldb->ascs->ase[i]; if (id) { if (ase->id != id) @@ -739,7 +723,7 @@ break; } - ep = queue_find(db->endpoints, bap_endpoint_match, ase->attr); + ep = queue_find(bap->local_eps, bap_endpoint_match, ase->attr); if (!ep) { attr = ase->attr; break; @@ -749,12 +733,12 @@ if (!attr) return NULL; - ep = bap_endpoint_new(db, attr); + ep = bap_endpoint_new(bap->ldb, attr); if (!ep) return NULL; ep->id = id; - queue_push_tail(db->endpoints, ep); + queue_push_tail(bap->local_eps, ep); return ep; } @@ -766,7 +750,8 @@ { struct bt_ase *ase = user_data; struct bt_bap *bap = bap_get_session(att, ase->ascs->bdb->db); - struct bt_bap_endpoint *ep = bap_get_endpoint(bap->ldb, attrib); + struct bt_bap_endpoint *ep = bap_get_endpoint(bap->local_eps, + bap->ldb, attrib); struct bt_ascs_ase_status rsp; if (!ase || !bap || !ep) { @@ -814,19 +799,6 @@ ascs->ase[i] = ase; } -static void *iov_pull_mem(struct iovec *iov, size_t len) -{ - void *data = iov->iov_base; - - if (iov->iov_len < len) - return NULL; - - iov->iov_base += len; - iov->iov_len -= len; - - return data; -} - static bool bap_codec_equal(const struct bt_bap_codec *c1, const struct bt_bap_codec *c2) { @@ -852,7 +824,7 @@ ep->stream = stream; stream->lpac = lpac; stream->rpac = rpac; - stream->cc = iov_dup(data, 1); + stream->cc = util_iov_dup(data, 1); stream->client = client; queue_push_tail(bap->streams, stream); @@ -910,6 +882,12 @@ put_le24(lpac->qos.ppd_min, config->ppd_min); put_le24(lpac->qos.ppd_max, config->ppd_max); config->codec = lpac->codec; + + if (config->codec.id == 0x0ff) { + config->codec.vid = cpu_to_le16(config->codec.vid); + config->codec.cid = cpu_to_le16(config->codec.cid); + } + config->cc_len = stream->cc->iov_len; memcpy(config->cc, stream->cc->iov_base, stream->cc->iov_len); @@ -936,15 +914,15 @@ status->state = ep->state; qos = (void *)status->params; - qos->cis_id = stream->qos.cis_id; - qos->cig_id = stream->qos.cig_id; - put_le24(stream->qos.interval, qos->interval); - qos->framing = stream->qos.framing; - qos->phy = stream->qos.phy; - qos->sdu = cpu_to_le16(stream->qos.sdu); - qos->rtn = stream->qos.rtn; - qos->latency = cpu_to_le16(stream->qos.latency); - put_le24(stream->qos.delay, qos->pd); + qos->cis_id = stream->qos.ucast.cis_id; + qos->cig_id = stream->qos.ucast.cig_id; + put_le24(stream->qos.ucast.io_qos.interval, qos->interval); + qos->framing = stream->qos.ucast.framing; + qos->phy = stream->qos.ucast.io_qos.phy; + qos->sdu = cpu_to_le16(stream->qos.ucast.io_qos.sdu); + qos->rtn = stream->qos.ucast.io_qos.rtn; + qos->latency = cpu_to_le16(stream->qos.ucast.io_qos.latency); + put_le24(stream->qos.ucast.delay, qos->pd); gatt_db_attribute_notify(ep->attr, (void *) status, len, bt_bap_get_att(stream->bap)); @@ -973,8 +951,8 @@ status->state = ep->state; meta = (void *)status->params; - meta->cis_id = stream->qos.cis_id; - meta->cig_id = stream->qos.cig_id; + meta->cis_id = stream->qos.ucast.cis_id; + meta->cig_id = stream->qos.ucast.cig_id; if (stream->meta) { meta->len = stream->meta->iov_len; @@ -1050,8 +1028,8 @@ queue_foreach(stream->links, bap_stream_unlink, stream); queue_destroy(stream->links, NULL); stream_io_unref(stream->io); - iov_free(stream->cc); - iov_free(stream->meta); + util_iov_free(stream->cc, 1); + util_iov_free(stream->meta, 1); free(stream); } @@ -1190,6 +1168,18 @@ return stream->io == io; } +static void stream_stop_disabling(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + + if (stream->io || stream->ep->state != BT_ASCS_ASE_STATE_DISABLING) + return; + + DBG(stream->bap, "stream %p", stream); + + bt_bap_stream_stop(stream, NULL, NULL); +} + static bool bap_stream_io_detach(struct bt_bap_stream *stream) { struct bt_bap_stream *link; @@ -1208,6 +1198,9 @@ /* Detach link if in QoS state */ if (link->ep->state == BT_ASCS_ASE_STATE_QOS) bap_stream_io_detach(link); + } else { + /* Links without IO on disabling state shall be stopped. */ + queue_foreach(stream->links, stream_stop_disabling, NULL); } stream_io_unref(io); @@ -1243,6 +1236,14 @@ } } +static struct bt_bap *bt_bap_ref_safe(struct bt_bap *bap) +{ + if (!bap || !bap->ref_count) + return NULL; + + return bt_bap_ref(bap); +} + static void bap_stream_state_changed(struct bt_bap_stream *stream) { struct bt_bap *bap = stream->bap; @@ -1253,7 +1254,14 @@ bt_bap_stream_statestr(stream->ep->old_state), bt_bap_stream_statestr(stream->ep->state)); - bt_bap_ref(bap); + /* Check if ref_count is already 0 which means detaching is in + * progress. + */ + bap = bt_bap_ref_safe(bap); + if (!bap) { + bap_stream_detach(stream); + return; + } /* Pre notification updates */ switch (stream->ep->state) { @@ -1305,6 +1313,37 @@ bt_bap_unref(bap); } +static void stream_set_state_broadcast(struct bt_bap_stream *stream, + uint8_t state) +{ + struct bt_bap_endpoint *ep = stream->ep; + struct bt_bap *bap = stream->bap; + const struct queue_entry *entry; + + ep->old_state = ep->state; + ep->state = state; + + bt_bap_ref(bap); + + for (entry = queue_get_entries(bap->state_cbs); entry; + entry = entry->next) { + struct bt_bap_state *state = entry->data; + + if (state->func) + state->func(stream, stream->ep->old_state, + stream->ep->state, state->data); + } + + /* Post notification updates */ + switch (stream->ep->state) { + case BT_ASCS_ASE_STATE_IDLE: + bap_stream_detach(stream); + break; + } + + bt_bap_unref(bap); +} + static void stream_set_state(struct bt_bap_stream *stream, uint8_t state) { struct bt_bap_endpoint *ep = stream->ep; @@ -1441,6 +1480,11 @@ if (err) return; + if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST) { + stream_set_state_broadcast(stream, BT_BAP_STREAM_STATE_CONFIG); + return; + } + stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); } @@ -1454,13 +1498,13 @@ /* TODO: Wait for pac->ops response */ ascs_ase_rsp_success(rsp, stream->ep->id); - if (!iov_memcmp(stream->cc, cc)) { + if (!util_iov_memcmp(stream->cc, cc)) { stream_set_state(stream, BT_BAP_STREAM_STATE_CONFIG); return 0; } - iov_free(stream->cc); - stream->cc = iov_dup(cc, 1); + util_iov_free(stream->cc, 1); + stream->cc = util_iov_dup(cc, 1); if (pac->ops && pac->ops->config) pac->ops->config(stream, cc, NULL, ep_config_cb, @@ -1497,7 +1541,7 @@ if (iov->iov_len < req->cc_len) return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; - cc.iov_base = iov_pull_mem(iov, req->cc_len); + cc.iov_base = util_iov_pull_mem(iov, req->cc_len); cc.iov_len = req->cc_len; if (!bap_print_cc(cc.iov_base, cc.iov_len, bap->debug_func, @@ -1548,12 +1592,12 @@ struct bt_bap_endpoint *ep; struct bt_ascs_config *req; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); DBG(bap, "codec 0x%02x phy 0x%02x latency %u", req->codec.id, req->phy, req->latency); - ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, @@ -1616,26 +1660,28 @@ struct bt_ascs_qos *req; struct bt_bap_qos qos; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); memset(&qos, 0, sizeof(qos)); - qos.cig_id = req->cig; - qos.cis_id = req->cis; - qos.interval = get_le24(req->interval); - qos.framing = req->framing; - qos.phy = req->phy; - qos.sdu = le16_to_cpu(req->sdu); - qos.rtn = req->rtn; - qos.latency = le16_to_cpu(req->latency); - qos.delay = get_le24(req->pd); + qos.ucast.cig_id = req->cig; + qos.ucast.cis_id = req->cis; + qos.ucast.io_qos.interval = get_le24(req->interval); + qos.ucast.framing = req->framing; + qos.ucast.io_qos.phy = req->phy; + qos.ucast.io_qos.sdu = le16_to_cpu(req->sdu); + qos.ucast.io_qos.rtn = req->rtn; + qos.ucast.io_qos.latency = le16_to_cpu(req->latency); + qos.ucast.delay = get_le24(req->pd); DBG(bap, "CIG 0x%02x CIS 0x%02x interval %u framing 0x%02x " "phy 0x%02x SDU %u rtn %u latency %u pd %u", - req->cig, req->cis, qos.interval, qos.framing, qos.phy, - qos.sdu, qos.rtn, qos.latency, qos.delay); + req->cig, req->cis, qos.ucast.io_qos.interval, + qos.ucast.framing, qos.ucast.io_qos.phy, + qos.ucast.io_qos.sdu, qos.ucast.io_qos.rtn, + qos.ucast.io_qos.latency, qos.ucast.delay); - ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "%s: Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, @@ -1653,8 +1699,8 @@ ascs_ase_rsp_success(rsp, stream->ep->id); - iov_free(stream->meta); - stream->meta = iov_dup(meta, 1); + util_iov_free(stream->meta, 1); + stream->meta = util_iov_dup(meta, 1); stream_set_state(stream, BT_BAP_STREAM_STATE_ENABLING); @@ -1677,7 +1723,7 @@ util_debug(func, user_data, "Length %zu", iov.iov_len); for (i = 0; iov.iov_len > 1; i++) { - struct bt_ltv *ltv = iov_pull_mem(&iov, sizeof(*ltv)); + struct bt_ltv *ltv = util_iov_pull_mem(&iov, sizeof(*ltv)); uint8_t *data; if (!ltv) { @@ -1689,7 +1735,7 @@ util_debug(func, user_data, "%s #%u: len %u type %u", label, i, ltv->len, ltv->type); - data = iov_pull_mem(&iov, ltv->len - 1); + data = util_iov_pull_mem(&iov, ltv->len - 1); if (!data) { util_debug(func, user_data, "Unable to parse %s", label); @@ -1728,7 +1774,7 @@ return 0; } - meta.iov_base = iov_pull_mem(iov, req->meta.len); + meta.iov_base = util_iov_pull_mem(iov, req->meta.len); meta.iov_len = req->meta.len; if (!bap_print_metadata(meta.iov_base, meta.iov_len, bap->debug_func, @@ -1756,9 +1802,9 @@ struct bt_bap_endpoint *ep; struct bt_ascs_enable *req; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); - ep = bap_get_endpoint_id(bap, bap->ldb, req->meta.ase); + ep = bap_get_local_endpoint_id(bap, req->meta.ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->meta.ase); ascs_ase_rsp_add(rsp, req->meta.ase, @@ -1820,9 +1866,9 @@ struct bt_bap_endpoint *ep; struct bt_ascs_start *req; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); - ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, @@ -1892,9 +1938,9 @@ struct bt_bap_endpoint *ep; struct bt_ascs_disable *req; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); - ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, @@ -1967,9 +2013,9 @@ struct bt_bap_endpoint *ep; struct bt_ascs_stop *req; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); - ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, @@ -1995,8 +2041,8 @@ ascs_ase_rsp_success(rsp, stream->ep->id); - iov_free(stream->meta); - stream->meta = iov_dup(meta, 1); + util_iov_free(stream->meta, 1); + stream->meta = util_iov_dup(meta, 1); return 0; } @@ -2032,9 +2078,9 @@ struct bt_bap_endpoint *ep; struct bt_ascs_metadata *req; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); - ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, @@ -2076,9 +2122,9 @@ struct bt_bap_endpoint *ep; struct bt_ascs_release *req; - req = iov_pull_mem(iov, sizeof(*req)); + req = util_iov_pull_mem(iov, sizeof(*req)); - ep = bap_get_endpoint_id(bap, bap->ldb, req->ase); + ep = bap_get_local_endpoint_id(bap, req->ase); if (!ep) { DBG(bap, "Invalid ASE ID 0x%02x", req->ase); ascs_ase_rsp_add(rsp, req->ase, @@ -2177,7 +2223,7 @@ return; } - hdr = iov_pull_mem(&iov, sizeof(*hdr)); + hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); rsp = ascs_ase_cp_rsp_new(hdr->op); for (handler = handlers; handler && handler->str; handler++) { @@ -2214,7 +2260,7 @@ gatt_db_attribute_notify(attrib, rsp->iov_base, rsp->iov_len, att); gatt_db_attribute_write_result(attrib, id, ret); - iov_free(rsp); + util_iov_free(rsp, 1); } static struct bt_ascs *ascs_new(struct gatt_db *db) @@ -2264,7 +2310,8 @@ bdb->db = gatt_db_ref(db); bdb->sinks = queue_new(); bdb->sources = queue_new(); - bdb->endpoints = queue_new(); + bdb->broadcast_sources = queue_new(); + bdb->broadcast_sinks = queue_new(); if (!bap_db) bap_db = queue_new(); @@ -2319,6 +2366,58 @@ return bap->rdb->ascs; } +static bool match_codec(const void *data, const void *user_data) +{ + const struct bt_bap_pac *pac = data; + const struct bt_bap_codec *codec = user_data; + + return bap_codec_equal(&pac->codec, codec); +} + +static struct bt_bap_pac *bap_pac_find(struct bt_bap_db *bdb, uint8_t type, + struct bt_bap_codec *codec) +{ + switch (type) { + case BT_BAP_SOURCE: + return queue_find(bdb->sources, match_codec, codec); + case BT_BAP_SINK: + return queue_find(bdb->sinks, match_codec, codec); + } + + return NULL; +} + +static void *ltv_merge(struct iovec *data, struct iovec *cont) +{ + uint8_t delimiter = 0; + + if (!data) + return NULL; + + if (!cont || !cont->iov_len || !cont->iov_base) + return data->iov_base; + + iov_append(data, sizeof(delimiter), &delimiter); + + return iov_append(data, cont->iov_len, cont->iov_base); +} + +static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data, + struct iovec *metadata) +{ + /* Merge data into existing record */ + if (pac->data) + ltv_merge(pac->data, data); + else + pac->data = util_iov_dup(data, 1); + + /* Merge metadata into existing record */ + if (pac->metadata) + ltv_merge(pac->metadata, metadata); + else + pac->metadata = util_iov_dup(metadata, 1); +} + static struct bt_bap_pac *bap_pac_new(struct bt_bap_db *bdb, const char *name, uint8_t type, struct bt_bap_codec *codec, @@ -2333,8 +2432,8 @@ pac->name = name ? strdup(name) : NULL; pac->type = type; pac->codec = *codec; - pac->data = iov_dup(data, 1); - pac->metadata = iov_dup(metadata, 1); + pac->data = util_iov_dup(data, 1); + pac->metadata = util_iov_dup(metadata, 1); if (qos) pac->qos = *qos; @@ -2347,8 +2446,8 @@ struct bt_bap_pac *pac = data; free(pac->name); - iov_free(pac->metadata); - iov_free(pac->data); + util_iov_free(pac->metadata, 1); + util_iov_free(pac->data, 1); free(pac); } @@ -2388,6 +2487,16 @@ iov.iov_len, NULL); } +static void bap_add_broadcast_source(struct bt_bap_pac *pac) +{ + queue_push_tail(pac->bdb->broadcast_sources, pac); +} + +static void bap_add_broadcast_sink(struct bt_bap_pac *pac) +{ + queue_push_tail(pac->bdb->broadcast_sinks, pac); +} + static void notify_pac_added(void *data, void *user_data) { struct bt_bap_pac_changed *changed = data; @@ -2397,6 +2506,13 @@ changed->added(pac, changed->data); } +static void notify_session_pac_added(void *data, void *user_data) +{ + struct bt_bap *bap = data; + + queue_foreach(bap->pac_cbs, notify_pac_added, user_data); +} + struct bt_bap_pac *bt_bap_add_vendor_pac(struct gatt_db *db, const char *name, uint8_t type, uint8_t id, uint16_t cid, uint16_t vid, @@ -2405,7 +2521,7 @@ struct iovec *metadata) { struct bt_bap_db *bdb; - struct bt_bap_pac *pac; + struct bt_bap_pac *pac, *pac_brodcast_sink; struct bt_bap_codec codec; if (!db) @@ -2415,6 +2531,9 @@ if (!bdb) return NULL; + if ((id != 0xff) && ((cid != 0U) || (vid != 0U))) + return NULL; + codec.id = id; codec.cid = cid; codec.vid = vid; @@ -2428,12 +2547,19 @@ case BT_BAP_SOURCE: bap_add_source(pac); break; + case BT_BAP_BCAST_SOURCE: + // For broadcast add local pac and remote pac + bap_add_broadcast_source(pac); + pac_brodcast_sink = bap_pac_new(bdb, name, type, &codec, qos, + data, metadata); + bap_add_broadcast_sink(pac_brodcast_sink); + break; default: bap_pac_free(pac); return NULL; } - queue_foreach(pac_cbs, notify_pac_added, pac); + queue_foreach(sessions, notify_session_pac_added, pac); return pac; } @@ -2456,6 +2582,37 @@ return pac->type; } +uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac) +{ + struct bt_pacs *pacs = pac->bdb->pacs; + + switch (pac->type) { + case BT_BAP_SOURCE: + return pacs->source_loc_value; + case BT_BAP_SINK: + return pacs->sink_loc_value; + default: + return 0; + } +} + +uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream) +{ + if (!stream) + return 0x00; + + switch (bt_bap_pac_get_type(stream->lpac)) { + case BT_BAP_SINK: + case BT_BAP_SOURCE: + return BT_BAP_STREAM_TYPE_UCAST; + case BT_BAP_BCAST_SOURCE: + case BT_BAP_BCAST_SINK: + return BT_BAP_STREAM_TYPE_BCAST; + } + + return 0x00; +} + static void notify_pac_removed(void *data, void *user_data) { struct bt_bap_pac_changed *changed = data; @@ -2465,6 +2622,13 @@ changed->removed(pac, changed->data); } +static void notify_session_pac_removed(void *data, void *user_data) +{ + struct bt_bap *bap = data; + + queue_foreach(bap->pac_cbs, notify_pac_removed, user_data); +} + bool bt_bap_pac_set_ops(struct bt_bap_pac *pac, struct bt_bap_pac_ops *ops, void *user_data) { @@ -2507,11 +2671,14 @@ if (queue_remove_if(pac->bdb->sources, NULL, pac)) goto found; + if (queue_remove_if(pac->bdb->broadcast_sources, NULL, pac)) + goto found; + return false; found: queue_foreach(sessions, remove_streams, pac); - queue_foreach(pac_cbs, notify_pac_removed, pac); + queue_foreach(sessions, notify_session_pac_removed, pac); bap_pac_free(pac); return true; } @@ -2525,7 +2692,6 @@ queue_destroy(bdb->sinks, bap_pac_free); queue_destroy(bdb->sources, bap_pac_free); - queue_destroy(bdb->endpoints, free); gatt_db_unref(bdb->db); free(bdb->pacs); @@ -2579,15 +2745,19 @@ { struct bt_bap *bap = data; + timeout_remove(bap->process_id); + bt_bap_detach(bap); bap_db_free(bap->rdb); + queue_destroy(bap->pac_cbs, pac_changed_free); queue_destroy(bap->ready_cbs, bap_ready_free); queue_destroy(bap->state_cbs, bap_state_free); + queue_destroy(bap->local_eps, free); + queue_destroy(bap->remote_eps, free); queue_destroy(bap->reqs, bap_req_free); - queue_destroy(bap->pending, NULL); queue_destroy(bap->notify, NULL); queue_destroy(bap->streams, bap_stream_free); @@ -2661,11 +2831,12 @@ bap = new0(struct bt_bap, 1); bap->ldb = bdb; bap->reqs = queue_new(); - bap->pending = queue_new(); bap->notify = queue_new(); + bap->pac_cbs = queue_new(); bap->ready_cbs = queue_new(); bap->streams = queue_new(); bap->state_cbs = queue_new(); + bap->local_eps = queue_new(); if (!rdb) goto done; @@ -2674,9 +2845,9 @@ bdb->db = gatt_db_ref(rdb); bdb->sinks = queue_new(); bdb->sources = queue_new(); - bdb->endpoints = queue_new(); bap->rdb = bdb; + bap->remote_eps = queue_new(); done: return bt_bap_ref(bap); @@ -2736,11 +2907,9 @@ { const struct queue_entry *entry; - if (!queue_isempty(bap->pending)) + if (!bt_bap_ref_safe(bap)) return; - bt_bap_ref(bap); - for (entry = queue_get_entries(bap->ready_cbs); entry; entry = entry->next) { struct bt_bap_ready *ready = entry->data; @@ -2769,7 +2938,7 @@ }; int i; - rsp = iov_pull_mem(&iov, sizeof(*rsp)); + rsp = util_iov_pull_mem(&iov, sizeof(*rsp)); if (!rsp) { DBG(bap, "Unable to parse PAC"); return; @@ -2784,25 +2953,30 @@ struct bt_pac_metadata *meta; struct iovec data, metadata; - p = iov_pull_mem(&iov, sizeof(*p)); + p = util_iov_pull_mem(&iov, sizeof(*p)); if (!p) { DBG(bap, "Unable to parse PAC"); return; } + if (p->codec.id == 0xff) { + p->codec.cid = le16_to_cpu(p->codec.cid); + p->codec.vid = le16_to_cpu(p->codec.vid); + } + pac = NULL; if (!bap_print_cc(iov.iov_base, p->cc_len, bap->debug_func, bap->debug_data)) return; - cc = iov_pull_mem(&iov, p->cc_len); + cc = util_iov_pull_mem(&iov, p->cc_len); if (!cc) { DBG(bap, "Unable to parse PAC codec capabilities"); return; } - meta = iov_pull_mem(&iov, sizeof(*meta)); + meta = util_iov_pull_mem(&iov, sizeof(*meta)); if (!meta) { DBG(bap, "Unable to parse PAC metadata"); return; @@ -2814,24 +2988,33 @@ metadata.iov_len = meta->len; metadata.iov_base = meta->data; - iov_pull_mem(&iov, meta->len); + util_iov_pull_mem(&iov, meta->len); + + DBG(bap, "PAC #%u: type %u codec 0x%02x cc_len %u meta_len %u", + i, type, p->codec.id, p->cc_len, meta->len); + + /* Check if there is already a PAC record for the codec */ + pac = bap_pac_find(bap->rdb, type, &p->codec); + if (pac) { + bap_pac_merge(pac, &data, &metadata); + continue; + } pac = bap_pac_new(bap->rdb, NULL, type, &p->codec, NULL, &data, &metadata); if (!pac) continue; - DBG(bap, "PAC #%u: type %u codec 0x%02x cc_len %u meta_len %u", - i, type, p->codec.id, p->cc_len, meta->len); - queue_push_tail(queue, pac); } } -static void read_source_pac(struct bt_bap *bap, bool success, uint8_t att_ecode, +static void read_source_pac(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { + struct bt_bap *bap = user_data; + if (!success) { DBG(bap, "Unable to read Source PAC: error 0x%02x", att_ecode); return; @@ -2840,10 +3023,12 @@ bap_parse_pacs(bap, BT_BAP_SOURCE, bap->rdb->sources, value, length); } -static void read_sink_pac(struct bt_bap *bap, bool success, uint8_t att_ecode, +static void read_sink_pac(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { + struct bt_bap *bap = user_data; + if (!success) { DBG(bap, "Unable to read Sink PAC: error 0x%02x", att_ecode); return; @@ -2852,10 +3037,11 @@ bap_parse_pacs(bap, BT_BAP_SINK, bap->rdb->sinks, value, length); } -static void read_source_pac_loc(struct bt_bap *bap, bool success, - uint8_t att_ecode, const uint8_t *value, - uint16_t length, void *user_data) +static void read_source_pac_loc(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) { + struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); if (!success) { @@ -2864,14 +3050,31 @@ return; } - gatt_db_attribute_write(pacs->source_loc, 0, value, length, 0, NULL, - NULL, NULL); + if (length != sizeof(uint32_t)) { + DBG(bap, "Invalid Source PAC Location size: %d", length); + return; + } + + pacs->source_loc_value = get_le32(value); + + /* Resume reading sinks if supported but for some reason is empty */ + if (pacs->source && queue_isempty(bap->rdb->sources)) { + uint16_t value_handle; + + if (gatt_db_attribute_get_char_data(pacs->source, + NULL, &value_handle, + NULL, NULL, NULL)) + bt_gatt_client_read_value(bap->client, value_handle, + read_source_pac, bap, + NULL); + } } -static void read_sink_pac_loc(struct bt_bap *bap, bool success, - uint8_t att_ecode, const uint8_t *value, - uint16_t length, void *user_data) +static void read_sink_pac_loc(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) { + struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); if (!success) { @@ -2880,30 +3083,55 @@ return; } - gatt_db_attribute_write(pacs->sink_loc, 0, value, length, 0, NULL, - NULL, NULL); + if (length != sizeof(uint32_t)) { + DBG(bap, "Invalid Sink PAC Location size: %d", length); + return; + } + + pacs->sink_loc_value = get_le32(value); + + /* Resume reading sinks if supported but for some reason is empty */ + if (pacs->sink && queue_isempty(bap->rdb->sinks)) { + uint16_t value_handle; + + if (gatt_db_attribute_get_char_data(pacs->sink, + NULL, &value_handle, + NULL, NULL, NULL)) + bt_gatt_client_read_value(bap->client, value_handle, + read_sink_pac, bap, + NULL); + } } -static void read_pac_context(struct bt_bap *bap, bool success, - uint8_t att_ecode, const uint8_t *value, - uint16_t length, void *user_data) +static void read_pac_context(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) { + struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); + const struct bt_pacs_context *ctx = (void *)value; if (!success) { DBG(bap, "Unable to read PAC Context: error 0x%02x", att_ecode); return; } - gatt_db_attribute_write(pacs->context, 0, value, length, 0, NULL, - NULL, NULL); + if (length != sizeof(*ctx)) { + DBG(bap, "Invalid PAC Context size: %d", length); + return; + } + + pacs->sink_context_value = le16_to_cpu(ctx->snk); + pacs->source_context_value = le16_to_cpu(ctx->src); } -static void read_pac_supported_context(struct bt_bap *bap, bool success, - uint8_t att_ecode, const uint8_t *value, - uint16_t length, void *user_data) +static void read_pac_supported_context(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) { + struct bt_bap *bap = user_data; struct bt_pacs *pacs = bap_get_pacs(bap); + const struct bt_pacs_context *ctx = (void *)value; if (!success) { DBG(bap, "Unable to read PAC Supproted Context: error 0x%02x", @@ -2911,52 +3139,13 @@ return; } - gatt_db_attribute_write(pacs->supported_context, 0, value, length, 0, - NULL, NULL, NULL); -} - -static void bap_pending_destroy(void *data) -{ - struct bt_bap_pending *pending = data; - struct bt_bap *bap = pending->bap; - - if (queue_remove_if(bap->pending, NULL, pending)) - free(pending); - - bap_notify_ready(bap); -} - -static void bap_pending_complete(bool success, uint8_t att_ecode, - const uint8_t *value, uint16_t length, - void *user_data) -{ - struct bt_bap_pending *pending = user_data; - - if (pending->func) - pending->func(pending->bap, success, att_ecode, value, length, - pending->user_data); -} - -static void bap_read_value(struct bt_bap *bap, uint16_t value_handle, - bap_func_t func, void *user_data) -{ - struct bt_bap_pending *pending; - - pending = new0(struct bt_bap_pending, 1); - pending->bap = bap; - pending->func = func; - pending->user_data = user_data; - - pending->id = bt_gatt_client_read_value(bap->client, value_handle, - bap_pending_complete, pending, - bap_pending_destroy); - if (!pending->id) { - DBG(bap, "Unable to send Read request"); - free(pending); + if (length != sizeof(*ctx)) { + DBG(bap, "Invalid PAC Supported Context size: %d", length); return; } - queue_push_tail(bap->pending, pending); + pacs->supported_sink_context_value = le16_to_cpu(ctx->snk); + pacs->supported_source_context_value = le16_to_cpu(ctx->src); } static void foreach_pacs_char(struct gatt_db_attribute *attr, void *user_data) @@ -2982,22 +3171,28 @@ DBG(bap, "Sink PAC found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); - if (!pacs || pacs->sink) + if (!pacs) return; - pacs->sink = attr; - bap_read_value(bap, value_handle, read_sink_pac, bap); + if (!pacs->sink) + pacs->sink = attr; + + bt_gatt_client_read_value(bap->client, value_handle, + read_sink_pac, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_source)) { DBG(bap, "Source PAC found: handle 0x%04x", value_handle); pacs = bap_get_pacs(bap); - if (!pacs || pacs->source) + if (!pacs) return; - pacs->source = attr; - bap_read_value(bap, value_handle, read_source_pac, NULL); + if (!pacs->source) + pacs->source = attr; + + bt_gatt_client_read_value(bap->client, value_handle, + read_source_pac, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_sink_loc)) { @@ -3009,7 +3204,8 @@ return; pacs->sink_loc = attr; - bap_read_value(bap, value_handle, read_sink_pac_loc, NULL); + bt_gatt_client_read_value(bap->client, value_handle, + read_sink_pac_loc, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_source_loc)) { @@ -3021,7 +3217,8 @@ return; pacs->source_loc = attr; - bap_read_value(bap, value_handle, read_source_pac_loc, NULL); + bt_gatt_client_read_value(bap->client, value_handle, + read_source_pac_loc, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_context)) { @@ -3032,7 +3229,8 @@ return; pacs->context = attr; - bap_read_value(bap, value_handle, read_pac_context, NULL); + bt_gatt_client_read_value(bap->client, value_handle, + read_pac_context, bap, NULL); } if (!bt_uuid_cmp(&uuid, &uuid_supported_context)) { @@ -3044,8 +3242,9 @@ return; pacs->supported_context = attr; - bap_read_value(bap, value_handle, read_pac_supported_context, - NULL); + bt_gatt_client_read_value(bap->client, value_handle, + read_pac_supported_context, + bap, NULL); } } @@ -3089,7 +3288,7 @@ uint32_t pd_min, pd_max, ppd_min, ppd_max; int i; - cfg = iov_pull_mem(iov, sizeof(*cfg)); + cfg = util_iov_pull_mem(iov, sizeof(*cfg)); if (!cfg) { DBG(bap, "Unable to parse Config Status"); return; @@ -3113,14 +3312,14 @@ } for (i = 0; iov->iov_len >= sizeof(*cc); i++) { - cc = iov_pull_mem(iov, sizeof(*cc)); + cc = util_iov_pull_mem(iov, sizeof(*cc)); if (!cc) break; DBG(bap, "Codec Config #%u: type 0x%02x len %u", i, cc->type, cc->len); - iov_pull_mem(iov, cc->len - 1); + util_iov_pull_mem(iov, cc->len - 1); } /* Any previously applied codec configuration may be cached by the @@ -3160,7 +3359,7 @@ if (!ep->stream->cc) ep->stream->cc = new0(struct iovec, 1); - iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len); + util_iov_memcpy(ep->stream->cc, cfg->cc, cfg->cc_len); } static void bap_stream_config_cfm_cb(struct bt_bap_stream *stream, int err) @@ -3200,7 +3399,7 @@ uint16_t sdu; uint16_t latency; - qos = iov_pull_mem(iov, sizeof(*qos)); + qos = util_iov_pull_mem(iov, sizeof(*qos)); if (!qos) { DBG(bap, "Unable to parse QoS Status"); return; @@ -3219,13 +3418,13 @@ if (!ep->stream) return; - ep->stream->qos.interval = interval; - ep->stream->qos.framing = qos->framing; - ep->stream->qos.phy = qos->phy; - ep->stream->qos.sdu = sdu; - ep->stream->qos.rtn = qos->rtn; - ep->stream->qos.latency = latency; - ep->stream->qos.delay = pd; + ep->stream->qos.ucast.io_qos.interval = interval; + ep->stream->qos.ucast.framing = qos->framing; + ep->stream->qos.ucast.io_qos.phy = qos->phy; + ep->stream->qos.ucast.io_qos.sdu = sdu; + ep->stream->qos.ucast.io_qos.rtn = qos->rtn; + ep->stream->qos.ucast.io_qos.latency = latency; + ep->stream->qos.ucast.delay = pd; if (ep->old_state == BT_ASCS_ASE_STATE_CONFIG) bap_stream_config_cfm(ep->stream); @@ -3236,7 +3435,7 @@ { struct bt_ascs_ase_status_metadata *meta; - meta = iov_pull_mem(iov, sizeof(*meta)); + meta = util_iov_pull_mem(iov, sizeof(*meta)); if (!meta) { DBG(bap, "Unable to parse Metadata Status"); return; @@ -3255,7 +3454,7 @@ .iov_len = length, }; - rsp = iov_pull_mem(&iov, sizeof(*rsp)); + rsp = util_iov_pull_mem(&iov, sizeof(*rsp)); if (!rsp) return; @@ -3293,14 +3492,17 @@ bap_stream_state_changed(ep->stream); } -static void read_ase_status(struct bt_bap *bap, bool success, uint8_t att_ecode, +static void read_ase_status(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct bt_bap_endpoint *ep = user_data; + struct bt_bap *bap = ep->bap; - if (!success) + if (!success) { + DBG(bap, "ASE read status failed: 0x%04x", att_ecode); return; + } bap_ep_set_status(bap, ep, value, length); } @@ -3378,7 +3580,10 @@ DBG(bap, "ASE handle 0x%04x", value_handle); - bap_read_value(bap, value_handle, read_ase_status, ep); + ep->bap = bap; + + bt_gatt_client_read_value(bap->client, value_handle, read_ase_status, + ep, NULL); ep->state_id = bap_register_notify(bap, value_handle, bap_endpoint_notify, ep); @@ -3391,7 +3596,24 @@ size_t i; for (i = 0; i < req->len; i++) - iov_add_mem(iov, req->iov[i].iov_len, req->iov[i].iov_base); + util_iov_push_mem(iov, req->iov[i].iov_len, + req->iov[i].iov_base); +} + +static uint16_t bap_req_len(struct bt_bap_req *req) +{ + uint16_t len = 0; + size_t i; + const struct queue_entry *e; + + for (i = 0; i < req->len; i++) + len += req->iov[i].iov_len; + + e = queue_get_entries(req->group); + for (; e; e = e->next) + len += bap_req_len(e->data); + + return len; } static bool bap_send(struct bt_bap *bap, struct bt_bap_req *req) @@ -3399,25 +3621,31 @@ struct bt_ascs *ascs = bap_get_ascs(bap); int ret; uint16_t handle; - uint8_t buf[64]; struct bt_ascs_ase_hdr hdr; - struct iovec iov = { - .iov_base = buf, - .iov_len = 0, - }; + struct iovec iov; size_t i; + iov.iov_len = sizeof(hdr) + bap_req_len(req); + + DBG(bap, "req %p len %u", req, iov.iov_len); + if (!gatt_db_attribute_get_char_data(ascs->ase_cp, NULL, &handle, - NULL, NULL, NULL)) + NULL, NULL, NULL)) { + DBG(bap, "Unable to find Control Point"); return false; + } + + iov.iov_base = alloca(iov.iov_len); + iov.iov_len = 0; hdr.op = req->op; hdr.num = 1 + queue_length(req->group); - iov_add_mem(&iov, sizeof(hdr), &hdr); + util_iov_push_mem(&iov, sizeof(hdr), &hdr); for (i = 0; i < req->len; i++) - iov_add_mem(&iov, req->iov[i].iov_len, req->iov[i].iov_base); + util_iov_push_mem(&iov, req->iov[i].iov_len, + req->iov[i].iov_base); /* Append the request group with the same opcode */ queue_foreach(req->group, append_group, &iov); @@ -3425,12 +3653,41 @@ ret = bt_gatt_client_write_without_response(bap->client, handle, false, iov.iov_base, iov.iov_len); - if (!ret) + if (!ret) { + DBG(bap, "Unable to Write to Control Point"); return false; + } bap->req = req; - return false; + return true; +} + +static void bap_req_complete(struct bt_bap_req *req, + const struct bt_ascs_ase_rsp *rsp) +{ + struct queue *group; + + if (!req->func) + goto done; + + if (rsp) + req->func(req->stream, rsp->code, rsp->reason, req->user_data); + else + req->func(req->stream, BT_ASCS_RSP_UNSPECIFIED, 0x00, + req->user_data); + +done: + /* Detach from request so it can be freed separately */ + group = req->group; + req->group = NULL; + + queue_foreach(group, (queue_foreach_func_t)bap_req_complete, + (void *)rsp); + + queue_destroy(group, NULL); + + bap_req_free(req); } static bool bap_process_queue(void *data) @@ -3438,14 +3695,17 @@ struct bt_bap *bap = data; struct bt_bap_req *req; + DBG(bap, ""); + if (bap->process_id) { timeout_remove(bap->process_id); bap->process_id = 0; } while ((req = queue_pop_head(bap->reqs))) { - if (!bap_send(bap, req)) + if (bap_send(bap, req)) break; + bap_req_complete(req, NULL); } return false; @@ -3463,9 +3723,19 @@ { struct bt_bap_req *pend; struct queue *queue; + struct bt_att *att = bt_bap_get_att(bap); + uint16_t mtu = bt_att_get_mtu(att); + uint16_t len = 2 + bap_req_len(req); + + if (len > mtu) { + DBG(bap, "Unable to queue request: req len %u > %u mtu", len, + mtu); + return false; + } pend = queue_find(bap->reqs, match_req, req); - if (pend) { + /* Check if req can be grouped together and it fits in the MTU */ + if (pend && (bap_req_len(pend) + len < mtu)) { if (!pend->group) pend->group = queue_new(); /* Group requests with the same opcode */ @@ -3491,33 +3761,6 @@ return true; } -static void bap_req_complete(struct bt_bap_req *req, - const struct bt_ascs_ase_rsp *rsp) -{ - struct queue *group; - - if (!req->func) - goto done; - - if (rsp) - req->func(req->stream, rsp->code, rsp->reason, req->user_data); - else - req->func(req->stream, BT_ASCS_RSP_UNSPECIFIED, 0x00, - req->user_data); - -done: - /* Detach from request so it can be freed separately */ - group = req->group; - req->group = NULL; - - queue_foreach(group, (queue_foreach_func_t)bap_req_complete, - (void *)rsp); - - queue_destroy(group, NULL); - - bap_req_free(req); -} - static void bap_cp_notify(struct bt_bap *bap, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) @@ -3602,7 +3845,7 @@ !bt_uuid_cmp(&uuid, &uuid_source)) { struct bt_bap_endpoint *ep; - ep = bap_get_endpoint(bap->rdb, attr); + ep = bap_get_endpoint(bap->remote_eps, bap->rdb, attr); if (!ep) return; @@ -3665,6 +3908,15 @@ bap, NULL); } +static void bap_idle(void *data) +{ + struct bt_bap *bap = data; + + bap->idle_id = 0; + + bap_notify_ready(bap); +} + bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client) { bt_uuid_t uuid; @@ -3700,6 +3952,9 @@ bap_attach_att(bap, bt_gatt_client_get_att(client)); + bap->idle_id = bt_gatt_client_idle_register(bap->client, bap_idle, + bap, NULL); + if (bap->rdb->pacs) { uint16_t value_handle; struct bt_pacs *pacs = bap->rdb->pacs; @@ -3709,8 +3964,10 @@ if (gatt_db_attribute_get_char_data(pacs->sink, NULL, &value_handle, NULL, NULL, NULL)) { - bap_read_value(bap, value_handle, - read_sink_pac, bap); + bt_gatt_client_read_value(bap->client, + value_handle, + read_sink_pac, + bap, NULL); } } @@ -3719,17 +3976,17 @@ if (gatt_db_attribute_get_char_data(pacs->source, NULL, &value_handle, NULL, NULL, NULL)) { - bap_read_value(bap, value_handle, - read_source_pac, bap); + bt_gatt_client_read_value(bap->client, + value_handle, + read_source_pac, + bap, NULL); } } - queue_foreach(bap->rdb->endpoints, bap_endpoint_foreach, bap); + queue_foreach(bap->remote_eps, bap_endpoint_foreach, bap); bap_cp_attach(bap); - bap_notify_ready(bap); - return true; } @@ -3742,6 +3999,25 @@ return true; } +bool bt_bap_attach_broadcast(struct bt_bap *bap) +{ + struct bt_bap_endpoint *ep; + + if (queue_find(sessions, NULL, bap)) + return true; + + if (!sessions) + sessions = queue_new(); + + queue_push_tail(sessions, bap); + + ep = bap_get_endpoint_bcast(bap->remote_eps, bap->ldb); + if (ep) + ep->bap = bap; + + return true; +} + static void stream_foreach_detach(void *data, void *user_data) { struct bt_bap_stream *stream = data; @@ -3749,6 +4025,13 @@ stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); } +static void bap_req_detach(void *data) +{ + struct bt_bap_req *req = data; + + bap_req_complete(req, NULL); +} + void bt_bap_detach(struct bt_bap *bap) { DBG(bap, "%p", bap); @@ -3756,6 +4039,17 @@ if (!queue_remove(sessions, bap)) return; + /* Cancel ongoing request */ + if (bap->req) { + bap_req_detach(bap->req); + bap->req = NULL; + } + + bt_gatt_client_idle_unregister(bap->client, bap->idle_id); + + /* Cancel queued requests */ + queue_remove_all(bap->reqs, NULL, NULL, bap_req_detach); + bt_gatt_client_unref(bap->client); bap->client = NULL; @@ -3928,7 +4222,11 @@ func, user_data); case BT_BAP_SOURCE: return bap_foreach_pac(bap->ldb->sinks, bap->rdb->sources, - func, user_data); + func, user_data); + case BT_BAP_BCAST_SOURCE: + return bap_foreach_pac(bap->ldb->broadcast_sources, + bap->ldb->broadcast_sinks, + func, user_data); } } @@ -4011,7 +4309,7 @@ req->id = ++id; req->stream = stream; req->op = op; - req->iov = iov_dup(iov, len); + req->iov = util_iov_dup(iov, len); req->len = len; req->func = func; req->user_data = user_data; @@ -4041,42 +4339,56 @@ if (!bap_stream_valid(stream)) return 0; - if (!stream->client) { - stream_config(stream, data, NULL); - return 0; - } + switch (bt_bap_stream_get_type(stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + if (!stream->client) { + stream_config(stream, data, NULL); + return 0; + } - memset(&config, 0, sizeof(config)); + memset(&config, 0, sizeof(config)); - config.ase = stream->ep->id; - config.latency = qos->target_latency; - config.phy = qos->phy; - config.codec = stream->rpac->codec; + config.ase = stream->ep->id; + config.latency = qos->ucast.target_latency; + config.phy = qos->ucast.io_qos.phy; + config.codec = stream->rpac->codec; + + if (config.codec.id == 0xff) { + config.codec.cid = cpu_to_le16(config.codec.cid); + config.codec.vid = cpu_to_le16(config.codec.vid); + } - iov[0].iov_base = &config; - iov[0].iov_len = sizeof(config); + iov[0].iov_base = &config; + iov[0].iov_len = sizeof(config); - if (data) { - if (!bap_print_cc(data->iov_base, data->iov_len, - stream->bap->debug_func, - stream->bap->debug_data)) - return 0; + if (data) { + if (!bap_print_cc(data->iov_base, data->iov_len, + stream->bap->debug_func, + stream->bap->debug_data)) + return 0; + + config.cc_len = data->iov_len; + iov[1] = *data; + iovlen++; + } - config.cc_len = data->iov_len; - iov[1] = *data; - iovlen++; - } + req = bap_req_new(stream, BT_ASCS_CONFIG, iov, iovlen, + func, user_data); - req = bap_req_new(stream, BT_ASCS_CONFIG, iov, iovlen, func, user_data); + if (!bap_queue_req(stream->bap, req)) { + bap_req_free(req); + return 0; + } - if (!bap_queue_req(stream->bap, req)) { - bap_req_free(req); - return 0; - } + stream->qos = *qos; - stream->qos = *qos; + return req->id; + case BT_BAP_STREAM_TYPE_BCAST: + stream->qos = *qos; + return 1; + } - return req->id; + return 0; } static bool match_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, @@ -4111,20 +4423,17 @@ return 0; } -struct bt_bap_stream *bt_bap_config(struct bt_bap *bap, +struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, struct bt_bap_qos *pqos, - struct iovec *data, - bt_bap_stream_func_t func, - void *user_data) + struct iovec *data) { struct bt_bap_stream *stream; struct bt_bap_endpoint *ep; struct match_pac match; - int id; - if (!bap || !bap->rdb || queue_isempty(bap->rdb->endpoints)) + if (!bap || !bap->rdb || queue_isempty(bap->remote_eps)) return NULL; if (lpac && rpac) { @@ -4165,10 +4474,10 @@ match.rpac = rpac; /* Check for existing stream */ - ep = queue_find(bap->rdb->endpoints, find_ep_pacs, &match); + ep = queue_find(bap->remote_eps, find_ep_pacs, &match); if (!ep) { /* Check for unused ASE */ - ep = queue_find(bap->rdb->endpoints, find_ep_unused, &match); + ep = queue_find(bap->remote_eps, find_ep_unused, &match); if (!ep) { DBG(bap, "Unable to find unused ASE"); return NULL; @@ -4179,15 +4488,6 @@ if (!stream) stream = bap_stream_new(bap, ep, lpac, rpac, data, true); - id = bt_bap_stream_config(stream, pqos, data, func, user_data); - if (!id) { - DBG(bap, "Unable to config stream"); - queue_remove(bap->streams, stream); - ep->stream = NULL; - free(stream); - return NULL; - } - return stream; } @@ -4214,6 +4514,10 @@ stream->user_data = user_data; + if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST) + stream->lpac->ops->config(stream, stream->cc, &stream->qos, + ep_config_cb, stream->lpac->user_data); + return true; } @@ -4234,27 +4538,25 @@ struct bt_ascs_qos qos; struct bt_bap_req *req; - if (!bap_stream_valid(stream)) - return 0; - - if (!stream->client) { - stream_qos(stream, data, NULL); + /* Table 3.2: ASE state machine transition + * Initiating device - client Only + */ + if (!bap_stream_valid(stream) || !stream->client) return 0; - } memset(&qos, 0, sizeof(qos)); /* TODO: Figure out how to pass these values around */ qos.ase = stream->ep->id; - qos.cig = data->cig_id; - qos.cis = data->cis_id; - put_le24(data->interval, qos.interval); - qos.framing = data->framing; - qos.phy = data->phy; - qos.sdu = cpu_to_le16(data->sdu); - qos.rtn = data->rtn; - qos.latency = cpu_to_le16(data->latency); - put_le24(data->delay, qos.pd); + qos.cig = data->ucast.cig_id; + qos.cis = data->ucast.cis_id; + put_le24(data->ucast.io_qos.interval, qos.interval); + qos.framing = data->ucast.framing; + qos.phy = data->ucast.io_qos.phy; + qos.sdu = cpu_to_le16(data->ucast.io_qos.sdu); + qos.rtn = data->ucast.io_qos.rtn; + qos.latency = cpu_to_le16(data->ucast.io_qos.latency); + put_le24(data->ucast.delay, qos.pd); iov.iov_base = &qos; iov.iov_len = sizeof(qos); @@ -4325,22 +4627,28 @@ bt_bap_stream_func_t func, void *user_data) { - int ret; - - if (!bap_stream_valid(stream)) - return 0; + int ret = 0; - if (!stream->client) { - stream_enable(stream, metadata, NULL); + /* Table 3.2: ASE state machine transition + * Initiating device - client Only + */ + if (!bap_stream_valid(stream) || !stream->client) return 0; - } - ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, func, - user_data); - if (!ret || !enable_links) - return ret; + switch (bt_bap_stream_get_type(stream)) { + case BT_BAP_STREAM_TYPE_UCAST: + ret = bap_stream_metadata(stream, BT_ASCS_ENABLE, metadata, + func, user_data); + if (!ret || !enable_links) + return ret; - queue_foreach(stream->links, bap_stream_enable_link, metadata); + queue_foreach(stream->links, bap_stream_enable_link, metadata); + break; + case BT_BAP_STREAM_TYPE_BCAST: + stream_set_state_broadcast(stream, + BT_BAP_STREAM_STATE_STREAMING); + return 1; + } return ret; } @@ -4500,6 +4808,7 @@ struct iovec iov; struct bt_ascs_release rel; struct bt_bap_req *req; + struct bt_bap *bap; if (!stream) return 0; @@ -4516,9 +4825,27 @@ iov.iov_base = &rel; iov.iov_len = sizeof(rel); + bap = stream->bap; + + /* If stream is broadcast, no BT_ASCS_RELEASE is required */ + if (bt_bap_stream_get_type(stream) == BT_BAP_STREAM_TYPE_BCAST) { + if (!bap_stream_valid(stream)) { + stream_set_state_broadcast(stream, + BT_BAP_STREAM_STATE_IDLE); + stream = NULL; + } + return 0; + } + + /* If stream does not belong to a client session, clean it up now */ + if (!bap_stream_valid(stream)) { + stream_set_state(stream, BT_BAP_STREAM_STATE_IDLE); + stream = NULL; + } + req = bap_req_new(stream, BT_ASCS_RELEASE, &iov, 1, func, user_data); - if (!bap_queue_req(stream->bap, req)) { + if (!bap_queue_req(bap, req)) { bap_req_free(req); return 0; } @@ -4536,14 +4863,22 @@ uint32_t bt_bap_stream_get_location(struct bt_bap_stream *stream) { - struct bt_bap_pac *pac; + struct bt_pacs *pacs; if (!stream) return 0x00000000; - pac = stream->rpac ? stream->rpac : stream->lpac; + pacs = stream->client ? stream->bap->rdb->pacs : stream->bap->ldb->pacs; - return pac->locations; + if (stream->ep->dir == BT_BAP_SOURCE) + return pacs->source_loc_value; + else if (stream->ep->dir == BT_BAP_SINK) + return pacs->sink_loc_value; + else + /* TO DO get the location values from metadata + * for brodcast source and sink + */ + return stream->bap->ldb->pacs->source_loc_value; } struct iovec *bt_bap_stream_get_config(struct bt_bap_stream *stream) @@ -4648,8 +4983,8 @@ return -EALREADY; if (stream->client != link->client || - stream->qos.cig_id != link->qos.cig_id || - stream->qos.cis_id != link->qos.cis_id) + stream->qos.ucast.cig_id != link->qos.ucast.cig_id || + stream->qos.ucast.cis_id != link->qos.ucast.cis_id) return -EINVAL; if (!stream->links) @@ -4686,7 +5021,7 @@ struct bt_bap_qos **qos = user_data; if (!qos || *qos || stream->ep->dir != BT_BAP_SOURCE || - !stream->qos.sdu) + !stream->qos.ucast.io_qos.sdu) return; *qos = &stream->qos; @@ -4697,7 +5032,8 @@ struct bt_bap_stream *stream = data; struct bt_bap_qos **qos = user_data; - if (!qos || *qos || stream->ep->dir != BT_BAP_SINK || !stream->qos.sdu) + if (!qos || *qos || stream->ep->dir != BT_BAP_SINK || + !stream->qos.ucast.io_qos.sdu) return; *qos = &stream->qos; diff -Nru bluez-5.66/src/shared/bap.h bluez-5.68/src/shared/bap.h --- bluez-5.66/src/shared/bap.h 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/bap.h 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. All rights reserved. + * Copyright 2023 NXP * */ @@ -16,6 +17,11 @@ #define BT_BAP_SINK 0x01 #define BT_BAP_SOURCE 0x02 +#define BT_BAP_BCAST_SOURCE 0x03 +#define BT_BAP_BCAST_SINK 0x04 + +#define BT_BAP_STREAM_TYPE_UCAST 0x01 +#define BT_BAP_STREAM_TYPE_BCAST 0x02 #define BT_BAP_STREAM_STATE_IDLE 0x00 #define BT_BAP_STREAM_STATE_CONFIG 0x01 @@ -26,7 +32,7 @@ #define BT_BAP_STREAM_STATE_RELEASING 0x06 #define BT_BAP_CONFIG_LATENCY_LOW 0x01 -#define BT_BAP_CONFIG_LATENCY_BALACED 0x02 +#define BT_BAP_CONFIG_LATENCY_BALANCED 0x02 #define BT_BAP_CONFIG_LATENCY_HIGH 0x03 #define BT_BAP_CONFIG_PHY_1M 0x01 @@ -39,8 +45,8 @@ struct bt_bap_codec { uint8_t id; - uint16_t vid; uint16_t cid; + uint16_t vid; } __packed; struct bt_ltv { @@ -49,17 +55,46 @@ uint8_t value[0]; } __packed; -struct bt_bap_qos { +struct bt_bap_io_qos { + uint32_t interval; /* Frame interval */ + uint16_t latency; /* Transport Latency */ + uint16_t sdu; /* Maximum SDU Size */ + uint8_t phy; /* PHY */ + uint8_t rtn; /* Retransmission Effort */ +}; + +struct bt_bap_ucast_qos { uint8_t cig_id; uint8_t cis_id; - uint32_t interval; /* Frame interval */ uint8_t framing; /* Frame framing */ - uint8_t phy; /* PHY */ - uint16_t sdu; /* Maximum SDU Size */ - uint8_t rtn; /* Retransmission Effort */ - uint16_t latency; /* Transport Latency */ uint32_t delay; /* Presentation Delay */ uint8_t target_latency; /* Target Latency */ + struct bt_bap_io_qos io_qos; +}; + +struct bt_bap_bcast_qos { + uint8_t big; + uint8_t bis; + uint8_t sync_interval; + uint8_t packing; + uint8_t framing; + uint8_t encryption; + struct iovec *bcode; + uint8_t options; + uint16_t skip; + uint16_t sync_timeout; + uint8_t sync_cte_type; + uint8_t mse; + uint16_t timeout; + uint8_t pa_sync; + struct bt_bap_io_qos io_qos; +}; + +struct bt_bap_qos { + union { + struct bt_bap_ucast_qos ucast; + struct bt_bap_bcast_qos bcast; + }; }; typedef void (*bt_bap_ready_func_t)(struct bt_bap *bap, void *user_data); @@ -87,12 +122,6 @@ typedef void (*bt_bap_func_t)(struct bt_bap *bap, void *user_data); /* Local PAC related functions */ - -unsigned int bt_bap_pac_register(bt_bap_pac_func_t added, - bt_bap_pac_func_t removed, void *user_data, - bt_bap_destroy_func_t destroy); -bool bt_bap_pac_unregister(unsigned int id); - struct bt_bap_pac_qos { uint8_t framing; uint8_t phy; @@ -134,6 +163,10 @@ uint8_t bt_bap_pac_get_type(struct bt_bap_pac *pac); +uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac); + +uint8_t bt_bap_stream_get_type(struct bt_bap_stream *stream); + struct bt_bap_stream *bt_bap_pac_get_stream(struct bt_bap_pac *pac); /* Session related function */ @@ -153,6 +186,7 @@ void bt_bap_unref(struct bt_bap *bap); bool bt_bap_attach(struct bt_bap *bap, struct bt_gatt_client *client); +bool bt_bap_attach_broadcast(struct bt_bap *bap); void bt_bap_detach(struct bt_bap *bap); bool bt_bap_set_debug(struct bt_bap *bap, bt_bap_debug_func_t cb, @@ -161,6 +195,11 @@ bool bap_print_cc(void *data, size_t len, util_debug_func_t func, void *user_data); +unsigned int bt_bap_pac_register(struct bt_bap *bap, bt_bap_pac_func_t added, + bt_bap_pac_func_t removed, void *user_data, + bt_bap_destroy_func_t destroy); +bool bt_bap_pac_unregister(struct bt_bap *bap, unsigned int id); + unsigned int bt_bap_ready_register(struct bt_bap *bap, bt_bap_ready_func_t func, void *user_data, bt_bap_destroy_func_t destroy); @@ -191,13 +230,11 @@ int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, bt_bap_pac_select_t func, void *user_data); -struct bt_bap_stream *bt_bap_config(struct bt_bap *bap, +struct bt_bap_stream *bt_bap_stream_new(struct bt_bap *bap, struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, struct bt_bap_qos *pqos, - struct iovec *data, - bt_bap_stream_func_t func, - void *user_data); + struct iovec *data); struct bt_bap *bt_bap_stream_get_session(struct bt_bap_stream *stream); uint8_t bt_bap_stream_get_state(struct bt_bap_stream *stream); diff -Nru bluez-5.66/src/shared/bass.c bluez-5.68/src/shared/bass.c --- bluez-5.66/src/shared/bass.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/src/shared/bass.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,1142 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright 2023 NXP + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/bass.h" + +#define DBG(_bass, fmt, arg...) \ + bass_debug(_bass, "%s:%s() " fmt, __FILE__, __func__, ## arg) + +struct bt_bass_db; + +struct bt_bass_cb { + unsigned int id; + bt_bass_func_t attached; + bt_bass_func_t detached; + void *user_data; +}; + +struct bt_bcast_recv_state { + struct bt_bass_db *bdb; + struct gatt_db_attribute *attr; + struct gatt_db_attribute *ccc; +}; + +struct bt_bass_db { + struct gatt_db *db; + struct queue *bcast_srcs; + struct gatt_db_attribute *service; + struct gatt_db_attribute *bcast_audio_scan_cp; + struct bt_bcast_recv_state *bcast_recv_states[NUM_BCAST_RECV_STATES]; +}; + +struct bt_bass { + int ref_count; + struct bt_bass_db *ldb; + struct bt_bass_db *rdb; + struct bt_gatt_client *client; + struct bt_att *att; + + struct queue *notify; + + bt_bass_debug_func_t debug_func; + bt_bass_destroy_func_t debug_destroy; + void *debug_data; + + void *user_data; +}; + +typedef void (*bass_notify_t)(struct bt_bass *bass, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data); + +struct bt_bass_notify { + unsigned int id; + struct bt_bass *bass; + bass_notify_t func; + void *user_data; +}; + +static struct queue *bass_db; +static struct queue *bass_cbs; +static struct queue *sessions; + +static void bass_bcast_src_free(void *data); + +static void bass_debug(struct bt_bass *bass, const char *format, ...) +{ + va_list ap; + + if (!bass || !format || !bass->debug_func) + return; + + va_start(ap, format); + util_debug_va(bass->debug_func, bass->debug_data, format, ap); + va_end(ap); +} + +static int +bass_build_bcast_src_from_notif(struct bt_bcast_src *bcast_src, + const uint8_t *value, uint16_t length) +{ + struct bt_bass_subgroup_data *subgroup_data = NULL; + uint8_t *id; + uint8_t *addr_type; + uint8_t *addr; + uint8_t *sid; + uint32_t bid; + uint8_t *pa_sync_state; + uint8_t *enc; + uint8_t *bad_code = NULL; + uint8_t *num_subgroups; + uint32_t bis_sync_state; + uint8_t *meta_len; + uint8_t *meta; + + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = length, + }; + + /* Extract all fields from notification */ + id = util_iov_pull_mem(&iov, sizeof(*id)); + if (!id) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + addr_type = util_iov_pull_mem(&iov, sizeof(*addr_type)); + if (!addr_type) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + addr = util_iov_pull_mem(&iov, sizeof(bdaddr_t)); + if (!addr) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + sid = util_iov_pull_mem(&iov, sizeof(*sid)); + if (!sid) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + if (!util_iov_pull_le24(&iov, &bid)) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + pa_sync_state = util_iov_pull_mem(&iov, sizeof(*pa_sync_state)); + if (!pa_sync_state) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + enc = util_iov_pull_mem(&iov, sizeof(*enc)); + if (!enc) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + if (*enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) { + bad_code = util_iov_pull_mem(&iov, BT_BASS_BCAST_CODE_SIZE); + if (!bad_code) { + DBG(bcast_src->bass, "Unable to parse " + "Broadcast Receive State"); + return -1; + } + } + + num_subgroups = util_iov_pull_mem(&iov, sizeof(*num_subgroups)); + if (!num_subgroups) { + DBG(bcast_src->bass, "Unable to parse Broadcast Receive State"); + return -1; + } + + if (*num_subgroups == 0) + goto done; + + subgroup_data = malloc((*num_subgroups) * sizeof(*subgroup_data)); + if (!subgroup_data) { + DBG(bcast_src->bass, "Unable to allocate memory"); + return -1; + } + + memset(subgroup_data, 0, (*num_subgroups) * sizeof(*subgroup_data)); + + for (int i = 0; i < *num_subgroups; i++) { + if (!util_iov_pull_le32(&iov, &bis_sync_state)) { + DBG(bcast_src->bass, "Unable to parse " + "Broadcast Receive State"); + + for (int j = 0; j < i; j++) + free(subgroup_data[j].meta); + + free(subgroup_data); + return -1; + } + + subgroup_data[i].bis_sync = bis_sync_state; + + meta_len = util_iov_pull_mem(&iov, sizeof(*meta_len)); + if (!meta_len) { + DBG(bcast_src->bass, "Unable to parse " + "Broadcast Receive State"); + + for (int j = 0; j < i; j++) + free(subgroup_data[j].meta); + + free(subgroup_data); + return -1; + } + + subgroup_data[i].meta_len = *meta_len; + + if (*meta_len == 0) + continue; + + subgroup_data[i].meta = malloc(*meta_len); + if (!subgroup_data[i].meta) { + DBG(bcast_src->bass, "Unable to allocate memory"); + + for (int j = 0; j < i; j++) + free(subgroup_data[j].meta); + + free(subgroup_data); + return -1; + } + + meta = util_iov_pull_mem(&iov, *meta_len); + if (!meta) { + DBG(bcast_src->bass, "Unable to parse " + "Broadcast Receive State"); + + for (int j = 0; j < i; j++) + free(subgroup_data[j].meta); + + free(subgroup_data); + return -1; + } + + memcpy(subgroup_data[i].meta, meta, *meta_len); + } + +done: + /* + * If no errors occurred, copy extracted fields into + * the broadcast source structure + */ + if (bcast_src->subgroup_data) { + for (int i = 0; i < bcast_src->num_subgroups; i++) + free(bcast_src->subgroup_data[i].meta); + + free(bcast_src->subgroup_data); + } + + bcast_src->id = *id; + bcast_src->addr_type = *addr_type; + memcpy(&bcast_src->addr, addr, sizeof(bdaddr_t)); + bcast_src->sid = *sid; + bcast_src->bid = bid; + bcast_src->sync_state = *pa_sync_state; + bcast_src->enc = *enc; + + if (*enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) + memcpy(bcast_src->bad_code, bad_code, BT_BASS_BCAST_CODE_SIZE); + else + memset(bcast_src->bad_code, 0, BT_BASS_BCAST_CODE_SIZE); + + bcast_src->num_subgroups = *num_subgroups; + + bcast_src->subgroup_data = subgroup_data; + + return 0; +} + +static int +bass_build_bcast_src_from_read_rsp(struct bt_bcast_src *bcast_src, + const uint8_t *value, uint16_t length) +{ + return bass_build_bcast_src_from_notif(bcast_src, value, length); +} + +static uint8_t *bass_build_notif_from_bcast_src(struct bt_bcast_src *bcast_src, + size_t *notif_len) +{ + size_t len = 0; + uint8_t *notif = NULL; + struct iovec iov; + + *notif_len = 0; + + if (!bcast_src) + return NULL; + + len = BT_BASS_BCAST_SRC_LEN + bcast_src->num_subgroups * + BT_BASS_BCAST_SRC_SUBGROUP_LEN; + + if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) + len += BT_BASS_BCAST_CODE_SIZE; + + for (size_t i = 0; i < bcast_src->num_subgroups; i++) { + /* Add length for subgroup metadata */ + len += bcast_src->subgroup_data[i].meta_len; + } + + notif = malloc(len); + if (!notif) + return NULL; + + memset(notif, 0, len); + + iov.iov_base = notif; + iov.iov_len = 0; + + util_iov_push_mem(&iov, sizeof(bcast_src->id), + &bcast_src->id); + util_iov_push_mem(&iov, sizeof(bcast_src->addr_type), + &bcast_src->addr_type); + util_iov_push_mem(&iov, sizeof(bcast_src->addr), + &bcast_src->addr); + util_iov_push_mem(&iov, sizeof(bcast_src->sid), + &bcast_src->sid); + util_iov_push_le24(&iov, bcast_src->bid); + util_iov_push_mem(&iov, sizeof(bcast_src->sync_state), + &bcast_src->sync_state); + util_iov_push_mem(&iov, sizeof(bcast_src->enc), + &bcast_src->enc); + + if (bcast_src->enc == BT_BASS_BIG_ENC_STATE_BAD_CODE) + util_iov_push_mem(&iov, sizeof(bcast_src->bad_code), + bcast_src->bad_code); + + util_iov_push_mem(&iov, sizeof(bcast_src->num_subgroups), + &bcast_src->num_subgroups); + + for (size_t i = 0; i < bcast_src->num_subgroups; i++) { + /* Add subgroup bis_sync */ + util_iov_push_le32(&iov, bcast_src->subgroup_data[i].bis_sync); + + /* Add subgroup meta_len */ + util_iov_push_mem(&iov, + sizeof(bcast_src->subgroup_data[i].meta_len), + &bcast_src->subgroup_data[i].meta_len); + + /* Add subgroup metadata */ + if (bcast_src->subgroup_data[i].meta_len > 0) + util_iov_push_mem(&iov, + bcast_src->subgroup_data[i].meta_len, + bcast_src->subgroup_data[i].meta); + } + + *notif_len = len; + return notif; +} + +static uint8_t * +bass_build_read_rsp_from_bcast_src(struct bt_bcast_src *bcast_src, + size_t *rsp_len) +{ + return bass_build_notif_from_bcast_src(bcast_src, rsp_len); +} + +static bool bass_check_cp_command_subgroup_data_len(uint8_t num_subgroups, + struct iovec *iov) +{ + uint32_t bis_sync_state; + uint8_t *meta_len; + uint8_t *meta; + + for (int i = 0; i < num_subgroups; i++) { + if (!util_iov_pull_le32(iov, &bis_sync_state)) + return false; + + meta_len = util_iov_pull_mem(iov, + sizeof(*meta_len)); + if (!meta_len) + return false; + + meta = util_iov_pull_mem(iov, *meta_len); + if (!meta) + return false; + } + + return true; +} + +static bool bass_check_cp_command_len(const uint8_t *value, size_t len) +{ + struct bt_bass_bcast_audio_scan_cp_hdr *hdr; + union { + struct bt_bass_add_src_params *add_src_params; + struct bt_bass_mod_src_params *mod_src_params; + struct bt_bass_set_bcast_code_params *set_bcast_code_params; + struct bt_bass_remove_src_params *remove_src_params; + } params; + + struct iovec iov = { + .iov_base = (void *)value, + .iov_len = len, + }; + + /* Get command header */ + hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); + + if (!hdr) + return false; + + /* Check command parameters */ + switch (hdr->op) { + case BT_BASS_ADD_SRC: + params.add_src_params = util_iov_pull_mem(&iov, + sizeof(*params.add_src_params)); + if (!params.add_src_params) + return false; + + if (!bass_check_cp_command_subgroup_data_len( + params.add_src_params->num_subgroups, + &iov)) + return false; + + break; + case BT_BASS_MOD_SRC: + params.mod_src_params = util_iov_pull_mem(&iov, + sizeof(*params.mod_src_params)); + if (!params.mod_src_params) + return false; + + if (!bass_check_cp_command_subgroup_data_len( + params.mod_src_params->num_subgroups, + &iov)) + return false; + + break; + case BT_BASS_SET_BCAST_CODE: + params.set_bcast_code_params = util_iov_pull_mem(&iov, + sizeof(*params.set_bcast_code_params)); + if (!params.set_bcast_code_params) + return false; + + break; + case BT_BASS_REMOVE_SRC: + params.remove_src_params = util_iov_pull_mem(&iov, + sizeof(*params.remove_src_params)); + if (!params.remove_src_params) + return false; + + break; + case BT_BASS_REMOTE_SCAN_STOPPED: + case BT_BASS_REMOTE_SCAN_STARTED: + break; + default: + return true; + } + + if (iov.iov_len > 0) + return false; + + return true; +} + +static void bass_handle_remote_scan_stopped_op(struct bt_bass_db *bdb, + struct gatt_db_attribute *attrib, + uint8_t opcode, + unsigned int id, + struct iovec *iov, + struct bt_att *att) +{ + if (opcode == BT_ATT_OP_WRITE_REQ) + gatt_db_attribute_write_result(attrib, id, 0x00); +} + +static void bass_handle_remote_scan_started_op(struct bt_bass_db *bdb, + struct gatt_db_attribute *attrib, + uint8_t opcode, + unsigned int id, + struct iovec *iov, + struct bt_att *att) +{ + if (opcode == BT_ATT_OP_WRITE_REQ) + gatt_db_attribute_write_result(attrib, id, 0x00); +} + +static bool bass_src_id_match(const void *data, const void *match_data) +{ + const struct bt_bcast_src *bcast_src = data; + const uint8_t *id = match_data; + + return (bcast_src->id == *id); +} + +static void bass_handle_remove_src_op(struct bt_bass_db *bdb, + struct gatt_db_attribute *attrib, + uint8_t opcode, + unsigned int id, + struct iovec *iov, + struct bt_att *att) +{ + struct bt_bass_remove_src_params *params; + struct bt_bcast_src *bcast_src; + + /* Get Remove Source command parameters */ + params = util_iov_pull_mem(iov, sizeof(*params)); + + bcast_src = queue_find(bdb->bcast_srcs, + bass_src_id_match, + ¶ms->id); + + if (!bcast_src) { + /* No source matches the written source id */ + if (opcode == BT_ATT_OP_WRITE_REQ) + gatt_db_attribute_write_result(attrib, id, + BT_BASS_ERROR_INVALID_SOURCE_ID); + + return; + } + + /* Ignore if server is synchronized to the PA + * of the source + */ + if (bcast_src->sync_state == BT_BASS_SYNCHRONIZED_TO_PA) + return; + + /* Ignore if server is synchronized to any BIS + * of the source + */ + for (int i = 0; i < bcast_src->num_subgroups; i++) + if (bcast_src->subgroup_data[i].bis_sync) + return; + + /* Accept the operation and remove source */ + queue_remove(bdb->bcast_srcs, bcast_src); + gatt_db_attribute_notify(bcast_src->attr, NULL, 0, att); + bass_bcast_src_free(bcast_src); + + if (opcode == BT_ATT_OP_WRITE_REQ) + gatt_db_attribute_write_result(attrib, id, 0x00); +} + +#define BASS_OP(_str, _op, _size, _func) \ + { \ + .str = _str, \ + .op = _op, \ + .size = _size, \ + .func = _func, \ + } + +struct bass_op_handler { + const char *str; + uint8_t op; + size_t size; + void (*func)(struct bt_bass_db *bdb, + struct gatt_db_attribute *attrib, + uint8_t opcode, + unsigned int id, + struct iovec *iov, + struct bt_att *att); +} bass_handlers[] = { + BASS_OP("Remote Scan Stopped", BT_BASS_REMOTE_SCAN_STOPPED, + 0, bass_handle_remote_scan_stopped_op), + BASS_OP("Remote Scan Started", BT_BASS_REMOTE_SCAN_STARTED, + 0, bass_handle_remote_scan_started_op), + BASS_OP("Remove Source", BT_BASS_REMOVE_SRC, + 0, bass_handle_remove_src_op), + {} +}; + +static void bass_bcast_audio_scan_cp_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_bass_db *bdb = user_data; + struct bt_bass_bcast_audio_scan_cp_hdr *hdr; + struct bass_op_handler *handler; + struct iovec iov = { + .iov_base = (void *)value, + .iov_len = len, + }; + + /* Validate written command length */ + if (!bass_check_cp_command_len(value, len)) { + if (opcode == BT_ATT_OP_WRITE_REQ) { + gatt_db_attribute_write_result(attrib, id, + BT_ERROR_WRITE_REQUEST_REJECTED); + } + return; + } + + /* Get command header */ + hdr = util_iov_pull_mem(&iov, sizeof(*hdr)); + + /* Call the appropriate opcode handler */ + for (handler = bass_handlers; handler && handler->str; handler++) { + if (handler->op == hdr->op) { + handler->func(bdb, attrib, opcode, id, &iov, att); + return; + } + } + + /* Send error response if unsupported opcode was written */ + if (opcode == BT_ATT_OP_WRITE_REQ) { + gatt_db_attribute_write_result(attrib, id, + BT_BASS_ERROR_OPCODE_NOT_SUPPORTED); + } +} + +static bool bass_src_match_attrib(const void *data, const void *match_data) +{ + const struct bt_bcast_src *bcast_src = data; + const struct gatt_db_attribute *attr = match_data; + + return (bcast_src->attr == attr); +} + +static void bass_bcast_recv_state_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_bass_db *bdb = user_data; + uint8_t *rsp; + size_t rsp_len; + struct bt_bcast_src *bcast_src; + + bcast_src = queue_find(bdb->bcast_srcs, + bass_src_match_attrib, + attrib); + + if (!bcast_src) { + gatt_db_attribute_read_result(attrib, id, 0, NULL, + 0); + return; + } + + /* Build read response */ + rsp = bass_build_read_rsp_from_bcast_src(bcast_src, &rsp_len); + + if (!rsp) { + gatt_db_attribute_read_result(attrib, id, + BT_ATT_ERROR_UNLIKELY, + NULL, 0); + return; + } + + gatt_db_attribute_read_result(attrib, id, 0, (void *)rsp, + rsp_len); + + free(rsp); +} + +static void bcast_recv_new(struct bt_bass_db *bdb, int i) +{ + struct bt_bcast_recv_state *bcast_recv_state; + bt_uuid_t uuid; + + if (!bdb) + return; + + bcast_recv_state = new0(struct bt_bcast_recv_state, 1); + bcast_recv_state->bdb = bdb; + + bt_uuid16_create(&uuid, BCAST_RECV_STATE_UUID); + bcast_recv_state->attr = + gatt_db_service_add_characteristic(bdb->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + bass_bcast_recv_state_read, NULL, + bdb); + + bcast_recv_state->ccc = gatt_db_service_add_ccc(bdb->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bdb->bcast_recv_states[i] = bcast_recv_state; +} + +static void bass_new(struct bt_bass_db *bdb) +{ + bt_uuid_t uuid; + int i; + + /* Populate DB with BASS attributes */ + bt_uuid16_create(&uuid, BASS_UUID); + bdb->service = gatt_db_add_service(bdb->db, &uuid, true, + 3 + (NUM_BCAST_RECV_STATES * 3)); + + for (i = 0; i < NUM_BCAST_RECV_STATES; i++) + bcast_recv_new(bdb, i); + + bt_uuid16_create(&uuid, BCAST_AUDIO_SCAN_CP_UUID); + bdb->bcast_audio_scan_cp = + gatt_db_service_add_characteristic(bdb->service, + &uuid, + BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE | + BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, + NULL, bass_bcast_audio_scan_cp_write, + bdb); + + gatt_db_service_set_active(bdb->service, true); +} + +static void bass_bcast_src_free(void *data) +{ + struct bt_bcast_src *bcast_src = data; + + for (int i = 0; i < bcast_src->num_subgroups; i++) + free(bcast_src->subgroup_data[i].meta); + + free(bcast_src->subgroup_data); + free(bcast_src); +} + +static void read_bcast_recv_state(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct bt_bcast_src *bcast_src = user_data; + + if (!success) { + DBG(bcast_src->bass, "Unable to read " + "Broadcast Receive State: error 0x%02x", + att_ecode); + return; + } + + if (length == 0) { + queue_remove(bcast_src->bass->rdb->bcast_srcs, bcast_src); + bass_bcast_src_free(bcast_src); + return; + } + + if (bass_build_bcast_src_from_read_rsp(bcast_src, value, length)) { + queue_remove(bcast_src->bass->rdb->bcast_srcs, bcast_src); + bass_bcast_src_free(bcast_src); + return; + } +} + +static void bcast_recv_state_notify(struct bt_bass *bass, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct gatt_db_attribute *attr = user_data; + struct bt_bcast_src *bcast_src; + bool new_src = false; + + bcast_src = queue_find(bass->rdb->bcast_srcs, + bass_src_match_attrib, attr); + if (!bcast_src) { + new_src = true; + bcast_src = malloc(sizeof(*bcast_src)); + + if (!bcast_src) { + DBG(bass, "Failed to allocate " + "memory for broadcast source"); + return; + } + + memset(bcast_src, 0, sizeof(struct bt_bcast_src)); + bcast_src->bass = bass; + bcast_src->attr = attr; + } + + if (bass_build_bcast_src_from_notif(bcast_src, value, length) + && new_src) { + bass_bcast_src_free(bcast_src); + return; + } + + if (new_src) + queue_push_tail(bass->rdb->bcast_srcs, bcast_src); +} + +static void bass_register(uint16_t att_ecode, void *user_data) +{ + struct bt_bass_notify *notify = user_data; + + if (att_ecode) + DBG(notify->bass, "BASS register notify failed: 0x%04x", + att_ecode); +} + +static void bass_notify(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_bass_notify *notify = user_data; + + if (notify->func) + notify->func(notify->bass, value_handle, value, length, + notify->user_data); +} + +static void bass_notify_destroy(void *data) +{ + struct bt_bass_notify *notify = data; + struct bt_bass *bass = notify->bass; + + if (queue_remove_if(bass->notify, NULL, notify)) + free(notify); +} + +static unsigned int bass_register_notify(struct bt_bass *bass, + uint16_t value_handle, + bass_notify_t func, + void *user_data) +{ + struct bt_bass_notify *notify; + + notify = new0(struct bt_bass_notify, 1); + notify->bass = bass; + notify->func = func; + notify->user_data = user_data; + + notify->id = bt_gatt_client_register_notify(bass->client, + value_handle, bass_register, + bass_notify, notify, + bass_notify_destroy); + if (!notify->id) { + DBG(bass, "Unable to register for notifications"); + free(notify); + return 0; + } + + queue_push_tail(bass->notify, notify); + + return notify->id; +} + +static void foreach_bass_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_bass *bass = user_data; + uint16_t value_handle; + bt_uuid_t uuid, uuid_bcast_audio_scan_cp, uuid_bcast_recv_state; + + /* Get attribute value handle and uuid */ + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, + NULL, NULL, &uuid)) + return; + + bt_uuid16_create(&uuid_bcast_audio_scan_cp, BCAST_AUDIO_SCAN_CP_UUID); + bt_uuid16_create(&uuid_bcast_recv_state, BCAST_RECV_STATE_UUID); + + if (!bt_uuid_cmp(&uuid, &uuid_bcast_audio_scan_cp)) { + /* Found Broadcast Audio Scan Control Point characteristic */ + bass->rdb->bcast_audio_scan_cp = attr; + + DBG(bass, "Broadcast Audio Scan Control Point " + "found: handle 0x%04x", value_handle); + } + + if (!bt_uuid_cmp(&uuid, &uuid_bcast_recv_state)) { + /* Found Broadcast Receive State characteristic */ + struct bt_bcast_src *bcast_src = + queue_find(bass->rdb->bcast_srcs, + bass_src_match_attrib, attr); + + if (!bcast_src) { + bcast_src = malloc(sizeof(struct bt_bcast_src)); + + if (bcast_src == NULL) { + DBG(bass, "Failed to allocate " + "memory for broadcast source"); + return; + } + + memset(bcast_src, 0, sizeof(struct bt_bcast_src)); + bcast_src->bass = bass; + bcast_src->attr = attr; + + queue_push_tail(bass->rdb->bcast_srcs, bcast_src); + } + + bt_gatt_client_read_value(bass->client, value_handle, + read_bcast_recv_state, + bcast_src, NULL); + + (void)bass_register_notify(bass, value_handle, + bcast_recv_state_notify, + attr); + + DBG(bass, "Broadcast Receive State found: handle 0x%04x", + value_handle); + } +} + +static void foreach_bass_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_bass *bass = user_data; + + /* Store BASS service reference */ + bass->rdb->service = attr; + + /* Handle BASS characteristics */ + gatt_db_service_foreach_char(attr, foreach_bass_char, bass); +} + +bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client) +{ + bt_uuid_t uuid; + + if (!sessions) + sessions = queue_new(); + + queue_push_tail(sessions, bass); + + if (!client) + return true; + + if (bass->client) + return false; + + bass->client = bt_gatt_client_clone(client); + if (!bass->client) + return false; + + bt_uuid16_create(&uuid, BASS_UUID); + gatt_db_foreach_service(bass->rdb->db, &uuid, foreach_bass_service, + bass); + + return true; +} + +static void bass_detached(void *data, void *user_data) +{ + struct bt_bass_cb *cb = data; + struct bt_bass *bass = user_data; + + cb->detached(bass, cb->user_data); +} + +void bt_bass_detach(struct bt_bass *bass) +{ + if (!queue_remove(sessions, bass)) + return; + + bt_gatt_client_unref(bass->client); + bass->client = NULL; + + queue_foreach(bass_cbs, bass_detached, bass); +} + +static void bass_db_free(void *data) +{ + struct bt_bass_db *bdb = data; + + if (!bdb) + return; + + gatt_db_unref(bdb->db); + queue_destroy(bdb->bcast_srcs, bass_bcast_src_free); + + free(bdb); +} + +static void bass_free(void *data) +{ + struct bt_bass *bass = data; + + bt_bass_detach(bass); + bass_db_free(bass->rdb); + queue_destroy(bass->notify, NULL); + + free(bass); +} + +void bt_bass_unref(struct bt_bass *bass) +{ + if (!bass) + return; + + if (__sync_sub_and_fetch(&bass->ref_count, 1)) + return; + + bass_free(bass); +} + +bool bt_bass_set_user_data(struct bt_bass *bass, void *user_data) +{ + if (!bass) + return false; + + bass->user_data = user_data; + + return true; +} + +static struct bt_bass_db *bass_db_new(struct gatt_db *db) +{ + struct bt_bass_db *bdb; + + if (!db) + return NULL; + + bdb = new0(struct bt_bass_db, 1); + bdb->db = gatt_db_ref(db); + bdb->bcast_srcs = queue_new(); + + if (!bass_db) + bass_db = queue_new(); + + bass_new(bdb); + + queue_push_tail(bass_db, bdb); + + return bdb; +} + +static bool bass_db_match(const void *data, const void *match_data) +{ + const struct bt_bass_db *bdb = data; + const struct gatt_db *db = match_data; + + return (bdb->db == db); +} + +static struct bt_bass_db *bass_get_db(struct gatt_db *db) +{ + struct bt_bass_db *bdb; + + bdb = queue_find(bass_db, bass_db_match, db); + if (bdb) + return bdb; + + return bass_db_new(db); +} + +static struct bt_bass *bt_bass_ref(struct bt_bass *bass) +{ + if (!bass) + return NULL; + + __sync_fetch_and_add(&bass->ref_count, 1); + + return bass; +} + +struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb) +{ + struct bt_bass *bass; + struct bt_bass_db *db; + + if (!ldb) + return NULL; + + db = bass_get_db(ldb); + if (!db) + return NULL; + + bass = new0(struct bt_bass, 1); + bass->ldb = db; + bass->notify = queue_new(); + + if (!rdb) + goto done; + + db = new0(struct bt_bass_db, 1); + db->db = gatt_db_ref(rdb); + db->bcast_srcs = queue_new(); + + bass->rdb = db; + +done: + bt_bass_ref(bass); + + return bass; +} + +struct bt_att *bt_bass_get_att(struct bt_bass *bass) +{ + if (!bass) + return NULL; + + if (bass->att) + return bass->att; + + return bt_gatt_client_get_att(bass->client); +} + +bool bt_bass_set_debug(struct bt_bass *bass, bt_bass_debug_func_t func, + void *user_data, bt_bass_destroy_func_t destroy) +{ + if (!bass) + return false; + + if (bass->debug_destroy) + bass->debug_destroy(bass->debug_data); + + bass->debug_func = func; + bass->debug_destroy = destroy; + bass->debug_data = user_data; + + return true; +} + +unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached, + void *user_data) +{ + struct bt_bass_cb *cb; + static unsigned int id; + + if (!attached && !detached) + return 0; + + if (!bass_cbs) + bass_cbs = queue_new(); + + cb = new0(struct bt_bass_cb, 1); + cb->id = ++id ? id : ++id; + cb->attached = attached; + cb->detached = detached; + cb->user_data = user_data; + + queue_push_tail(bass_cbs, cb); + + return cb->id; +} +static bool match_id(const void *data, const void *match_data) +{ + const struct bt_bass_cb *cb = data; + unsigned int id = PTR_TO_UINT(match_data); + + return (cb->id == id); +} + +bool bt_bass_unregister(unsigned int id) +{ + struct bt_bass_cb *cb; + + cb = queue_remove_if(bass_cbs, match_id, UINT_TO_PTR(id)); + if (!cb) + return false; + + free(cb); + + return true; +} diff -Nru bluez-5.66/src/shared/bass.h bluez-5.68/src/shared/bass.h --- bluez-5.66/src/shared/bass.h 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/src/shared/bass.h 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright 2023 NXP + * + */ + +struct bt_bass; + +#define NUM_BCAST_RECV_STATES 2 +#define BT_BASS_BCAST_CODE_SIZE 16 +#define BT_BASS_BIG_SYNC_FAILED_BITMASK 0xFFFFFFFF +#define BT_BASS_BCAST_SRC_LEN 15 +#define BT_BASS_BCAST_SRC_SUBGROUP_LEN 5 + +/* Application error codes */ +#define BT_BASS_ERROR_OPCODE_NOT_SUPPORTED 0x80 +#define BT_BASS_ERROR_INVALID_SOURCE_ID 0x81 + +/* PA_Sync_State values */ +#define BT_BASS_NOT_SYNCHRONIZED_TO_PA 0x00 +#define BT_BASS_SYNC_INFO_RE 0x01 +#define BT_BASS_SYNCHRONIZED_TO_PA 0x02 +#define BT_BASS_FAILED_TO_SYNCHRONIZE_TO_PA 0x03 +#define BT_BASS_NO_PAST 0x04 + +/* BIG_Encryption values */ +#define BT_BASS_BIG_ENC_STATE_NO_ENC 0x00 +#define BT_BASS_BIG_ENC_STATE_BCODE_REQ 0x01 +#define BT_BASS_BIG_ENC_STATE_DEC 0x02 +#define BT_BASS_BIG_ENC_STATE_BAD_CODE 0x03 + +/* BASS subgroup field of the Broadcast + * Receive State characteristic + */ +struct bt_bass_subgroup_data { + uint32_t bis_sync; + uint32_t pending_bis_sync; + uint8_t meta_len; + uint8_t *meta; +}; + +/* BASS Broadcast Source structure */ +struct bt_bcast_src { + struct bt_bass *bass; + struct gatt_db_attribute *attr; + uint8_t id; + uint8_t addr_type; + bdaddr_t addr; + uint8_t sid; + uint32_t bid; + uint8_t sync_state; + uint8_t enc; + uint8_t bad_code[BT_BASS_BCAST_CODE_SIZE]; + uint8_t num_subgroups; + struct bt_bass_subgroup_data *subgroup_data; +}; + +/* Broadcast Audio Scan Control Point + * header structure + */ +struct bt_bass_bcast_audio_scan_cp_hdr { + uint8_t op; +} __packed; + +#define BT_BASS_REMOTE_SCAN_STOPPED 0x00 + +#define BT_BASS_REMOTE_SCAN_STARTED 0x01 + +#define BT_BASS_ADD_SRC 0x02 + +struct bt_bass_add_src_params { + uint8_t addr_type; + bdaddr_t addr; + uint8_t sid; + uint8_t bid[3]; + uint8_t pa_sync; + uint16_t pa_interval; + uint8_t num_subgroups; + uint8_t subgroup_data[]; +} __packed; + +#define BT_BASS_MOD_SRC 0x03 + +struct bt_bass_mod_src_params { + uint8_t id; + uint8_t pa_sync; + uint16_t pa_interval; + uint8_t num_subgroups; + uint8_t subgroup_data[]; +} __packed; + +#define BT_BASS_SET_BCAST_CODE 0x04 + +struct bt_bass_set_bcast_code_params { + uint8_t id; + uint8_t bcast_code[BT_BASS_BCAST_CODE_SIZE]; +} __packed; + +#define BT_BASS_REMOVE_SRC 0x05 + +struct bt_bass_remove_src_params { + uint8_t id; +} __packed; + +typedef void (*bt_bass_func_t)(struct bt_bass *bass, void *user_data); +typedef void (*bt_bass_destroy_func_t)(void *user_data); +typedef void (*bt_bass_debug_func_t)(const char *str, void *user_data); + +struct bt_att *bt_bass_get_att(struct bt_bass *bass); +unsigned int bt_bass_register(bt_bass_func_t attached, bt_bass_func_t detached, + void *user_data); +bool bt_bass_unregister(unsigned int id); +bool bt_bass_set_debug(struct bt_bass *bass, bt_bass_debug_func_t func, + void *user_data, bt_bass_destroy_func_t destroy); +struct bt_bass *bt_bass_new(struct gatt_db *ldb, struct gatt_db *rdb); +bool bt_bass_set_user_data(struct bt_bass *bass, void *user_data); +void bt_bass_unref(struct bt_bass *bass); +bool bt_bass_attach(struct bt_bass *bass, struct bt_gatt_client *client); +void bt_bass_detach(struct bt_bass *bass); diff -Nru bluez-5.66/src/shared/btsnoop.c bluez-5.68/src/shared/btsnoop.c --- bluez-5.66/src/shared/btsnoop.c 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/src/shared/btsnoop.c 2023-06-30 08:10:20.000000000 +0000 @@ -513,7 +513,7 @@ return false; } - toread = be32toh(pkt.size); + toread = be32toh(pkt.len); if (toread > BTSNOOP_MAX_PACKET_SIZE) { btsnoop->aborted = true; return false; diff -Nru bluez-5.66/src/shared/crypto.c bluez-5.68/src/shared/crypto.c --- bluez-5.66/src/shared/crypto.c 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/src/shared/crypto.c 2023-06-30 08:10:20.000000000 +0000 @@ -586,41 +586,55 @@ return bt_crypto_e(crypto, k, res, res); } -static bool aes_cmac(struct bt_crypto *crypto, const uint8_t key[16], +static bool aes_cmac_be(struct bt_crypto *crypto, const uint8_t key[16], const uint8_t *msg, size_t msg_len, uint8_t res[16]) { - uint8_t key_msb[16], out[16], msg_msb[CMAC_MSG_MAX]; ssize_t len; int fd; if (msg_len > CMAC_MSG_MAX) return false; - swap_buf(key, key_msb, 16); - fd = alg_new(crypto->cmac_aes, key_msb, 16); + fd = alg_new(crypto->cmac_aes, key, 16); if (fd < 0) return false; - swap_buf(msg, msg_msb, msg_len); - len = send(fd, msg_msb, msg_len, 0); + len = send(fd, msg, msg_len, 0); if (len < 0) { close(fd); return false; } - len = read(fd, out, 16); + len = read(fd, res, 16); if (len < 0) { close(fd); return false; } - swap_buf(out, res, 16); - close(fd); return true; } +static bool aes_cmac(struct bt_crypto *crypto, const uint8_t key[16], + const uint8_t *msg, size_t msg_len, uint8_t res[16]) +{ + uint8_t key_msb[16], out[16], msg_msb[CMAC_MSG_MAX]; + + if (msg_len > CMAC_MSG_MAX) + return false; + + swap_buf(key, key_msb, 16); + swap_buf(msg, msg_msb, msg_len); + + if (!aes_cmac_be(crypto, key_msb, msg_msb, msg_len, out)) + return false; + + swap_buf(out, res, 16); + + return true; +} + bool bt_crypto_f4(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], uint8_t x[16], uint8_t z, uint8_t res[16]) { @@ -737,3 +751,218 @@ return true; } + +/* + * Resolvable Set Identifier hash function sih + * + * The RSI hash function sih is used to generate a hash value that is used in + * RSIs. + * + * The following variables are the inputs to the RSI hash function sih: + * + * k is 128 bits + * r is 24 bits + * padding is 104 bits, all set to 0 + * + * r is concatenated with padding to generate r', which is used as the 128-bit + * input parameter plaintextData to security function e: + * + * r'=padding||r + * + * The LSO of r becomes the LSO of r', and the MSO of padding becomes the MSO + * of r'. + * + * For example, if the 24-bit value r is 0x3A98B5, then r' is + * 0x000000000000000000000000003A98B5. + * + * The output of the Resolvable Set Identifier function sih is: + * + * sih(k, r)=e(k, r') mod 2^24 + * + * The output of the security function e is truncated to 24 bits by taking the + * least significant 24 bits of the output of e as the result of sih. + */ +bool bt_crypto_sih(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r[3], uint8_t hash[3]) +{ + return bt_crypto_ah(crypto, k, r, hash); +} + +static bool aes_cmac_zero(struct bt_crypto *crypto, const uint8_t *msg, + size_t msg_len, uint8_t res[16]) +{ + const uint8_t zero[16] = {}; + + return aes_cmac_be(crypto, zero, msg, msg_len, res); +} + +/* The inputs to function s1 are: + * + * M is a non-zero length octet array or ASCII encoded string + * + * If M is an ASCII encoded string, M shall be converted into an integer number + * by replacing each string character with its ASCII code preserving the order. + * For example, if M is the string “CSIS”, M is converted into the integer + * number: 0x4353 4953. + * + * ZERO is the 128-bit value: + * + * 0x0000 0000 0000 0000 0000 0000 0000 0000 + * + * The output of the salt generation function s1 shall be calculated as follows: + * + * s1(M)=AES‐CMACZERO(M) + * + * Where AES-CMACZERO is the CMAC function defined in Section 4.2. + */ +static bool sef_s1(struct bt_crypto *crypto, const uint8_t *m, + size_t m_len, uint8_t res[16]) +{ + /* s1(M)=AES‐CMACZERO(M) */ + return aes_cmac_zero(crypto, m, m_len, res); +} + +/* The key derivation function k1 is used to derive a key. The derived key is + * used to encrypt and decrypt the value of the Set Identity Resolving Key + * characteristic (see Section 5.1). + * + * The definition of this key generation function uses the MAC function + * AES-CMACT with a 128-bit key T. + * + * The inputs to function k1 are: + * + * N is 0 or more octets + * + * SALT is 128 bits + * + * P is 0 or more octets + * + * The key (T) shall be computed as follows: + * + * T=AES‐CMACSALT(N) + * + * Where AES-CMACSALT is the CMAC function defined in Section 4.2. + * + * The output of the key generation function k1 shall be calculated as follows: + * + * k1(N, SALT, P)=AES‐CMACT(P) + * + * Where AES-CMACT is the CMAC function defined in Section 4.2. + */ +static bool sef_k1(struct bt_crypto *crypto, const uint8_t n[16], + uint8_t salt[16], const uint8_t *p, + size_t p_len, uint8_t res[16]) +{ + uint8_t res1[16]; + + /* T=AES‐CMACSALT(N) */ + if (!aes_cmac_be(crypto, salt, n, 16, res1)) + return false; + + /* k1(N, SALT, P)=AES‐CMACT(P) */ + return aes_cmac_be(crypto, res1, p, p_len, res); +} + +/* + * SIRK encryption function sef + * + * The SIRK encryption function sef shall be used by the server to encrypt the + * SIRK with a key K. The value of K depends on the transport on which the Set + * Identity Resolving Key characteristic is read or notified. + * + * If the Set Identity Resolving Key characteristic is read or notified on the + * Basic Rate/Enhanced Data Rate (BR/EDR) transport, K shall be equal to the + * Link Key shared by the server and the client. + * + * K=LinkKey + * + * If the Set Identity Resolving Key characteristic is read or notified on the + * Bluetooth Low Energy (LE) transport, K shall be equal to the LTK shared by + * the server and client. That is, + * + * K=LTK + * + * The inputs to the function sef are: + * + * K is the key defined above in this section + * + * SIRK is the value of the SIRK to be encrypted + * + * The output of the SIRK encryption function sef is as follows: + * + * sef(K, SIRK)=k1(K, s1(“SIRKenc”), “csis”)^SIRK + * + * Where ^ is the bitwise exclusive or operation. + */ +bool bt_crypto_sef(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t sirk[16], uint8_t out[16]) +{ + const uint8_t m[] = {'S', 'I', 'R', 'K', 'e', 'n', 'c'}; + const uint8_t p[] = {'c', 's', 'i', 's'}; + uint8_t k_msb[16]; + uint8_t salt[16]; + uint8_t res_msb[16]; + uint8_t res[16]; + + if (!crypto) + return false; + + /* salt = s1(“SIRKenc”) */ + if (!sef_s1(crypto, m, sizeof(m), salt)) + return false; + + /* Convert K to MSB/BE format */ + swap_buf(k, k_msb, 16); + + /* res_msb = k1(K, salt, “csis”) */ + if (!sef_k1(crypto, k_msb, salt, p, sizeof(p), res_msb)) + return false; + + /* Convert back to LSB/LE format */ + swap_buf(res_msb, res, 16); + + /* res^SIRK */ + u128_xor(res, sirk, out); + + return true; +} + +/* Generates a SIRK from a string using the following steps: + * - Generate a hash (k) using the str as input + * - Generate a hash (sirk) using vendor, product, version and source as input + * - Encrypt sirk using k as LTK with sef function. + */ +bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor, + uint16_t product, uint16_t version, uint16_t source, + uint8_t sirk[16]) +{ + struct iovec iov[4]; + uint8_t k[16]; + uint8_t sirk_plaintext[16]; + + if (!crypto) + return false; + + iov[0].iov_base = (void *)str; + iov[0].iov_len = strlen(str); + + /* Generate a k using the str as input */ + if (!bt_crypto_gatt_hash(crypto, iov, 1, k)) + return false; + + iov[0].iov_base = &vendor; + iov[0].iov_len = sizeof(vendor); + iov[1].iov_base = &product; + iov[1].iov_len = sizeof(product); + iov[2].iov_base = &version; + iov[2].iov_len = sizeof(version); + iov[3].iov_base = &source; + iov[3].iov_len = sizeof(source); + + /* Generate a sirk using vendor, product, version and source as input */ + if (!bt_crypto_gatt_hash(crypto, iov, 4, sirk_plaintext)) + return false; + + /* Encrypt sirk using k as LTK with sef function */ + return bt_crypto_sef(crypto, k, sirk_plaintext, sirk); +} diff -Nru bluez-5.66/src/shared/crypto.h bluez-5.68/src/shared/crypto.h --- bluez-5.66/src/shared/crypto.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/src/shared/crypto.h 2023-06-30 08:10:20.000000000 +0000 @@ -53,3 +53,10 @@ const uint8_t *pdu, uint16_t pdu_len); bool bt_crypto_gatt_hash(struct bt_crypto *crypto, struct iovec *iov, size_t iov_len, uint8_t res[16]); +bool bt_crypto_sef(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t sirk[16], uint8_t out[16]); +bool bt_crypto_sih(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r[3], uint8_t hash[3]); +bool bt_crypto_sirk(struct bt_crypto *crypto, const char *str, uint16_t vendor, + uint16_t product, uint16_t version, uint16_t source, + uint8_t sirk[16]); diff -Nru bluez-5.66/src/shared/csip.c bluez-5.68/src/shared/csip.c --- bluez-5.66/src/shared/csip.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/src/shared/csip.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,871 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "src/shared/timeout.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" +#include "src/shared/gatt-client.h" +#include "src/shared/crypto.h" +#include "src/shared/csip.h" + +#define DBG(_csip, fmt, arg...) \ + csip_debug(_csip, "%s:%s() " fmt, __FILE__, __func__, ## arg) + +/* SIRK is now hardcoded in the code. This can be moved + * to a configuration file. Since the code is to validate + * the CSIP use case of set member + */ +#define SIRK "761FAE703ED681F0C50B34155B6434FB" +#define CSIS_SIZE 0x02 +#define CSIS_LOCK 0x01 +#define CSIS_RANK 0x01 +#define CSIS_PLAINTEXT 0x01 +#define CSIS_ENC 0x02 + +struct bt_csip_db { + struct gatt_db *db; + struct bt_csis *csis; +}; + +struct csis_sirk { + uint8_t type; + uint8_t val[16]; +} __packed; + +struct bt_csis { + struct bt_csip_db *cdb; + struct csis_sirk *sirk_val; + uint8_t size_val; + uint8_t lock_val; + uint8_t rank_val; + struct gatt_db_attribute *service; + struct gatt_db_attribute *sirk; + struct gatt_db_attribute *size; + struct gatt_db_attribute *lock; + struct gatt_db_attribute *lock_ccc; + struct gatt_db_attribute *rank; +}; + +struct bt_csip_cb { + unsigned int id; + bt_csip_func_t attached; + bt_csip_func_t detached; + void *user_data; +}; + +struct bt_csip_ready { + unsigned int id; + bt_csip_ready_func_t func; + bt_csip_destroy_func_t destroy; + void *data; +}; + +struct bt_csip { + int ref_count; + struct bt_csip_db *ldb; + struct bt_csip_db *rdb; + struct bt_gatt_client *client; + struct bt_att *att; + + unsigned int idle_id; + struct queue *ready_cbs; + + bt_csip_debug_func_t debug_func; + bt_csip_destroy_func_t debug_destroy; + void *debug_data; + + bt_csip_ltk_func_t ltk_func; + void *ltk_data; + + bt_csip_sirk_func_t sirk_func; + void *sirk_data; + + void *user_data; +}; + +static struct queue *csip_db; +static struct queue *csip_cbs; +static struct queue *sessions; + +static void csip_detached(void *data, void *user_data) +{ + struct bt_csip_cb *cb = data; + struct bt_csip *csip = user_data; + + cb->detached(csip, cb->user_data); +} + +void bt_csip_detach(struct bt_csip *csip) +{ + if (!queue_remove(sessions, csip)) + return; + + bt_gatt_client_idle_unregister(csip->client, csip->idle_id); + + bt_gatt_client_unref(csip->client); + csip->client = NULL; + + queue_foreach(csip_cbs, csip_detached, csip); +} + +static void csip_db_free(void *data) +{ + struct bt_csip_db *cdb = data; + + if (!cdb) + return; + + gatt_db_unref(cdb->db); + + free(cdb->csis); + free(cdb); +} + +static void csip_ready_free(void *data) +{ + struct bt_csip_ready *ready = data; + + if (ready->destroy) + ready->destroy(ready->data); + + free(ready); +} + +static void csip_free(void *data) +{ + struct bt_csip *csip = data; + + bt_csip_detach(csip); + + csip_db_free(csip->rdb); + + queue_destroy(csip->ready_cbs, csip_ready_free); + + free(csip); +} + +struct bt_att *bt_csip_get_att(struct bt_csip *csip) +{ + if (!csip) + return NULL; + + if (csip->att) + return csip->att; + + return bt_gatt_client_get_att(csip->client); +} + +struct bt_csip *bt_csip_ref(struct bt_csip *csip) +{ + if (!csip) + return NULL; + + __sync_fetch_and_add(&csip->ref_count, 1); + + return csip; +} + +static struct bt_csip *bt_csip_ref_safe(struct bt_csip *csip) +{ + if (!csip || !csip->ref_count) + return NULL; + + return bt_csip_ref(csip); +} + +void bt_csip_unref(struct bt_csip *csip) +{ + if (!csip) + return; + + if (__sync_sub_and_fetch(&csip->ref_count, 1)) + return; + + csip_free(csip); +} + +static void csip_debug(struct bt_csip *csip, const char *format, ...) +{ + va_list ap; + + if (!csip || !format || !csip->debug_func) + return; + + va_start(ap, format); + util_debug_va(csip->debug_func, csip->debug_data, format, ap); + va_end(ap); +} + +static bool csip_match_att(const void *data, const void *match_data) +{ + const struct bt_csip *csip = data; + const struct bt_att *att = match_data; + + return bt_csip_get_att((void *)csip) == att; +} + +static bool csis_sirk_enc(struct bt_csis *csis, struct bt_att *att, + struct csis_sirk *sirk) +{ + struct bt_csip *csip; + uint8_t k[16]; + struct bt_crypto *crypto; + bool ret; + + csip = queue_find(sessions, csip_match_att, att); + if (!csip) + return false; + + if (!csip->ltk_func(csip, k, csip->ltk_data)) { + DBG(csip, "Unable to read sef key"); + return false; + } + + crypto = bt_crypto_new(); + if (!crypto) { + DBG(csip, "Failed to open crypto"); + return false; + } + + ret = bt_crypto_sef(crypto, k, sirk->val, sirk->val); + if (!ret) + DBG(csip, "Failed to encrypt SIRK using sef"); + + bt_crypto_unref(crypto); + + return ret; +} + +static void csis_sirk_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_csis *csis = user_data; + struct csis_sirk sirk; + struct iovec iov; + + memcpy(&sirk, csis->sirk_val, sizeof(sirk)); + + if (sirk.type == BT_CSIP_SIRK_ENCRYPT && + !csis_sirk_enc(csis, att, &sirk)) { + gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, + NULL, 0); + return; + } + + iov.iov_base = &sirk; + iov.iov_len = sizeof(sirk); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void csis_size_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_csis *csis = user_data; + struct iovec iov; + + iov.iov_base = &csis->size; + iov.iov_len = sizeof(csis->size); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void csis_lock_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t value = CSIS_LOCK; + + gatt_db_attribute_read_result(attrib, id, 0, &value, sizeof(value)); +} + +static void csis_lock_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + gatt_db_attribute_write_result(attrib, id, 0); +} + +static void csis_rank_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t value = CSIS_RANK; + + gatt_db_attribute_read_result(attrib, id, 0, &value, sizeof(value)); +} + +static struct bt_csis *csis_new(struct gatt_db *db) +{ + struct bt_csis *csis; + + if (!db) + return NULL; + + csis = new0(struct bt_csis, 1); + + return csis; +} + +static struct bt_csip_db *csip_db_new(struct gatt_db *db) +{ + struct bt_csip_db *cdb; + + if (!db) + return NULL; + + cdb = new0(struct bt_csip_db, 1); + cdb->db = gatt_db_ref(db); + + if (!csip_db) + csip_db = queue_new(); + + cdb->csis = csis_new(db); + cdb->csis->cdb = cdb; + + queue_push_tail(csip_db, cdb); + + return cdb; +} + +bool bt_csip_set_user_data(struct bt_csip *csip, void *user_data) +{ + if (!csip) + return false; + + csip->user_data = user_data; + + return true; +} + +static bool csip_db_match(const void *data, const void *match_data) +{ + const struct bt_csip_db *cdb = data; + const struct gatt_db *db = match_data; + + return (cdb->db == db); +} + +static struct bt_csip_db *csip_get_db(struct gatt_db *db) +{ + struct bt_csip_db *cdb; + + cdb = queue_find(csip_db, csip_db_match, db); + if (cdb) + return cdb; + + return csip_db_new(db); +} + +void bt_csip_add_db(struct gatt_db *db) +{ + csip_db_new(db); +} + +bool bt_csip_set_debug(struct bt_csip *csip, bt_csip_debug_func_t func, + void *user_data, bt_csip_destroy_func_t destroy) +{ + if (!csip) + return false; + + if (csip->debug_destroy) + csip->debug_destroy(csip->debug_data); + + csip->debug_func = func; + csip->debug_destroy = destroy; + csip->debug_data = user_data; + + return true; +} + +unsigned int bt_csip_register(bt_csip_func_t attached, bt_csip_func_t detached, + void *user_data) +{ + struct bt_csip_cb *cb; + static unsigned int id; + + if (!attached && !detached) + return 0; + + if (!csip_cbs) + csip_cbs = queue_new(); + + cb = new0(struct bt_csip_cb, 1); + cb->id = ++id ? id : ++id; + cb->attached = attached; + cb->detached = detached; + cb->user_data = user_data; + + queue_push_tail(csip_cbs, cb); + + return cb->id; +} + +static bool match_id(const void *data, const void *match_data) +{ + const struct bt_csip_cb *cb = data; + unsigned int id = PTR_TO_UINT(match_data); + + return (cb->id == id); +} + +bool bt_csip_unregister(unsigned int id) +{ + struct bt_csip_cb *cb; + + cb = queue_remove_if(csip_cbs, match_id, UINT_TO_PTR(id)); + if (!cb) + return false; + + free(cb); + + return true; +} + +struct bt_csip *bt_csip_new(struct gatt_db *ldb, struct gatt_db *rdb) +{ + struct bt_csip *csip; + struct bt_csip_db *db; + + if (!ldb) + return NULL; + + db = csip_get_db(ldb); + if (!db) + return NULL; + + csip = new0(struct bt_csip, 1); + csip->ldb = db; + csip->ready_cbs = queue_new(); + + if (!rdb) + goto done; + + db = new0(struct bt_csip_db, 1); + db->db = gatt_db_ref(rdb); + + csip->rdb = db; + +done: + bt_csip_ref(csip); + + return csip; +} + +static struct bt_csis *csip_get_csis(struct bt_csip *csip) +{ + if (!csip) + return NULL; + + if (csip->rdb->csis) + return csip->rdb->csis; + + csip->rdb->csis = new0(struct bt_csis, 1); + csip->rdb->csis->cdb = csip->rdb; + + return csip->rdb->csis; +} + +static void read_sirk(bool success, uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_csip *csip = user_data; + struct bt_csis *csis; + struct csis_sirk *sirk; + struct iovec iov = { + .iov_base = (void *)value, + .iov_len = length + }; + + if (!success) { + DBG(csip, "Unable to read SIRK: error 0x%02x", att_ecode); + return; + } + + csis = csip_get_csis(csip); + if (!csis) + return; + + sirk = util_iov_pull_mem(&iov, sizeof(*sirk)); + if (!sirk) { + DBG(csip, "Invalid size for SIRK: len %u", length); + return; + } + + if (!csis->sirk_val) + csis->sirk_val = new0(struct csis_sirk, 1); + + memcpy(csis->sirk_val, sirk, sizeof(*sirk)); +} + +static void read_size(bool success, uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_csip *csip = user_data; + struct bt_csis *csis; + + if (!success) { + DBG(csip, "Unable to read Size: error 0x%02x", att_ecode); + return; + } + + csis = csip_get_csis(csip); + if (!csis) + return; + + csis->size_val = *value; +} + +static void read_rank(bool success, uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_csip *csip = user_data; + struct bt_csis *csis; + + if (!success) { + DBG(csip, "Unable to read Rank: error 0x%02x", att_ecode); + return; + } + + csis = csip_get_csis(csip); + if (!csis) + return; + + csis->rank_val = *value; +} + +static void csip_notify_ready(struct bt_csip *csip) +{ + const struct queue_entry *entry; + + if (!bt_csip_ref_safe(csip)) + return; + + for (entry = queue_get_entries(csip->ready_cbs); entry; + entry = entry->next) { + struct bt_csip_ready *ready = entry->data; + + ready->func(csip, ready->data); + } + + bt_csip_unref(csip); +} + +static void foreach_csis_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_csip *csip = user_data; + uint16_t value_handle; + bt_uuid_t uuid, uuid_sirk, uuid_size, uuid_rank; + struct bt_csis *csis; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, + NULL, NULL, &uuid)) + return; + + bt_uuid16_create(&uuid_sirk, CS_SIRK); + bt_uuid16_create(&uuid_size, CS_SIZE); + bt_uuid16_create(&uuid_rank, CS_RANK); + + if (!bt_uuid_cmp(&uuid, &uuid_sirk)) { + DBG(csip, "SIRK found: handle 0x%04x", value_handle); + + csis = csip_get_csis(csip); + if (!csis || csis->sirk) + return; + + csis->sirk = attr; + + bt_gatt_client_read_value(csip->client, value_handle, read_sirk, + csip, NULL); + + return; + } + + if (!bt_uuid_cmp(&uuid, &uuid_size)) { + DBG(csip, "Size found: handle 0x%04x", value_handle); + + csis = csip_get_csis(csip); + if (!csis) + return; + + csis->size = attr; + + bt_gatt_client_read_value(csip->client, value_handle, read_size, + csip, NULL); + } + + if (!bt_uuid_cmp(&uuid, &uuid_rank)) { + DBG(csip, "Rank found: handle 0x%04x", value_handle); + + csis = csip_get_csis(csip); + if (!csis) + return; + + csis->rank = attr; + + bt_gatt_client_read_value(csip->client, value_handle, read_rank, + csip, NULL); + } +} +static void foreach_csis_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_csip *csip = user_data; + struct bt_csis *csis = csip_get_csis(csip); + + csis->service = attr; + + gatt_db_service_set_claimed(attr, true); + + gatt_db_service_foreach_char(attr, foreach_csis_char, csip); +} + +static void csip_idle(void *data) +{ + struct bt_csip *csip = data; + + csip->idle_id = 0; + + csip_notify_ready(csip); +} + +bool bt_csip_attach(struct bt_csip *csip, struct bt_gatt_client *client) +{ + bt_uuid_t uuid; + + if (!sessions) + sessions = queue_new(); + + queue_push_tail(sessions, csip); + + if (!client) + return true; + + if (csip->client) + return false; + + csip->client = bt_gatt_client_clone(client); + if (!csip->client) + return false; + + csip->idle_id = bt_gatt_client_idle_register(csip->client, csip_idle, + csip, NULL); + + bt_uuid16_create(&uuid, CSIS_UUID); + gatt_db_foreach_service(csip->rdb->db, &uuid, foreach_csis_service, + csip); + + return true; +} + +static struct csis_sirk *sirk_new(struct bt_csis *csis, struct gatt_db *db, + uint8_t type, uint8_t k[16], + uint8_t size, uint8_t rank) +{ + struct csis_sirk *sirk; + bt_uuid_t uuid; + struct gatt_db_attribute *cas; + + if (!csis) + return NULL; + + if (csis->sirk) + sirk = csis->sirk_val; + else + sirk = new0(struct csis_sirk, 1); + + sirk->type = type; + memcpy(sirk->val, k, sizeof(sirk->val)); + csis->sirk_val = sirk; + csis->size_val = size; + csis->lock_val = 1; + csis->rank_val = rank; + + /* Check if service already active as that means the attributes have + * already been registered. + */ + if (gatt_db_service_get_active(csis->service)) + return sirk; + + /* Populate DB with CSIS attributes */ + bt_uuid16_create(&uuid, CSIS_UUID); + csis->service = gatt_db_add_service(db, &uuid, true, 10); + + bt_uuid16_create(&uuid, CS_SIRK); + csis->sirk = gatt_db_service_add_characteristic(csis->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + csis_sirk_read, NULL, + csis); + + bt_uuid16_create(&uuid, CS_SIZE); + csis->size = gatt_db_service_add_characteristic(csis->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + csis_size_read, NULL, + csis); + + /* Lock */ + bt_uuid16_create(&uuid, CS_LOCK); + csis->lock = gatt_db_service_add_characteristic(csis->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_WRITE | + BT_GATT_CHRC_PROP_NOTIFY, + csis_lock_read_cb, + csis_lock_write_cb, + csis); + + csis->lock_ccc = gatt_db_service_add_ccc(csis->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + /* Rank */ + bt_uuid16_create(&uuid, CS_RANK); + csis->rank = gatt_db_service_add_characteristic(csis->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + csis_rank_read_cb, + NULL, csis); + + /* Add the CAS service */ + bt_uuid16_create(&uuid, 0x1853); + cas = gatt_db_add_service(db, &uuid, true, 2); + gatt_db_service_add_included(cas, csis->service); + gatt_db_service_set_active(cas, true); + gatt_db_service_add_included(cas, csis->service); + + gatt_db_service_set_active(csis->service, true); + + return sirk; +} + +bool bt_csip_set_sirk(struct bt_csip *csip, bool encrypt, + uint8_t k[16], uint8_t size, uint8_t rank, + bt_csip_ltk_func_t func, void *user_data) +{ + uint8_t zero[16] = {}; + uint8_t type; + + if (!csip || !csip->ldb || !memcmp(k, zero, sizeof(zero))) + return false; + + type = encrypt ? BT_CSIP_SIRK_ENCRYPT : BT_CSIP_SIRK_CLEARTEXT; + + /* In case of encrypted type requires sef key function */ + if (type == BT_CSIP_SIRK_ENCRYPT && !func) + return false; + + if (!sirk_new(csip->ldb->csis, csip->ldb->db, type, k, size, rank)) + return false; + + csip->ltk_func = func; + csip->ltk_data = user_data; + + return true; +} + +bool bt_csip_get_sirk(struct bt_csip *csip, uint8_t *type, + uint8_t k[16], uint8_t *size, uint8_t *rank) +{ + struct bt_csis *csis; + + if (!csip) + return false; + + csis = csip_get_csis(csip); + if (!csis) + return false; + + if (!csis->sirk_val) + return false; + + if (type) + *type = csis->sirk_val->type; + + memcpy(k, csis->sirk_val->val, sizeof(csis->sirk_val->val)); + + if (size) + *size = csis->size_val; + + if (rank) + *rank = csis->rank_val; + + return true; +} + +unsigned int bt_csip_ready_register(struct bt_csip *csip, + bt_csip_ready_func_t func, void *user_data, + bt_csip_destroy_func_t destroy) +{ + struct bt_csip_ready *ready; + static unsigned int id; + + if (!csip) + return 0; + + ready = new0(struct bt_csip_ready, 1); + ready->id = ++id ? id : ++id; + ready->func = func; + ready->destroy = destroy; + ready->data = user_data; + + queue_push_tail(csip->ready_cbs, ready); + + return ready->id; +} + +static bool match_ready_id(const void *data, const void *match_data) +{ + const struct bt_csip_ready *ready = data; + unsigned int id = PTR_TO_UINT(match_data); + + return (ready->id == id); +} + +bool bt_csip_ready_unregister(struct bt_csip *csip, unsigned int id) +{ + struct bt_csip_ready *ready; + + ready = queue_remove_if(csip->ready_cbs, match_ready_id, + UINT_TO_PTR(id)); + if (!ready) + return false; + + csip_ready_free(ready); + + return true; +} diff -Nru bluez-5.66/src/shared/csip.h bluez-5.68/src/shared/csip.h --- bluez-5.66/src/shared/csip.h 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/src/shared/csip.h 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + * + */ + +#include +#include + +#include "src/shared/io.h" + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +struct bt_csip; + +enum { + BT_CSIP_SIRK_ENCRYPT = 0x00, + BT_CSIP_SIRK_CLEARTEXT = 0x01 +}; + +typedef void (*bt_csip_ready_func_t)(struct bt_csip *csip, void *user_data); +typedef void (*bt_csip_destroy_func_t)(void *user_data); +typedef void (*bt_csip_debug_func_t)(const char *str, void *user_data); +typedef void (*bt_csip_func_t)(struct bt_csip *csip, void *user_data); +typedef bool (*bt_csip_ltk_func_t)(struct bt_csip *csip, uint8_t k[16], + void *user_data); +typedef bool (*bt_csip_sirk_func_t)(struct bt_csip *csip, uint8_t type, + uint8_t k[16], uint8_t size, uint8_t rank, + void *user_data); + +struct bt_csip *bt_csip_ref(struct bt_csip *csip); +void bt_csip_unref(struct bt_csip *csip); + +void bt_csip_add_db(struct gatt_db *db); + +bool bt_csip_attach(struct bt_csip *csip, struct bt_gatt_client *client); +void bt_csip_detach(struct bt_csip *csip); + +bool bt_csip_set_debug(struct bt_csip *csip, bt_csip_debug_func_t func, + void *user_data, bt_csip_destroy_func_t destroy); + +struct bt_att *bt_csip_get_att(struct bt_csip *csip); + +bool bt_csip_set_user_data(struct bt_csip *csip, void *user_data); + +/* Session related function */ +unsigned int bt_csip_register(bt_csip_func_t added, bt_csip_func_t removed, + void *user_data); +bool bt_csip_unregister(unsigned int id); +struct bt_csip *bt_csip_new(struct gatt_db *ldb, struct gatt_db *rdb); + +bool bt_csip_set_sirk(struct bt_csip *csip, bool encrypt, + uint8_t k[16], uint8_t size, uint8_t rank, + bt_csip_ltk_func_t func, void *user_data); + +bool bt_csip_get_sirk(struct bt_csip *csip, uint8_t *type, + uint8_t k[16], uint8_t *size, uint8_t *rank); + +unsigned int bt_csip_ready_register(struct bt_csip *csip, + bt_csip_ready_func_t func, void *user_data, + bt_csip_destroy_func_t destroy); +bool bt_csip_ready_unregister(struct bt_csip *csip, unsigned int id); diff -Nru bluez-5.66/src/shared/gatt-client.c bluez-5.68/src/shared/gatt-client.c --- bluez-5.66/src/shared/gatt-client.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/gatt-client.c 2023-06-30 08:10:20.000000000 +0000 @@ -38,7 +38,8 @@ #define GATT_SVC_UUID 0x1801 #define SVC_CHNGD_UUID 0x2a05 #define DBG(_client, _format, arg...) \ - gatt_log(_client, "%s:%s() " _format, __FILE__, __func__, ## arg) + gatt_log(_client, "[%p] %s:%s() " _format, _client, __FILE__, \ + __func__, ## arg) struct ready_cb { bt_gatt_client_callback_t callback; @@ -46,6 +47,12 @@ void *data; }; +struct idle_cb { + bt_gatt_client_idle_callback_t callback; + bt_gatt_client_destroy_func_t destroy; + void *data; +}; + struct bt_gatt_client { struct bt_att *att; int ref_count; @@ -55,6 +62,7 @@ struct queue *clones; struct queue *ready_cbs; + struct queue *idle_cbs; bt_gatt_client_service_changed_callback_t svc_chngd_callback; bt_gatt_client_destroy_func_t svc_chngd_destroy; @@ -146,9 +154,49 @@ return request_ref(req); } +static void idle_destroy(void *data) +{ + struct idle_cb *idle = data; + + if (idle->destroy) + idle->destroy(idle->data); + + free(idle); +} + +static bool idle_notify(const void *data, const void *user_data) +{ + const struct idle_cb *idle = data; + + idle->callback(idle->data); + + return true; +} + +static struct bt_gatt_client * +bt_gatt_client_ref_safe(struct bt_gatt_client *client) +{ + if (!client || !client->ref_count) + return NULL; + + return bt_gatt_client_ref(client); +} + +static void notify_client_idle(struct bt_gatt_client *client) +{ + client = bt_gatt_client_ref_safe(client); + if (!client) + return; + + queue_remove_all(client->idle_cbs, idle_notify, NULL, idle_destroy); + + bt_gatt_client_unref(client); +} + static void request_unref(void *data) { struct request *req = data; + struct bt_gatt_client *client = req->client; if (__sync_sub_and_fetch(&req->ref_count, 1)) return; @@ -156,8 +204,11 @@ if (req->destroy) req->destroy(req->data); - if (!req->removed) - queue_remove(req->client->pending_requests, req); + if (!req->removed) { + queue_remove(client->pending_requests, req); + if (queue_isempty(client->pending_requests)) + notify_client_idle(client); + } free(req); } @@ -357,15 +408,28 @@ static bool read_db_hash(struct discovery_op *op); +static void gatt_log_va(struct bt_gatt_client *client, const char *format, + va_list va) +{ + if (!client || !format) + return; + + if (client->debug_callback) + util_debug_va(client->debug_callback, client->debug_data, + format, va); + else + gatt_log_va(client->parent, format, va); +} + static void gatt_log(struct bt_gatt_client *client, const char *format, ...) { va_list ap; - if (!client || !format || !client->debug_callback) + if (!client || !format) return; va_start(ap, format); - util_debug_va(client->debug_callback, client->debug_data, format, ap); + gatt_log_va(client, format, ap); va_end(ap); } @@ -500,6 +564,24 @@ client->discovery_req = NULL; } +static void discover_remove_pending(struct discovery_op *op, + struct gatt_db_attribute *attr) +{ + struct gatt_db_attribute *svc; + + svc = gatt_db_attribute_get_service(attr); + if (!svc) + return; + + if (!queue_remove(op->pending_svcs, svc)) + return; + + gatt_db_service_set_active(svc, true); + + if (op->cur_svc == svc) + op->cur_svc = NULL; +} + static void discover_chrcs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data); @@ -576,12 +658,26 @@ gatt_db_attribute_get_handle(attr), handle); goto failed; } + + if (!gatt_db_attribute_get_service_data(attr, NULL, &end, + NULL, NULL)) { + DBG(client, "Unable to get service data at 0x%04x", + handle); + goto failed; + } + + /* Skip if there are no attributes */ + if (handle == end) + discover_remove_pending(op, attr); } next: range = queue_pop_head(op->discov_ranges); - if (!range) + if (!range) { + /* Skip if there are no attributes */ + discover_remove_pending(op, op->cur_svc); goto failed; + } client->discovery_req = bt_gatt_discover_characteristics(client->att, range->start, @@ -725,6 +821,9 @@ goto failed; } + /* Done with the current service */ + discover_remove_pending(op, op->cur_svc); + done: free(chrc_data); return true; @@ -798,9 +897,6 @@ if (discovering) return; - /* Done with the current service */ - gatt_db_service_set_active(op->cur_svc, true); - goto done; failed: @@ -888,9 +984,6 @@ if (discovering) return; - /* Done with the current service */ - gatt_db_service_set_active(op->cur_svc, true); - goto done; failed: @@ -997,9 +1090,6 @@ if (discovering) return; - /* Done with the current service */ - gatt_db_service_set_active(op->cur_svc, true); - goto done; failed: @@ -1018,6 +1108,20 @@ (match_range->start <= range->end); } +static struct handle_range *range_new(uint16_t start, uint16_t end) +{ + struct handle_range *range; + + if (!start || !end || start > end) + return NULL; + + range = new0(struct handle_range, 1); + range->start = start; + range->end = end; + + return range; +} + static void remove_discov_range(struct discovery_op *op, uint16_t start, uint16_t end) { @@ -1034,16 +1138,18 @@ if ((range->start == start) && (range->end == end)) { queue_remove(op->discov_ranges, range); free(range); - } else if (range->start == start) + } else if (range->start == start) { range->start = end + 1; - else if (range->end == end) + if (!range->start || range->start > range->end) { + queue_remove(op->discov_ranges, range); + free(range); + } + } else if (range->end == end) range->end = start - 1; else { - new_range = new0(struct handle_range, 1); - new_range->start = end + 1; - new_range->end = range->end; - - queue_push_after(op->discov_ranges, range, new_range); + new_range = range_new(end + 1, range->end); + if (new_range) + queue_push_after(op->discov_ranges, range, new_range); range->end = start - 1; } @@ -1265,10 +1371,13 @@ { const struct queue_entry *entry; - if (client->ready) + client = bt_gatt_client_ref_safe(client); + if (!client) return; - bt_gatt_client_ref(client); + if (client->ready) + goto done; + client->ready = success; if (client->parent) @@ -1291,6 +1400,7 @@ notify_client_ready(clone, success, att_ecode); } +done: bt_gatt_client_unref(client); } @@ -1550,31 +1660,30 @@ } static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable, - bt_att_response_func_t callback) + bt_gatt_client_callback_t callback) { - uint8_t pdu[4]; unsigned int att_id; + uint16_t value; uint16_t properties = notify_data->chrc->properties; assert(notify_data->chrc->ccc_handle); - memset(pdu, 0, sizeof(pdu)); - put_le16(notify_data->chrc->ccc_handle, pdu); if (enable) { /* Try to enable notifications or indications based on * whatever the characteristic supports. */ if (properties & BT_GATT_CHRC_PROP_NOTIFY) - pdu[2] = 0x01; + value = cpu_to_le16(0x0001); else if (properties & BT_GATT_CHRC_PROP_INDICATE) - pdu[2] = 0x02; - - if (!pdu[2]) + value = cpu_to_le16(0x0002); + else return false; } - att_id = bt_att_send(notify_data->client->att, BT_ATT_OP_WRITE_REQ, - pdu, sizeof(pdu), callback, + att_id = bt_gatt_client_write_value(notify_data->client, + notify_data->chrc->ccc_handle, + (void *)&value, sizeof(value), + callback, notify_data_ref(notify_data), notify_data_unref); notify_data->chrc->ccc_write_id = notify_data->att_id = att_id; @@ -1604,8 +1713,8 @@ return true; } -static void enable_ccc_callback(uint8_t opcode, const void *pdu, - uint16_t length, void *user_data) +static void enable_ccc_callback(bool success, uint8_t att_ecode, + void *user_data) { struct notify_data *notify_data = user_data; @@ -1613,10 +1722,9 @@ notify_data->chrc->ccc_write_id = 0; - bt_gatt_client_ref(notify_data->client); + bt_gatt_client_ref_safe(notify_data->client); - if (opcode == BT_ATT_OP_ERROR_RSP) - notify_data->att_ecode = process_error(pdu, length); + notify_data->att_ecode = att_ecode; /* Notify for all remaining requests. */ complete_notify_request(notify_data); @@ -1655,8 +1763,11 @@ * descriptor. */ chrc = notify_chrc_create(client, handle); - if (!chrc) + if (!chrc) { + DBG(client, "Unable to locate characteristic at 0x%04x", + handle); return 0; + } } /* Fail if we've hit the maximum allowed notify sessions */ @@ -1694,9 +1805,10 @@ } /* - * If the ref count > 1, then notifications are already enabled. + * If the ref count > 1, ccc handle cannot be found or registration + * callback is not set consider notifications are already enabled. */ - if (chrc->notify_count > 1 || !chrc->ccc_handle) { + if (chrc->notify_count > 1 || !chrc->ccc_handle || !callback) { complete_notify_request(notify_data); return notify_data->id; } @@ -2051,8 +2163,8 @@ const void *data; }; -static void disable_ccc_callback(uint8_t opcode, const void *pdu, - uint16_t length, void *user_data) +static void disable_ccc_callback(bool success, uint8_t att_ecode, + void *user_data) { struct notify_data *notify_data = user_data; struct notify_data *next_data; @@ -2120,6 +2232,9 @@ struct bt_gatt_client *client = user_data; struct value_data data; + if (queue_isempty(client->notify_list)) + return; + bt_gatt_client_ref(client); memset(&data, 0, sizeof(data)); @@ -2171,6 +2286,7 @@ queue_destroy(client->notify_list, notify_data_cleanup); queue_destroy(client->ready_cbs, ready_destroy); + queue_destroy(client->idle_cbs, idle_destroy); if (client->debug_destroy) client->debug_destroy(client->debug_data); @@ -2229,6 +2345,7 @@ client->clones = queue_new(); client->ready_cbs = queue_new(); + client->idle_cbs = queue_new(); client->long_write_queue = queue_new(); client->svc_chngd_queue = queue_new(); client->notify_list = queue_new(); @@ -2698,7 +2815,7 @@ void *user_data, bt_gatt_client_destroy_func_t destroy) { - uint8_t pdu[num_handles * 2]; + uint8_t *pdu = newa(uint8_t, num_handles * 2); struct request *req; struct read_op *op; uint8_t opcode; @@ -2735,7 +2852,7 @@ BT_GATT_CHRC_CLI_FEAT_EATT ? BT_ATT_OP_READ_MULT_VL_REQ : BT_ATT_OP_READ_MULT_REQ; - req->att_id = bt_att_send(client->att, opcode, pdu, sizeof(pdu), + req->att_id = bt_att_send(client->att, opcode, pdu, num_handles * 2, read_multiple_cb, req, request_unref); if (!req->att_id) { @@ -2928,7 +3045,7 @@ uint16_t value_handle, bool signed_write, const uint8_t *value, uint16_t length) { - uint8_t pdu[2 + length]; + uint8_t *pdu = newa(uint8_t, 2 + length); struct request *req; int security; uint8_t op; @@ -2951,7 +3068,7 @@ put_le16(value_handle, pdu); memcpy(pdu + 2, value, length); - req->att_id = bt_att_send(client->att, op, pdu, sizeof(pdu), NULL, req, + req->att_id = bt_att_send(client->att, op, pdu, 2 + length, NULL, req, request_unref); if (!req->att_id) { request_unref(req); @@ -3009,7 +3126,7 @@ { struct request *req; struct write_op *op; - uint8_t pdu[2 + length]; + uint8_t *pdu = newa(uint8_t, 2 + length); if (!client) return 0; @@ -3033,7 +3150,7 @@ memcpy(pdu + 2, value, length); req->att_id = bt_att_send(client->att, BT_ATT_OP_WRITE_REQ, - pdu, sizeof(pdu), + pdu, 2 + length, write_cb, req, request_unref); if (!req->att_id) { @@ -3448,7 +3565,7 @@ { struct request *req; struct prep_write_op *op; - uint8_t pdu[4 + length]; + uint8_t *pdu = newa(uint8_t, 4 + length); if (!client) return 0; @@ -3507,7 +3624,7 @@ * Note that request_unref will be done on write execute */ req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu, - sizeof(pdu), prep_write_cb, req, + length, prep_write_cb, req, NULL); if (!req->att_id) { op->destroy = NULL; @@ -3614,7 +3731,8 @@ void *user_data, bt_gatt_client_destroy_func_t destroy) { - if (!client || !client->db || !chrc_value_handle || !callback) + if (!client || !client->db || !chrc_value_handle || + (!callback && !notify)) return 0; if (client->in_svc_chngd) @@ -3663,3 +3781,39 @@ return bt_att_get_security(client->att, NULL); } + +unsigned int bt_gatt_client_idle_register(struct bt_gatt_client *client, + bt_gatt_client_idle_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct idle_cb *idle; + + if (!client) + return 0; + + idle = new0(struct idle_cb, 1); + idle->callback = callback; + idle->destroy = destroy; + idle->data = user_data; + + queue_push_tail(client->idle_cbs, idle); + + return PTR_TO_UINT(idle); +} + +bool bt_gatt_client_idle_unregister(struct bt_gatt_client *client, + unsigned int id) +{ + struct idle_cb *idle = UINT_TO_PTR(id); + + if (!client || !id) + return false; + + if (queue_remove(client->idle_cbs, idle)) { + idle_destroy(idle); + return true; + } + + return false; +} diff -Nru bluez-5.66/src/shared/gatt-client.h bluez-5.68/src/shared/gatt-client.h --- bluez-5.66/src/shared/gatt-client.h 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/src/shared/gatt-client.h 2023-06-30 08:10:20.000000000 +0000 @@ -26,6 +26,7 @@ void bt_gatt_client_unref(struct bt_gatt_client *client); typedef void (*bt_gatt_client_destroy_func_t)(void *user_data); +typedef void (*bt_gatt_client_idle_callback_t)(void *user_data); typedef void (*bt_gatt_client_callback_t)(bool success, uint8_t att_ecode, void *user_data); typedef void (*bt_gatt_client_debug_func_t)(const char *str, void *user_data); @@ -126,3 +127,10 @@ bool bt_gatt_client_set_security(struct bt_gatt_client *client, int level); int bt_gatt_client_get_security(struct bt_gatt_client *client); + +unsigned int bt_gatt_client_idle_register(struct bt_gatt_client *client, + bt_gatt_client_idle_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +bool bt_gatt_client_idle_unregister(struct bt_gatt_client *client, + unsigned int id); diff -Nru bluez-5.66/src/shared/gatt-db.c bluez-5.68/src/shared/gatt-db.c --- bluez-5.66/src/shared/gatt-db.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/gatt-db.c 2023-06-30 08:10:20.000000000 +0000 @@ -1549,7 +1549,7 @@ return -1; } -static struct gatt_db_attribute * +struct gatt_db_attribute * gatt_db_attribute_get_value(struct gatt_db_attribute *attrib) { struct gatt_db_service *service; @@ -1559,18 +1559,18 @@ return NULL; index = gatt_db_attribute_get_index(attrib); - if (index < 0) + if (index <= 0) return NULL; service = attrib->service; if (!bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) - index++; - else if (bt_uuid_cmp(&characteristic_uuid, + return service->attributes[index + 1]; + else if (!bt_uuid_cmp(&characteristic_uuid, &service->attributes[index - 1]->uuid)) - return NULL; + return service->attributes[index]; - return service->attributes[index]; + return gatt_db_attribute_get_value(service->attributes[index - 1]); } void gatt_db_service_foreach_desc(struct gatt_db_attribute *attrib, @@ -1712,6 +1712,15 @@ return attrib->handle; } +struct gatt_db_attribute * +gatt_db_attribute_get_service(const struct gatt_db_attribute *attrib) +{ + if (!attrib) + return NULL; + + return attrib->service->attributes[0]; +} + bool gatt_db_attribute_get_service_uuid(const struct gatt_db_attribute *attrib, bt_uuid_t *uuid) { @@ -2095,7 +2104,7 @@ { uint8_t err = 0; - if (!attrib || !func) + if (!attrib || (!func && attrib->write_func)) return false; if (attrib->write_func) { @@ -2158,7 +2167,8 @@ memcpy(&attrib->value[offset], value, len); done: - func(attrib, err, user_data); + if (func) + func(attrib, err, user_data); return true; } diff -Nru bluez-5.66/src/shared/gatt-db.h bluez-5.68/src/shared/gatt-db.h --- bluez-5.66/src/shared/gatt-db.h 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/src/shared/gatt-db.h 2023-06-30 08:10:20.000000000 +0000 @@ -225,6 +225,9 @@ uint16_t gatt_db_attribute_get_handle(const struct gatt_db_attribute *attrib); +struct gatt_db_attribute * +gatt_db_attribute_get_service(const struct gatt_db_attribute *attrib); + bool gatt_db_attribute_get_service_uuid(const struct gatt_db_attribute *attrib, bt_uuid_t *uuid); @@ -282,6 +285,8 @@ unsigned int id, int err); struct gatt_db_attribute * +gatt_db_attribute_get_value(struct gatt_db_attribute *attrib); +struct gatt_db_attribute * gatt_db_attribute_get_ccc(struct gatt_db_attribute *attrib); bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib, diff -Nru bluez-5.66/src/shared/gatt-server.c bluez-5.68/src/shared/gatt-server.c --- bluez-5.66/src/shared/gatt-server.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/src/shared/gatt-server.c 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. + * Copyright 2023 NXP * * */ @@ -1785,7 +1786,9 @@ length = MIN(data->len - data->offset, length); } - memcpy(data->pdu + data->offset, value, length); + if (value) + memcpy(data->pdu + data->offset, value, length); + data->offset += length; if (multiple) { diff -Nru bluez-5.66/src/shared/lc3.h bluez-5.68/src/shared/lc3.h --- bluez-5.66/src/shared/lc3.h 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/lc3.h 2023-06-30 08:10:20.000000000 +0000 @@ -7,11 +7,12 @@ * */ -#define LTV(_type, _bytes...) \ +#define data(args...) ((const unsigned char[]) { args }) + +#define LC3_IOV(args...) \ { \ - .len = 1 + sizeof((uint8_t []) { _bytes }), \ - .type = _type, \ - .data = { _bytes }, \ + .iov_base = (void *)data(args), \ + .iov_len = sizeof(data(args)), \ } #define LC3_ID 0x06 @@ -52,13 +53,11 @@ #define LC3_FRAME_COUNT (LC3_BASE + 4) #define LC3_CAPABILITIES(_freq, _duration, _chan_count, _len_min, _len_max) \ - { \ - LTV(LC3_FREQ, _freq), \ - LTV(LC3_DURATION, _duration), \ - LTV(LC3_CHAN_COUNT, _chan_count), \ - LTV(LC3_FRAME_LEN, _len_min, _len_min >> 8, \ - _len_max, _len_max >> 8), \ - } + LC3_IOV(0x02, LC3_FREQ, _freq, _freq >> 8, \ + 0x02, LC3_DURATION, _duration, \ + 0x02, LC3_CHAN_COUNT, _chan_count, \ + 0x05, LC3_FRAME_LEN, _len_min, _len_min >> 8, \ + _len_max, _len_max >> 8) #define LC3_CONFIG_BASE 0x01 @@ -81,32 +80,78 @@ #define LC3_CONFIG_FRAME_LEN (LC3_CONFIG_BASE + 3) #define LC3_CONFIG(_freq, _duration, _len) \ - { \ - LTV(LC3_CONFIG_FREQ, _freq), \ - LTV(LC3_CONFIG_DURATION, _duration), \ - LTV(LC3_CONFIG_FRAME_LEN, _len, _len >> 8), \ - } + LC3_IOV(0x02, LC3_CONFIG_FREQ, _freq, \ + 0x02, LC3_CONFIG_DURATION, _duration, \ + 0x03, LC3_CONFIG_FRAME_LEN, _len, _len >> 8) -#define LC3_CONFIG_8KHZ(_duration, _len) \ +#define LC3_CONFIG_8(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_8KHZ, _duration, _len) -#define LC3_CONFIG_11KHZ(_duration, _len) \ +#define LC3_CONFIG_11(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_11KHZ, _duration, _len) -#define LC3_CONFIG_16KHZ(_duration, _len) \ +#define LC3_CONFIG_16(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_16KHZ, _duration, _len) -#define LC3_CONFIG_22KHZ(_duration, _len) \ +#define LC3_CONFIG_22(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_22KHZ, _duration, _len) -#define LC3_CONFIG_24KHZ(_duration, _len) \ +#define LC3_CONFIG_24(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_24KHZ, _duration, _len) -#define LC3_CONFIG_32KHZ(_duration, _len) \ +#define LC3_CONFIG_32(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_32KHZ, _duration, _len) -#define LC3_CONFIG_44KHZ(_duration, _len) \ +#define LC3_CONFIG_44(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_44KHZ, _duration, _len) -#define LC3_CONFIG_48KHZ(_duration, _len) \ +#define LC3_CONFIG_48(_duration, _len) \ LC3_CONFIG(LC3_CONFIG_FREQ_48KHZ, _duration, _len) + +#define LC3_CONFIG_8_1 \ + LC3_CONFIG_8(LC3_CONFIG_DURATION_7_5, 26u) + +#define LC3_CONFIG_8_2 \ + LC3_CONFIG_8(LC3_CONFIG_DURATION_10, 30u) + +#define LC3_CONFIG_16_1 \ + LC3_CONFIG_16(LC3_CONFIG_DURATION_7_5, 30u) + +#define LC3_CONFIG_16_2 \ + LC3_CONFIG_16(LC3_CONFIG_DURATION_10, 40u) + +#define LC3_CONFIG_24_1 \ + LC3_CONFIG_24(LC3_CONFIG_DURATION_7_5, 45u) + +#define LC3_CONFIG_24_2 \ + LC3_CONFIG_24(LC3_CONFIG_DURATION_10, 60u) + +#define LC3_CONFIG_32_1 \ + LC3_CONFIG_32(LC3_CONFIG_DURATION_7_5, 60u) + +#define LC3_CONFIG_32_2 \ + LC3_CONFIG_32(LC3_CONFIG_DURATION_10, 80u) + +#define LC3_CONFIG_44_1 \ + LC3_CONFIG_44(LC3_CONFIG_DURATION_7_5, 98u) + +#define LC3_CONFIG_44_2 \ + LC3_CONFIG_44(LC3_CONFIG_DURATION_10, 130u) + +#define LC3_CONFIG_48_1 \ + LC3_CONFIG_48(LC3_CONFIG_DURATION_7_5, 75u) + +#define LC3_CONFIG_48_2 \ + LC3_CONFIG_48(LC3_CONFIG_DURATION_10, 100u) + +#define LC3_CONFIG_48_3 \ + LC3_CONFIG_48(LC3_CONFIG_DURATION_7_5, 90u) + +#define LC3_CONFIG_48_4 \ + LC3_CONFIG_48(LC3_CONFIG_DURATION_10, 120u) + +#define LC3_CONFIG_48_5 \ + LC3_CONFIG_48(LC3_CONFIG_DURATION_7_5, 117u) + +#define LC3_CONFIG_48_6 \ + LC3_CONFIG_48(LC3_CONFIG_DURATION_10, 155u) diff -Nru bluez-5.66/src/shared/shell.c bluez-5.68/src/shared/shell.c --- bluez-5.66/src/shared/shell.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/shell.c 2023-06-30 08:10:20.000000000 +0000 @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -69,7 +70,12 @@ bool zsh; bool monitor; int timeout; - struct io *input; + int init_fd; + FILE *f; + struct queue *inputs; + + char *line; + struct queue *queue; bool saved_prompt; bt_shell_prompt_input_func saved_func; @@ -535,7 +541,7 @@ char *saved_line; int saved_point; - if (!data.input) + if (queue_isempty(data.inputs)) return; if (data.mode) { @@ -576,6 +582,26 @@ } } +void bt_shell_echo(const char *fmt, ...) +{ + va_list args; + char *str; + int ret; + + va_start(args, fmt); + ret = vasprintf(&str, fmt, args); + if (ret >= 0) + ret = asprintf(&str, COLOR_HIGHLIGHT "%s " COLOR_OFF "#", str); + va_end(args); + + if (ret < 0) + return; + + rl_save_prompt(); + bt_shell_set_prompt(str); + rl_restore_prompt(); +} + static void print_string(const char *str, void *user_data) { bt_shell_printf("%s\n", str); @@ -586,7 +612,7 @@ util_hexdump(' ', buf, len, print_string, NULL); } -void bt_shell_usage() +void bt_shell_usage(void) { if (!data.exec) return; @@ -595,6 +621,32 @@ data.exec->arg ? data.exec->arg : ""); } +static void bt_shell_dequeue_exec(void) +{ + int err; + + if (!data.line) + return; + + free(data.line); + data.line = NULL; + + data.line = queue_pop_head(data.queue); + if (!data.line) + return; + + bt_shell_printf("%s\n", data.line); + + if (!bt_shell_release_prompt(data.line)) { + bt_shell_dequeue_exec(); + return; + } + + err = bt_shell_exec(data.line); + if (err) + bt_shell_dequeue_exec(); +} + static void prompt_input(const char *str, bt_shell_prompt_input_func func, void *user_data) { @@ -968,6 +1020,11 @@ static bool io_hup(struct io *io, void *user_data) { + if (queue_remove(data.inputs, io)) { + if (!queue_isempty(data.inputs)) + return false; + } + mainloop_quit(); return false; @@ -979,7 +1036,7 @@ switch (signum) { case SIGINT: - if (data.input && !data.mode) { + if (!queue_isempty(data.inputs) && !data.mode) { rl_replace_line("", 0); rl_crlf(); rl_on_new_line(); @@ -1071,6 +1128,7 @@ static const struct option main_options[] = { { "version", no_argument, 0, 'v' }, { "help", no_argument, 0, 'h' }, + { "init-script", required_argument, 0, 'i' }, { "timeout", required_argument, 0, 't' }, { "monitor", no_argument, 0, 'm' }, { "zsh-complete", no_argument, 0, 'z' }, @@ -1092,6 +1150,7 @@ printf("\t--monitor \tEnable monitor output\n" "\t--timeout \tTimeout in seconds for non-interactive mode\n" "\t--version \tDisplay version\n" + "\t--init-script \tInit script file\n" "\t--help \t\tDisplay help\n"); } @@ -1110,9 +1169,9 @@ if (opt) { memcpy(options + offset, opt->options, sizeof(struct option) * opt->optno); - snprintf(optstr, sizeof(optstr), "+mhvt:%s", opt->optstr); + snprintf(optstr, sizeof(optstr), "+mhvi:t:%s", opt->optstr); } else - snprintf(optstr, sizeof(optstr), "+mhvt:"); + snprintf(optstr, sizeof(optstr), "+mhvi:t:"); data.name = strrchr(argv[0], '/'); if (!data.name) @@ -1120,6 +1179,8 @@ else data.name = strdup(++data.name); + data.init_fd = -1; + while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) { switch (c) { case 'v': @@ -1132,6 +1193,13 @@ data.argv = &cmplt; data.mode = 1; goto done; + case 'i': + if (optarg) + data.init_fd = open(optarg, O_RDONLY); + if (data.init_fd < 0) + printf("Unable to open %s: %s (%d)\n", optarg, + strerror(errno), errno); + break; case 't': if (optarg) data.timeout = strtol(optarg, &endptr, 0); @@ -1185,6 +1253,8 @@ rl_init(); data.init = true; + data.inputs = queue_new(); + data.queue = queue_new(); data.prompts = queue_new(); } @@ -1219,6 +1289,28 @@ return status; } +static int bt_shell_queue_exec(char *line) +{ + int err; + + /* Queue if already executing */ + if (data.line) { + /* Check if prompt is being held then release using the line */ + if (!bt_shell_release_prompt(line)) + return 0; + queue_push_tail(data.queue, strdup(line)); + return 0; + } + + bt_shell_printf("%s\n", line); + + err = bt_shell_exec(line); + if (!err) + data.line = strdup(line); + + return err; +} + int bt_shell_exec(const char *input) { wordexp_t w; @@ -1227,8 +1319,20 @@ if (!input) return 0; - if (wordexp(input, &w, WRDE_NOCMD)) - return -ENOEXEC; + err = wordexp(input, &w, WRDE_NOCMD); + switch (err) { + case WRDE_BADCHAR: + return -EBADMSG; + case WRDE_BADVAL: + case WRDE_SYNTAX: + return -EINVAL; + case WRDE_NOSPACE: + return -ENOMEM; + case WRDE_CMDSUB: + if (wordexp(input, &w, 0)) + return -ENOEXEC; + break; + }; if (w.we_wordc == 0) { wordfree(&w); @@ -1257,6 +1361,8 @@ rl_cleanup(); + queue_destroy(data.inputs, NULL); + queue_destroy(data.queue, free); queue_destroy(data.prompts, prompt_free); data.prompts = NULL; @@ -1274,8 +1380,10 @@ void bt_shell_noninteractive_quit(int status) { - if (!data.mode || data.timeout) + if (!data.mode || data.timeout) { + bt_shell_dequeue_exec(); return; + } bt_shell_quit(status); } @@ -1320,7 +1428,43 @@ static bool input_read(struct io *io, void *user_data) { - rl_callback_read_char(); + int fd; + char *line = NULL; + size_t len = 0; + ssize_t nread; + + fd = io_get_fd(io); + + if (fd == STDIN_FILENO) { + rl_callback_read_char(); + return true; + } + + if (!data.f) { + data.f = fdopen(fd, "r"); + if (!data.f) { + printf("fdopen: %s (%d)\n", strerror(errno), errno); + return false; + } + } + + nread = getline(&line, &len, data.f); + if (nread > 0) { + int err; + + if (line[nread - 1] == '\n') + line[nread - 1] = '\0'; + + err = bt_shell_queue_exec(line); + if (err < 0) + printf("%s: %s (%d)\n", line, strerror(-err), -err); + } else { + fclose(data.f); + data.f = NULL; + } + + free(line); + return true; } @@ -1335,18 +1479,16 @@ { struct io *io; - /* TODO: Allow more than one input? */ - if (data.input) - return false; - io = io_new(fd); + if (!io) + return false; if (!data.mode) { io_set_read_handler(io, input_read, NULL, NULL); io_set_disconnect_handler(io, io_hup, NULL, NULL); } - data.input = io; + queue_push_tail(data.inputs, io); if (data.mode) { if (shell_exec(data.argc, data.argv) < 0) { @@ -1357,6 +1499,12 @@ if (data.timeout) timeout_add(data.timeout * 1000, shell_quit, NULL, NULL); + } else if (data.init_fd >= 0) { + int fd = data.init_fd; + + data.init_fd = -1; + if (!bt_shell_attach(fd)) + return false; } return true; @@ -1364,11 +1512,11 @@ bool bt_shell_detach(void) { - if (!data.input) + if (queue_isempty(data.inputs)) return false; - io_destroy(data.input); - data.input = NULL; + queue_remove_all(data.inputs, NULL, NULL, + (queue_destroy_func_t) io_destroy); return true; } diff -Nru bluez-5.66/src/shared/shell.h bluez-5.68/src/shared/shell.h --- bluez-5.66/src/shared/shell.h 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/src/shared/shell.h 2023-06-30 08:10:20.000000000 +0000 @@ -70,6 +70,8 @@ void bt_shell_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +void bt_shell_echo(const char *fmt, + ...) __attribute__((format(printf, 1, 2))); void bt_shell_hexdump(const unsigned char *buf, size_t len); void bt_shell_usage(void); diff -Nru bluez-5.66/src/shared/tester.c bluez-5.68/src/shared/tester.c --- bluez-5.66/src/shared/tester.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/tester.c 2023-06-30 08:10:20.000000000 +0000 @@ -89,6 +89,7 @@ tester_data_func_t test_func; tester_data_func_t teardown_func; tester_data_func_t post_teardown_func; + tester_data_func_t io_complete_func; gdouble start_time; gdouble end_time; unsigned int timeout; @@ -621,6 +622,9 @@ test->timeout_id = 0; } + if (test->result == TEST_RESULT_FAILED) + result = TEST_RESULT_FAILED; + test->result = result; switch (result) { case TEST_RESULT_PASSED: @@ -913,6 +917,13 @@ g_assert_cmpint(len, ==, iov->iov_len); + if (!test->iovcnt && test->io_complete_func) { + test->io_complete_func(test->test_data); + } else if (test->iovcnt && !test->iov->iov_base) { + test_get_iov(test); + return test_io_send(io, user_data); + } + return false; } @@ -937,10 +948,15 @@ g_assert_cmpint(len, ==, iov->iov_len); + if (memcmp(buf, iov->iov_base, len)) + tester_monitor('!', 0x0004, 0x0000, iov->iov_base, len); + g_assert(memcmp(buf, iov->iov_base, len) == 0); if (test->iovcnt) io_set_write_handler(io, test_io_send, NULL, NULL); + else if (test->io_complete_func) + test->io_complete_func(test->test_data); return true; } @@ -1004,6 +1020,13 @@ io_set_write_handler(ios[1], test_io_send, NULL, NULL); } +void tester_io_set_complete_func(tester_data_func_t func) +{ + struct test_case *test = tester_get_test(); + + test->io_complete_func = func; +} + int tester_run(void) { int ret; diff -Nru bluez-5.66/src/shared/tester.h bluez-5.68/src/shared/tester.h --- bluez-5.66/src/shared/tester.h 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/tester.h 2023-06-30 08:10:20.000000000 +0000 @@ -21,6 +21,8 @@ .iov_len = sizeof(data(args)), \ } +#define IOV_NULL {} + void tester_init(int *argc, char ***argv); int tester_run(void); @@ -78,3 +80,4 @@ struct io *tester_setup_io(const struct iovec *iov, int iovcnt); void tester_io_send(void); +void tester_io_set_complete_func(tester_data_func_t func); diff -Nru bluez-5.66/src/shared/util.c bluez-5.68/src/shared/util.c --- bluez-5.66/src/shared/util.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/util.c 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. + * Copyright 2023 NXP * * */ @@ -28,6 +29,13 @@ #include #endif +#include + +/* define MAX_INPUT for musl */ +#ifndef MAX_INPUT +#define MAX_INPUT _POSIX_MAX_INPUT +#endif + #include "src/shared/util.h" void *util_malloc(size_t size) @@ -189,6 +197,335 @@ *bitmap &= ~(((uint64_t)1) << (id - 1)); } +struct iovec *util_iov_dup(const struct iovec *iov, size_t cnt) +{ + struct iovec *dup; + size_t i; + + if (!iov) + return NULL; + + dup = new0(struct iovec, cnt); + + for (i = 0; i < cnt; i++) + util_iov_memcpy(&dup[i], iov[i].iov_base, iov[i].iov_len); + + return dup; +} + +int util_iov_memcmp(const struct iovec *iov1, const struct iovec *iov2) +{ + if (!iov1) + return 1; + + if (!iov2) + return -1; + + if (iov1->iov_len != iov2->iov_len) + return iov1->iov_len - iov2->iov_len; + + return memcmp(iov1->iov_base, iov2->iov_base, iov1->iov_len); +} + +void util_iov_memcpy(struct iovec *iov, void *src, size_t len) +{ + if (!iov || !src || !len) + return; + + iov->iov_base = realloc(iov->iov_base, len); + iov->iov_len = len; + memcpy(iov->iov_base, src, len); +} + +void util_iov_free(struct iovec *iov, size_t cnt) +{ + size_t i; + + if (!iov) + return; + + for (i = 0; i < cnt; i++) + free(iov[i].iov_base); + + free(iov); +} + +void *util_iov_push(struct iovec *iov, size_t len) +{ + void *data; + + if (!iov) + return NULL; + + data = iov->iov_base + iov->iov_len; + iov->iov_len += len; + + return data; +} + +void *util_iov_push_mem(struct iovec *iov, size_t len, const void *data) +{ + void *p; + + p = util_iov_push(iov, len); + if (!p) + return NULL; + + if (data) + memcpy(p, data, len); + + return p; +} + +void *util_iov_push_le64(struct iovec *iov, uint64_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(val)); + if (!p) + return NULL; + + put_le64(val, p); + + return p; +} + +void *util_iov_push_be64(struct iovec *iov, uint64_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(val)); + if (!p) + return NULL; + + put_be64(val, p); + + return p; +} + +void *util_iov_push_le32(struct iovec *iov, uint32_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(val)); + if (!p) + return NULL; + + put_le32(val, p); + + return p; +} + +void *util_iov_push_be32(struct iovec *iov, uint32_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(val)); + if (!p) + return NULL; + + put_be32(val, p); + + return p; +} + +void *util_iov_push_le24(struct iovec *iov, uint32_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(uint24_t)); + if (!p) + return NULL; + + put_le24(val, p); + + return p; +} + +void *util_iov_push_be24(struct iovec *iov, uint32_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(uint24_t)); + if (!p) + return NULL; + + put_le24(val, p); + + return p; +} + +void *util_iov_push_le16(struct iovec *iov, uint16_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(val)); + if (!p) + return NULL; + + put_le16(val, p); + + return p; +} + +void *util_iov_push_be16(struct iovec *iov, uint16_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(val)); + if (!p) + return NULL; + + put_be16(val, p); + + return p; +} + +void *util_iov_push_u8(struct iovec *iov, uint8_t val) +{ + void *p; + + p = util_iov_push(iov, sizeof(val)); + if (!p) + return NULL; + + put_u8(val, p); + + return p; +} + +void *util_iov_pull(struct iovec *iov, size_t len) +{ + if (!iov) + return NULL; + + if (iov->iov_len < len) + return NULL; + + iov->iov_base += len; + iov->iov_len -= len; + + return iov->iov_base; +} + +void *util_iov_pull_mem(struct iovec *iov, size_t len) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, len)) + return data; + + return NULL; +} + +void *util_iov_pull_le64(struct iovec *iov, uint64_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(*val))) { + *val = get_le64(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_be64(struct iovec *iov, uint64_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(*val))) { + *val = get_be64(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_le32(struct iovec *iov, uint32_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(*val))) { + *val = get_le32(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_be32(struct iovec *iov, uint32_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(*val))) { + *val = get_be32(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_le24(struct iovec *iov, uint32_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(uint24_t))) { + *val = get_le24(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_be24(struct iovec *iov, uint32_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(uint24_t))) { + *val = get_be24(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_le16(struct iovec *iov, uint16_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(*val))) { + *val = get_le16(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_be16(struct iovec *iov, uint16_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(*val))) { + *val = get_be16(data); + return data; + } + + return NULL; +} + +void *util_iov_pull_u8(struct iovec *iov, uint8_t *val) +{ + void *data = iov->iov_base; + + if (util_iov_pull(iov, sizeof(*val))) { + *val = get_u8(data); + return data; + } + + return NULL; +} + static const struct { uint16_t uuid; const char *str; @@ -344,7 +681,11 @@ { 0x1850, "Published Audio Capabilities" }, { 0x1851, "Basic Audio Announcement" }, { 0x1852, "Broadcast Audio Announcement" }, - /* 0x1853 to 0x27ff undefined */ + { 0x1853, "Common Audio" }, + { 0x1854, "Hearing Aid" }, + { 0x1855, "Telephony and Media Audio" }, + { 0x1856, "Public Broadcast Announcement" }, + /* 0x1857 to 0x27ff undefined */ { 0x2800, "Primary Service" }, { 0x2801, "Secondary Service" }, { 0x2802, "Include" }, @@ -578,6 +919,7 @@ { 0x2b29, "Client Supported Features" }, { 0x2b2A, "Database Hash" }, { 0x2b3a, "Server Supported Features" }, + { 0x2b51, "Telephony and Media Audio Profile Role" }, { 0x2b77, "Audio Input State" }, { 0x2b78, "Gain Settings Attribute" }, { 0x2b79, "Audio Input Type" }, @@ -592,6 +934,9 @@ { 0x2b82, "Volume Offset Control Point" }, { 0x2b83, "Audio Output Description" }, { 0x2b84, "Set Identity Resolving Key" }, + { 0x2b85, "Coordinated Set Size" }, + { 0x2b86, "Set Member Lock" }, + { 0x2b87, "Set Member Rank" }, { 0x2b93, "Media Player Name" }, { 0x2b94, "Media Player Icon Object ID" }, { 0x2b95, "Media Player Icon URL" }, @@ -645,6 +990,9 @@ { 0x2bcc, "Source Audio Locations" }, { 0x2bcd, "Available Audio Contexts" }, { 0x2bce, "Supported Audio Contexts" }, + { 0x2bda, "Hearing Aid Features" }, + { 0x2bdb, "Hearing Aid Preset Control Point" }, + { 0x2bdc, "Active Preset Index" }, /* vendor defined */ { 0xfeff, "GN Netcom" }, { 0xfefe, "GN ReSound A/S" }, diff -Nru bluez-5.66/src/shared/util.h bluez-5.68/src/shared/util.h --- bluez-5.66/src/shared/util.h 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/src/shared/util.h 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. + * Copyright 2023 NXP * * */ @@ -15,6 +16,7 @@ #include #include #include +#include #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define BIT(n) (1 << (n)) @@ -109,6 +111,33 @@ uint8_t util_get_uid(uint64_t *bitmap, uint8_t max); void util_clear_uid(uint64_t *bitmap, uint8_t id); +struct iovec *util_iov_dup(const struct iovec *iov, size_t cnt); +int util_iov_memcmp(const struct iovec *iov1, const struct iovec *iov2); +void util_iov_memcpy(struct iovec *iov, void *src, size_t len); +void *util_iov_push(struct iovec *iov, size_t len); +void *util_iov_push_mem(struct iovec *iov, size_t len, const void *data); +void *util_iov_push_le64(struct iovec *iov, uint64_t val); +void *util_iov_push_be64(struct iovec *iov, uint64_t val); +void *util_iov_push_le32(struct iovec *iov, uint32_t val); +void *util_iov_push_be32(struct iovec *iov, uint32_t val); +void *util_iov_push_le24(struct iovec *iov, uint32_t val); +void *util_iov_push_be24(struct iovec *iov, uint32_t val); +void *util_iov_push_le16(struct iovec *iov, uint16_t val); +void *util_iov_push_be16(struct iovec *iov, uint16_t val); +void *util_iov_push_u8(struct iovec *iov, uint8_t val); +void *util_iov_pull(struct iovec *iov, size_t len); +void *util_iov_pull_mem(struct iovec *iov, size_t len); +void *util_iov_pull_le64(struct iovec *iov, uint64_t *val); +void *util_iov_pull_be64(struct iovec *iov, uint64_t *val); +void *util_iov_pull_le32(struct iovec *iov, uint32_t *val); +void *util_iov_pull_be32(struct iovec *iov, uint32_t *val); +void *util_iov_pull_le24(struct iovec *iov, uint32_t *val); +void *util_iov_pull_be24(struct iovec *iov, uint32_t *val); +void *util_iov_pull_le16(struct iovec *iov, uint16_t *val); +void *util_iov_pull_be16(struct iovec *iov, uint16_t *val); +void *util_iov_pull_u8(struct iovec *iov, uint8_t *val); +void util_iov_free(struct iovec *iov, size_t cnt); + const char *bt_uuid16_to_str(uint16_t uuid); const char *bt_uuid32_to_str(uint32_t uuid); const char *bt_uuid128_to_str(const uint8_t uuid[16]); @@ -169,6 +198,11 @@ return be64_to_cpu(get_unaligned((const uint64_t *) ptr)); } +static inline void put_u8(uint8_t val, void *dst) +{ + put_unaligned(val, (uint8_t *) dst); +} + static inline void put_le16(uint16_t val, void *dst) { put_unaligned(cpu_to_le16(val), (uint16_t *) dst); diff -Nru bluez-5.66/src/shared/vcp.c bluez-5.68/src/shared/vcp.c --- bluez-5.66/src/shared/vcp.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/src/shared/vcp.c 2023-06-30 08:10:20.000000000 +0000 @@ -32,13 +32,48 @@ #define VCP_STEP_SIZE 1 +#define VOCS_VOL_OFFSET_UPPER_LIMIT 255 +#define VOCS_VOL_OFFSET_LOWER_LIMIT -255 + /* Apllication Error Code */ #define BT_ATT_ERROR_INVALID_CHANGE_COUNTER 0x80 #define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED 0x81 +#define BT_ATT_ERROR_VALUE_OUT_OF_RANGE 0x82 + +#define BT_VCP_NA BIT(0) +#define BT_VCP_FRONT_LEFT BIT(1) +#define BT_VCP_FRONT_RIGHT BIT(2) +#define BT_VCP_FRONT_CENTER BIT(3) +#define BT_VCP_LOW_FRQ_EFF_1 BIT(4) +#define BT_VCP_BACK_LEFT BIT(5) +#define BT_VCP_BACK_RIGHT BIT(6) +#define BT_VCP_FRONT_LEFT_CENTER BIT(7) +#define BT_VCP_FRONT_RIGHT_CENTER BIT(8) +#define BT_VCP_BACK_CENTER BIT(9) +#define BT_VCP_LOW_FRQ_EFF_2 BIT(10) +#define BT_VCP_SIDE_LEFT BIT(11) +#define BT_VCP_SIDE_RIGHT BIT(12) +#define BT_VCP_TOP_FRONT_LEFT BIT(13) +#define BT_VCP_TOP_FRONT_RIGHT BIT(14) +#define BT_VCP_TOP_FRONT_CENTER BIT(15) +#define BT_VCP_TOP_CENTER BIT(16) +#define BT_VCP_TOP_BACK_LEFT BIT(17) +#define BT_VCP_TOP_BACK_RIGHT BIT(18) +#define BT_VCP_TOP_SIDE_LEFT BIT(19) +#define BT_VCP_TOP_SIDE_RIGHT BIT(20) +#define BT_VCP_TOP_BACK_CENTER BIT(21) +#define BT_VCP_BOTTOM_FRONT_CENTER BIT(22) +#define BT_VCP_BOTTOM_FRONT_LEFT BIT(23) +#define BT_VCP_BOTTOM_FRONT_RIGHT BIT(24) +#define BT_VCP_FRONT_LEFT_WIDE BIT(25) +#define BT_VCP_FRONT_RIGHT_WIDE BIT(26) +#define BT_VCP_LEFT_SURROUND BIT(27) +#define BT_VCP_RIGHT_SURROUND BIT(28) struct bt_vcp_db { struct gatt_db *db; struct bt_vcs *vcs; + struct bt_vocs *vocs; }; typedef void (*vcp_func_t)(struct bt_vcp *vcp, bool success, uint8_t att_ecode, @@ -57,11 +92,21 @@ uint8_t change_counter; } __packed; +struct bt_vocs_param { + uint8_t op; + uint8_t change_counter; +} __packed; + struct bt_vcs_ab_vol { uint8_t change_counter; uint8_t vol_set; } __packed; +struct bt_vocs_set_vol_off { + uint8_t change_counter; + int16_t set_vol_offset; +} __packed; + struct bt_vcp_cb { unsigned int id; bt_vcp_func_t attached; @@ -89,6 +134,10 @@ unsigned int vstate_id; unsigned int vflag_id; + unsigned int state_id; + unsigned int audio_loc_id; + unsigned int ao_dec_id; + struct queue *notify; struct queue *pending; @@ -120,6 +169,27 @@ struct gatt_db_attribute *vf_ccc; }; +/* Contains local bt_vcp_db */ +struct vol_offset_state { + int16_t vol_offset; + uint8_t counter; +} __packed; + +struct bt_vocs { + struct bt_vcp_db *vdb; + struct vol_offset_state *vostate; + uint32_t vocs_audio_loc; + char *vocs_ao_dec; + struct gatt_db_attribute *service; + struct gatt_db_attribute *vos; + struct gatt_db_attribute *vos_ccc; + struct gatt_db_attribute *voal; + struct gatt_db_attribute *voal_ccc; + struct gatt_db_attribute *vo_cp; + struct gatt_db_attribute *voaodec; + struct gatt_db_attribute *voaodec_ccc; +}; + static struct queue *vcp_db; static struct queue *vcp_cbs; static struct queue *sessions; @@ -159,6 +229,17 @@ return NULL; } +static struct vol_offset_state *vdb_get_vostate(struct bt_vcp_db *vdb) +{ + if (!vdb->vocs) + return NULL; + + if (vdb->vocs->vostate) + return vdb->vocs->vostate; + + return NULL; +} + static struct bt_vcs *vcp_get_vcs(struct bt_vcp *vcp) { if (!vcp) @@ -173,6 +254,20 @@ return vcp->rdb->vcs; } +static struct bt_vocs *vcp_get_vocs(struct bt_vcp *vcp) +{ + if (!vcp) + return NULL; + + if (vcp->rdb->vocs) + return vcp->rdb->vocs; + + vcp->rdb->vocs = new0(struct bt_vocs, 1); + vcp->rdb->vocs->vdb = vcp->rdb; + + return vcp->rdb->vocs; +} + static void vcp_detached(void *data, void *user_data) { struct bt_vcp_cb *cb = data; @@ -202,6 +297,7 @@ gatt_db_unref(vdb->db); free(vdb->vcs); + free(vdb->vocs); free(vdb); } @@ -583,6 +679,50 @@ return 0; } +static uint8_t vocs_set_vol_offset(struct bt_vocs *vocs, struct bt_vcp *vcp, + struct iovec *iov) +{ + struct bt_vcp_db *vdb; + struct vol_offset_state *vstate; + struct bt_vocs_set_vol_off *req; + + DBG(vcp, "Set Volume Offset"); + + vdb = vcp_get_vdb(vcp); + if (!vdb) { + DBG(vcp, "error: VDB not available"); + return 0; + } + + vstate = vdb_get_vostate(vdb); + if (!vstate) { + DBG(vcp, "error: VSTATE not available"); + return 0; + } + + req = iov_pull_mem(iov, sizeof(*req)); + if (!req) + return 0; + + if (req->change_counter != vstate->counter) { + DBG(vcp, "Change Counter Mismatch Volume not decremented!"); + return BT_ATT_ERROR_INVALID_CHANGE_COUNTER; + } + + if (req->set_vol_offset > VOCS_VOL_OFFSET_UPPER_LIMIT || + req->set_vol_offset < VOCS_VOL_OFFSET_LOWER_LIMIT) { + DBG(vcp, "error: Value Out of Range"); + return BT_ATT_ERROR_VALUE_OUT_OF_RANGE; + } + vstate->vol_offset = req->set_vol_offset; + vstate->counter = -~vstate->counter; /*Increment Change Counter*/ + + gatt_db_attribute_notify(vdb->vocs->vos, (void *)vstate, + sizeof(struct vol_offset_state), + bt_vcp_get_att(vcp)); + return 0; +} + #define BT_VCS_REL_VOL_DOWN 0x00 #define BT_VCS_REL_VOL_UP 0x01 #define BT_VCS_UNMUTE_REL_VOL_DOWN 0x02 @@ -591,6 +731,8 @@ #define BT_VCS_UNMUTE 0x05 #define BT_VCS_MUTE 0x06 +#define BT_VOCS_SET_VOL_OFFSET 0x01 + #define VCS_OP(_str, _op, _size, _func) \ { \ .str = _str, \ @@ -623,6 +765,26 @@ {} }; +#define VOCS_OP(_str, _op, _size, _func) \ + { \ + .str = _str, \ + .op = _op, \ + .size = _size, \ + .func = _func, \ + } + +struct vocs_op_handler { + const char *str; + uint8_t op; + size_t size; + uint8_t (*func)(struct bt_vocs *vocs, struct bt_vcp *vcp, + struct iovec *iov); +} vocp_handlers[] = { + VOCS_OP("Set Volume Offset", BT_VOCS_SET_VOL_OFFSET, + sizeof(uint8_t), vocs_set_vol_offset), + {} +}; + static void vcs_cp_write(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, const uint8_t *value, size_t len, @@ -683,6 +845,66 @@ gatt_db_attribute_write_result(attrib, id, ret); } +static void vocs_cp_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_vocs *vocs = user_data; + struct bt_vcp *vcp = vcp_get_session(att, vocs->vdb->db); + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = len, + }; + uint8_t *vcp_op; + struct vocs_op_handler *handler; + uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + + DBG(vcp, "VOCP Control Point Write"); + + if (offset) { + DBG(vcp, "invalid offset %d", offset); + ret = BT_ATT_ERROR_INVALID_OFFSET; + goto respond; + } + + if (len < sizeof(*vcp_op)) { + DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len, + sizeof(*vcp_op)); + ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto respond; + } + + vcp_op = iov_pull_mem(&iov, sizeof(*vcp_op)); + + for (handler = vocp_handlers; handler && handler->str; handler++) { + if (handler->op != *vcp_op) + continue; + + if (iov.iov_len < handler->size) { + DBG(vcp, "invalid len %ld < %ld handler->size", len, + handler->size); + ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; + goto respond; + } + + break; + } + + if (handler && handler->str) { + DBG(vcp, "%s", handler->str); + + ret = handler->func(vocs, vcp, &iov); + } else { + DBG(vcp, "Unknown opcode 0x%02x", *vcp_op); + ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED; + } + +respond: + gatt_db_attribute_write_result(attrib, id, ret); +} + static void vcs_state_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, @@ -698,6 +920,21 @@ iov.iov_len); } +static void vocs_state_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_vocs *vocs = user_data; + struct iovec iov; + + iov.iov_base = vocs->vostate; + iov.iov_len = sizeof(*vocs->vostate); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + static void vcs_flag_read(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, @@ -713,7 +950,37 @@ iov.iov_len); } -static struct bt_vcs *vcs_new(struct gatt_db *db) +static void vocs_voal_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_vocs *vocs = user_data; + struct iovec iov; + + iov.iov_base = &vocs->vocs_audio_loc; + iov.iov_len = sizeof(vocs->vocs_audio_loc); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static void vocs_voaodec_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct bt_vocs *vocs = user_data; + struct iovec iov; + + iov.iov_base = &vocs->vocs_ao_dec; + iov.iov_len = strlen(vocs->vocs_ao_dec); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +} + +static struct bt_vcs *vcs_new(struct gatt_db *db, struct bt_vcp_db *vdb) { struct bt_vcs *vcs; struct vol_state *vstate; @@ -732,6 +999,8 @@ /* Populate DB with VCS attributes */ bt_uuid16_create(&uuid, VCS_UUID); vcs->service = gatt_db_add_service(db, &uuid, true, 9); + gatt_db_service_add_included(vcs->service, vdb->vocs->service); + gatt_db_service_set_active(vdb->vocs->service, true); bt_uuid16_create(&uuid, VOL_STATE_CHRC_UUID); vcs->vs = gatt_db_service_add_characteristic(vcs->service, @@ -771,6 +1040,75 @@ return vcs; } +static struct bt_vocs *vocs_new(struct gatt_db *db) +{ + struct bt_vocs *vocs; + struct vol_offset_state *vostate; + bt_uuid_t uuid; + + if (!db) + return NULL; + + vocs = new0(struct bt_vocs, 1); + + vostate = new0(struct vol_offset_state, 1); + + vocs->vostate = vostate; + vocs->vocs_audio_loc = BT_VCP_FRONT_LEFT; + vocs->vocs_ao_dec = "Left Speaker"; + + /* Populate DB with VOCS attributes */ + bt_uuid16_create(&uuid, VOL_OFFSET_CS_UUID); + + vocs->service = gatt_db_add_service(db, &uuid, false, 12); + + bt_uuid16_create(&uuid, VOCS_STATE_CHAR_UUID); + vocs->vos = gatt_db_service_add_characteristic(vocs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + vocs_state_read, NULL, + vocs); + + vocs->vos_ccc = gatt_db_service_add_ccc(vocs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, VOCS_AUDIO_LOC_CHRC_UUID); + vocs->voal = gatt_db_service_add_characteristic(vocs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + vocs_voal_read, NULL, + vocs); + + vocs->voal_ccc = gatt_db_service_add_ccc(vocs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, VOCS_CP_CHRC_UUID); + vocs->vo_cp = gatt_db_service_add_characteristic(vocs->service, + &uuid, + BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE, + NULL, vocs_cp_write, + vocs); + + bt_uuid16_create(&uuid, VOCS_AUDIO_OP_DESC_CHAR_UUID); + vocs->voaodec = gatt_db_service_add_characteristic(vocs->service, + &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + vocs_voaodec_read, NULL, + vocs); + + vocs->voaodec_ccc = gatt_db_service_add_ccc(vocs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + return vocs; +} + static struct bt_vcp_db *vcp_db_new(struct gatt_db *db) { struct bt_vcp_db *vdb; @@ -784,7 +1122,9 @@ if (!vcp_db) vcp_db = queue_new(); - vdb->vcs = vcs_new(db); + vdb->vocs = vocs_new(db); + vdb->vocs->vdb = vdb; + vdb->vcs = vcs_new(db, vdb); vdb->vcs->vdb = vdb; queue_push_tail(vcp_db, vdb); @@ -911,6 +1251,47 @@ DBG(vcp, "Vol Counter 0x%x", vstate.counter); } +static void vcp_voffset_state_notify(struct bt_vcp *vcp, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct vol_offset_state vostate; + + memcpy(&vostate, value, sizeof(struct vol_offset_state)); + + DBG(vcp, "Vol Offset 0x%x", vostate.vol_offset); + DBG(vcp, "Vol Offset Counter 0x%x", vostate.counter); +} + +static void vcp_audio_loc_notify(struct bt_vcp *vcp, uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data) +{ + uint32_t *vocs_audio_loc_n = malloc(sizeof(uint32_t)); + *vocs_audio_loc_n = 0; + + if (value != NULL) + memcpy(vocs_audio_loc_n, value, sizeof(uint32_t)); + + DBG(vcp, "VOCS Audio Location 0x%x", *vocs_audio_loc_n); + + free(vocs_audio_loc_n); +} + + +static void vcp_audio_descriptor_notify(struct bt_vcp *vcp, + uint16_t value_handle, + const uint8_t *value, + uint16_t length, + void *user_data) +{ + char vocs_audio_dec_n[256] = {'\0'}; + + memcpy(vocs_audio_dec_n, value, length); + + DBG(vcp, "VOCS Audio Descriptor 0x%s", *vocs_audio_dec_n); +} + static void vcp_vflag_notify(struct bt_vcp *vcp, uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) @@ -972,6 +1353,86 @@ DBG(vcp, "Vol Counter:%x", vs->counter); } +static void read_vol_offset_state(struct bt_vcp *vcp, bool success, + uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct vol_offset_state *vos; + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = length, + }; + + if (!success) { + DBG(vcp, "Unable to read Vol Offset State: error 0x%02x", + att_ecode); + return; + } + + vos = iov_pull_mem(&iov, sizeof(*vos)); + if (!vos) { + DBG(vcp, "Unable to get Vol Offset State"); + return; + } + + DBG(vcp, "Vol Set:%x", vos->vol_offset); + DBG(vcp, "Vol Counter:%x", vos->counter); +} + +static void read_vocs_audio_location(struct bt_vcp *vcp, bool success, + uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + uint32_t *vocs_audio_loc; + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = length, + }; + + if (!success) { + DBG(vcp, "Unable to read VOCS Audio Location: error 0x%02x", + att_ecode); + return; + } + + vocs_audio_loc = iov_pull_mem(&iov, sizeof(uint32_t)); + if (!*vocs_audio_loc) { + DBG(vcp, "Unable to get VOCS Audio Location"); + return; + } + + DBG(vcp, "VOCS Audio Loc:%x", *vocs_audio_loc); +} + + +static void read_vocs_audio_descriptor(struct bt_vcp *vcp, bool success, + uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + char *vocs_ao_dec_r; + struct iovec iov = { + .iov_base = (void *) value, + .iov_len = length, + }; + + if (!success) { + DBG(vcp, "Unable to read VOCS Audio Descriptor: error 0x%02x", + att_ecode); + return; + } + + vocs_ao_dec_r = iov_pull_mem(&iov, length); + if (!*vocs_ao_dec_r) { + DBG(vcp, "Unable to get VOCS Audio Descriptor"); + return; + } + + DBG(vcp, "VOCS Audio Descriptor:%s", *vocs_ao_dec_r); +} + static void vcp_pending_destroy(void *data) { struct bt_vcp_pending *pending = data; @@ -1128,6 +1589,90 @@ } } +static void foreach_vocs_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_vcp *vcp = user_data; + uint16_t value_handle; + bt_uuid_t uuid, uuid_vostate, uuid_audio_loc, uuid_vo_cp, + uuid_audio_op_decs; + struct bt_vocs *vocs; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, + NULL, NULL, &uuid)) + return; + + bt_uuid16_create(&uuid_vostate, VOCS_STATE_CHAR_UUID); + bt_uuid16_create(&uuid_audio_loc, VOCS_AUDIO_LOC_CHRC_UUID); + bt_uuid16_create(&uuid_vo_cp, VOCS_CP_CHRC_UUID); + bt_uuid16_create(&uuid_audio_op_decs, VOCS_AUDIO_OP_DESC_CHAR_UUID); + + if (!bt_uuid_cmp(&uuid, &uuid_vostate)) { + DBG(vcp, "VOCS Vol state found: handle 0x%04x", value_handle); + + vocs = vcp_get_vocs(vcp); + if (!vocs || vocs->vos) + return; + + vocs->vos = attr; + + vcp_read_value(vcp, value_handle, read_vol_offset_state, vcp); + + vcp->state_id = vcp_register_notify(vcp, value_handle, + vcp_voffset_state_notify, NULL); + + return; + } + + if (!bt_uuid_cmp(&uuid, &uuid_audio_loc)) { + DBG(vcp, "VOCS Volume Audio Location found: handle 0x%04x", + value_handle); + + vocs = vcp_get_vocs(vcp); + if (!vocs || vocs->voal) + return; + + vocs->voal = attr; + + vcp_read_value(vcp, value_handle, read_vocs_audio_location, + vcp); + + vcp->audio_loc_id = vcp_register_notify(vcp, value_handle, + vcp_audio_loc_notify, NULL); + + return; + } + + if (!bt_uuid_cmp(&uuid, &uuid_vo_cp)) { + DBG(vcp, "VOCS Volume CP found: handle 0x%04x", value_handle); + + vocs = vcp_get_vocs(vcp); + if (!vocs || vocs->vo_cp) + return; + + vocs->vo_cp = attr; + + return; + } + + if (!bt_uuid_cmp(&uuid, &uuid_audio_op_decs)) { + DBG(vcp, "VOCS Vol Audio Descriptor found: handle 0x%04x", + value_handle); + + vocs = vcp_get_vocs(vcp); + if (!vocs || vocs->voaodec) + return; + + vocs->voaodec = attr; + + vcp_read_value(vcp, value_handle, read_vocs_audio_descriptor, + vcp); + vcp->ao_dec_id = vcp_register_notify(vcp, value_handle, + vcp_audio_descriptor_notify, NULL); + + } + +} + static void foreach_vcs_service(struct gatt_db_attribute *attr, void *user_data) { @@ -1141,6 +1686,19 @@ gatt_db_service_foreach_char(attr, foreach_vcs_char, vcp); } +static void foreach_vocs_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_vcp *vcp = user_data; + struct bt_vocs *vocs = vcp_get_vocs(vcp); + + vocs->service = attr; + + gatt_db_service_set_claimed(attr, true); + + gatt_db_service_foreach_char(attr, foreach_vocs_char, vcp); +} + bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client) { bt_uuid_t uuid; @@ -1163,6 +1721,9 @@ bt_uuid16_create(&uuid, VCS_UUID); gatt_db_foreach_service(vcp->ldb->db, &uuid, foreach_vcs_service, vcp); + bt_uuid16_create(&uuid, VOL_OFFSET_CS_UUID); + gatt_db_foreach_service(vcp->ldb->db, &uuid, foreach_vocs_service, vcp); + return true; } diff -Nru bluez-5.66/tools/advtest.c bluez-5.68/tools/advtest.c --- bluez-5.66/tools/advtest.c 2021-02-22 20:26:59.000000000 +0000 +++ bluez-5.68/tools/advtest.c 2023-06-30 08:10:20.000000000 +0000 @@ -13,6 +13,13 @@ #include #endif +#include + +#include +#include +#include +#include + #include #include "lib/bluetooth.h" @@ -32,6 +39,9 @@ "\xe1\x23\x99\xc1\xca\x9a\xc3\x31" #define SCAN_IRK "\xfa\x73\x09\x11\x3f\x03\x37\x0f" \ "\xf4\xf9\x93\x1e\xf9\xa3\x63\xa6" +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif static struct mgmt *mgmt; static uint16_t index1 = MGMT_INDEX_NONE; @@ -43,13 +53,73 @@ static void print_rpa(const uint8_t addr[6]) { - printf(" Address: %02x:%02x:%02x:%02x:%02x:%02x\n", + printf(" RSI:\t0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); printf(" Random: %02x%02x%02x\n", addr[3], addr[4], addr[5]); printf(" Hash: %02x%02x%02x\n", addr[0], addr[1], addr[2]); } +static size_t hex2bin(const char *hexstr, uint8_t *buf, size_t buflen) +{ + size_t i, len; + + len = MIN((strlen(hexstr) / 2), buflen); + memset(buf, 0, len); + + for (i = 0; i < len; i++) + if (sscanf(hexstr + (i * 2), "%02hhX", &buf[i]) != 1) + continue; + + + return len; +} + +static bool get_random_bytes(void *buf, size_t num_bytes) +{ + ssize_t len; + int fd; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return false; + + len = read(fd, buf, num_bytes); + + close(fd); + + if (len < 0) + return false; + + return true; +} + +static void generate_rsi(char *val) +{ + uint8_t sirk[16], hash[3]; + uint8_t rsi[6] = {0}; + + hex2bin(val, sirk, sizeof(sirk)); + + get_random_bytes(&rsi[3], 3); + + rsi[5] &= 0x3f; /* Clear 2 msb */ + rsi[5] |= 0x40; /* Set 2nd msb */ + + crypto = bt_crypto_new(); + if (!crypto) { + fprintf(stderr, "Failed to open crypto interface\n"); + mainloop_exit_failure(); + return; + } + + bt_crypto_ah(crypto, sirk, rsi + 3, hash); + memcpy(rsi, hash, 3); + + print_rpa(rsi); +} + + static void scan_le_adv_report(const void *data, uint8_t size, void *user_data) { @@ -351,9 +421,11 @@ printf("\tadvtest [options]\n"); printf("options:\n" "\t-h, --help Show help options\n"); + printf(" \t-i <128bit SIRK>, Generate RSI ADV Data\n"); } static const struct option main_options[] = { + { "hash", no_argument, NULL, 'i' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { } @@ -366,11 +438,15 @@ for (;;) { int opt; - opt = getopt_long(argc, argv, "vh", main_options, NULL); + opt = getopt_long(argc, argv, "i:vh", main_options, NULL); if (opt < 0) break; switch (opt) { + case 'i': + printf("SIRK: %s\n", optarg); + generate_rsi(optarg); + return EXIT_SUCCESS; case 'v': printf("%s\n", VERSION); return EXIT_SUCCESS; diff -Nru bluez-5.66/tools/bdaddr.1 bluez-5.68/tools/bdaddr.1 --- bluez-5.66/tools/bdaddr.1 2022-11-10 20:41:45.000000000 +0000 +++ bluez-5.68/tools/bdaddr.1 2023-06-30 22:21:22.000000000 +0000 @@ -41,7 +41,7 @@ .sp \fBbdaddr(1)\fP is used to query or set the local Bluetooth device address (BD_ADDR). If run with no arguments, \fBbdaddr\fP prints the chip manufacturer\(aqs -name, and the current BD_ADDR. If the IEEE OUI index file "oui.txt" is +name, and the current BD_ADDR. If the IEEE OUI index file \(dqoui.txt\(dq is installed on the system, the BD_ADDR owner will be displayed. If the optional [\fInew_bdaddr\fP] argument is given, the device will be reprogrammed with that address. This can either be permanent or temporary, as specified by the \-t diff -Nru bluez-5.66/tools/btiotest.c bluez-5.68/tools/btiotest.c --- bluez-5.66/tools/btiotest.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/tools/btiotest.c 2023-06-30 08:10:20.000000000 +0000 @@ -5,6 +5,7 @@ * * Copyright (C) 2009-2010 Marcel Holtmann * Copyright (C) 2009-2010 Nokia Corporation + * Copyright 2023 NXP * * */ @@ -39,13 +40,15 @@ } struct bt_iso_qos qos = { - .cig = BT_ISO_QOS_CIG_UNSET, - .cis = BT_ISO_QOS_CIG_UNSET, - .sca = 0x07, - .packing = 0x00, - .framing = 0x00, - .in = DEFAULT_IO_QOS, - .out = DEFAULT_IO_QOS, + .ucast = { + .cig = BT_ISO_QOS_CIG_UNSET, + .cis = BT_ISO_QOS_CIG_UNSET, + .sca = 0x07, + .packing = 0x00, + .framing = 0x00, + .in = DEFAULT_IO_QOS, + .out = DEFAULT_IO_QOS, + }, }; struct io_data { diff -Nru bluez-5.66/tools/btmgmt.c bluez-5.68/tools/btmgmt.c --- bluez-5.66/tools/btmgmt.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/tools/btmgmt.c 2023-06-30 08:10:20.000000000 +0000 @@ -353,6 +353,8 @@ "static-addr", "phy-configuration", "wide-band-speech", + "cis-central", + "cis-peripheral", }; static const char *settings2str(uint32_t settings) diff -Nru bluez-5.66/tools/btsnoop.c bluez-5.68/tools/btsnoop.c --- bluez-5.66/tools/btsnoop.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/tools/btsnoop.c 2023-06-30 08:10:20.000000000 +0000 @@ -283,7 +283,7 @@ if (len < 0 || len != BTSNOOP_PKT_SIZE) goto close_input; - toread = be32toh(pkt.size); + toread = be32toh(pkt.len); flags = be32toh(pkt.flags); opcode = flags & 0x00ff; @@ -356,7 +356,7 @@ if (len < 0 || len != BTSNOOP_PKT_SIZE) goto close_input; - toread = be32toh(pkt.size); + toread = be32toh(pkt.len); flags = be32toh(pkt.flags); opcode = flags & 0x00ff; @@ -433,7 +433,7 @@ if (len < 0 || len != BTSNOOP_PKT_SIZE) goto close_input; - toread = be32toh(pkt.size); + toread = be32toh(pkt.len); len = read(fd, buf, toread); if (len < 0 || len != (ssize_t) toread) { diff -Nru bluez-5.66/tools/isotest.1 bluez-5.68/tools/isotest.1 --- bluez-5.66/tools/isotest.1 2022-11-10 20:41:35.000000000 +0000 +++ bluez-5.68/tools/isotest.1 2023-06-30 22:21:13.000000000 +0000 @@ -239,6 +239,44 @@ T} _ .TE +.INDENT 0.0 +.TP +.BI \-e\fP,\fB \-\-enc\fB= +Socket QoS BIG Encryption +.UNINDENT +.TS +center; +|l|l|. +_ +T{ +\fIENCRYPTION\fP +T} T{ +Description +T} +_ +T{ +\fB0x00\fP +T} T{ +BIG unencrypted +T} +_ +T{ +\fB0x01\fP +T} T{ +BIG encrypted +T} +_ +.TE +.INDENT 0.0 +.TP +.BI \-k\fP,\fB \-\-bcode\fB= +Socket QoS Broadcast Code +.TP +.BI \-N\fP,\fB \-\-nbis\fB= +Number of BISes to create as part of a +BIG (BIS broadcaster) or to synchronize +to (BIS broadcast receiver) +.UNINDENT .SH EXAMPLES .SS Unicast Central .INDENT 0.0 diff -Nru bluez-5.66/tools/isotest.c bluez-5.68/tools/isotest.c --- bluez-5.66/tools/isotest.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/tools/isotest.c 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. + * Copyright 2023 NXP * */ @@ -30,6 +31,9 @@ #include #include #include +#include +#include +#include #include "lib/bluetooth.h" #include "lib/hci.h" @@ -43,6 +47,9 @@ #define SEC_USEC(_t) (_t * 1000000L) #define TS_USEC(_ts) (SEC_USEC((_ts)->tv_sec) + NSEC_USEC((_ts)->tv_nsec)) +#define DEFAULT_BIG_ID 0x01 +#define DEFAULT_BIS_ID 0x01 + /* Test modes */ enum { SEND, @@ -70,6 +77,8 @@ struct bt_iso_qos *iso_qos; static bool inout; +static uint8_t num_bis = 1; + struct lookup_table { const char *name; int flag; @@ -238,7 +247,7 @@ return err < 0 ? err : 0; } -static void print_qos(int sk, struct sockaddr_iso *addr) +static void print_ucast_qos(int sk) { struct bt_iso_qos qos; socklen_t len; @@ -253,21 +262,60 @@ return; } - if (!bacmp(&addr->iso_bdaddr, BDADDR_ANY)) { - syslog(LOG_INFO, "QoS BIG 0x%02x BIS 0x%02x Packing 0x%02x " - "Framing 0x%02x]", qos.big, qos.bis, qos.packing, - qos.framing); - } else { - syslog(LOG_INFO, "QoS CIG 0x%02x CIS 0x%02x Packing 0x%02x " - "Framing 0x%02x]", qos.cig, qos.cis, qos.packing, - qos.framing); - syslog(LOG_INFO, "Input QoS [Interval %u us Latency %u " - "ms SDU %u PHY 0x%02x RTN %u]", qos.in.interval, - qos.in.latency, qos.in.sdu, qos.in.phy, qos.in.rtn); + syslog(LOG_INFO, "QoS CIG 0x%02x CIS 0x%02x Packing 0x%02x " + "Framing 0x%02x]", qos.ucast.cig, qos.ucast.cis, + qos.ucast.packing, qos.ucast.framing); + + syslog(LOG_INFO, "Input QoS [Interval %u us Latency %u " + "ms SDU %u PHY 0x%02x RTN %u]", qos.ucast.in.interval, + qos.ucast.in.latency, qos.ucast.in.sdu, qos.ucast.in.phy, + qos.ucast.in.rtn); + + syslog(LOG_INFO, "Output QoS [Interval %u us Latency %u " + "ms SDU %u PHY 0x%02x RTN %u]", qos.ucast.out.interval, + qos.ucast.out.latency, qos.ucast.out.sdu, qos.ucast.out.phy, + qos.ucast.out.rtn); +} + +static void print_bcast_qos(int sk) +{ + struct bt_iso_qos qos; + socklen_t len; + + /* Read Out QOS */ + memset(&qos, 0, sizeof(qos)); + len = sizeof(qos); + + if (getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) { + syslog(LOG_ERR, "Can't get QoS socket option: %s (%d)", + strerror(errno), errno); + return; } + + syslog(LOG_INFO, "QoS [BIG 0x%02x BIS 0x%02x Packing 0x%02x " + "Framing 0x%02x Encryption 0x%02x]", qos.bcast.big, + qos.bcast.bis, qos.bcast.packing, qos.bcast.framing, + qos.bcast.encryption); + + if (qos.bcast.encryption == 0x01) + syslog(LOG_INFO, "Broadcast Code 0x%02x 0x%02x 0x%02x 0x%02x " + "0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x " + "0x%02x 0x%02x 0x%02x 0x%02x", qos.bcast.bcode[0], + qos.bcast.bcode[1], qos.bcast.bcode[2], qos.bcast.bcode[3], + qos.bcast.bcode[4], qos.bcast.bcode[5], qos.bcast.bcode[6], + qos.bcast.bcode[7], qos.bcast.bcode[8], qos.bcast.bcode[9], + qos.bcast.bcode[10], qos.bcast.bcode[11], qos.bcast.bcode[12], + qos.bcast.bcode[13], qos.bcast.bcode[14], qos.bcast.bcode[15]); + + syslog(LOG_INFO, "Input QoS [Interval %u us Latency %u " + "ms SDU %u PHY 0x%02x RTN %u]", qos.bcast.in.interval, + qos.bcast.in.latency, qos.bcast.in.sdu, + qos.bcast.in.phy, qos.bcast.in.rtn); + syslog(LOG_INFO, "Output QoS [Interval %u us Latency %u " - "ms SDU %u PHY 0x%02x RTN %u]", qos.out.interval, - qos.out.latency, qos.out.sdu, qos.out.phy, qos.out.rtn); + "ms SDU %u PHY 0x%02x RTN %u]", qos.bcast.out.interval, + qos.bcast.out.latency, qos.bcast.out.sdu, + qos.bcast.out.phy, qos.bcast.out.rtn); } static int do_connect(char *peer) @@ -275,8 +323,6 @@ struct sockaddr_iso addr; int sk; - mgmt_set_experimental(); - /* Create socket */ sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); if (sk < 0) { @@ -301,8 +347,8 @@ /* Set QoS if available */ if (iso_qos) { if (!inout || !strcmp(peer, "00:00:00:00:00:00")) { - iso_qos->in.phy = 0x00; - iso_qos->in.sdu = 0; + iso_qos->ucast.in.phy = 0x00; + iso_qos->ucast.in.sdu = 0; } if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, iso_qos, @@ -337,7 +383,10 @@ syslog(LOG_INFO, "Connected [%s]", peer); - print_qos(sk, &addr); + if (!strcmp(peer, "00:00:00:00:00:00")) + print_bcast_qos(sk); + else + print_ucast_qos(sk); return sk; @@ -346,6 +395,45 @@ return -1; } +static int *bcast_do_connect_mbis(uint8_t count, char *peer) +{ + int *sk; + uint8_t sk_cnt = 0; + + sk = malloc(count * sizeof(*sk)); + if (!sk) { + syslog(LOG_ERR, "Can't allocate socket array"); + return NULL; + } + + defer_setup = 1; + + for (int i = 0; i < count; i++) { + if (i == count - 1) + defer_setup = 0; + + sk[i] = do_connect(peer); + if (sk[i] < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + + goto error; + } + + sk_cnt++; + } + + return sk; + +error: + for (int i = 0; i < sk_cnt; i++) + close(sk[i]); + + free(sk); + return NULL; + +} + static void do_listen(char *filename, void (*handler)(int fd, int sk), char *peer) { @@ -353,6 +441,9 @@ socklen_t optlen; int sk, nsk, fd = -1; char ba[18]; + struct pollfd fds; + int err, sk_err; + socklen_t len; if (filename) { fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644); @@ -387,8 +478,11 @@ if (peer) { str2ba(peer, &addr->iso_bc->bc_bdaddr); addr->iso_bc->bc_bdaddr_type = bdaddr_type; - addr->iso_bc->bc_num_bis = 1; - addr->iso_bc->bc_bis[0] = 1; + addr->iso_bc->bc_num_bis = num_bis; + + for (int i = 0; i < num_bis; i++) + addr->iso_bc->bc_bis[i] = i + 1; + optlen += sizeof(*addr->iso_bc); } @@ -406,6 +500,16 @@ goto error; } + /* Set QoS if available */ + if (iso_qos) { + if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, iso_qos, + sizeof(*iso_qos)) < 0) { + syslog(LOG_ERR, "Can't set QoS socket option: " + "%s (%d)", strerror(errno), errno); + goto error; + } + } + /* Listen for connections */ if (listen(sk, 10)) { syslog(LOG_ERR, "Can not listen on the socket: %s (%d)", @@ -429,6 +533,28 @@ goto error; } + /* Check if connection was successful */ + memset(&fds, 0, sizeof(fds)); + fds.fd = nsk; + fds.events = POLLERR; + + if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLERR)) { + len = sizeof(sk_err); + + if (getsockopt(nsk, SOL_SOCKET, SO_ERROR, + &sk_err, &len) < 0) + err = -errno; + else + err = -sk_err; + + if (err < 0) + syslog(LOG_ERR, "Connection failed: %s (%d)", + strerror(-err), -err); + + close(nsk); + continue; + } + if (fork()) { /* Parent */ close(nsk); @@ -440,7 +566,10 @@ ba2str(&addr->iso_bdaddr, ba); syslog(LOG_INFO, "Connected [%s]", ba); - print_qos(nsk, addr); + if (peer) + print_bcast_qos(nsk); + else + print_ucast_qos(nsk); /* Handle deferred setup */ if (defer_setup) { @@ -527,6 +656,7 @@ strerror(errno), errno); if (errno != ENOTCONN) return; + r = 0; } @@ -584,17 +714,21 @@ } t_diff.tv_sec = t_now.tv_sec - t_start->tv_sec; + if (t_start->tv_nsec > t_now.tv_nsec) { + t_diff.tv_sec--; + t_now.tv_nsec += 1000000000L; + } t_diff.tv_nsec = t_now.tv_nsec - t_start->tv_nsec; delta_us = us - TS_USEC(&t_diff); if (delta_us < 0) { - syslog(LOG_INFO, "Send is behind: %zd us", delta_us); + syslog(LOG_INFO, "Send is behind: %" PRId64 " us", delta_us); delta_us = 1000; } if (!quiet) - syslog(LOG_INFO, "Waiting (%zd us)...", delta_us); + syslog(LOG_INFO, "Waiting (%" PRId64 " us)...", delta_us); usleep(delta_us); @@ -643,12 +777,66 @@ return len; } -static void do_send(int sk, int fd, struct bt_iso_qos *qos, uint32_t num, - bool repeat) +static void do_send(int sk, int fd, char *peer, bool repeat) { uint32_t seq; struct timespec t_start; - int len, used; + int send_len, used; + socklen_t len; + struct bt_iso_qos qos; + uint32_t num; + struct bt_iso_io_qos *out; + + syslog(LOG_INFO, "Sending ..."); + + /* Read QoS */ + if (!strcmp(peer, "00:00:00:00:00:00")) + out = &qos.bcast.out; + else + out = &qos.ucast.out; + + memset(&qos, 0, sizeof(qos)); + len = sizeof(qos); + if (getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) { + syslog(LOG_ERR, "Can't get Output QoS socket option: %s (%d)", + strerror(errno), errno); + out->sdu = ISO_DEFAULT_MTU; + } + + /* num of packets = latency (ms) / interval (us) */ + num = (out->latency * 1000 / out->interval); + + syslog(LOG_INFO, "Number of packets: %d", num); + + if (!sndbuf) + /* Use socket buffer as a jitter buffer for the entire buffer + * latency: + * jitter buffer = 2 * (SDU * subevents) + */ + sndbuf = 2 * ((out->latency * 1000 / out->interval) * + out->sdu); + + len = sizeof(sndbuf); + if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &sndbuf, len) < 0) { + syslog(LOG_ERR, "Can't set socket SO_SNDBUF option: %s (%d)", + strerror(errno), errno); + } + + syslog(LOG_INFO, "Socket jitter buffer: %d buffer", sndbuf); + + if (sndto.tv_usec) { + len = sizeof(sndto); + if (setsockopt(sk, SOL_SOCKET, SO_SNDTIMEO, &sndto, len) < 0) { + syslog(LOG_ERR, "Can't set socket SO_SNDTIMEO option: " + "%s (%d)", strerror(errno), errno); + } else { + syslog(LOG_INFO, "Socket send timeout: %ld usec", + sndto.tv_usec); + } + } + + for (int i = 6; i < out->sdu; i++) + buf[i] = 0x7f; if (clock_gettime(CLOCK_MONOTONIC, &t_start) < 0) { perror("clock_gettime"); @@ -657,17 +845,17 @@ for (seq = 0; ; seq++) { if (fd >= 0) { - len = read_file(fd, qos->out.sdu, repeat); - if (len < 0) { + send_len = read_file(fd, out->sdu, repeat); + if (send_len < 0) { syslog(LOG_ERR, "read failed: %s (%d)", - strerror(-len), -len); + strerror(-send_len), -send_len); exit(1); } } else - len = qos->out.sdu; + send_len = out->sdu; - len = send(sk, buf, len, 0); - if (len <= 0) { + send_len = send(sk, buf, send_len, 0); + if (send_len <= 0) { syslog(LOG_ERR, "send failed: %s (%d)", strerror(errno), errno); exit(1); @@ -678,19 +866,20 @@ if (!quiet) syslog(LOG_INFO, "[seq %d] %d bytes buffered %d (%d bytes)", - seq, len, used / len, used); + seq, send_len, used / send_len, used); if (seq && !((seq + 1) % num)) - send_wait(&t_start, num * qos->out.interval); + send_wait(&t_start, num * out->interval); } } static void send_mode(char *filename, char *peer, int i, bool repeat) { - struct bt_iso_qos qos; - socklen_t len; int sk, fd = -1; - uint32_t num; + int *sk_arr; + uint8_t nconn = strcmp(peer, "00:00:00:00:00:00") ? 1 : num_bis; + + mgmt_set_experimental(); if (filename) { char altername[PATH_MAX]; @@ -707,6 +896,33 @@ fd = open_file(filename); } + if (nconn > 1) { + sk_arr = bcast_do_connect_mbis(nconn, peer); + if (!sk_arr) + exit(1); + + for (int i = 0; i < nconn; i++) { + if (fork()) { + /* Parent */ + continue; + } + + /* Child */ + do_send(sk_arr[i], fd, peer, repeat); + exit(0); + } + + /* Wait for children to exit */ + while (wait(NULL) > 0) + ; + + for (int i = 0; i < nconn; i++) + close(sk_arr[i]); + + free(sk_arr); + return; + } + sk = do_connect(peer); if (sk < 0) { syslog(LOG_ERR, "Can't connect to the server: %s (%d)", @@ -720,57 +936,13 @@ sleep(abs(defer_setup) - 1); } - syslog(LOG_INFO, "Sending ..."); - - /* Read QoS */ - memset(&qos, 0, sizeof(qos)); - len = sizeof(qos); - if (getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) { - syslog(LOG_ERR, "Can't get Output QoS socket option: %s (%d)", - strerror(errno), errno); - qos.out.sdu = ISO_DEFAULT_MTU; - } - - /* num of packets = latency (ms) / interval (us) */ - num = (qos.out.latency * 1000 / qos.out.interval); - - syslog(LOG_INFO, "Number of packets: %d", num); - - if (!sndbuf) - /* Use socket buffer as a jitter buffer for the entire buffer - * latency: - * jitter buffer = 2 * (SDU * subevents) - */ - sndbuf = 2 * ((qos.out.latency * 1000 / qos.out.interval) * - qos.out.sdu); - - len = sizeof(sndbuf); - if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &sndbuf, len) < 0) { - syslog(LOG_ERR, "Can't set socket SO_SNDBUF option: %s (%d)", - strerror(errno), errno); - } - - syslog(LOG_INFO, "Socket jitter buffer: %d buffer", sndbuf); - - if (sndto.tv_usec) { - len = sizeof(sndto); - if (setsockopt(sk, SOL_SOCKET, SO_SNDTIMEO, &sndto, len) < 0) { - syslog(LOG_ERR, "Can't set socket SO_SNDTIMEO option: " - "%s (%d)", strerror(errno), errno); - } else { - syslog(LOG_INFO, "Socket send timeout: %ld usec", - sndto.tv_usec); - } - } - - for (i = 6; i < qos.out.sdu; i++) - buf[i] = 0x7f; - - do_send(sk, fd, &qos, num, repeat); + do_send(sk, fd, peer, repeat); } static void reconnect_mode(char *peer) { + mgmt_set_experimental(); + while (1) { int sk; @@ -789,6 +961,8 @@ static void multy_connect_mode(char *peer) { + mgmt_set_experimental(); + while (1) { int i, sk; @@ -821,12 +995,22 @@ #define QOS(_interval, _latency, _sdu, _phy, _rtn) \ { \ - .cig = BT_ISO_QOS_CIG_UNSET, \ - .cis = BT_ISO_QOS_CIS_UNSET, \ - .sca = 0x07, \ - .packing = 0x00, \ - .framing = 0x00, \ - .out = QOS_IO(_interval, _latency, _sdu, _phy, _rtn), \ + .bcast = { \ + .big = BT_ISO_QOS_BIG_UNSET, \ + .bis = BT_ISO_QOS_BIS_UNSET, \ + .sync_interval = 0x07, \ + .packing = 0x00, \ + .framing = 0x00, \ + .out = QOS_IO(_interval, _latency, _sdu, _phy, _rtn), \ + .encryption = 0x00, \ + .bcode = {0}, \ + .options = 0x00, \ + .skip = 0x0000, \ + .sync_timeout = 0x4000, \ + .sync_cte_type = 0x00, \ + .mse = 0x00, \ + .timeout = 0x4000, \ + }, \ } #define QOS_PRESET(_name, _inout, _interval, _latency, _sdu, _phy, _rtn) \ @@ -912,7 +1096,8 @@ "\t[-B, --preset ]\n" "\t[-G, --CIG/BIG ]\n" "\t[-T, --CIS/BIS ]\n" - "\t[-V, --type ] address type (help for list)\n"); + "\t[-V, --type ] address type (help for list)\n" + "\t[-N, --nbis ] Number of BISes to create/synchronize to\n"); } static const struct option main_options[] = { @@ -942,9 +1127,29 @@ { "CIG/BIG", required_argument, NULL, 'G'}, { "CIS/BIS", required_argument, NULL, 'T'}, { "type", required_argument, NULL, 'V'}, + { "nbis", required_argument, NULL, 'N'}, {} }; +static bool str2hex(const char *str, uint16_t in_len, uint8_t *out, + uint16_t out_len) +{ + uint16_t i; + + if (in_len < out_len * 2) + return false; + + if (!strncasecmp(str, "0x", 2)) + str += 2; + + for (i = 0; i < out_len; i++) { + if (sscanf(&str[i * 2], "%02hhx", &out[i]) != 1) + return false; + } + + return true; +} + int main(int argc, char *argv[]) { struct sigaction sa; @@ -952,6 +1157,8 @@ char *filename = NULL; bool repeat = false; unsigned int i; + uint8_t nconn = 1; + char *peer; iso_qos = malloc(sizeof(*iso_qos)); /* Default to 16_2_1 */ @@ -962,7 +1169,7 @@ int opt; opt = getopt_long(argc, argv, - "d::cmr::s::nb:i:j:hqt:CV:W:M:S:P:F:I:L:Y:R:B:G:T:", + "d::cmr::s::nb:i:j:hqt:CV:W:M:S:P:F:I:L:Y:R:B:G:T:e:k:N:", main_options, NULL); if (opt < 0) break; @@ -1052,43 +1259,43 @@ case 'M': if (optarg) - iso_qos->out.sdu = atoi(optarg); + iso_qos->ucast.out.sdu = atoi(optarg); break; case 'S': if (optarg) - iso_qos->sca = atoi(optarg); + iso_qos->ucast.sca = atoi(optarg); break; case 'P': if (optarg) - iso_qos->packing = atoi(optarg); + iso_qos->ucast.packing = atoi(optarg); break; case 'F': if (optarg) - iso_qos->framing = atoi(optarg); + iso_qos->ucast.framing = atoi(optarg); break; case 'I': if (optarg) - iso_qos->out.interval = atoi(optarg); + iso_qos->ucast.out.interval = atoi(optarg); break; case 'L': if (optarg) - iso_qos->out.latency = atoi(optarg); + iso_qos->ucast.out.latency = atoi(optarg); break; case 'Y': if (optarg) - iso_qos->out.phy = atoi(optarg); + iso_qos->ucast.out.phy = atoi(optarg); break; case 'R': if (optarg) - iso_qos->out.rtn = atoi(optarg); + iso_qos->ucast.out.rtn = atoi(optarg); break; case 'B': @@ -1107,12 +1314,42 @@ case 'G': if (optarg) - iso_qos->cig = atoi(optarg); + iso_qos->ucast.cig = atoi(optarg); break; case 'T': if (optarg) - iso_qos->cis = atoi(optarg); + iso_qos->ucast.cis = atoi(optarg); + break; + + case 'e': + if (optarg) + iso_qos->bcast.encryption = + strtol(optarg, NULL, 16); + break; + + case 'k': + if (optarg) + if (!str2hex(optarg, strlen(optarg), + iso_qos->bcast.bcode, 16)) + exit(1); + break; + + case 'N': + if (optarg) + num_bis = atoi(optarg); + + if (num_bis > 1) { + /* If the user requested multiple BISes, + * make sure that all BISes are bound + * for the same BIG and advertising set + */ + if (iso_qos->bcast.big == BT_ISO_QOS_BIG_UNSET) + iso_qos->bcast.big = DEFAULT_BIG_ID; + + if (iso_qos->bcast.bis == BT_ISO_QOS_BIS_UNSET) + iso_qos->bcast.bis = DEFAULT_BIS_ID; + } break; /* fall through */ @@ -1123,11 +1360,11 @@ } if (inout) { - iso_qos->in = iso_qos->out; + iso_qos->ucast.in = iso_qos->ucast.out; } else { /* Align interval and latency even if is unidirectional */ - iso_qos->in.interval = iso_qos->out.interval; - iso_qos->in.latency = iso_qos->out.latency; + iso_qos->ucast.in.interval = iso_qos->ucast.out.interval; + iso_qos->ucast.in.latency = iso_qos->ucast.out.latency; } buf = malloc(data_size); @@ -1188,10 +1425,46 @@ break; case CONNECT: - sk = do_connect(argv[optind + i]); - if (sk < 0) - exit(1); - dump_mode(-1, sk); + peer = argv[optind + i]; + + mgmt_set_experimental(); + + if (!strcmp(peer, "00:00:00:00:00:00")) + nconn = num_bis; + + if (nconn > 1) { + int *sk_arr = bcast_do_connect_mbis(nconn, + peer); + + if (!sk_arr) + exit(1); + + for (int i = 0; i < nconn; i++) { + if (fork()) { + /* Parent */ + continue; + } + + /* Child */ + dump_mode(-1, sk_arr[i]); + exit(0); + } + + /* Wait for children to exit */ + while (wait(NULL) > 0) + ; + + for (int i = 0; i < nconn; i++) + close(sk_arr[i]); + + free(sk_arr); + } else { + sk = do_connect(argv[optind + i]); + if (sk < 0) + exit(1); + dump_mode(-1, sk); + } + break; case RECV: diff -Nru bluez-5.66/tools/iso-tester.c bluez-5.68/tools/iso-tester.c --- bluez-5.66/tools/iso-tester.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/tools/iso-tester.c 2023-06-30 08:10:20.000000000 +0000 @@ -4,6 +4,7 @@ * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2022 Intel Corporation. + * Copyright 2023 NXP * */ @@ -42,13 +43,15 @@ #define QOS_FULL(_cig, _cis, _in, _out) \ { \ - .cig = _cig, \ - .cis = _cis, \ - .sca = 0x07, \ - .packing = 0x00, \ - .framing = 0x00, \ - .in = _in, \ - .out = _out, \ + .ucast = { \ + .cig = _cig, \ + .cis = _cis, \ + .sca = 0x07, \ + .packing = 0x00, \ + .framing = 0x00, \ + .in = _in, \ + .out = _out, \ + },\ } #define QOS(_interval, _latency, _sdu, _phy, _rtn) \ @@ -61,11 +64,21 @@ QOS_IO(_interval, _latency, _sdu, _phy, _rtn), \ QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) +#define QOS_2(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_FULL(0x02, BT_ISO_QOS_CIS_UNSET, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) + #define QOS_1_1(_interval, _latency, _sdu, _phy, _rtn) \ QOS_FULL(0x01, 0x01, \ QOS_IO(_interval, _latency, _sdu, _phy, _rtn), \ QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) +#define QOS_1_2(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_FULL(0x01, 0x02, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) + #define QOS_OUT(_interval, _latency, _sdu, _phy, _rtn) \ QOS_FULL(BT_ISO_QOS_CIG_UNSET, BT_ISO_QOS_CIS_UNSET, \ {}, QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) @@ -78,9 +91,25 @@ QOS_FULL(0x01, 0x01, \ {}, QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) +#define QOS_OUT_1_2(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_FULL(0x01, 0x02, \ + {}, QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) + #define QOS_IN(_interval, _latency, _sdu, _phy, _rtn) \ QOS_FULL(BT_ISO_QOS_CIG_UNSET, BT_ISO_QOS_CIS_UNSET, \ QOS_IO(_interval, _latency, _sdu, _phy, _rtn), {}) +#define QOS_IN_1(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_FULL(0x01, BT_ISO_QOS_CIS_UNSET, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), {}) +#define QOS_IN_2(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_FULL(0x02, BT_ISO_QOS_CIS_UNSET, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), {}) +#define QOS_IN_1_1(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_FULL(0x01, 0x01, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), {}) +#define QOS_IN_1_2(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_FULL(0x01, 0x02, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), {}) /* QoS Configuration settings for low latency audio data */ #define QOS_8_1_1 QOS(7500, 8, 26, 0x02, 2) @@ -88,6 +117,7 @@ #define QOS_16_1_1 QOS(7500, 8, 30, 0x02, 2) #define QOS_16_2_1 QOS(10000, 10, 40, 0x02, 2) #define QOS_1_16_2_1 QOS_1(10000, 10, 40, 0x02, 2) +#define QOS_2_16_2_1 QOS_2(10000, 10, 40, 0x02, 2) #define QOS_1_1_16_2_1 QOS_1_1(10000, 10, 40, 0x02, 2) #define QOS_24_1_1 QOS(7500, 8, 45, 0x02, 2) #define QOS_24_2_1 QOS(10000, 10, 60, 0x02, 2) @@ -102,27 +132,245 @@ #define QOS_48_5_1 QOS_OUT(7500, 15, 117, 0x02, 5) #define QOS_48_6_1 QOS_OUT(10000, 20, 155, 0x02, 5) /* QoS Configuration settings for high reliability audio data */ -#define QOS_8_1_2 QOS(7500, 45, 26, 0x02, 41) -#define QOS_8_2_2 QOS(10000, 60, 30, 0x02, 53) -#define QOS_16_1_2 QOS(7500, 45, 30, 0x02, 41) -#define QOS_16_2_2 QOS(10000, 60, 40, 0x02, 47) -#define QOS_24_1_2 QOS(7500, 45, 45, 0x02, 35) -#define QOS_24_2_2 QOS(10000, 60, 60, 0x02, 41) -#define QOS_32_1_2 QOS(7500, 45, 60, 0x02, 29) -#define QOS_32_2_2 QOS(10000, 60, 80, 0x02, 35) -#define QOS_44_1_2 QOS_OUT(8163, 54, 98, 0x02, 23) -#define QOS_44_2_2 QOS_OUT(10884, 71, 130, 0x02, 23) -#define QOS_48_1_2 QOS_OUT(7500, 45, 75, 0x02, 23) -#define QOS_48_2_2 QOS_OUT(10000, 60, 100, 0x02, 23) -#define QOS_48_3_2 QOS_OUT(7500, 45, 90, 0x02, 23) -#define QOS_48_4_2 QOS_OUT(10000, 60, 120, 0x02, 23) -#define QOS_48_5_2 QOS_OUT(7500, 45, 117, 0x02, 23) -#define QOS_48_6_2 QOS_OUT(10000, 60, 155, 0x02, 23) - -#define QOS_OUT_16_2_1 QOS_OUT(10000, 10, 40, 0x02, 2) -#define QOS_OUT_1_16_2_1 QOS_OUT_1(10000, 10, 40, 0x02, 2) -#define QOS_OUT_1_1_16_2_1 QOS_OUT_1_1(10000, 10, 40, 0x02, 2) -#define QOS_IN_16_2_1 QOS_IN(10000, 10, 40, 0x02, 2) +#define QOS_8_1_2 QOS(7500, 75, 26, 0x02, 13) +#define QOS_8_2_2 QOS(10000, 95, 30, 0x02, 13) +#define QOS_16_1_2 QOS(7500, 75, 30, 0x02, 13) +#define QOS_16_2_2 QOS(10000, 95, 40, 0x02, 13) +#define QOS_24_1_2 QOS(7500, 75, 45, 0x02, 13) +#define QOS_24_2_2 QOS(10000, 95, 60, 0x02, 13) +#define QOS_32_1_2 QOS(7500, 65, 60, 0x02, 13) +#define QOS_32_2_2 QOS(10000, 95, 80, 0x02, 13) +#define QOS_44_1_2 QOS_OUT(8163, 80, 98, 0x02, 13) +#define QOS_44_2_2 QOS_OUT(10884, 85, 130, 0x02, 13) +#define QOS_48_1_2 QOS_OUT(7500, 75, 75, 0x02, 13) +#define QOS_48_2_2 QOS_OUT(10000, 95, 100, 0x02, 13) +#define QOS_48_3_2 QOS_OUT(7500, 75, 90, 0x02, 13) +#define QOS_48_4_2 QOS_OUT(10000, 100, 120, 0x02, 13) +#define QOS_48_5_2 QOS_OUT(7500, 75, 117, 0x02, 13) +#define QOS_48_6_2 QOS_OUT(10000, 100, 155, 0x02, 13) + +/* One unidirectional CIS. Unicast Server is Audio Sink */ +#define AC_1_4 QOS_OUT(10000, 10, 40, 0x02, 2) +/* One unidirectional CIS. Unicast Server is Audio Sink CIG 0x01 */ +#define AC_1_4_1 QOS_OUT_1(10000, 10, 40, 0x02, 2) +/* One unidirectional CIS. Unicast Server is Audio Source. */ +#define AC_2_10 QOS_IN(10000, 10, 40, 0x02, 2) +/* One unidirectional CIS. Unicast Server is Audio Source CIG 0x02 */ +#define AC_2_10_2 QOS_IN_2(10000, 10, 40, 0x02, 2) +/* One bidirectional CIS. Unicast Server is Audio Sink and Audio Source. */ +#define AC_3_5 QOS(10000, 10, 40, 0x02, 2) +/* Two unidirectional CISes. Unicast Server is Audio Sink. + * #1 - CIG 1 CIS 1 (output) + * #2 - CIG 1 CIS 2 (output) + */ +#define AC_6i_1 QOS_OUT_1_1(10000, 10, 40, 0x02, 2) +#define AC_6i_2 QOS_OUT_1_2(10000, 10, 40, 0x02, 2) +/* Two Unicast Servers. Unicast Server 1 is Audio Sink. Unicast Server 2 is + * Audio Sink. + * #1 - CIG 1 CIS auto (output) + * #2 - CIG 1 CIS auto (output) + */ +#define AC_6ii_1 QOS_OUT_1(10000, 10, 40, 0x02, 2) +#define AC_6ii_2 QOS_OUT_1(10000, 10, 40, 0x02, 2) +/* Two unidirectional CISes. Unicast Server is Audio Sink and Audio Source. + * #1 - CIG 1 CIS 1 (input) + * #2 - CIG 1 CIS 2 (output) + */ +#define AC_7i_1 QOS_OUT_1_1(10000, 10, 40, 0x02, 2) +#define AC_7i_2 QOS_IN_1_2(10000, 10, 40, 0x02, 2) +/* Two Unidirectional CISes. Two Unicast Servers. Unicast Server 1 is Audio + * Sink. Unicast Server 2 is Audio Source. + * #1 - CIG 1 CIS auto (output) + * #2 - CIG 1 CIS auto (output) + */ +#define AC_7ii_1 QOS_OUT_1(10000, 10, 40, 0x02, 2) +#define AC_7ii_2 QOS_IN_1(10000, 10, 40, 0x02, 2) +/* One bidirectional CIS and one unidirectional CIS. Unicast Server is Audio + * Sink and Audio Source. + * #1 - CIG 1 CIS 1 (output) + * #2 - CIG 1 CIS 2 (input/output) + */ +#define AC_8i_1 QOS_OUT_1_1(10000, 10, 40, 0x02, 2) +#define AC_8i_2 QOS_1_2(10000, 10, 40, 0x02, 2) +/* One bidirectional CIS and one unidirectional CIS. Two Unicast Servers. + * Unicast Server 1 is Audio Sink and Audio Source. Unicast Server 2 is + * Audio Sink. + * #1 - CIG 1 CIS auto (input/output) + * #2 - CIG 1 CIS auto (output) + */ +#define AC_8ii_1 QOS_1(10000, 10, 40, 0x02, 2) +#define AC_8ii_2 QOS_OUT_1(10000, 10, 40, 0x02, 2) +/* Two unidirectional CISes. Unicast Server is Audio Source. + * #1 - CIG 1 CIS 1 (input) + * #2 - CIG 1 CIS 2 (input) + */ +#define AC_9i_1 QOS_IN_1_1(10000, 10, 40, 0x02, 2) +#define AC_9i_2 QOS_IN_1_2(10000, 10, 40, 0x02, 2) +/* Two unidirectional CISes. Two Unicast Servers. Unicast Server 1 is Audio + * Source. Unicast Server 2 is Audio Source. + * #1 - CIG 1 CIS auto (input) + * #2 - CIG 1 CIS auto (input) + */ +#define AC_9ii_1 QOS_IN_1(10000, 10, 40, 0x02, 2) +#define AC_9ii_2 QOS_IN_1(10000, 10, 40, 0x02, 2) +/* Two bidirectional CISes. Unicast Server is Audio Sink and Audio Source. + * #1 - CIG 1 CIS 1 (input/output) + * #2 - CIG 1 CIS 2 (input/output) + */ +#define AC_11i_1 QOS_1_1(10000, 10, 40, 0x02, 2) +#define AC_11i_2 QOS_1_2(10000, 10, 40, 0x02, 2) +/* Two bidirectional CISes. Two Unicast Servers. Unicast Server 1 is Audio Sink + * and Audio Source. Unicast Server 2 is Audio Sink and Audio Source. + * #1 - CIG 1 CIS auto (input/output) + * #2 - CIG 1 CIS auto (input/output) + */ +#define AC_11ii_1 QOS_1(10000, 10, 40, 0x02, 2) +#define AC_11ii_2 QOS_1(10000, 10, 40, 0x02, 2) + +#define BCODE {0x01, 0x02, 0x68, 0x05, 0x53, 0xf1, 0x41, 0x5a, \ + 0xa2, 0x65, 0xbb, 0xaf, 0xc6, 0xea, 0x03, 0xb8} + +#define QOS_BCAST_FULL(_big, _bis, _encryption, _bcode, _in, _out) \ +{ \ + .bcast = { \ + .big = _big, \ + .bis = _bis, \ + .sync_interval = 0x07, \ + .packing = 0x00, \ + .framing = 0x00, \ + .in = _in, \ + .out = _out, \ + .encryption = _encryption, \ + .bcode = _bcode, \ + .options = 0x00, \ + .skip = 0x0000, \ + .sync_timeout = 0x4000, \ + .sync_cte_type = 0x00, \ + .mse = 0x00, \ + .timeout = 0x4000, \ + }, \ +} + +#define BCAST_QOS_OUT(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_BCAST_FULL(BT_ISO_QOS_BIG_UNSET, BT_ISO_QOS_BIS_UNSET, \ + 0x00, {0x00}, {}, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) + +#define BCAST_QOS_OUT_ENC(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_BCAST_FULL(BT_ISO_QOS_BIG_UNSET, BT_ISO_QOS_BIS_UNSET, \ + 0x01, BCODE, {}, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) + +#define BCAST_QOS_OUT_1(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_BCAST_FULL(0x01, BT_ISO_QOS_BIS_UNSET, \ + 0x00, {0x00}, {}, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) + +#define BCAST_QOS_OUT_1_1(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_BCAST_FULL(0x01, 0x01, \ + 0x00, {0x00}, {}, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn)) + +#define BCAST_QOS_IN(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_BCAST_FULL(BT_ISO_QOS_BIG_UNSET, BT_ISO_QOS_BIS_UNSET, \ + 0x00, {0x00}, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), {}) + +#define BCAST_QOS_IN_ENC(_interval, _latency, _sdu, _phy, _rtn) \ + QOS_BCAST_FULL(BT_ISO_QOS_BIG_UNSET, BT_ISO_QOS_BIS_UNSET, \ + 0x01, BCODE, \ + QOS_IO(_interval, _latency, _sdu, _phy, _rtn), {}) + +#define QOS_OUT_16_2_1 BCAST_QOS_OUT(10000, 10, 40, 0x02, 2) +#define QOS_OUT_ENC_16_2_1 BCAST_QOS_OUT_ENC(10000, 10, 40, 0x02, 2) +#define QOS_OUT_1_16_2_1 BCAST_QOS_OUT_1(10000, 10, 40, 0x02, 2) +#define QOS_OUT_1_1_16_2_1 BCAST_QOS_OUT_1_1(10000, 10, 40, 0x02, 2) +#define QOS_IN_16_2_1 BCAST_QOS_IN(10000, 10, 40, 0x02, 2) +#define QOS_IN_ENC_16_2_1 BCAST_QOS_IN_ENC(10000, 10, 40, 0x02, 2) + +static const uint8_t base_lc3_16_2_1[] = { + 0x28, 0x00, 0x00, /* Presentation Delay */ + 0x01, /* Number of Subgroups */ + 0x01, /* Number of BIS */ + 0x06, 0x00, 0x00, 0x00, 0x00, /* Code ID = LC3 (0x06) */ + 0x11, /* Codec Specific Configuration */ + 0x02, 0x01, 0x03, /* 16 KHZ */ + 0x02, 0x02, 0x01, /* 10 ms */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Front Left */ + 0x03, 0x04, 0x28, 0x00, /* Frame Length 40 bytes */ + 0x04, /* Metadata */ + 0x03, 0x02, 0x02, 0x00, /* Audio Context: Convertional */ + 0x01, /* BIS */ + 0x00, /* Codec Specific Configuration */ +}; + +/* Single Audio Channel. One BIS. */ +#define BCAST_AC_12 BCAST_QOS_OUT_1_1(10000, 10, 40, 0x02, 2) + +static const uint8_t base_lc3_ac_12[] = { + 0x28, 0x00, 0x00, /* Presentation Delay */ + 0x01, /* Number of Subgroups */ + 0x01, /* Number of BIS */ + 0x06, 0x00, 0x00, 0x00, 0x00, /* Code ID = LC3 (0x06) */ + 0x11, /* Codec Specific Configuration */ + 0x02, 0x01, 0x03, /* 16 KHZ */ + 0x02, 0x02, 0x01, /* 10 ms */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Front Left */ + 0x03, 0x04, 0x28, 0x00, /* Frame Length 40 bytes */ + 0x04, /* Metadata */ + 0x03, 0x02, 0x02, 0x00, /* Audio Context: Convertional */ + 0x01, /* BIS */ + 0x00, /* Codec Specific Configuration */ +}; + +/* Multiple Audio Channels. Two BISes. */ +#define BCAST_AC_13 BCAST_QOS_OUT_1_1(10000, 10, 40, 0x02, 2) + +static const uint8_t base_lc3_ac_13[] = { + 0x28, 0x00, 0x00, /* Presentation Delay */ + 0x01, /* Number of Subgroups */ + 0x02, /* Number of BIS */ + 0x06, 0x00, 0x00, 0x00, 0x00, /* Code ID = LC3 (0x06) */ + 0x11, /* Codec Specific Configuration */ + 0x02, 0x01, 0x03, /* 16 KHZ */ + 0x02, 0x02, 0x01, /* 10 ms */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Front Left */ + 0x03, 0x04, 0x28, 0x00, /* Frame Length 40 bytes */ + 0x04, /* Metadata */ + 0x03, 0x02, 0x02, 0x00, /* Audio Context: Convertional */ + 0x01, /* BIS 1 */ + 0x06, /* Codec Specific Configuration */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Audio_Channel_Allocation: + * Front left + */ + 0x01, /* BIS 2 */ + 0x06, /* Codec Specific Configuration */ + 0x05, 0x03, 0x02, 0x00, 0x00, 0x00, /* Audio_Channel_Allocation: + * Front right + */ +}; + +/* Multiple Audio Channels. One BIS. */ +#define BCAST_AC_14 BCAST_QOS_OUT_1_1(10000, 10, 40, 0x02, 2) + +static const uint8_t base_lc3_ac_14[] = { + 0x28, 0x00, 0x00, /* Presentation Delay */ + 0x01, /* Number of Subgroups */ + 0x01, /* Number of BIS */ + 0x06, 0x00, 0x00, 0x00, 0x00, /* Code ID = LC3 (0x06) */ + 0x11, /* Codec Specific Configuration */ + 0x02, 0x01, 0x03, /* 16 KHZ */ + 0x02, 0x02, 0x01, /* 10 ms */ + 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Front Left */ + 0x03, 0x04, 0x28, 0x00, /* Frame Length 40 bytes */ + 0x04, /* Metadata */ + 0x03, 0x02, 0x02, 0x00, /* Audio Context: Convertional */ + 0x01, /* BIS */ + 0x06, /* Codec Specific Configuration */ + 0x05, 0x03, 0x03, 0x00, 0x00, 0x00, /* Audio_Channel_Allocation: + * Front left, Front right + */ +}; struct test_data { const void *test_data; @@ -130,6 +378,7 @@ uint16_t mgmt_index; struct hciemu *hciemu; enum hciemu_type hciemu_type; + uint8_t accept_reason; uint16_t handle; uint16_t acl_handle; GIOChannel *io; @@ -141,6 +390,7 @@ struct iso_client_data { struct bt_iso_qos qos; + struct bt_iso_qos qos_2; int expect_err; const struct iovec *send; const struct iovec *recv; @@ -148,6 +398,10 @@ bool bcast; bool defer; bool disconnect; + bool ts; + bool mconn; + const uint8_t *base; + size_t base_len; }; static void mgmt_debug(const char *str, void *user_data) @@ -342,7 +596,7 @@ free(data); } -#define test_iso_full(name, data, setup, func, num) \ +#define test_iso_full(name, data, setup, func, num, reason) \ do { \ struct test_data *user; \ user = new0(struct test_data, 1); \ @@ -351,16 +605,20 @@ user->hciemu_type = HCIEMU_TYPE_BREDRLE; \ user->test_data = data; \ user->client_num = num; \ + user->accept_reason = reason; \ tester_add_full(name, data, \ test_pre_setup, setup, func, NULL, \ test_post_teardown, 2, user, test_data_free); \ } while (0) #define test_iso(name, data, setup, func) \ - test_iso_full(name, data, setup, func, 1) + test_iso_full(name, data, setup, func, 1, 0x00) #define test_iso2(name, data, setup, func) \ - test_iso_full(name, data, setup, func, 2) + test_iso_full(name, data, setup, func, 2, 0x00) + +#define test_iso_rej(name, data, setup, func, reason) \ + test_iso_full(name, data, setup, func, 1, reason) static const struct iso_client_data connect_8_1_1 = { .qos = QOS_8_1_1, @@ -537,6 +795,11 @@ .expect_err = -EINVAL }; +static const struct iso_client_data connect_reject = { + .qos = QOS_16_1_2, + .expect_err = -ENOSYS +}; + static const uint8_t data_16_2_1[40] = { [0 ... 39] = 0xff }; static const struct iovec send_16_2_1 = { .iov_base = (void *)data_16_2_1, @@ -562,12 +825,26 @@ .server = true, }; +static const struct iso_client_data listen_16_2_1_recv_ts = { + .qos = QOS_16_2_1, + .expect_err = 0, + .recv = &send_16_2_1, + .server = true, + .ts = true, +}; + static const struct iso_client_data defer_16_2_1 = { .qos = QOS_16_2_1, .expect_err = 0, .defer = true, }; +static const struct iso_client_data defer_1_16_2_1 = { + .qos = QOS_1_16_2_1, + .expect_err = 0, + .defer = true, +}; + static const struct iso_client_data connect_16_2_1_defer_send = { .qos = QOS_16_2_1, .expect_err = 0, @@ -625,11 +902,149 @@ .disconnect = true, }; +static const struct iso_client_data connect_ac_1_4 = { + .qos = AC_1_4, + .expect_err = 0 +}; + +static const struct iso_client_data connect_ac_2_10 = { + .qos = AC_2_10, + .expect_err = 0 +}; + +static const struct iso_client_data connect_ac_3_5 = { + .qos = AC_3_5, + .expect_err = 0 +}; + +static const struct iso_client_data connect_ac_6i = { + .qos = AC_6i_1, + .qos_2 = AC_6i_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data reconnect_ac_6i = { + .qos = AC_6i_1, + .qos_2 = AC_6i_2, + .expect_err = 0, + .mconn = true, + .defer = true, + .disconnect = true, +}; + +static const struct iso_client_data connect_ac_6ii = { + .qos = AC_6ii_1, + .qos_2 = AC_6ii_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data reconnect_ac_6ii = { + .qos = AC_6ii_1, + .qos_2 = AC_6ii_2, + .expect_err = 0, + .mconn = true, + .defer = true, + .disconnect = true, +}; + +static const struct iso_client_data connect_ac_7i = { + .qos = AC_7i_1, + .qos_2 = AC_7i_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_7ii = { + .qos = AC_7ii_1, + .qos_2 = AC_7ii_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_8i = { + .qos = AC_8i_1, + .qos_2 = AC_8i_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_8ii = { + .qos = AC_8ii_1, + .qos_2 = AC_8ii_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_9i = { + .qos = AC_9i_1, + .qos_2 = AC_9i_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_9ii = { + .qos = AC_9ii_1, + .qos_2 = AC_9ii_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_11i = { + .qos = AC_11i_1, + .qos_2 = AC_11i_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_11ii = { + .qos = AC_11ii_1, + .qos_2 = AC_11ii_2, + .expect_err = 0, + .mconn = true, + .defer = true, +}; + +static const struct iso_client_data connect_ac_1_2 = { + .qos = AC_1_4, + .qos_2 = AC_2_10, + .expect_err = 0, + .mconn = true, +}; + +static const struct iso_client_data connect_ac_1_2_cig_1_2 = { + .qos = AC_1_4_1, + .qos_2 = AC_2_10_2, + .expect_err = 0, + .mconn = true, +}; + static const struct iso_client_data bcast_16_2_1_send = { .qos = QOS_OUT_16_2_1, .expect_err = 0, .send = &send_16_2_1, .bcast = true, + .base = base_lc3_16_2_1, + .base_len = sizeof(base_lc3_16_2_1), +}; + +static const struct iso_client_data bcast_enc_16_2_1_send = { + .qos = QOS_OUT_ENC_16_2_1, + .expect_err = 0, + .send = &send_16_2_1, + .bcast = true, + .base = base_lc3_16_2_1, + .base_len = sizeof(base_lc3_16_2_1), }; static const struct iso_client_data bcast_1_16_2_1_send = { @@ -637,6 +1052,8 @@ .expect_err = 0, .send = &send_16_2_1, .bcast = true, + .base = base_lc3_16_2_1, + .base_len = sizeof(base_lc3_16_2_1), }; static const struct iso_client_data bcast_1_1_16_2_1_send = { @@ -644,6 +1061,8 @@ .expect_err = 0, .send = &send_16_2_1, .bcast = true, + .base = base_lc3_16_2_1, + .base_len = sizeof(base_lc3_16_2_1), }; static const struct iso_client_data bcast_16_2_1_recv = { @@ -651,6 +1070,40 @@ .expect_err = 0, .recv = &send_16_2_1, .bcast = true, + .server = true, +}; + +static const struct iso_client_data bcast_enc_16_2_1_recv = { + .qos = QOS_IN_ENC_16_2_1, + .expect_err = 0, + .recv = &send_16_2_1, + .bcast = true, + .server = true, +}; + +static const struct iso_client_data bcast_ac_12 = { + .qos = BCAST_AC_12, + .expect_err = 0, + .bcast = true, + .base = base_lc3_ac_12, + .base_len = sizeof(base_lc3_ac_12), +}; + +static const struct iso_client_data bcast_ac_13 = { + .qos = BCAST_AC_13, + .expect_err = 0, + .bcast = true, + .mconn = true, + .base = base_lc3_ac_13, + .base_len = sizeof(base_lc3_ac_13), +}; + +static const struct iso_client_data bcast_ac_14 = { + .qos = BCAST_AC_14, + .expect_err = 0, + .bcast = true, + .base = base_lc3_ac_14, + .base_len = sizeof(base_lc3_ac_14), }; static void client_connectable_complete(uint16_t opcode, uint8_t status, @@ -714,6 +1167,16 @@ bthost_iso_disconnected); } +static uint8_t iso_accept_conn(uint16_t handle, void *user_data) +{ + struct test_data *data = user_data; + + tester_print("Accept client connection with handle 0x%04x: 0x%02x", + handle, data->accept_reason); + + return data->accept_reason; +} + static void acl_new_conn(uint16_t handle, void *user_data) { struct test_data *data = user_data; @@ -751,13 +1214,17 @@ if (!isodata) continue; - if (isodata->send || isodata->recv || isodata->disconnect) - bthost_set_iso_cb(host, iso_new_conn, data); + if (isodata->send || isodata->recv || isodata->disconnect || + data->accept_reason) + bthost_set_iso_cb(host, iso_accept_conn, iso_new_conn, + data); if (isodata->bcast) { bthost_set_pa_params(host); bthost_set_pa_enable(host, 0x01); - bthost_create_big(host, 1); + bthost_create_big(host, 1, + isodata->qos.bcast.encryption, + isodata->qos.bcast.bcode); } else if (!isodata->send && isodata->recv) { const uint8_t *bdaddr; @@ -925,46 +1392,42 @@ return sk; } -static const uint8_t base_lc3_16_2_1[] = { - 0x28, 0x00, 0x00, /* Presentation Delay */ - 0x01, /* Number of Subgroups */ - 0x01, /* Number of BIS */ - 0x06, 0x00, 0x00, 0x00, 0x00, /* Code ID = LC3 (0x06) */ - 0x11, /* Codec Specific Configuration */ - 0x02, 0x01, 0x03, /* 16 KHZ */ - 0x02, 0x02, 0x01, /* 10 ms */ - 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Front Left */ - 0x03, 0x04, 0x28, 0x00, /* Frame Length 40 bytes */ - 0x04, /* Metadata */ - 0x03, 0x02, 0x02, 0x00, /* Audio Context: Convertional */ - 0x01, /* BIS */ - 0x00, /* Codec Specific Configuration */ -}; - static int connect_iso_sock(struct test_data *data, uint8_t num, int sk) { const struct iso_client_data *isodata = data->test_data; struct hciemu_client *client; const uint8_t *client_bdaddr = NULL; + const struct bt_iso_qos *qos = &isodata->qos; struct sockaddr_iso addr; char str[18]; int err; client = hciemu_get_client(data->hciemu, num); if (!client) { - tester_warn("No client"); - return -ENODEV; + if (!isodata->mconn) { + tester_warn("No client"); + return -ENODEV; + } + + client = hciemu_get_client(data->hciemu, 0); + if (!client) { + tester_warn("No client"); + return -ENODEV; + } } + if (!isodata->bcast && num && isodata->mconn) + qos = &isodata->qos_2; + if (!isodata->bcast) { client_bdaddr = hciemu_client_bdaddr(client); if (!client_bdaddr) { tester_warn("No client bdaddr"); return -ENODEV; } - } else { + } else if (!isodata->server) { err = setsockopt(sk, SOL_BLUETOOTH, BT_ISO_BASE, - base_lc3_16_2_1, sizeof(base_lc3_16_2_1)); + isodata->base, isodata->base_len); if (err < 0) { tester_warn("Can't set socket BT_ISO_BASE option: " "%s (%d)", strerror(errno), errno); @@ -973,8 +1436,7 @@ } } - err = setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &isodata->qos, - sizeof(isodata->qos)); + err = setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, qos, sizeof(*qos)); if (err < 0) { tester_warn("Can't set socket BT_ISO_QOS option : %s (%d)", strerror(errno), errno); @@ -982,7 +1444,7 @@ return -EINVAL; } - if (isodata->defer) { + if (isodata->defer || (isodata->bcast && isodata->mconn && !num)) { int opt = 1; if (setsockopt(sk, SOL_BLUETOOTH, BT_DEFER_SETUP, &opt, @@ -1018,14 +1480,14 @@ static bool check_io_qos(const struct bt_iso_io_qos *io1, const struct bt_iso_io_qos *io2) { - if (io1->interval && io2->interval && io1->interval != io2->interval) { - tester_warn("Unexpected IO interval: %u != %u", + if (io1->interval && io2->interval && io1->interval > io2->interval) { + tester_warn("Unexpected IO interval: %u > %u", io1->interval, io2->interval); return false; } - if (io1->latency && io2->latency && io1->latency != io2->latency) { - tester_warn("Unexpected IO latency: %u != %u", + if (io1->latency && io2->latency && io1->latency > io2->latency) { + tester_warn("Unexpected IO latency: %u > %u", io1->latency, io2->latency); return false; } @@ -1049,43 +1511,62 @@ return true; } -static bool check_qos(const struct bt_iso_qos *qos1, - const struct bt_iso_qos *qos2) -{ - if (qos1->cig != BT_ISO_QOS_CIG_UNSET && - qos2->cig != BT_ISO_QOS_CIG_UNSET && - qos1->cig != qos2->cig) { +static bool check_ucast_qos(const struct bt_iso_qos *qos1, + const struct bt_iso_qos *qos2, + const struct bt_iso_qos *qos2_2) +{ + if (qos1->ucast.cig != BT_ISO_QOS_CIG_UNSET && + qos2->ucast.cig != BT_ISO_QOS_CIG_UNSET && + qos1->ucast.cig != qos2->ucast.cig) { + if (qos2_2) + return check_ucast_qos(qos1, qos2_2, NULL); + tester_warn("Unexpected CIG ID: 0x%02x != 0x%02x", - qos1->cig, qos2->cig); + qos1->ucast.cig, qos2->ucast.cig); return false; } - if (qos1->cis != BT_ISO_QOS_CIS_UNSET && - qos2->cis != BT_ISO_QOS_CIS_UNSET && - qos1->cis != qos2->cis) { + if (qos1->ucast.cis != BT_ISO_QOS_CIS_UNSET && + qos2->ucast.cis != BT_ISO_QOS_CIS_UNSET && + qos1->ucast.cis != qos2->ucast.cis) { + if (qos2_2) + return check_ucast_qos(qos1, qos2_2, NULL); + tester_warn("Unexpected CIS ID: 0x%02x != 0x%02x", - qos1->cis, qos2->cis); + qos1->ucast.cis, qos2->ucast.cis); return false; } - if (qos1->packing != qos2->packing) { + if (qos1->ucast.packing != qos2->ucast.packing) { + if (qos2_2) + return check_ucast_qos(qos1, qos2_2, NULL); + tester_warn("Unexpected QoS packing: 0x%02x != 0x%02x", - qos1->packing, qos2->packing); + qos1->ucast.packing, qos2->ucast.packing); return false; } - if (qos1->framing != qos2->framing) { + if (qos1->ucast.framing != qos2->ucast.framing) { + if (qos2_2) + return check_ucast_qos(qos1, qos2_2, NULL); + tester_warn("Unexpected QoS framing: 0x%02x != 0x%02x", - qos1->framing, qos2->framing); + qos1->ucast.framing, qos2->ucast.framing); return false; } - if (!check_io_qos(&qos1->in, &qos2->in)) { + if (!check_io_qos(&qos1->ucast.in, &qos2->ucast.in)) { + if (qos2_2) + return check_ucast_qos(qos1, qos2_2, NULL); + tester_warn("Unexpected Input QoS"); return false; } - if (!check_io_qos(&qos1->out, &qos2->out)) { + if (!check_io_qos(&qos1->ucast.out, &qos2->ucast.out)) { + if (qos2_2) + return check_ucast_qos(qos1, qos2_2, NULL); + tester_warn("Unexpected Output QoS"); return false; } @@ -1093,6 +1574,104 @@ return true; } +static bool check_bcast_qos(const struct bt_iso_qos *qos1, + const struct bt_iso_qos *qos2) +{ + if (qos1->bcast.big != BT_ISO_QOS_BIG_UNSET && + qos2->bcast.big != BT_ISO_QOS_BIG_UNSET && + qos1->bcast.big != qos2->bcast.big) { + tester_warn("Unexpected BIG ID: 0x%02x != 0x%02x", + qos1->bcast.big, qos2->bcast.big); + return false; + } + + if (qos1->bcast.bis != BT_ISO_QOS_BIS_UNSET && + qos2->bcast.bis != BT_ISO_QOS_BIS_UNSET && + qos1->bcast.bis != qos2->bcast.bis) { + tester_warn("Unexpected BIS ID: 0x%02x != 0x%02x", + qos1->bcast.bis, qos2->bcast.bis); + return false; + } + + if (qos1->bcast.sync_interval != qos2->bcast.sync_interval) { + tester_warn("Unexpected QoS sync interval: 0x%02x != 0x%02x", + qos1->bcast.sync_interval, qos2->bcast.sync_interval); + return false; + } + + if (qos1->bcast.packing != qos2->bcast.packing) { + tester_warn("Unexpected QoS packing: 0x%02x != 0x%02x", + qos1->bcast.packing, qos2->bcast.packing); + return false; + } + + if (qos1->bcast.framing != qos2->bcast.framing) { + tester_warn("Unexpected QoS framing: 0x%02x != 0x%02x", + qos1->bcast.framing, qos2->bcast.framing); + return false; + } + + if (!check_io_qos(&qos1->ucast.in, &qos2->ucast.in)) { + tester_warn("Unexpected Input QoS"); + return false; + } + + if (!check_io_qos(&qos1->ucast.out, &qos2->ucast.out)) { + tester_warn("Unexpected Output QoS"); + return false; + } + + if (qos1->bcast.encryption != qos2->bcast.encryption) { + tester_warn("Unexpected QoS encryption: 0x%02x != 0x%02x", + qos1->bcast.encryption, qos2->bcast.encryption); + return false; + } + + if (memcmp(qos1->bcast.bcode, qos2->bcast.bcode, + sizeof(qos1->bcast.bcode))) { + tester_warn("Unexpected QoS Broadcast Code"); + return false; + } + + if (qos1->bcast.options != qos2->bcast.options) { + tester_warn("Unexpected QoS options: 0x%02x != 0x%02x", + qos1->bcast.options, qos2->bcast.options); + return false; + } + + if (qos1->bcast.skip != qos2->bcast.skip) { + tester_warn("Unexpected QoS skip: 0x%04x != 0x%04x", + qos1->bcast.skip, qos2->bcast.skip); + return false; + } + + if (qos1->bcast.sync_timeout != qos2->bcast.sync_timeout) { + tester_warn("Unexpected QoS sync timeout: 0x%04x != 0x%04x", + qos1->bcast.sync_timeout, qos2->bcast.sync_timeout); + return false; + } + + if (qos1->bcast.sync_cte_type != qos2->bcast.sync_cte_type) { + tester_warn("Unexpected QoS sync cte type: 0x%02x != 0x%02x", + qos1->bcast.sync_cte_type, qos2->bcast.sync_cte_type); + return false; + } + + if (qos1->bcast.mse != qos2->bcast.mse) { + tester_warn("Unexpected QoS MSE: 0x%02x != 0x%02x", + qos1->bcast.mse, qos2->bcast.mse); + return false; + } + + if (qos1->bcast.timeout != qos2->bcast.timeout) { + tester_warn("Unexpected QoS MSE: 0x%04x != 0x%04x", + qos1->bcast.timeout, qos2->bcast.timeout); + return false; + } + + return true; +} + static gboolean iso_recv_data(GIOChannel *io, GIOCondition cond, gpointer user_data) { @@ -1124,6 +1703,7 @@ { const struct iso_client_data *isodata = data->test_data; struct bthost *host; + static uint16_t sn; tester_print("Receive %zu bytes of data", isodata->recv->iov_len); @@ -1134,7 +1714,8 @@ } host = hciemu_client_get_host(data->hciemu); - bthost_send_iso(host, data->handle, isodata->recv, 1); + bthost_send_iso(host, data->handle, isodata->ts, sn++, 0, + isodata->recv, 1); data->io_id[0] = g_io_add_watch(io, G_IO_IN, iso_recv_data, data); } @@ -1166,7 +1747,7 @@ iso_recv(data, io); } -static void setup_connect(struct test_data *data, uint8_t num, GIOFunc func); +static void test_connect(const void *test_data); static gboolean iso_connect_cb(GIOChannel *io, GIOCondition cond, gpointer user_data); @@ -1182,7 +1763,7 @@ if (data->reconnect) { data->reconnect = false; - setup_connect(data, 0, iso_connect_cb); + test_connect(data->test_data); return FALSE; } @@ -1217,6 +1798,7 @@ int err, sk_err, sk; socklen_t len; struct bt_iso_qos qos; + bool ret = true; sk = g_io_channel_unix_get_fd(io); @@ -1231,7 +1813,13 @@ return FALSE; } - if (!check_qos(&qos, &isodata->qos)) { + if (!isodata->bcast) { + ret = check_ucast_qos(&qos, &isodata->qos, + isodata->mconn ? &isodata->qos_2 : NULL); + } else if (!isodata->server) + ret = check_bcast_qos(&qos, &isodata->qos); + + if (!ret) { tester_warn("Unexpected QoS parameter"); tester_test_failed(); return FALSE; @@ -1249,7 +1837,7 @@ else tester_print("Successfully connected"); - if (-err != isodata->expect_err) { + if (err != isodata->expect_err) { tester_warn("Expect error: %s (%d) != %s (%d)", strerror(-isodata->expect_err), -isodata->expect_err, strerror(-err), -err); @@ -1291,13 +1879,9 @@ return iso_connect(io, cond, user_data); } -static void setup_connect(struct test_data *data, uint8_t num, GIOFunc func) +static int setup_sock(struct test_data *data, uint8_t num) { - const struct iso_client_data *isodata = data->test_data; - GIOChannel *io; int sk, err; - char c; - struct pollfd pfd; sk = create_iso_sock(data); if (sk < 0) { @@ -1305,7 +1889,8 @@ tester_test_abort(); else tester_test_failed(); - return; + + return sk; } err = connect_iso_sock(data, num, sk); @@ -1319,73 +1904,114 @@ else tester_test_failed(); - return; + return err; } - if (isodata->defer) { - int defer; - socklen_t len; + return sk; +} + +static int connect_deferred(int sk) +{ + int defer; + socklen_t len; + struct pollfd pfd; + char c; + + /* Check if socket has DEFER_SETUP set */ + len = sizeof(defer); + if (getsockopt(sk, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer, + &len) < 0) { + tester_warn("getsockopt: %s (%d)", strerror(errno), + errno); + tester_test_failed(); + return 0; + } + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = sk; + pfd.events = POLLOUT; + + if (poll(&pfd, 1, 0) < 0) { + tester_warn("poll: %s (%d)", strerror(errno), errno); + tester_test_failed(); + return -EIO; + } - /* Check if socket has DEFER_SETUP set */ - len = sizeof(defer); - if (getsockopt(sk, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer, - &len) < 0) { - tester_warn("getsockopt: %s (%d)", strerror(errno), - errno); + if (!(pfd.revents & POLLOUT)) { + if (read(sk, &c, 1) < 0) { + tester_warn("read: %s (%d)", strerror(errno), + errno); tester_test_failed(); - return; + return -EIO; } + } + + return 0; +} - memset(&pfd, 0, sizeof(pfd)); - pfd.fd = sk; - pfd.events = POLLOUT; +static void setup_connect_many(struct test_data *data, uint8_t n, uint8_t *num, + GIOFunc *func) +{ + const struct iso_client_data *isodata = data->test_data; + int sk[256]; + GIOChannel *io; + unsigned int i; - if (poll(&pfd, 1, 0) < 0) { - tester_warn("poll: %s (%d)", strerror(errno), errno); - tester_test_failed(); + for (i = 0; i < n; ++i) { + sk[i] = setup_sock(data, num[i]); + if (sk[i] < 0) return; - } + } - if (!(pfd.revents & POLLOUT)) { - if (read(sk, &c, 1) < 0) { - tester_warn("read: %s (%d)", strerror(errno), - errno); - tester_test_failed(); + if (isodata->defer) { + for (i = 0; i < n; ++i) + if (connect_deferred(sk[i]) < 0) return; - } - } } - io = g_io_channel_unix_new(sk); - g_io_channel_set_close_on_unref(io, TRUE); + for (i = 0; i < n; ++i) { + io = g_io_channel_unix_new(sk[i]); + g_io_channel_set_close_on_unref(io, TRUE); - data->io_id[num] = g_io_add_watch(io, G_IO_OUT, func, NULL); + data->io_id[num[i]] = g_io_add_watch(io, G_IO_OUT, func[i], + NULL); - g_io_channel_unref(io); + g_io_channel_unref(io); - tester_print("Connect in progress"); + tester_print("Connect %d in progress", num[i]); - data->step++; + data->step++; + } } -static void test_connect(const void *test_data) +static void setup_connect(struct test_data *data, uint8_t num, GIOFunc func) { - struct test_data *data = tester_get_data(); - - setup_connect(data, 0, iso_connect_cb); + return setup_connect_many(data, 1, &num, &func); } -static void setup_reconnect(struct test_data *data, uint8_t num, GIOFunc func) +static void test_connect(const void *test_data) { - data->reconnect = true; - setup_connect(data, num, func); + struct test_data *data = tester_get_data(); + const struct iso_client_data *isodata = test_data; + uint8_t n = 0; + GIOFunc func[2]; + uint8_t num[2] = {0, 1}; + + func[n++] = iso_connect_cb; + + /* Check if configuration requires multiple CIS setup */ + if (!isodata->bcast && isodata->mconn) + func[n++] = iso_connect2_cb; + + setup_connect_many(data, n, num, func); } static void test_reconnect(const void *test_data) { struct test_data *data = tester_get_data(); - setup_reconnect(data, 0, iso_connect_cb); + data->reconnect = true; + test_connect(test_data); } static void test_defer(const void *test_data) @@ -1490,6 +2116,13 @@ } } + if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &isodata->qos, + sizeof(isodata->qos)) < 0) { + tester_print("Can't set socket BT_ISO_QOS option: %s (%d)", + strerror(errno), errno); + goto fail; + } + if (listen(sk, 10)) { err = -errno; tester_warn("Can't listen socket: %s (%d)", strerror(errno), @@ -1630,9 +2263,29 @@ static void test_connect2(const void *test_data) { struct test_data *data = tester_get_data(); + uint8_t num[2] = {0, 1}; + GIOFunc funcs[2] = {iso_connect_cb, iso_connect2_cb}; + + setup_connect_many(data, 2, num, funcs); +} + +static gboolean iso_connect2_seq_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct test_data *data = tester_get_data(); + + data->io_id[0] = 0; - setup_connect(data, 0, iso_connect_cb); setup_connect(data, 1, iso_connect2_cb); + + return iso_connect(io, cond, user_data); +} + +static void test_connect2_seq(const void *test_data) +{ + struct test_data *data = tester_get_data(); + + setup_connect(data, 0, iso_connect2_seq_cb); } static void test_bcast(const void *test_data) @@ -1642,6 +2295,15 @@ setup_connect(data, 0, iso_connect_cb); } +static void test_bcast2(const void *test_data) +{ + struct test_data *data = tester_get_data(); + uint8_t num[2] = {0, 1}; + GIOFunc funcs[2] = {iso_connect_cb, iso_connect2_cb}; + + setup_connect_many(data, 2, num, funcs); +} + static void test_bcast_recv(const void *test_data) { struct test_data *data = tester_get_data(); @@ -1773,9 +2435,8 @@ test_iso("ISO QoS - Invalid", &connect_invalid, setup_powered, test_connect); - test_iso2("ISO Connect2 CIG 0x01 - Success", &connect_1_16_2_1, - setup_powered, - test_connect2); + test_iso_rej("ISO Connect - Reject", &connect_reject, setup_powered, + test_connect, BT_HCI_ERR_CONN_FAILED_TO_ESTABLISH); test_iso("ISO Send - Success", &connect_16_2_1_send, setup_powered, test_connect); @@ -1783,9 +2444,20 @@ test_iso("ISO Receive - Success", &listen_16_2_1_recv, setup_powered, test_listen); + test_iso("ISO Receive Timestamped - Success", &listen_16_2_1_recv_ts, + setup_powered, + test_listen); + test_iso("ISO Defer - Success", &defer_16_2_1, setup_powered, test_defer); + test_iso("ISO Defer Connect - Success", &defer_16_2_1, setup_powered, + test_connect); + + test_iso2("ISO Defer Connect2 CIG 0x01 - Success", &defer_1_16_2_1, + setup_powered, + test_connect2); + test_iso("ISO Defer Send - Success", &connect_16_2_1_defer_send, setup_powered, test_connect); @@ -1816,8 +2488,66 @@ setup_powered, test_reconnect); + test_iso("ISO AC 1 & 4 - Success", &connect_ac_1_4, setup_powered, + test_connect); + + test_iso("ISO AC 2 & 10 - Success", &connect_ac_2_10, setup_powered, + test_connect); + + test_iso("ISO AC 3 & 5 - Success", &connect_ac_3_5, setup_powered, + test_connect); + + test_iso("ISO AC 6(i) - Success", &connect_ac_6i, setup_powered, + test_connect); + + test_iso2("ISO AC 6(ii) - Success", &connect_ac_6ii, setup_powered, + test_connect2); + + test_iso("ISO AC 7(i) - Success", &connect_ac_7i, setup_powered, + test_connect); + + test_iso2("ISO AC 7(ii) - Success", &connect_ac_7ii, setup_powered, + test_connect2); + + test_iso("ISO AC 8(i) - Success", &connect_ac_8i, setup_powered, + test_connect); + + test_iso2("ISO AC 8(ii) - Success", &connect_ac_8ii, setup_powered, + test_connect2); + + test_iso("ISO AC 9(i) - Success", &connect_ac_9i, setup_powered, + test_connect); + + test_iso2("ISO AC 9(ii) - Success", &connect_ac_9ii, setup_powered, + test_connect2); + + test_iso("ISO AC 11(i) - Success", &connect_ac_11i, setup_powered, + test_connect); + + test_iso2("ISO AC 11(ii) - Success", &connect_ac_11ii, setup_powered, + test_connect2); + + test_iso2("ISO AC 1 + 2 - Success", &connect_ac_1_2, setup_powered, + test_connect2_seq); + + test_iso2("ISO AC 1 + 2 CIG 0x01/0x02 - Success", + &connect_ac_1_2_cig_1_2, + setup_powered, + test_connect2_seq); + + test_iso2("ISO Reconnect AC 6(i) - Success", &reconnect_ac_6i, + setup_powered, + test_reconnect); + + test_iso2("ISO Reconnect AC 6(ii) - Success", &reconnect_ac_6ii, + setup_powered, + test_reconnect); + test_iso("ISO Broadcaster - Success", &bcast_16_2_1_send, setup_powered, test_bcast); + test_iso("ISO Broadcaster Encrypted - Success", &bcast_enc_16_2_1_send, + setup_powered, + test_bcast); test_iso("ISO Broadcaster BIG 0x01 - Success", &bcast_1_16_2_1_send, setup_powered, test_bcast); @@ -1829,6 +2559,19 @@ test_iso("ISO Broadcaster Receiver - Success", &bcast_16_2_1_recv, setup_powered, test_bcast_recv); + test_iso("ISO Broadcaster Receiver Encrypted - Success", + &bcast_enc_16_2_1_recv, + setup_powered, + test_bcast_recv); + + test_iso("ISO Broadcaster AC 12 - Success", &bcast_ac_12, setup_powered, + test_bcast); + + test_iso("ISO Broadcaster AC 13 - Success", &bcast_ac_13, setup_powered, + test_bcast2); + + test_iso("ISO Broadcaster AC 14 - Success", &bcast_ac_14, setup_powered, + test_bcast); return tester_run(); } diff -Nru bluez-5.66/tools/isotest.rst bluez-5.68/tools/isotest.rst --- bluez-5.66/tools/isotest.rst 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/tools/isotest.rst 2023-06-30 08:10:20.000000000 +0000 @@ -153,6 +153,29 @@ * - **le_random** - LE Random Address +-e, --enc= Socket QoS BIG Encryption + +.. list-table:: + :header-rows: 1 + :widths: auto + :stub-columns: 1 + :align: left + + * - *ENCRYPTION* + - Description + + * - **0x00** + - BIG unencrypted + + * - **0x01** + - BIG encrypted + +-k, --bcode= Socket QoS Broadcast Code + +-N, --nbis= Number of BISes to create as part of a + BIG (BIS broadcaster) or to synchronize + to (BIS broadcast receiver) + EXAMPLES ======== diff -Nru bluez-5.66/tools/l2cap-tester.c bluez-5.68/tools/l2cap-tester.c --- bluez-5.66/tools/l2cap-tester.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/tools/l2cap-tester.c 2023-06-30 08:10:20.000000000 +0000 @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,7 @@ bool server_not_advertising; bool direct_advertising; bool close_1; + bool defer; bool shut_sock_wr; }; @@ -540,6 +542,64 @@ .expect_cmd_len = sizeof(nval_le_connect_rsp), }; +static const uint8_t ecred_connect_req[] = { 0x80, 0x00, /* PSM */ + 0x40, 0x00, /* MTU */ + 0x40, 0x00, /* MPS */ + 0x05, 0x00, /* Credits */ + 0x41, 0x00, /* SCID #1 */ + 0x42, 0x00, /* SCID #2 */ + 0x43, 0x00, /* SCID #3 */ + 0x44, 0x00, /* SCID #4 */ + 0x45, 0x00, /* SCID #5 */ +}; + +static const uint8_t ecred_connect_rsp[] = { 0xa0, 0x02, /* MTU */ + 0xbc, 0x00, /* MPS */ + 0x04, 0x00, /* Credits */ + 0x00, 0x00, /* Result */ + 0x40, 0x00, /* DCID #1 */ + 0x41, 0x00, /* DCID #2 */ + 0x42, 0x00, /* DCID #3 */ + 0x43, 0x00, /* DCID #4 */ + 0x44, 0x00, /* DCID #5 */ +}; + +static const struct l2cap_data ext_flowctl_server_success_test = { + .server_psm = 0x0080, + .send_cmd_code = BT_L2CAP_PDU_ECRED_CONN_REQ, + .send_cmd = ecred_connect_req, + .send_cmd_len = sizeof(ecred_connect_req), + .expect_cmd_code = BT_L2CAP_PDU_ECRED_CONN_RSP, + .expect_cmd = ecred_connect_rsp, + .expect_cmd_len = sizeof(ecred_connect_rsp), +}; + +static const uint8_t nval_ecred_connect_req[] = { + 0x80, 0x00, /* PSM */ + 0x40, 0x00, /* MTU */ + 0x40, 0x00, /* MPS */ + 0x05, 0x00, /* Credits */ + 0x01, 0x00, /* SCID #1 */ +}; + +static const uint8_t nval_ecred_connect_rsp[] = { + 0x00, 0x00, /* MTU */ + 0x00, 0x00, /* MPS */ + 0x00, 0x00, /* Credits */ + 0x09, 0x00, /* Result */ + 0x00, 0x00, /* DCID #1 */ +}; + +static const struct l2cap_data ext_flowctl_server_nval_scid_test = { + .server_psm = 0x0080, + .send_cmd_code = BT_L2CAP_PDU_ECRED_CONN_REQ, + .send_cmd = nval_ecred_connect_req, + .send_cmd_len = sizeof(nval_ecred_connect_req), + .expect_cmd_code = BT_L2CAP_PDU_ECRED_CONN_RSP, + .expect_cmd = nval_ecred_connect_rsp, + .expect_cmd_len = sizeof(nval_ecred_connect_rsp), +}; + static const struct l2cap_data le_att_client_connect_success_test_1 = { .cid = 0x0004, .sec_level = BT_SECURITY_LOW, @@ -549,6 +609,69 @@ .cid = 0x0004, }; +static const struct l2cap_data le_eatt_client_connect_success_test_1 = { + .client_psm = 0x0027, + .server_psm = 0x0027, + .mode = BT_MODE_EXT_FLOWCTL, + .sec_level = BT_SECURITY_LOW, +}; + +static const uint8_t eatt_connect_req[] = { 0x27, 0x00, /* PSM */ + 0x40, 0x00, /* MTU */ + 0x40, 0x00, /* MPS */ + 0x05, 0x00, /* Credits */ + 0x41, 0x00, /* SCID #1 */ +}; + +static const uint8_t eatt_connect_rsp[] = { 0xa0, 0x02, /* MTU */ + 0xbc, 0x00, /* MPS */ + 0x04, 0x00, /* Credits */ + 0x00, 0x00, /* Result */ + 0x40, 0x00, /* DCID #1 */ +}; + +static const struct l2cap_data le_eatt_server_success_test_1 = { + .server_psm = 0x0027, + .mode = BT_MODE_EXT_FLOWCTL, + .send_cmd_code = BT_L2CAP_PDU_ECRED_CONN_REQ, + .send_cmd = eatt_connect_req, + .send_cmd_len = sizeof(eatt_connect_req), + .expect_cmd_code = BT_L2CAP_PDU_ECRED_CONN_RSP, + .expect_cmd = eatt_connect_rsp, + .expect_cmd_len = sizeof(eatt_connect_rsp), + .defer = true, +}; + +static const uint8_t eatt_reject_req[] = { 0x27, 0x00, /* PSM */ + 0x40, 0x00, /* MTU */ + 0x40, 0x00, /* MPS */ + 0x05, 0x00, /* Credits */ + 0x41, 0x00, /* SCID #1 */ + 0x42, 0x00, /* SCID #2 */ + 0x43, 0x00, /* SCID #3 */ + 0x44, 0x00, /* SCID #4 */ + 0x45, 0x00, /* SCID #5 */ +}; + +static const uint8_t eatt_reject_rsp[] = { 0xa0, 0x02, /* MTU */ + 0xbc, 0x00, /* MPS */ + 0x04, 0x00, /* Credits */ + 0x06, 0x00, /* Result */ +}; + +static const struct l2cap_data le_eatt_server_reject_test_1 = { + .server_psm = 0x0027, + .mode = BT_MODE_EXT_FLOWCTL, + .send_cmd_code = BT_L2CAP_PDU_ECRED_CONN_REQ, + .send_cmd = eatt_reject_req, + .send_cmd_len = sizeof(eatt_reject_req), + .expect_cmd_code = BT_L2CAP_PDU_ECRED_CONN_RSP, + .expect_cmd = eatt_reject_rsp, + .expect_cmd_len = sizeof(eatt_reject_rsp), + .defer = true, + .expect_err = -1, +}; + static const struct l2cap_data ext_flowctl_client_connect_success_test_1 = { .client_psm = 0x0080, .server_psm = 0x0080, @@ -1689,43 +1812,29 @@ defer); } -static gboolean l2cap_listen_cb(GIOChannel *io, GIOCondition cond, +static gboolean l2cap_accept_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { struct test_data *data = tester_get_data(); const struct l2cap_data *l2data = data->test_data; - int sk, new_sk; - - data->io_id = 0; + int sk; sk = g_io_channel_unix_get_fd(io); - new_sk = accept(sk, NULL, NULL); - if (new_sk < 0) { - tester_warn("accept failed: %s (%u)", strerror(errno), errno); - tester_test_failed(); - return FALSE; - } - - if (!check_mtu(data, new_sk)) { + if (!check_mtu(data, sk)) { tester_test_failed(); - close(new_sk); return FALSE; } if (l2data->read_data) { struct bthost *bthost; - GIOChannel *new_io; - - new_io = g_io_channel_unix_new(new_sk); - g_io_channel_set_close_on_unref(new_io, TRUE); bthost = hciemu_client_get_host(data->hciemu); - g_io_add_watch(new_io, G_IO_IN, server_received_data, NULL); + g_io_add_watch(io, G_IO_IN, server_received_data, NULL); bthost_send_cid(bthost, data->handle, data->dcid, l2data->read_data, l2data->data_len); - g_io_channel_unref(new_io); + g_io_channel_unref(io); return FALSE; } else if (l2data->write_data) { @@ -1736,8 +1845,7 @@ bthost_add_cid_hook(bthost, data->handle, data->scid, server_bthost_received_data, NULL); - ret = write(new_sk, l2data->write_data, l2data->data_len); - close(new_sk); + ret = write(sk, l2data->write_data, l2data->data_len); if (ret != l2data->data_len) { tester_warn("Unable to write all data"); @@ -1749,13 +1857,81 @@ tester_print("Successfully connected"); - close(new_sk); - tester_test_passed(); return FALSE; } +static bool defer_accept(struct test_data *data, GIOChannel *io) +{ + int sk; + char c; + struct pollfd pfd; + + sk = g_io_channel_unix_get_fd(io); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = sk; + pfd.events = POLLOUT; + + if (poll(&pfd, 1, 0) < 0) { + tester_warn("poll: %s (%d)", strerror(errno), errno); + return false; + } + + if (!(pfd.revents & POLLOUT)) { + if (read(sk, &c, 1) < 0) { + tester_warn("read: %s (%d)", strerror(errno), errno); + return false; + } + } + + data->io_id = g_io_add_watch(io, G_IO_OUT, l2cap_accept_cb, NULL); + + g_io_channel_unref(io); + + tester_print("Accept deferred setup"); + + return true; +} + +static gboolean l2cap_listen_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct test_data *data = tester_get_data(); + const struct l2cap_data *l2data = data->test_data; + int sk, new_sk; + + data->io_id = 0; + + sk = g_io_channel_unix_get_fd(io); + + new_sk = accept(sk, NULL, NULL); + if (new_sk < 0) { + tester_warn("accept failed: %s (%u)", strerror(errno), errno); + tester_test_failed(); + return FALSE; + } + + io = g_io_channel_unix_new(new_sk); + g_io_channel_set_close_on_unref(io, TRUE); + + if (l2data->defer) { + if (l2data->expect_err < 0) { + g_io_channel_unref(io); + return FALSE; + } + + if (!defer_accept(data, io)) { + tester_warn("Unable to accept deferred setup"); + tester_test_failed(); + } + return FALSE; + } + + return l2cap_accept_cb(io, cond, user_data); +} + static void client_l2cap_rsp(uint8_t code, const void *data, uint16_t len, void *user_data) { @@ -1767,7 +1943,7 @@ if (code != l2data->expect_cmd_code) { tester_warn("Unexpected L2CAP response code (expected 0x%02x)", l2data->expect_cmd_code); - return; + goto failed; } if (code == BT_L2CAP_PDU_CONN_RSP) { @@ -1844,6 +2020,8 @@ int sk; if (l2data->server_psm || l2data->cid) { + int opt = 1; + sk = create_l2cap_sock(data, l2data->server_psm, l2data->cid, l2data->sec_level, l2data->mode); @@ -1852,6 +2030,15 @@ return; } + if (l2data->defer && setsockopt(sk, SOL_BLUETOOTH, + BT_DEFER_SETUP, &opt, sizeof(opt)) < 0) { + tester_warn("Can't enable deferred setup: %s (%d)", + strerror(errno), errno); + tester_test_failed(); + close(sk); + return; + } + if (listen(sk, 5) < 0) { tester_warn("listening on socket failed: %s (%u)", strerror(errno), errno); @@ -2066,6 +2253,13 @@ setup_powered_client, test_connect_2); + test_l2cap_le("L2CAP Ext-Flowctl Server - Success", + &ext_flowctl_server_success_test, + setup_powered_server, test_server); + test_l2cap_le("L2CAP Ext-Flowctl Server - Nval SCID", + &ext_flowctl_server_nval_scid_test, + setup_powered_server, test_server); + test_l2cap_le("L2CAP LE ATT Client - Success", &le_att_client_connect_success_test_1, setup_powered_client, test_connect); @@ -2073,5 +2267,15 @@ &le_att_server_success_test_1, setup_powered_server, test_server); + test_l2cap_le("L2CAP LE EATT Client - Success", + &le_eatt_client_connect_success_test_1, + setup_powered_client, test_connect); + test_l2cap_le("L2CAP LE EATT Server - Success", + &le_eatt_server_success_test_1, + setup_powered_server, test_server); + test_l2cap_le("L2CAP LE EATT Server - Reject", + &le_eatt_server_reject_test_1, + setup_powered_server, test_server); + return tester_run(); } diff -Nru bluez-5.66/tools/l2test.c bluez-5.68/tools/l2test.c --- bluez-5.66/tools/l2test.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/tools/l2test.c 2023-06-30 08:10:20.000000000 +0000 @@ -155,6 +155,24 @@ { NULL, 0 }, }; +static int bt_mode_to_l2cap_mode(int mode) +{ + switch (mode) { + case BT_MODE_BASIC: + return L2CAP_MODE_BASIC; + case BT_MODE_ERTM: + return L2CAP_MODE_ERTM; + case BT_MODE_STREAMING: + return L2CAP_MODE_STREAMING; + case BT_MODE_LE_FLOWCTL: + return L2CAP_MODE_LE_FLOWCTL; + case BT_MODE_EXT_FLOWCTL: + return L2CAP_MODE_FLOWCTL; + default: + return mode; + } +} + static int get_lookup_flag(struct lookup_table *table, char *name) { int i; @@ -287,9 +305,11 @@ static int setopts(int sk, struct l2cap_options *opts) { - if (bdaddr_type == BDADDR_BREDR) + if (bdaddr_type == BDADDR_BREDR) { + opts->mode = bt_mode_to_l2cap_mode(opts->mode); return setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, opts, sizeof(*opts)); + } if (opts->mode) { if (setsockopt(sk, SOL_BLUETOOTH, BT_MODE, &opts->mode, @@ -1416,7 +1436,7 @@ break; case 'P': - psm = atoi(optarg); + psm = strtoul(optarg, NULL, 0); break; case 'I': diff -Nru bluez-5.66/tools/mesh/cfgcli.c bluez-5.68/tools/mesh/cfgcli.c --- bluez-5.66/tools/mesh/cfgcli.c 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/tools/mesh/cfgcli.c 2023-06-30 08:10:20.000000000 +0000 @@ -21,6 +21,7 @@ #include "src/shared/util.h" #include "mesh/mesh-defs.h" +#include "mesh/prv-beacon.h" #include "mesh/util.h" #include "mesh/crypto.h" @@ -73,9 +74,12 @@ { OP_APPKEY_UPDATE, OP_APPKEY_STATUS, "AppKeyUpdate" }, { OP_DEV_COMP_GET, OP_DEV_COMP_STATUS, "DeviceCompositionGet" }, { OP_DEV_COMP_STATUS, NO_RESPONSE, "DeviceCompositionStatus" }, - { OP_CONFIG_BEACON_GET, OP_CONFIG_BEACON_STATUS, "BeaconGet" }, - { OP_CONFIG_BEACON_SET, OP_CONFIG_BEACON_STATUS, "BeaconSet" }, - { OP_CONFIG_BEACON_STATUS, NO_RESPONSE, "BeaconStatus" }, + { OP_CONFIG_BEACON_GET, OP_CONFIG_BEACON_STATUS, "SNBGet" }, + { OP_CONFIG_BEACON_SET, OP_CONFIG_BEACON_STATUS, "SNBSet" }, + { OP_CONFIG_BEACON_STATUS, NO_RESPONSE, "SNBStatus" }, + { OP_PRIVATE_BEACON_GET, OP_PRIVATE_BEACON_STATUS, "MPBGet" }, + { OP_PRIVATE_BEACON_SET, OP_PRIVATE_BEACON_STATUS, "MPBSet" }, + { OP_PRIVATE_BEACON_STATUS, NO_RESPONSE, "MPBStatus" }, { OP_CONFIG_DEFAULT_TTL_GET, OP_CONFIG_DEFAULT_TTL_STATUS, "DefaultTTLGet" }, { OP_CONFIG_DEFAULT_TTL_SET, OP_CONFIG_DEFAULT_TTL_STATUS, @@ -266,14 +270,21 @@ return mod_id; } -static void print_composition(uint8_t *data, uint16_t len) +static uint8_t print_composition(uint8_t *data, uint16_t len) { uint16_t features; int i = 0; + bool nppi = false; - bt_shell_printf("Received composion:\n"); + bt_shell_printf("Received composition:\n"); + + /* We only support Pages 0 && 128 */ + if (*data == 128) { + bt_shell_printf("Dev Key Refresh (NPPI) required\n"); + nppi = true; + } else if (*data != 0) + return 0; - /* skip page -- We only support Page Zero */ data++; len--; @@ -328,6 +339,11 @@ i++; } + + if (nppi) + return (uint8_t) i; + else + return 0; } static void print_pub(uint16_t ele_addr, uint32_t mod_id, @@ -402,6 +418,7 @@ const struct cfg_cmd *cmd; uint16_t app_idx, net_idx, addr, ele_addr, features; struct mesh_group *grp; + uint8_t page128_cnt; struct model_pub pub; int n; struct pending_req *req; @@ -431,7 +448,19 @@ if (len < MIN_COMPOSITION_LEN) return true; - print_composition(data, len); + page128_cnt = print_composition(data, len); + if (page128_cnt) { + if (page128_cnt != remote_ele_cnt(src)) { + bt_shell_printf("Ele count was %d, now %d\n", + remote_ele_cnt(src), page128_cnt); + bt_shell_printf("Reprovision with NPPI-1\n"); + } else { + bt_shell_printf("Models or Features changed\n"); + bt_shell_printf("Reprovision with NPPI-2\n"); + } + + break; + } saved = mesh_db_node_set_composition(src, data, len); if (saved) @@ -592,12 +621,20 @@ if (len != 1) return true; - bt_shell_printf("Node %4.4x: Config Beacon Status 0x%02x\n", + bt_shell_printf("Node %4.4x: SecBeacon Status 0x%02x\n", src, data[0]); saved = mesh_db_node_set_beacon(src, data[0] != 0); break; + case OP_PRIVATE_BEACON_STATUS: + if (len != 2) + return true; + + bt_shell_printf("Node %4.4x: PrivBeacon Status 0x%02x 0x%02x\n", + src, data[0], data[1]); + break; + case OP_CONFIG_RELAY_STATUS: if (len != 2) return true; @@ -1044,6 +1081,21 @@ return bt_shell_noninteractive_quit(EXIT_SUCCESS); } + +bool cfgcli_get_comp(uint16_t unicast, uint8_t page) +{ + uint16_t n; + uint8_t msg[32]; + + n = mesh_opcode_set(OP_DEV_COMP_GET, msg); + + msg[n++] = page; + + target = unicast; + + return config_send(msg, n, OP_DEV_COMP_GET); +} + static void cmd_composition_get(int argc, char *argv[]) { uint16_t n; @@ -1051,8 +1103,8 @@ n = mesh_opcode_set(OP_DEV_COMP_GET, msg); - /* By default, use page 0 */ - msg[n++] = (read_input_parameters(argc, argv) == 1) ? parms[0] : 0; + /* By default, use page 128 */ + msg[n++] = (read_input_parameters(argc, argv) == 1) ? parms[0] : 128; if (!config_send(msg, n, OP_DEV_COMP_GET)) return bt_shell_noninteractive_quit(EXIT_FAILURE); @@ -1320,7 +1372,7 @@ cmd_bind(OP_MODEL_APP_UNBIND, argc, argv); } -static void cmd_beacon_set(int argc, char *argv[]) +static void cmd_snb_set(int argc, char *argv[]) { uint16_t n; uint8_t msg[2 + 1]; @@ -1342,11 +1394,41 @@ return bt_shell_noninteractive_quit(EXIT_SUCCESS); } -static void cmd_beacon_get(int argc, char *argv[]) +static void cmd_mpb_set(int argc, char *argv[]) +{ + uint16_t n; + uint8_t msg[2 + 2]; + uint32_t parm_cnt; + + n = mesh_opcode_set(OP_PRIVATE_BEACON_SET, msg); + + parm_cnt = read_input_parameters(argc, argv); + if (parm_cnt != 1 && parm_cnt != 2) { + bt_shell_printf("bad arguments\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + msg[n++] = parms[0]; + + if (parm_cnt == 2) + msg[n++] = parms[1]; + + if (!config_send(msg, n, OP_PRIVATE_BEACON_SET)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_snb_get(int argc, char *argv[]) { cmd_default(OP_CONFIG_BEACON_GET); } +static void cmd_mpb_get(int argc, char *argv[]) +{ + cmd_default(OP_PRIVATE_BEACON_GET); +} + static void cmd_ident_set(int argc, char *argv[]) { uint16_t n; @@ -2052,10 +2134,10 @@ "Set node identity state"}, {"ident-get", "", cmd_ident_get, "Get node identity state"}, - {"beacon-set", "", cmd_beacon_set, - "Set node identity state"}, - {"beacon-get", NULL, cmd_beacon_get, - "Get node beacon state"}, + {"snb-set", "", cmd_snb_set, "Set node SNB state"}, + {"snb-get", NULL, cmd_snb_get, "Get node SNB state"}, + {"mpb-set", " ", cmd_mpb_set, "Set node MPB state"}, + {"mpb-get", NULL, cmd_mpb_get, "Get node MPB state"}, {"relay-set", " ", cmd_relay_set, "Set relay"}, {"relay-get", NULL, cmd_relay_get, diff -Nru bluez-5.66/tools/mesh/cfgcli.h bluez-5.68/tools/mesh/cfgcli.h --- bluez-5.66/tools/mesh/cfgcli.h 2021-02-22 20:27:00.000000000 +0000 +++ bluez-5.68/tools/mesh/cfgcli.h 2023-06-30 08:10:20.000000000 +0000 @@ -19,4 +19,6 @@ struct model_info *cfgcli_init(key_send_func_t key_func, delete_remote_func_t del_node, void *user_data); + +bool cfgcli_get_comp(uint16_t unicast, uint8_t page); void cfgcli_cleanup(void); diff -Nru bluez-5.66/tools/mesh/mesh-db.c bluez-5.68/tools/mesh/mesh-db.c --- bluez-5.66/tools/mesh/mesh-db.c 2022-07-24 21:02:15.000000000 +0000 +++ bluez-5.68/tools/mesh/mesh-db.c 2023-06-30 08:10:20.000000000 +0000 @@ -1702,6 +1702,29 @@ return jelements; } +bool mesh_db_reset_node(uint16_t original, uint16_t unicast, uint8_t num_els) +{ + json_object *jnode, *jelements; + + if (!cfg || !cfg->jcfg) + return false; + + jnode = get_node_by_unicast(cfg->jcfg, original); + if (!jnode) { + l_error("Node %4.4x does not exist", original); + return false; + } + + if (!write_uint16_hex(jnode, "unicastAddress", unicast)) + return false; + + json_object_object_del(jnode, "elements"); + jelements = init_elements(num_els); + json_object_object_add(jnode, "elements", jelements); + + return save_config(); +} + bool mesh_db_add_node(uint8_t uuid[16], uint8_t num_els, uint16_t unicast, uint16_t net_idx) { @@ -1864,13 +1887,11 @@ if (!jnode) return false; - /* skip page -- We only support Page Zero */ - data++; - len--; + /* This is for page-0 only */ + if (*data++ != 0) + return false; - /* If "crpl" property is present, composition is already recorded */ - if (json_object_object_get_ex(jnode, "crpl", &jobj)) - return true; + len--; if (!write_uint16_hex(jnode, "cid", l_get_le16(&data[0]))) return false; @@ -1943,29 +1964,35 @@ while (len >= 2 && m--) { mod_id = l_get_le16(data); + data += 2; + len -= 2; + + jobj = get_model(unicast, unicast + i, mod_id, false); + if (jobj) + continue; jobj = init_model(mod_id); if (!jobj) goto fail; json_object_array_add(jmods, jobj); - data += 2; - len -= 2; } while (len >= 4 && v--) { - jobj = json_object_new_object(); mod_id = l_get_le16(data + 2); mod_id = l_get_le16(data) << 16 | mod_id; + data += 4; + len -= 4; + + jobj = get_model(unicast, unicast + i, mod_id, true); + if (jobj) + continue; jobj = init_vendor_model(mod_id); if (!jobj) goto fail; json_object_array_add(jmods, jobj); - - data += 4; - len -= 4; } i++; @@ -1984,7 +2011,8 @@ fail: /* Reset elements array */ json_object_object_del(jnode, "elements"); - init_elements(sz); + jelements = init_elements(sz); + json_object_object_add(jnode, "elements", jelements); return false; } diff -Nru bluez-5.66/tools/mesh/mesh-db.h bluez-5.68/tools/mesh/mesh-db.h --- bluez-5.66/tools/mesh/mesh-db.h 2021-10-13 18:38:35.000000000 +0000 +++ bluez-5.68/tools/mesh/mesh-db.h 2023-06-30 08:10:20.000000000 +0000 @@ -29,6 +29,7 @@ bool mesh_db_get_addr_range(uint16_t *low, uint16_t *high); bool mesh_db_add_node(uint8_t uuid[16], uint8_t num_els, uint16_t unicast, uint16_t net_idx); +bool mesh_db_reset_node(uint16_t original, uint16_t unicast, uint8_t num_els); bool mesh_db_del_node(uint16_t unicast); bool mesh_db_node_set_composition(uint16_t unicast, uint8_t *data, uint16_t len); diff -Nru bluez-5.66/tools/mesh/remote.c bluez-5.68/tools/mesh/remote.c --- bluez-5.66/tools/mesh/remote.c 2021-10-13 18:38:35.000000000 +0000 +++ bluez-5.68/tools/mesh/remote.c 2023-06-30 08:10:20.000000000 +0000 @@ -30,6 +30,12 @@ bool updated; }; +struct foreach_data { + remote_foreach_t each; + void *user_data; + uint16_t dst; +}; + struct remote_node { uint16_t unicast; struct l_queue *net_keys; @@ -48,6 +54,11 @@ static struct l_queue *nodes; static struct l_queue *reject_list; +static bool match_mod_id(const void *a, const void *b) +{ + return a == b; +} + static int compare_mod_id(const void *a, const void *b, void *user_data) { uint32_t id1 = L_PTR_TO_UINT(a); @@ -138,6 +149,40 @@ return num_ele; } +bool remote_reset_node(uint16_t original, uint16_t unicast, uint8_t ele_cnt, + uint32_t iv_index) +{ + struct remote_node *rmt; + bool reject = true; + int i; + + rmt = l_queue_remove_if(nodes, match_node_addr, + L_UINT_TO_PTR(original)); + if (!rmt) + return false; + + if (unicast == rmt->unicast) + reject = false; + + for (i = 0; i < rmt->num_ele; ++i) { + l_queue_destroy(rmt->els[i], NULL); + if (reject) + remote_add_rejected_address(rmt->unicast + i, + iv_index, true); + } + + if (ele_cnt != rmt->num_ele) { + l_free(rmt->els); + rmt->els = l_new(struct l_queue *, ele_cnt); + } else + memset(rmt->els, 0, sizeof(struct l_queue *) * ele_cnt); + + rmt->unicast = unicast; + rmt->num_ele = ele_cnt; + l_queue_insert(nodes, rmt, compare_unicast, NULL); + return true; +} + bool remote_add_node(const uint8_t uuid[16], uint16_t unicast, uint8_t ele_cnt, uint16_t net_idx) { @@ -187,6 +232,10 @@ if (!vendor) mod_id = VENDOR_ID_MASK | mod_id; + if (l_queue_find(rmt->els[ele_idx], match_mod_id, + L_UINT_TO_PTR(mod_id))) + return true; + l_queue_insert(rmt->els[ele_idx], L_UINT_TO_PTR(mod_id), compare_mod_id, NULL); @@ -526,6 +575,76 @@ l_queue_foreach(nodes, print_node, NULL); } +static void each_node(void *rmt, void *user_data) +{ + struct remote_node *node = rmt; + struct foreach_data *data = user_data; + + data->each(data->user_data, node->unicast, (uint32_t) -1); +} + +static void each_addr(void *rmt, void *user_data) +{ + struct remote_node *node = rmt; + struct foreach_data *data = user_data; + uint16_t cnt; + + for (cnt = 0; cnt <= node->num_ele; cnt++) + data->each(data->user_data, node->unicast + cnt, (uint32_t) -1); +} + +static void parse_model(void *model, void *user_data) +{ + struct foreach_data *data = user_data; + + data->each(data->user_data, data->dst, L_PTR_TO_UINT(model)); +} + +static void each_model(void *rmt, void *user_data) +{ + struct remote_node *node = rmt; + struct foreach_data *data = user_data; + uint16_t cnt; + + for (cnt = 0; cnt < node->num_ele; cnt++) { + data->dst = node->unicast + cnt; + l_queue_foreach(node->els[cnt], parse_model, data); + } +} + +void remote_foreach(remote_foreach_t each, void *user_data) +{ + struct foreach_data data = { + .each = each, + .user_data = user_data + }; + + if (each) + l_queue_foreach(nodes, each_node, &data); +} + +void remote_foreach_unicast(remote_foreach_t each, void *user_data) +{ + struct foreach_data data = { + .each = each, + .user_data = user_data + }; + + if (each) + l_queue_foreach(nodes, each_addr, &data); +} + +void remote_foreach_model(remote_foreach_t each, void *user_data) +{ + struct foreach_data data = { + .each = each, + .user_data = user_data + }; + + if (each) + l_queue_foreach(nodes, each_model, &data); +} + uint16_t remote_get_next_unicast(uint16_t low, uint16_t high, uint8_t ele_cnt) { struct remote_node *rmt; @@ -598,3 +717,15 @@ mesh_db_clear_rejected(iv_index); } + +uint8_t remote_ele_cnt(uint16_t unicast) +{ + struct remote_node *rmt; + + rmt = l_queue_find(nodes, match_node_addr, L_UINT_TO_PTR(unicast)); + + if (rmt) + return rmt->num_ele; + + return 0; +} diff -Nru bluez-5.66/tools/mesh/remote.h bluez-5.68/tools/mesh/remote.h --- bluez-5.66/tools/mesh/remote.h 2021-10-13 18:38:35.000000000 +0000 +++ bluez-5.68/tools/mesh/remote.h 2023-06-30 08:10:20.000000000 +0000 @@ -8,8 +8,13 @@ * */ +typedef void (*remote_foreach_t)(void *user_data, uint16_t dst, + uint32_t model); + bool remote_add_node(const uint8_t uuid[16], uint16_t unicast, uint8_t ele_cnt, uint16_t net_idx); +bool remote_reset_node(uint16_t original, uint16_t unicast, uint8_t ele_cnt, + uint32_t iv_index); uint8_t remote_del_node(uint16_t unicast); bool remote_set_model(uint16_t unicast, uint8_t ele_idx, uint32_t mod_id, bool vendor); @@ -30,3 +35,7 @@ uint16_t remote_get_subnet_idx(uint16_t addr); void remote_print_node(uint16_t addr); void remote_print_all(void); +void remote_foreach(remote_foreach_t each, void *user_data); +void remote_foreach_unicast(remote_foreach_t each, void *user_data); +void remote_foreach_model(remote_foreach_t each, void *user_data); +uint8_t remote_ele_cnt(uint16_t unicast); diff -Nru bluez-5.66/tools/mesh/util.c bluez-5.68/tools/mesh/util.c --- bluez-5.66/tools/mesh/util.c 2021-02-22 20:27:00.000000000 +0000 +++ bluez-5.68/tools/mesh/util.c 2023-06-30 08:10:20.000000000 +0000 @@ -20,6 +20,7 @@ #include "src/shared/util.h" #include "mesh/mesh-defs.h" +#include "mesh/prv-beacon.h" #include "tools/mesh/util.h" @@ -137,6 +138,10 @@ case 0x0001: return "Configuration Client"; case 0x0002: return "Health Server"; case 0x0003: return "Health Client"; + case 0x0004: return "Remote Provisioning Server"; + case 0x0005: return "Remote Provisioning Client"; + case 0x0008: return "Private Beacon Server"; + case 0x0009: return "Private Beacon Client"; case 0x1000: return "Generic OnOff Server"; case 0x1001: return "Generic OnOff Client"; case 0x1002: return "Generic Level Server"; diff -Nru bluez-5.66/tools/mesh-cfgclient.c bluez-5.68/tools/mesh-cfgclient.c --- bluez-5.66/tools/mesh-cfgclient.c 2021-10-13 18:38:35.000000000 +0000 +++ bluez-5.68/tools/mesh-cfgclient.c 2023-06-30 08:10:20.000000000 +0000 @@ -43,6 +43,10 @@ #define CFG_SRV_MODEL 0x0000 #define CFG_CLI_MODEL 0x0001 +#define RPR_SVR_MODEL 0x0004 +#define RPR_CLI_MODEL 0x0005 +#define PRV_BEACON_SVR 0x0008 +#define PRV_BEACON_CLI 0x0009 #define UNPROV_SCAN_MAX_SECS 300 @@ -57,7 +61,7 @@ struct meshcfg_el { const char *path; uint8_t index; - uint16_t mods[2]; + uint16_t mods[4]; }; struct meshcfg_app { @@ -83,8 +87,12 @@ struct unprov_device { time_t last_seen; - int16_t rssi; + int id; + uint32_t uri_hash; uint8_t uuid[16]; + int16_t rssi; + uint16_t server; + uint16_t oob_info; }; struct generic_request { @@ -96,8 +104,16 @@ const char *str; }; +struct scan_data { + uint16_t dst; + uint16_t secs; +}; + +static void *finalized = L_UINT_TO_PTR(-1); + static struct l_dbus *dbus; +static struct l_timeout *scan_timeout; static struct l_queue *node_proxies; static struct l_dbus_proxy *net_proxy; static struct meshcfg_node *local; @@ -130,7 +146,8 @@ .ele = { .path = "/mesh/cfgclient/ele0", .index = 0, - .mods = {CFG_SRV_MODEL, CFG_CLI_MODEL} + .mods = {CFG_SRV_MODEL, CFG_CLI_MODEL, + PRV_BEACON_SVR, PRV_BEACON_CLI} } }; @@ -197,23 +214,57 @@ static bool match_device_uuid(const void *a, const void *b) { const struct unprov_device *dev = a; - const uint8_t *uuid = b; - return (memcmp(dev->uuid, uuid, 16) == 0); + if (a == finalized) + return false; + + return memcmp(dev->uuid, b, 16) == 0; } -static void print_device(void *a, void *b) +static bool match_by_id(const void *a, const void *b) { const struct unprov_device *dev = a; - struct tm *tm = localtime(&dev->last_seen); + int id = L_PTR_TO_UINT(b); + + if (a == finalized) + return false; + + l_info("test %d %d", dev->id, id); + return dev->id == id; +} + +static bool match_by_srv_uuid(const void *a, const void *b) +{ + const struct unprov_device *dev = a; + const struct unprov_device *new_dev = b; + + if (a == finalized) + return false; + + return (dev->server == new_dev->server) && + (memcmp(dev->uuid, new_dev->uuid, 16) == 0); +} + +static void print_device(void *a, void *b) +{ + struct unprov_device *dev = a; + int *cnt = b; + struct tm *tm; char buf[80]; char *str; + if (a == finalized) + return; + + tm = localtime(&dev->last_seen); assert(strftime(buf, sizeof(buf), "%c", tm)); + (*cnt)++; + dev->id = *cnt; str = l_util_hexstring_upper(dev->uuid, sizeof(dev->uuid)); - bt_shell_printf("UUID: %s, RSSI %d, Seen: %s\n", - str, dev->rssi, buf); + bt_shell_printf(COLOR_YELLOW "#%d" COLOR_OFF + " UUID: %s, RSSI %d, Server: %4.4x\n Seen: %s\n", + *cnt, str, dev->rssi, dev->server, buf); l_free(str); } @@ -725,6 +776,10 @@ remote_clear_rejected_addresses(ivi); } + /* Read own node composition */ + if (!cfgcli_get_comp(0x0001, 128)) + l_error("Failed to read own composition"); + return; fail: @@ -794,15 +849,56 @@ static void scan_setup(struct l_dbus_message *msg, void *user_data) { - uint16_t secs = (uint16_t) L_PTR_TO_UINT(user_data); + struct scan_data *data = user_data; struct l_dbus_message_builder *builder; builder = l_dbus_message_builder_new(msg); l_dbus_message_builder_enter_array(builder, "{sv}"); - append_dict_entry_basic(builder, "Seconds", "q", &secs); + append_dict_entry_basic(builder, "Seconds", "q", &data->secs); + append_dict_entry_basic(builder, "Server", "q", &data->dst); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); + + /* Destination info not needed after call */ + l_free(data); +} + +static void scan_start(void *user_data, uint16_t dst, uint32_t model) +{ + struct scan_data *data; + + if (model != (0xffff0000 | RPR_SVR_MODEL)) + return; + + data = l_malloc(sizeof(struct scan_data)); + data->secs = L_PTR_TO_UINT(user_data); + data->dst = dst; + + if (!l_dbus_proxy_method_call(local->mgmt_proxy, "UnprovisionedScan", + scan_setup, scan_reply, data, NULL)) + l_free(data); +} + +static void scan_to(struct l_timeout *timeout, void *user_data) +{ + int cnt = 0; + + if (l_queue_peek_head(devices) != finalized) + l_queue_push_head(devices, finalized); + + l_timeout_remove(timeout); + scan_timeout = NULL; + bt_shell_printf(COLOR_YELLOW "Unprovisioned devices:\n" COLOR_OFF); + l_queue_foreach(devices, print_device, &cnt); +} + +static void free_devices(void *a) +{ + if (a == finalized) + return; + + l_free(a); } static void cmd_scan_unprov(int argc, char *argv[]) @@ -820,21 +916,28 @@ return bt_shell_noninteractive_quit(EXIT_FAILURE); } - if (argc == 3) + if (argc == 3) { sscanf(argv[2], "%u", &secs); - if (secs > UNPROV_SCAN_MAX_SECS) - secs = UNPROV_SCAN_MAX_SECS; + if (secs > UNPROV_SCAN_MAX_SECS) + secs = UNPROV_SCAN_MAX_SECS; + } else + secs = 60; - if (enable) - l_dbus_proxy_method_call(local->mgmt_proxy, "UnprovisionedScan", - scan_setup, scan_reply, - L_UINT_TO_PTR(secs), NULL); - else + l_timeout_remove(scan_timeout); + scan_timeout = NULL; + + if (enable) { + l_queue_clear(devices, free_devices); + remote_foreach_model(scan_start, L_UINT_TO_PTR(secs)); + scan_timeout = l_timeout_create(secs, scan_to, NULL, NULL); + } else { + /* Mark devices queue as finalized */ + l_queue_push_head(devices, finalized); l_dbus_proxy_method_call(local->mgmt_proxy, "UnprovisionedScanCancel", NULL, NULL, NULL, NULL); - + } } static uint8_t *parse_key(struct l_dbus_message_iter *iter, uint16_t id, @@ -1030,8 +1133,10 @@ static void cmd_list_unprov(int argc, char *argv[]) { + int cnt = 0; + bt_shell_printf(COLOR_YELLOW "Unprovisioned devices:\n" COLOR_OFF); - l_queue_foreach(devices, print_device, NULL); + l_queue_foreach(devices, print_device, &cnt); } static void cmd_list_nodes(int argc, char *argv[]) @@ -1505,32 +1610,56 @@ bt_shell_printf("Provisioning started\n"); } -static void add_node_setup(struct l_dbus_message *msg, void *user_data) +static void reprov_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *msg, void *user_data) { - char *str = user_data; - size_t sz; - unsigned char *uuid; - struct l_dbus_message_builder *builder; + if (l_dbus_message_is_error(msg)) { + const char *name; - uuid = l_util_from_hexstring(str, &sz); - if (!uuid || sz != 16 || !l_uuid_is_valid(uuid)) { - l_error("Failed to generate UUID array from %s", str); + prov_in_progress = false; + l_dbus_message_get_error(msg, &name, NULL); + l_error("Failed to start provisioning: %s", name); return; } + bt_shell_printf("Reprovisioning started\n"); +} + +static void reprovision_setup(struct l_dbus_message *msg, void *user_data) +{ + uint16_t target = L_PTR_TO_UINT(user_data); + uint8_t nppi = L_PTR_TO_UINT(user_data) >> 16; + struct l_dbus_message_builder *builder; + builder = l_dbus_message_builder_new(msg); - append_byte_array(builder, uuid, 16); + l_dbus_message_builder_append_basic(builder, 'q', &target); l_dbus_message_builder_enter_array(builder, "{sv}"); /* TODO: populate with options when defined */ + append_dict_entry_basic(builder, "NPPI", "y", &nppi); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_finalize(builder); l_dbus_message_builder_destroy(builder); +} - l_free(uuid); +static void add_node_setup(struct l_dbus_message *msg, void *user_data) +{ + struct unprov_device *dev = user_data; + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(msg); + append_byte_array(builder, dev->uuid, 16); + l_dbus_message_builder_enter_array(builder, "{sv}"); + /* TODO: populate with options when defined */ + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); } static void cmd_start_prov(int argc, char *argv[]) { + struct unprov_device *dev = NULL; + int id; + if (!local || !local->proxy || !local->mgmt_proxy) { bt_shell_printf("Node is not attached\n"); return; @@ -1541,14 +1670,96 @@ return; } - if (!argv[1] || (strlen(argv[1]) != 32)) { + if (!argv[1]) { + bt_shell_printf(COLOR_RED "Requires UUID\n" COLOR_RED); + return; + } + + if (*(argv[1]) == '#') { + if (sscanf(argv[1] + 1, "%d", &id) == 1) + dev = l_queue_find(devices, match_by_id, + L_UINT_TO_PTR(id)); + + if (!dev) { + bt_shell_printf(COLOR_RED "unknown id\n" COLOR_RED); + return; + } + } else if (strlen(argv[1]) == 32) { + size_t sz; + uint8_t *uuid = l_util_from_hexstring(argv[1], &sz); + + if (sz != 16) { + bt_shell_printf(COLOR_RED "Invalid UUID\n" COLOR_RED); + return; + } + + dev = l_queue_find(devices, match_device_uuid, uuid); + + if (!dev) { + dev = l_new(struct unprov_device, 1); + memcpy(dev->uuid, uuid, 16); + l_queue_push_tail(devices, dev); + } + + l_free(uuid); + + } else { bt_shell_printf(COLOR_RED "Requires UUID\n" COLOR_RED); return; } if (l_dbus_proxy_method_call(local->mgmt_proxy, "AddNode", add_node_setup, add_node_reply, - argv[1], NULL)) + dev, NULL)) + prov_in_progress = true; +} + +static void cmd_start_reprov(int argc, char *argv[]) +{ + uint16_t target = 0; + uint8_t nppi = 0; + + if (!local || !local->proxy || !local->mgmt_proxy) { + bt_shell_printf("Node is not attached\n"); + return; + } + + if (prov_in_progress) { + bt_shell_printf("Provisioning is already in progress\n"); + return; + } + + if (!argv[1]) { + bt_shell_printf(COLOR_RED "Requires Unicast\n" COLOR_RED); + return; + } + + if (argv[2]) { + char *end; + + nppi = strtol(argv[2], &end, 16); + } + + if (strlen(argv[1]) == 4) { + char *end; + + target = strtol(argv[1], &end, 16); + + if (end != (argv[1] + 4)) { + bt_shell_printf(COLOR_RED "Invalid Unicast\n" + COLOR_RED); + return; + } + + } else { + bt_shell_printf(COLOR_RED "Requires Unicast\n" COLOR_RED); + return; + } + + if (l_dbus_proxy_method_call(local->mgmt_proxy, "Reprovision", + reprovision_setup, reprov_reply, + L_UINT_TO_PTR(target + (nppi << 16)), + NULL)) prov_in_progress = true; } @@ -1581,6 +1792,8 @@ "List unprovisioned devices" }, { "provision", "", cmd_start_prov, "Initiate provisioning"}, + { "reprovision", " [0|1|2]", cmd_start_reprov, + "Refresh Device Key"}, { "node-import", " ", cmd_import_node, "Import an externally provisioned remote node"}, @@ -1684,6 +1897,8 @@ l_dbus_message_builder_enter_array(builder, "(qa{sv})"); build_model(builder, app.ele.mods[0], false, false); build_model(builder, app.ele.mods[1], false, false); + build_model(builder, app.ele.mods[2], false, false); + build_model(builder, app.ele.mods[3], false, false); l_dbus_message_builder_leave_array(builder); return true; @@ -1758,18 +1973,34 @@ /* TODO: Other methods */ } +static int sort_rssi(const void *a, const void *b, void *user_data) +{ + const struct unprov_device *new_dev = a; + const struct unprov_device *dev = b; + + if (b == finalized) + return 1; + + return dev->rssi - new_dev->rssi; +} + static struct l_dbus_message *scan_result_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { - struct l_dbus_message_iter iter, opts; + struct l_dbus_message_iter iter, opts, var; + struct unprov_device result, *dev; int16_t rssi; + uint16_t server = 0; uint32_t n; uint8_t *prov_data; - char *str; - struct unprov_device *dev; + const char *key; const char *sig = "naya{sv}"; + if (finalized == l_queue_peek_head(devices)) + goto done; + + if (!l_dbus_message_get_arguments(msg, sig, &rssi, &iter, &opts)) { l_error("Cannot parse scan results"); return l_dbus_message_new_error(msg, dbus_err_args, NULL); @@ -1781,42 +2012,72 @@ return l_dbus_message_new_error(msg, dbus_err_args, NULL); } - bt_shell_printf("Scan result:\n"); - bt_shell_printf("\t" COLOR_GREEN "rssi = %d\n" COLOR_OFF, rssi); - str = l_util_hexstring_upper(prov_data, 16); - bt_shell_printf("\t" COLOR_GREEN "UUID = %s\n" COLOR_OFF, str); - l_free(str); - - if (n >= 18) { - str = l_util_hexstring_upper(prov_data + 16, 2); - bt_shell_printf("\t" COLOR_GREEN "OOB = %s\n" COLOR_OFF, str); - l_free(str); + while (l_dbus_message_iter_next_entry(&opts, &key, &var)) { + if (!strcmp(key, "Server")) + l_dbus_message_iter_get_variant(&var, "q", &server); } - if (n >= 22) { - str = l_util_hexstring_upper(prov_data + 18, 4); - bt_shell_printf("\t" COLOR_GREEN "URI Hash = %s\n" COLOR_OFF, - str); - l_free(str); - } + memcpy(result.uuid, prov_data, 16); + result.server = server; + result.rssi = rssi; + result.id = 0; - /* TODO: Handle the rest of provisioning data if present */ + if (n > 16 && n <= 18) + result.oob_info = l_get_be16(prov_data + 16); + else + result.oob_info = 0; + + if (n > 18 && n <= 22) + result.uri_hash = l_get_be32(prov_data + 18); + else + result.uri_hash = 0; + + dev = l_queue_remove_if(devices, match_by_srv_uuid, &result); - dev = l_queue_find(devices, match_device_uuid, prov_data); if (!dev) { - dev = l_new(struct unprov_device, 1); - memcpy(dev->uuid, prov_data, sizeof(dev->uuid)); - /* TODO: timed self-destructor */ - l_queue_push_tail(devices, dev); - } + bt_shell_printf("\r" COLOR_YELLOW "Results = %d\n" COLOR_OFF, + l_queue_length(devices) + 1); + dev = l_malloc(sizeof(struct unprov_device)); + *dev = result; + + } else if (dev->rssi < result.rssi) + *dev = result; - /* Update with the latest rssi */ - dev->rssi = rssi; dev->last_seen = time(NULL); + l_queue_insert(devices, dev, sort_rssi, NULL); + +done: return l_dbus_message_new_method_return(msg); } +static struct l_dbus_message *req_reprov_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + uint8_t cnt; + uint16_t unicast, original; + struct l_dbus_message *reply; + + + if (!l_dbus_message_get_arguments(msg, "qy", &original, &cnt) || + !IS_UNICAST(original)) { + l_error("Cannot parse request for reprov data"); + return l_dbus_message_new_error(msg, dbus_err_args, NULL); + + } + + unicast = remote_get_next_unicast(low_addr, high_addr, cnt); + + bt_shell_printf("Assign addresses for %u elements\n", cnt); + bt_shell_printf("Original: %4.4x New: %4.4x\n", original, unicast); + + reply = l_dbus_message_new_method_return(msg); + l_dbus_message_set_arguments(reply, "q", unicast); + + return reply; +} + static struct l_dbus_message *req_prov_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) @@ -1825,6 +2086,7 @@ uint16_t unicast; struct l_dbus_message *reply; + /* Both calls handled identicaly except for parameter list */ if (!l_dbus_message_get_arguments(msg, "y", &cnt)) { l_error("Cannot parse request for prov data"); return l_dbus_message_new_error(msg, dbus_err_args, NULL); @@ -1833,14 +2095,14 @@ unicast = remote_get_next_unicast(low_addr, high_addr, cnt); - if (unicast == 0) { + if (!IS_UNICAST(unicast)) { l_error("Failed to allocate addresses for %u elements\n", cnt); return l_dbus_message_new_error(msg, "org.freedesktop.DBus.Error." "Failed to allocate address", NULL); } - bt_shell_printf("Assign addresses for %u elements\n", cnt); + bt_shell_printf("Assign addresses: %4.4x (cnt: %d)\n", unicast, cnt); reply = l_dbus_message_new_method_return(msg); l_dbus_message_set_arguments(reply, "qq", prov_net_idx, unicast); @@ -1852,11 +2114,13 @@ { struct unprov_device *dev; - dev = l_queue_remove_if(devices, match_device_uuid, uuid); - l_free(dev); + do { + dev = l_queue_remove_if(devices, match_device_uuid, uuid); + l_free(dev); + } while (dev); } -static struct l_dbus_message *add_node_cmplt_call(struct l_dbus *dbus, +static struct l_dbus_message *prov_cmplt_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { @@ -1866,6 +2130,7 @@ uint32_t n; uint8_t *uuid; + l_debug("ProvComplete"); if (!prov_in_progress) return l_dbus_message_new_error(msg, dbus_err_fail, NULL); @@ -1896,7 +2161,49 @@ return l_dbus_message_new_method_return(msg); } -static struct l_dbus_message *add_node_fail_call(struct l_dbus *dbus, +static struct l_dbus_message *reprov_cmplt_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + uint16_t unicast, original; + uint8_t old_cnt, cnt, nppi; + + l_debug("ReprovComplete"); + if (!prov_in_progress) + return l_dbus_message_new_error(msg, dbus_err_fail, NULL); + + prov_in_progress = false; + + if (!l_dbus_message_get_arguments(msg, "qyqy", &original, &nppi, + &unicast, &cnt)) { + l_error("Cannot parse reprov complete message"); + return l_dbus_message_new_error(msg, dbus_err_args, NULL); + + } + + l_debug("ReprovComplete org: %4.4x, nppi: %d, new: %4.4x, cnt: %d", + original, nppi, unicast, cnt); + old_cnt = remote_ele_cnt(original); + + if (nppi != 1 && (original != unicast || cnt != old_cnt)) { + l_error("Invalid reprov complete message (NPPI == %d)", nppi); + return l_dbus_message_new_error(msg, dbus_err_args, NULL); + } + + if (nppi) + remote_reset_node(original, unicast, cnt, + mesh_db_get_iv_index()); + + bt_shell_printf("Reprovisioning done (nppi: %d):\n", nppi); + remote_print_node(unicast); + + if (!mesh_db_reset_node(original, unicast, cnt)) + l_error("Failed to reset remote node"); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *prov_fail_call(struct l_dbus *dbus, struct l_dbus_message *msg, void *user_data) { @@ -1911,24 +2218,49 @@ prov_in_progress = false; if (!l_dbus_message_get_arguments(msg, "ays", &iter, &reason)) { - l_error("Cannot parse add node failed message"); + l_error("Cannot parse failed message"); return l_dbus_message_new_error(msg, dbus_err_args, NULL); - } - if (!l_dbus_message_iter_get_fixed_array(&iter, &uuid, &n) || - n != 16) { - l_error("Cannot parse add node failed message: uuid"); + if (!l_dbus_message_iter_get_fixed_array(&iter, &uuid, &n) || n != 16) { + l_error("Cannot parse failed message: uuid"); return l_dbus_message_new_error(msg, dbus_err_args, NULL); } bt_shell_printf("Provisioning failed:\n"); + str = l_util_hexstring_upper(uuid, 16); bt_shell_printf("\t" COLOR_RED "UUID = %s\n" COLOR_OFF, str); l_free(str); + remove_device(uuid); bt_shell_printf("\t" COLOR_RED "%s\n" COLOR_OFF, reason); - remove_device(uuid); + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *reprov_fail_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct l_dbus_message_iter iter; + uint16_t original = UNASSIGNED_ADDRESS; + char *reason; + + if (!prov_in_progress) + return l_dbus_message_new_error(msg, dbus_err_fail, NULL); + + prov_in_progress = false; + + if (!l_dbus_message_get_arguments(msg, "qs", &iter, &reason) || + !IS_UNICAST(original)) { + + l_error("Cannot parse Reprov failed message"); + return l_dbus_message_new_error(msg, dbus_err_args, NULL); + } + + bt_shell_printf("Reprovisioning failed:\n"); + bt_shell_printf("\t" COLOR_RED "UNICAST = %4.4x\n" COLOR_OFF, original); + bt_shell_printf("\t" COLOR_RED "%s\n" COLOR_OFF, reason); return l_dbus_message_new_method_return(msg); } @@ -1941,12 +2273,23 @@ l_dbus_interface_method(iface, "RequestProvData", 0, req_prov_call, "qq", "y", "net_index", "unicast", "count"); + l_dbus_interface_method(iface, "RequestReprovData", 0, req_reprov_call, + "q", "qy", "unicast", + "original", "count"); + l_dbus_interface_method(iface, "AddNodeComplete", 0, - add_node_cmplt_call, "", "ayqy", + prov_cmplt_call, "", "ayqy", "uuid", "unicast", "count"); - l_dbus_interface_method(iface, "AddNodeFailed", 0, add_node_fail_call, + l_dbus_interface_method(iface, "ReprovComplete", 0, + reprov_cmplt_call, "", "qyqy", + "original", "nppi", "unicast", "count"); + + l_dbus_interface_method(iface, "AddNodeFailed", 0, prov_fail_call, "", "ays", "uuid", "reason"); + + l_dbus_interface_method(iface, "ReprovFailed", 0, reprov_fail_call, + "", "qs", "unicast", "reason"); } static bool cid_getter(struct l_dbus *dbus, diff -Nru bluez-5.66/tools/mesh-cfgtest.c bluez-5.68/tools/mesh-cfgtest.c --- bluez-5.66/tools/mesh-cfgtest.c 2022-03-16 15:06:20.000000000 +0000 +++ bluez-5.68/tools/mesh-cfgtest.c 2023-06-30 08:10:20.000000000 +0000 @@ -38,14 +38,21 @@ #define MAX_CRPL_SIZE 0x7fff #define CFG_SRV_MODEL 0x0000 #define CFG_CLI_MODEL 0x0001 +#define RMT_PROV_SRV_MODEL 0x0004 +#define RMT_PROV_CLI_MODEL 0x0005 +#define PVT_BEACON_SRV_MODEL 0x0008 #define DEFAULT_IV_INDEX 0x0000 -#define IS_CONFIG_MODEL(x) ((x) == CFG_SRV_MODEL || (x) == CFG_CLI_MODEL) +#define IS_CONFIG_MODEL(x) (((x) == (CFG_SRV_MODEL)) || \ + ((x) == (CFG_CLI_MODEL)) || \ + ((x) == (RMT_PROV_SRV_MODEL)) || \ + ((x) == (RMT_PROV_CLI_MODEL))) struct meshcfg_el { const char *path; uint8_t index; - uint16_t mods[2]; + uint16_t location; + uint16_t mods[4]; uint32_t vmods[2]; }; @@ -77,6 +84,11 @@ uint8_t data[MAX_MSG_LEN]; }; +struct exp_rsp { + uint8_t test_id; + void *rsp; +}; + struct key_data { uint16_t idx; bool update; @@ -140,7 +152,9 @@ { .path = cli_ele_path_00, .index = PRIMARY_ELE_IDX, - .mods = {CFG_SRV_MODEL, CFG_CLI_MODEL}, + .location = 0x0001, + .mods = {CFG_SRV_MODEL, CFG_CLI_MODEL, + RMT_PROV_SRV_MODEL, PVT_BEACON_SRV_MODEL}, .vmods = {0xffffffff, 0xffffffff} } } @@ -158,13 +172,16 @@ { .path = srv_ele_path_00, .index = PRIMARY_ELE_IDX, - .mods = {CFG_SRV_MODEL, 0xffff}, + .location = 0x0001, + .mods = {CFG_SRV_MODEL, RMT_PROV_SRV_MODEL, + PVT_BEACON_SRV_MODEL, 0xffff}, .vmods = {0xffffffff, 0xffffffff} }, { .path = srv_ele_path_01, .index = PRIMARY_ELE_IDX + 1, - .mods = {0x1000, 0xffff}, + .location = 0x0002, + .mods = {0x1000, 0xffff, 0xffff, 0xffff}, .vmods = {0x5F10001, 0xffffffff} } } @@ -262,6 +279,11 @@ .data = {0x80, 0x03, 0x00, 0x01, 0x20, 0x00} }; +static struct exp_rsp test_add_appkey_expected = { + .test_id = 1, + .rsp = &test_add_appkey_rsp, +}; + static struct key_data test_add_appkey_req = { .idx = 0x002, .update = false @@ -285,6 +307,11 @@ .data = { 0x80, 0x0E, 0x7} }; +static struct exp_rsp test_set_ttl_expected = { + .test_id = 2, + .rsp = &test_set_ttl_rsp +}; + static struct msg_data test_set_ttl_req = { .len = 3, .data = { 0x80, 0x0D, 0x7} @@ -295,27 +322,42 @@ .data = { 0x80, 0x3E, 0x00, 0xCE, 0x0B, 0x01, 0x00, 0x00, 0x10}, }; +static struct exp_rsp test_bind_expected = { + .test_id = 3, + .rsp = &test_bind_rsp +}; + 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 exp_rsp test_bind_inv_mod_expected = { + .test_id = 4, + .rsp = &test_bind_inv_mod_rsp +}; + 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} + .len = 32, + .data = { 0x02, 0x00, 0xf1, 0x05, 0x02, 0x00, 0x01, 0x00, + 0xff, 0x7f, 0x05, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, + 0x02, 0x00, 0x01, 0x01, 0x00, 0x10, 0xf1, 0x05, 0x01, 0x00} +}; + +static struct exp_rsp test_dev_comp_expected = { + .test_id = 5, + .rsp = &test_dev_comp_rsp }; static struct msg_data test_dev_comp_req = { @@ -955,11 +997,101 @@ return true; } +static bool location_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, 'q', &ele->location); + + return true; +} + +static bool find_model(uint8_t *buf, uint32_t len, uint8_t *mod, uint8_t sz) +{ + bool found = false; + + while (len >= sz) { + if (!memcmp(buf, mod, sz)) { + /* Disallow duplicates */ + if (found) + return false; + + found = true; + } + + buf += sz; + len -= sz; + } + + return found; +} + +static bool check_device_composition(struct msg_data *rsp, uint32_t len, + uint8_t *data) +{ + uint32_t cnt; + + if (len != rsp->len) + return false; + + if (!memcmp(data, rsp->data, len)) + return true; + + /* Allow for a different ordering of model IDs */ + + /* First, check that the fixed length data matches */ + if (memcmp(data, rsp->data, 12)) + return false; + + cnt = 12; + data += 12; + + while (cnt < len) { + uint8_t s, v, i; + + if ((len - cnt) < 4) + return false; + + /* Check element index, location and model count */ + if (memcmp(data, rsp->data + cnt, 4)) + return false; + + s = data[2]; + v = data[3]; + + if ((cnt + s * 2 + v * 4) > len) + return false; + + data += 4; + cnt += 4; + + for (i = 0; i < s; i++) { + if (!find_model(&rsp->data[cnt], s * 2, data, 2)) + return false; + data += 2; + } + + cnt += s * 2; + + for (i = 0; i < v; i++) { + if (!find_model(&rsp->data[cnt], v * 4, data, 4)) + return false; + data += 4; + } + + cnt += v * 4; + } + + 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; @@ -983,7 +1115,7 @@ uint32_t i; for (i = 0; i < n; i++) - printf("%x ", data[i]); + printf("%02x ", data[i]); printf("\n"); } @@ -999,9 +1131,24 @@ l_tester_pre_setup_failed(tester); } } else { - rsp = l_tester_get_data(tester); + struct exp_rsp *exp = l_tester_get_data(tester); + bool res = false; + + if (exp && exp->rsp) { + if (exp->test_id == 5) + /* Check device composition */ + res = check_device_composition(exp->rsp, n, + data); + else { + struct msg_data *rsp = exp->rsp; + + if (n == rsp->len && + !memcmp(data, rsp->data, n)) + res = true; + } + } - if (rsp && rsp->len == n && !memcmp(data, rsp->data, n)) + if (res) l_idle_oneshot(test_success, NULL, NULL); else l_idle_oneshot(test_fail, NULL, NULL); @@ -1019,7 +1166,8 @@ vmod_getter, NULL); l_dbus_interface_property(iface, "Models", 0, "a(qa{sv})", mod_getter, NULL); - + l_dbus_interface_property(iface, "Location", 0, "q", location_getter, + NULL); /* Methods */ l_dbus_interface_method(iface, "DevKeyMessageReceived", 0, dev_msg_recv_call, "", "qbqay", "source", @@ -1411,23 +1559,23 @@ 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); + NULL, NULL, 2, &test_add_appkey_expected, NULL); tester_add_with_response("Config Default TTL Set: Success", &test_set_ttl_req, send_cfg_msg, - &test_set_ttl_rsp); + &test_set_ttl_expected); tester_add_with_response("Config Get Device Composition: Success", &test_dev_comp_req, send_cfg_msg, - &test_dev_comp_rsp); + &test_dev_comp_expected); tester_add_with_response("Config Bind: Success", &test_bind_req, send_cfg_msg, - &test_bind_rsp); + &test_bind_expected); tester_add_with_response("Config Bind: Error Invalid Model", &test_bind_inv_mod_req, send_cfg_msg, - &test_bind_inv_mod_rsp); + &test_bind_inv_mod_expected); l_tester_start(tester, done_callback); diff -Nru bluez-5.66/tools/mgmt-tester.c bluez-5.68/tools/mgmt-tester.c --- bluez-5.66/tools/mgmt-tester.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/tools/mgmt-tester.c 2023-06-30 08:10:20.000000000 +0000 @@ -290,6 +290,20 @@ tester_post_teardown_complete(); } +#define MAX_COREDUMP_LINE_LEN 40 + +struct devcoredump_test_data { + enum devcoredump_state { + HCI_DEVCOREDUMP_IDLE, + HCI_DEVCOREDUMP_ACTIVE, + HCI_DEVCOREDUMP_DONE, + HCI_DEVCOREDUMP_ABORT, + HCI_DEVCOREDUMP_TIMEOUT, + } state; + unsigned int timeout; + char data[MAX_COREDUMP_LINE_LEN]; +}; + struct hci_cmd_data { uint16_t opcode; uint8_t len; @@ -362,6 +376,8 @@ bool set_adv; const uint8_t *adv_data; uint8_t adv_data_len; + const struct devcoredump_test_data *dump_data; + const char (*expect_dump_data)[MAX_COREDUMP_LINE_LEN]; }; static const uint8_t set_exp_feat_param_debug[] = { @@ -9429,7 +9445,7 @@ static const uint8_t get_phy_param[] = { 0xff, 0x7f, 0x00, 0x00, /* All PHYs */ 0xfe, 0x79, 0x00, 0x00, /* All PHYs except BR 1M 1SLOT, LE 1M TX & LE 1M RX */ - 0xff, 0x07, 0x00, 0x00, /* All BREDR PHYs and LE 1M TX & LE 1M RX */ + 0xff, 0x7f, 0x00, 0x00, /* All PHYs */ }; static const struct generic_data get_phy_success = { @@ -9492,26 +9508,6 @@ 0xff, 0x7f, 0x00, 0x00 /* All PHYs */ }; -static const uint8_t set_default_phy_all_param[] = { - 0x00, /* preference is there for tx and rx */ - 0x07, /* 1m 2m coded tx */ - 0x07, /* 1m 2m coded rx */ -}; - -static const struct generic_data set_phy_all_success = { - .setup_settings = settings_powered_le, - .send_opcode = MGMT_OP_SET_PHY_CONFIGURATION, - .send_param = set_phy_all_param, - .send_len = sizeof(set_phy_all_param), - .expect_status = MGMT_STATUS_SUCCESS, - .expect_hci_command = BT_HCI_CMD_LE_SET_DEFAULT_PHY, - .expect_hci_param = set_default_phy_all_param, - .expect_hci_len = sizeof(set_default_phy_all_param), - .expect_alt_ev = MGMT_EV_PHY_CONFIGURATION_CHANGED, - .expect_alt_ev_param = set_phy_all_param, - .expect_alt_ev_len = sizeof(set_phy_all_param), -}; - static const uint8_t set_phy_2m_tx_param[] = { 0xff, 0x0f, 0x00, 0x00 /* 1mtxrx, 2m tx */ }; @@ -9619,10 +9615,13 @@ .expect_alt_ev_len = sizeof(start_discovery_le_evt), }; -static const char start_discovery_valid_ext_scan_param[] = { +static const char start_discovery_ext_scan_param[] = { 0x01, /* Own Addr type*/ 0x00, /* Scan filter policy*/ - 0x01, /*Phys - 1m */ + 0x05, /* Phys - 1m and Coded*/ + 0x01, /* Type */ + 0x12, 0x00, /* Interval */ + 0x12, 0x00, /* Window */ 0x01, /* Type */ 0x12, 0x00, /* Interval */ 0x12, 0x00, /* Window */ @@ -9637,8 +9636,8 @@ .expect_param = start_discovery_le_param, .expect_len = sizeof(start_discovery_le_param), .expect_hci_command = BT_HCI_CMD_LE_SET_EXT_SCAN_PARAMS, - .expect_hci_param = start_discovery_valid_ext_scan_param, - .expect_hci_len = sizeof(start_discovery_valid_ext_scan_param), + .expect_hci_param = start_discovery_ext_scan_param, + .expect_hci_len = sizeof(start_discovery_ext_scan_param), .expect_alt_ev = MGMT_EV_DISCOVERING, .expect_alt_ev_param = start_discovery_le_evt, .expect_alt_ev_len = sizeof(start_discovery_le_evt), @@ -9670,6 +9669,15 @@ .expect_alt_ev_len = sizeof(stop_discovery_evt), }; +static const char start_discovery_2m_ext_scan_param[] = { + 0x01, /* Own Addr type*/ + 0x00, /* Scan filter policy*/ + 0x01, /* Phys - 1m and Coded*/ + 0x01, /* Type */ + 0x12, 0x00, /* Interval */ + 0x12, 0x00, /* Window */ +}; + static const struct generic_data start_discovery_le_2m_scan_param = { .setup_settings = settings_powered_le, .setup_send_opcode = MGMT_OP_SET_PHY_CONFIGURATION, @@ -9682,8 +9690,8 @@ .expect_param = start_discovery_bredrle_param, .expect_len = sizeof(start_discovery_bredrle_param), .expect_hci_command = BT_HCI_CMD_LE_SET_EXT_SCAN_PARAMS, - .expect_hci_param = start_discovery_valid_ext_scan_param, - .expect_hci_len = sizeof(start_discovery_valid_ext_scan_param), + .expect_hci_param = start_discovery_2m_ext_scan_param, + .expect_hci_len = sizeof(start_discovery_2m_ext_scan_param), .expect_alt_ev = MGMT_EV_DISCOVERING, .expect_alt_ev_param = start_discovery_evt, .expect_alt_ev_len = sizeof(start_discovery_evt), @@ -12511,6 +12519,139 @@ tester_wait(2, trigger_force_resume, NULL); } +#define MAX_COREDUMP_BUF_LEN 512 + +static const struct devcoredump_test_data data_complete_dump = { + .state = HCI_DEVCOREDUMP_DONE, + .data = "test data", +}; + +static const char expected_complete_dump[][MAX_COREDUMP_LINE_LEN] = { + "Bluetooth devcoredump", + "State: 2", + "Controller Name: vhci_ctrl", + "Firmware Version: vhci_fw", + "Driver: vhci_drv", + "Vendor: vhci", + "--- Start dump ---", + "", /* end of header data */ +}; + +static const struct generic_data dump_complete = { + .dump_data = &data_complete_dump, + .expect_dump_data = expected_complete_dump, +}; + +static const struct devcoredump_test_data data_abort_dump = { + .state = HCI_DEVCOREDUMP_ABORT, + .data = "test data", +}; + +static const char expected_abort_dump[][MAX_COREDUMP_LINE_LEN] = { + "Bluetooth devcoredump", + "State: 3", + "Controller Name: vhci_ctrl", + "Firmware Version: vhci_fw", + "Driver: vhci_drv", + "Vendor: vhci", + "--- Start dump ---", + "", /* end of header data */ +}; + +static const struct generic_data dump_abort = { + .dump_data = &data_abort_dump, + .expect_dump_data = expected_abort_dump, +}; + +static const struct devcoredump_test_data data_timeout_dump = { + .state = HCI_DEVCOREDUMP_TIMEOUT, + .timeout = 1, + .data = "test data", +}; + +static const char expected_timeout_dump[][MAX_COREDUMP_LINE_LEN] = { + "Bluetooth devcoredump", + "State: 4", + "Controller Name: vhci_ctrl", + "Firmware Version: vhci_fw", + "Driver: vhci_drv", + "Vendor: vhci", + "--- Start dump ---", + "", /* end of header data */ +}; + +static const struct generic_data dump_timeout = { + .dump_data = &data_timeout_dump, + .expect_dump_data = expected_timeout_dump, +}; + +static void verify_devcd(void *user_data) +{ + struct test_data *data = tester_get_data(); + const struct generic_data *test = data->test_data; + struct vhci *vhci = hciemu_get_vhci(data->hciemu); + char buf[MAX_COREDUMP_BUF_LEN] = {0}; + char delim[] = "\n"; + char *line; + char *saveptr; + int i = 0; + + /* Read the generated devcoredump file */ + if (vhci_read_devcd(vhci, buf, sizeof(buf)) <= 0) { + tester_warn("Unable to read devcoredump"); + tester_test_failed(); + return; + } + + /* Verify if all devcoredump header fields are present */ + line = strtok_r(buf, delim, &saveptr); + while (strlen(test->expect_dump_data[i])) { + if (!line || strcmp(line, test->expect_dump_data[i])) { + tester_warn("Incorrect coredump data: %s (expected %s)", + line, test->expect_dump_data[i]); + tester_test_failed(); + return; + } + + if (!strcmp(strtok(line, ":"), "State")) { + /* After updating the devcoredump state, the HCI + * devcoredump API adds a `\0` at the end. Skip it + * before reading the next line. + */ + saveptr++; + } + + line = strtok_r(NULL, delim, &saveptr); + i++; + } + + /* Verify the devcoredump data */ + if (!line || strcmp(line, test->dump_data->data)) { + tester_warn("Incorrect coredump data: %s (expected %s)", line, + test->dump_data->data); + tester_test_failed(); + return; + } + + tester_test_passed(); +} + +static void test_hci_devcd(const void *test_data) +{ + struct test_data *data = tester_get_data(); + const struct generic_data *test = data->test_data; + struct vhci *vhci = hciemu_get_vhci(data->hciemu); + + /* Triggers the devcoredump */ + if (vhci_force_devcd(vhci, test->dump_data, sizeof(*test->dump_data))) { + tester_warn("Unable to set force_devcoredump"); + tester_test_abort(); + return; + } + + tester_wait(test->dump_data->timeout + 1, verify_devcd, NULL); +} + int main(int argc, char *argv[]) { tester_init(&argc, &argv); @@ -14018,9 +14159,6 @@ test_bredrle50("Set PHY coded Succcess", &set_phy_coded_success, NULL, test_command_generic); - test_bredrle50("Set PHY 1m 2m coded Succcess", &set_phy_all_success, - NULL, test_command_generic); - test_bredrle50("Set PHY 2m tx success", &set_phy_2m_tx_success, NULL, test_command_generic); @@ -14651,5 +14789,29 @@ setup_ll_privacy_add_device, test_command_generic); + /* HCI Devcoredump + * Setup : Power on + * Run: Trigger devcoredump via force_devcoredump + * Expect: Devcoredump is generated with correct data + */ + test_bredrle("HCI Devcoredump - Dump Complete", &dump_complete, NULL, + test_hci_devcd); + + /* HCI Devcoredump + * Setup : Power on + * Run: Trigger devcoredump via force_devcoredump + * Expect: Devcoredump is generated with correct data + */ + test_bredrle("HCI Devcoredump - Dump Abort", &dump_abort, NULL, + test_hci_devcd); + + /* HCI Devcoredump + * Setup : Power on + * Run: Trigger devcoredump via force_devcoredump + * Expect: Devcoredump is generated with correct data + */ + test_bredrle_full("HCI Devcoredump - Dump Timeout", &dump_timeout, NULL, + test_hci_devcd, 3); + return tester_run(); } diff -Nru bluez-5.66/tools/rfcomm.1 bluez-5.68/tools/rfcomm.1 --- bluez-5.66/tools/rfcomm.1 2022-11-10 20:41:43.000000000 +0000 +++ bluez-5.68/tools/rfcomm.1 2023-06-30 22:21:20.000000000 +0000 @@ -49,7 +49,7 @@ Prints information about all configured RFCOMM devices. .TP .B \-r -Switch TTY into raw mode (doesn\(aqt work with "bind"). +Switch TTY into raw mode (doesn\(aqt work with \(dqbind\(dq). .UNINDENT .INDENT 0.0 .TP diff -Nru bluez-5.66/tools/sdptool.1 bluez-5.68/tools/sdptool.1 --- bluez-5.66/tools/sdptool.1 2022-11-10 20:41:44.000000000 +0000 +++ bluez-5.68/tools/sdptool.1 2023-06-30 22:21:21.000000000 +0000 @@ -45,7 +45,7 @@ .sp Services are identified and manipulated with a 4\-byte \fBrecord_handle\fP (NOT the service name). To find a service\(aqs \fBrecord_handle\fP, look for the -"Service RecHandle" line in the \fBsearch\fP or \fBbrowse\fP results +\(dqService RecHandle\(dq line in the \fBsearch\fP or \fBbrowse\fP results .INDENT 0.0 .TP .B search [\-\-bdaddr bdaddr] [\-\-tree] [\-\-raw] [\-\-xml] service_name diff -Nru bluez-5.66/tools/test-runner.c bluez-5.68/tools/test-runner.c --- bluez-5.66/tools/test-runner.c 2022-11-10 20:24:03.000000000 +0000 +++ bluez-5.68/tools/test-runner.c 2023-06-30 08:10:20.000000000 +0000 @@ -54,7 +54,7 @@ static int num_devs = 0; static const char *qemu_binary = NULL; static const char *kernel_image = NULL; -static bool audio_support; +static char *audio_server; static const char *qemu_table[] = { "qemu-system-x86_64", @@ -247,21 +247,19 @@ snprintf(cmdline, sizeof(cmdline), "console=ttyS0,115200n8 earlyprintk=serial " - "rootfstype=9p " + "no_hash_pointers=1 rootfstype=9p " "rootflags=trans=virtio,version=9p2000.u " "acpi=off pci=noacpi noapic quiet ro init=%s " "TESTHOME=%s TESTDBUS=%u TESTDAEMON=%u " "TESTDBUSSESSION=%u XDG_RUNTIME_DIR=/run/user/0 " - "TESTAUDIO=%u " "TESTMONITOR=%u TESTEMULATOR=%u TESTDEVS=%d " - "TESTAUTO=%u TESTARGS=\'%s\'", + "TESTAUTO=%u TESTAUDIO='%s' TESTARGS=\'%s\'", initcmd, cwd, start_dbus, start_daemon, - start_dbus_session, audio_support, + start_dbus_session, start_monitor, start_emulator, num_devs, - run_auto, testargs); + run_auto, audio_server, testargs); argv = alloca(sizeof(qemu_argv) + - (audio_support ? 4 : 0) + (sizeof(char *) * (4 + (num_devs * 4)))); memcpy(argv, qemu_argv, sizeof(qemu_argv)); @@ -274,24 +272,6 @@ } argv[0] = (char *) qemu_binary; - if (audio_support) { - char *xdg_runtime_dir, *audiodev; - - xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); - if (!xdg_runtime_dir) { - fprintf(stderr, "XDG_RUNTIME_DIR not set\n"); - exit(1); - } - audiodev = alloca(40 + strlen(xdg_runtime_dir)); - sprintf(audiodev, "id=audio,driver=pa,server=%s/pulse/native", - xdg_runtime_dir); - - argv[pos++] = "-audiodev"; - argv[pos++] = audiodev; - argv[pos++] = "-device"; - argv[pos++] = "AC97,audiodev=audio"; - } - argv[pos++] = "-kernel"; argv[pos++] = (char *) kernel_image; argv[pos++] = "-append"; @@ -744,61 +724,110 @@ return pid; } -static void trigger_udev(void) +static int create_pipewire_conf(void) { - char *argv[3], *envp[1]; - pid_t pid; + static const char *const dirs[] = { + "/run/conf", + "/run/conf/wireplumber", + "/run/conf/wireplumber/bluetooth.lua.d", + "/run/conf/wireplumber/main.lua.d", + NULL + }; + int i; + FILE *f; - argv[0] = "/bin/udevadm"; - argv[1] = "trigger"; - argv[2] = NULL; + for (i = 0; dirs[i]; ++i) + mkdir(dirs[i], 0755); - envp[0] = NULL; + /* Enable only Bluetooth part, disable whatever requires user DBus */ + f = fopen("/run/conf/wireplumber/main.lua.d/51-custom.lua", "w"); + if (!f) + goto fail; + + fprintf(f, "alsa_monitor.enabled = false\n" + "v4l2_monitor.enabled = false\n" + "libcamera_monitor.enabled = false\n" + "default_access.properties[\"enable-flatpak-portal\"]" + " = false\n"); + fclose(f); + + f = fopen("/run/conf/wireplumber/bluetooth.lua.d/51-custom.lua", "w"); + if (!f) + goto fail; + + fprintf(f, "bluez_monitor.properties[\"with-logind\"] = false\n" + "bluez_midi_monitor.enabled = false\n"); + fclose(f); - printf("Triggering udev events\n"); + return 0; - pid = fork(); - if (pid < 0) { - perror("Failed to fork new process"); - return; - } - - if (pid == 0) { - execve(argv[0], argv, envp); - exit(EXIT_SUCCESS); - } - - printf("udev trigger process %d created\n", pid); +fail: + perror("Failed to create Pipewire config"); + return -1; } -static pid_t start_udevd(void) +static int start_audio_server(pid_t pids[2]) { - char *argv[2], *envp[1]; - pid_t pid; + char *daemons[2] = {NULL, NULL}; + char wp_exe[PATH_MAX]; + char *ptr; + char *envp[5]; + int i; - argv[0] = "/lib/systemd/systemd-udevd"; - argv[1] = NULL; + for (i = 0; i < 2; ++i) + pids[i] = -1; - envp[0] = NULL; + daemons[0] = audio_server; - printf("Starting udevd daemon\n"); + ptr = strrchr(audio_server, '/'); + if (ptr && !strcmp(ptr, "/pipewire")) { + if (create_pipewire_conf()) + return -1; - pid = fork(); - if (pid < 0) { - perror("Failed to fork new process"); - return -1; - } + snprintf(wp_exe, sizeof(wp_exe), "%.*s/wireplumber", + (int)(ptr - audio_server), audio_server); + daemons[1] = wp_exe; + + setenv("PIPEWIRE_RUNTIME_DIR", "/run", 1); + } + + envp[0] = "DBUS_SYSTEM_BUS_ADDRESS=unix:" + "path=/run/dbus/system_bus_socket"; + envp[1] = "XDG_CONFIG_HOME=/run/conf"; + envp[2] = "XDG_STATE_HOME=/run"; + envp[3] = "XDG_RUNTIME_DIR=/run"; + envp[4] = NULL; + + for (i = 0; i < 2; ++i) { + const char *daemon = daemons[i]; + char *argv[2]; + pid_t pid; - if (pid == 0) { - execve(argv[0], argv, envp); - exit(EXIT_SUCCESS); - } + if (!daemon) + continue; - printf("udevd daemon process %d created\n", pid); + printf("Starting audio server %s\n", daemon); - trigger_udev(); + argv[0] = (char *) daemon; + argv[1] = NULL; - return pid; + pid = fork(); + if (pid < 0) { + perror("Failed to fork new process"); + return -1; + } + + if (pid == 0) { + execve(argv[0], argv, envp); + exit(EXIT_SUCCESS); + } + + pids[i] = pid; + + printf("Audio server process %d created\n", pid); + } + + return 0; } static void run_command(char *cmdname, char *home) @@ -807,7 +836,8 @@ int pos = 0, idx = 0; int serial_fd; pid_t pid, dbus_pid, daemon_pid, monitor_pid, emulator_pid, - dbus_session_pid, udevd_pid; + dbus_session_pid, audio_pid[2]; + int i; if (!home) { perror("Invalid parameter: TESTHOME"); @@ -828,11 +858,6 @@ } else serial_fd = -1; - if (audio_support) - udevd_pid = start_udevd(); - else - udevd_pid = -1; - if (start_dbus) { create_dbus_system_conf(); dbus_pid = start_dbus_daemon(false); @@ -860,6 +885,11 @@ else emulator_pid = -1; + if (audio_server) + start_audio_server(audio_pid); + else + audio_pid[0] = audio_pid[1] = -1; + start_next: if (run_auto) { if (chdir(home + 5) < 0) { @@ -961,9 +991,11 @@ monitor_pid = -1; } - if (corpse == udevd_pid) { - printf("udevd terminated\n"); - udevd_pid = -1; + for (i = 0; i < 2; ++i) { + if (corpse == audio_pid[i]) { + printf("Audio server %d terminated\n", i); + audio_pid[i] = -1; + } } if (corpse == pid) @@ -975,6 +1007,11 @@ goto start_next; } + for (i = 0; i < 2; ++i) { + if (audio_pid[i] > 0) + kill(audio_pid[i], SIGTERM); + } + if (daemon_pid > 0) kill(daemon_pid, SIGTERM); @@ -990,9 +1027,6 @@ if (monitor_pid > 0) kill(monitor_pid, SIGTERM); - if (udevd_pid > 0) - kill(udevd_pid, SIGTERM); - if (serial_fd >= 0) close(serial_fd); } @@ -1073,10 +1107,15 @@ start_emulator = true; } - ptr = strstr(cmdline, "TESTAUDIO=1"); + ptr = strstr(cmdline, "TESTAUDIO='"); if (ptr) { - printf("Audio support requested\n"); - audio_support = true; + const char *start = ptr + 11; + const char *end = strchr(start, '\''); + + if (end) { + audio_server = strndup(start, end - start); + printf("Audio server %s requested\n", audio_server); + } } ptr = strstr(cmdline, "TESTHOME="); @@ -1102,10 +1141,10 @@ "\t-d, --daemon Start bluetoothd\n" "\t-m, --monitor Start btmon\n" "\t-l, --emulator Start btvirt\n" + "\t-A, --audio[=path] Start audio server\n" "\t-u, --unix [path] Provide serial device\n" "\t-q, --qemu QEMU binary\n" "\t-k, --kernel Kernel image (bzImage)\n" - "\t-A, --audio Add audio support\n" "\t-h, --help Show help options\n"); } @@ -1120,7 +1159,7 @@ { "monitor", no_argument, NULL, 'm' }, { "qemu", required_argument, NULL, 'q' }, { "kernel", required_argument, NULL, 'k' }, - { "audio", no_argument, NULL, 'A' }, + { "audio", optional_argument, NULL, 'A' }, { "version", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { } @@ -1140,7 +1179,7 @@ for (;;) { int opt; - opt = getopt_long(argc, argv, "aubdslmq:k:Avh", main_options, + opt = getopt_long(argc, argv, "aubdslmq:k:A::vh", main_options, NULL); if (opt < 0) break; @@ -1175,7 +1214,7 @@ kernel_image = optarg; break; case 'A': - audio_support = true; + audio_server = optarg ? optarg : "/usr/bin/pipewire"; break; case 'v': printf("%s\n", VERSION); diff -Nru bluez-5.66/unit/test-bap.c bluez-5.68/unit/test-bap.c --- bluez-5.66/unit/test-bap.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/unit/test-bap.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,1202 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2022 Intel Corporation. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/shared/util.h" +#include "src/shared/io.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/bap.h" +#include "src/shared/lc3.h" + +struct test_config { + struct bt_bap_pac_qos pqos; + struct iovec cc; + struct bt_bap_qos qos; + bool snk; + bool src; + bool vs; +}; + +struct test_data { + struct bt_gatt_client *client; + struct gatt_db *db; + struct bt_bap *bap; + struct bt_bap_pac *snk; + struct bt_bap_pac *src; + struct iovec *caps; + struct test_config *cfg; + struct bt_bap_stream *stream; + size_t iovcnt; + struct iovec *iov; +}; + +/* + * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz + * Duration: 7.5 ms 10 ms + * Channel count: 3 + * Frame length: 30-240 + */ +static struct iovec lc3_caps = LC3_CAPABILITIES(LC3_FREQ_ANY, LC3_DURATION_ANY, + 3u, 30, 240); + +#define iov_data(args...) ((const struct iovec[]) { args }) + +#define define_test(name, function, _cfg, args...) \ + do { \ + const struct iovec iov[] = { args }; \ + static struct test_data data; \ + data.caps = &lc3_caps; \ + data.cfg = _cfg; \ + data.iovcnt = ARRAY_SIZE(iov_data(args)); \ + data.iov = util_iov_dup(iov, ARRAY_SIZE(iov_data(args))); \ + tester_add(name, &data, test_setup, function, \ + test_teardown); \ + } while (0) + +static void client_ready_cb(bool success, uint8_t att_ecode, void *user_data) +{ + if (!success) + tester_setup_failed(); + else + tester_setup_complete(); +} + +/* GATT Discover All procedure */ +static const struct iovec setup_data[] = { + /* ATT: Exchange MTU Response (0x03) len 2 + * Server RX MTU: 64 + */ + IOV_DATA(0x02, 0x40, 0x00), + /* ATT: Exchange MTU Request (0x02) len 2 + * Client RX MTU: 64 + */ + IOV_DATA(0x03, 0x40, 0x00), + /* ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0001-0xffff + * Attribute type: Server Supported Features (0x2b3a) + */ + IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x3a, 0x2b), + /* ATT: Error Response (0x01) len 4 + * Read By Type Request (0x08) + * Handle: 0x0001 + * Error: Attribute Not Found (0x0a) + */ + IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a), + /* + * ATT: Read By Group Type Request (0x10) len 6 + * Handle range: 0x0001-0xffff + * Attribute group type: Primary Service (0x2800) + */ + IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), + /* + * ATT: Read By Group Type Response (0x11) len 37 + * Attribute data length: 6 + * Attribute group list: 2 entries + * Handle range: 0x0001-0x0013 + * UUID: Published Audio Capabilities (0x1850) + * Handle range: 0x0014-0x0023 + * UUID: Audio Stream Control (0x184e) + */ + IOV_DATA(0x11, 0x06, + 0x01, 0x00, 0x13, 0x00, 0x50, 0x18, + 0x14, 0x00, 0x23, 0x00, 0x4e, 0x18), + /* ATT: Read By Group Type Request (0x10) len 6 + * Handle range: 0x0024-0xffff + * Attribute group type: Primary Service (0x2800) + */ + IOV_DATA(0x10, 0x24, 0x00, 0xff, 0xff, 0x00, 0x28), + /* ATT: Error Response (0x01) len 4 + * Read By Group Type Request (0x10) + * Handle: 0x0024 + * Error: Attribute Not Found (0x0a) + */ + IOV_DATA(0x01, 0x10, 0x24, 0x00, 0x0a), + /* ATT: Read By Group Type Request (0x10) len 6 + * Handle range: 0x0001-0xffff + * Attribute group type: Secondary Service (0x2801) + */ + IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28), + /* ATT: Error Response (0x01) len 4 + * Read By Group Type Request (0x10) + * Handle: 0x0001 + * Error: Attribute Not Found (0x0a) + */ + IOV_DATA(0x01, 0x10, 0x01, 0x00, 0x0a), + /* ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0001-0x0023 + * Attribute group type: Include (0x2802) + */ + IOV_DATA(0x08, 0x01, 0x00, 0x23, 0x00, 0x02, 0x28), + /* ATT: Error Response (0x01) len 4 + * Read By Group Type Request (0x10) + * Handle: 0x0001 + * Error: Attribute Not Found (0x0a) + */ + IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a), + /* ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0001-0x0023 + * Attribute type: Characteristic (0x2803) + */ + IOV_DATA(0x08, 0x01, 0x00, 0x23, 0x00, 0x03, 0x28), + /* ATT: Read By Type Response (0x09) len 57 + * Attribute data length: 7 + * Attribute data list: 8 entries + * Handle: 0x0002 + * Value: 120300c92b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0003 + * Value UUID: Sink PAC (0x2bc9) + * Handle: 0x0005 + * Value: 120600ca2b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0006 + * Value UUID: Sink Audio Locations (0x2bca) + * Handle: 0x0008 + * Value: 120900cb2b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0009 + * Value UUID: Source PAC (0x2bcb) + * Handle: 0x000b + * Value: 120c00cc2b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x000c + * Value UUID: Source Audio Locations (0x2bcc) + * Handle: 0x000e + * Value: 120f00cd2b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x000f + * Value UUID: Available Audio Contexts (0x2bcd) + * Handle: 0x0011 + * Value: 121200ce2b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0012 + * Value UUID: Supported Audio Contexts (0x2bce) + * Handle: 0x0015 + * Value: 121600c42b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0016 + * Value UUID: Sink ASE (0x2bc4) + * Handle: 0x0018 + * Value: 121900c42b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0019 + * Value UUID: Sink ASE (0x2bc4) + */ + IOV_DATA(0x09, 0x07, + 0x02, 0x00, 0x12, 0x03, 0x00, 0xc9, 0x2b, + 0x05, 0x00, 0x12, 0x06, 0x00, 0xca, 0x2b, + 0x08, 0x00, 0x12, 0x09, 0x00, 0xcb, 0x2b, + 0x0b, 0x00, 0x12, 0x0c, 0x00, 0xcc, 0x2b, + 0x0e, 0x00, 0x12, 0x0f, 0x00, 0xcd, 0x2b, + 0x11, 0x00, 0x12, 0x12, 0x00, 0xce, 0x2b, + 0x15, 0x00, 0x12, 0x16, 0x00, 0xc4, 0x2b, + 0x18, 0x00, 0x12, 0x19, 0x00, 0xc4, 0x2b), + /* ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0001-0x0023 + * Attribute type: Characteristic (0x2803) + */ + IOV_DATA(0x08, 0x19, 0x00, 0x23, 0x00, 0x03, 0x28), + /* ATT: Read By Type Response (0x09) len 22 + * Attribute data length: 7 + * Attribute data list: 3 entries + * Handle: 0x001b + * Value: 121c00c52b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x001c + * Value UUID: Source ASE (0x2bc5) + * Handle: 0x001e + * Value: 121f00c52b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x001f + * Value UUID: Source ASE (0x2bc5) + * Handle: 0x0021 + * Value: 182200c62b + * Properties: 0x18 + * Write (0x08) + * Notify (0x10) + * Value Handle: 0x0022 + * Value UUID: ASE Control Point (0x2bc6) + */ + IOV_DATA(0x09, 0x07, + 0x1b, 0x00, 0x12, 0x1c, 0x00, 0xc5, 0x2b, + 0x1e, 0x00, 0x12, 0x1f, 0x00, 0xc5, 0x2b, + 0x21, 0x00, 0x18, 0x22, 0x00, 0xc6, 0x2b), + /* ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0022-0x0023 + * Attribute type: Characteristic (0x2803) + */ + IOV_DATA(0x08, 0x22, 0x00, 0x23, 0x00, 0x03, 0x28), + /* ATT: Error Response (0x01) len 4 + * Read By Type Request (0x08) + * Handle: 0x0022 + * Error: Attribute Not Found (0x0a) + */ + IOV_DATA(0x01, 0x08, 0x23, 0x00, 0x0a), + /* ACL Data TX: Handle 42 flags 0x00 dlen 11 + * ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0001-0xffff + * Attribute type: Database Hash (0x2b2a) + */ + IOV_DATA(0x08, 0x01, 0x00, 0xff, 0xff, 0x2a, 0x2b), + /* ATT: Error Response (0x01) len 4 + * Read By Type Request (0x08) + * Handle: 0x0001 + * Error: Attribute Not Found (0x0a) + */ + IOV_DATA(0x01, 0x08, 0x01, 0x00, 0x0a), +}; + +static void print_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + if (tester_use_debug()) + tester_debug("%s%s", prefix, str); +} + +static void test_setup(const void *user_data) +{ + struct test_data *data = (void *)user_data; + struct bt_att *att; + struct gatt_db *db; + struct io *io; + + io = tester_setup_io(setup_data, ARRAY_SIZE(setup_data)); + g_assert(io); + + att = bt_att_new(io_get_fd(io), false); + g_assert(att); + + bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL); + + db = gatt_db_new(); + g_assert(db); + + data->client = bt_gatt_client_new(db, att, 64, 0); + g_assert(data->client); + + bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:", + NULL); + + bt_gatt_client_ready_register(data->client, client_ready_cb, data, + NULL); + + bt_att_unref(att); + gatt_db_unref(db); +} + +static void test_complete_cb(const void *user_data) +{ + tester_test_passed(); +} + +static void bap_config(struct bt_bap_stream *stream, + uint8_t code, uint8_t reason, + void *user_data) +{ + if (code) + tester_test_failed(); +} + +static bool pac_found(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + void *user_data) +{ + struct test_data *data = user_data; + unsigned int config_id; + + data->stream = bt_bap_stream_new(data->bap, lpac, rpac, + &data->cfg->qos, + &data->cfg->cc); + g_assert(data->stream); + + config_id = bt_bap_stream_config(data->stream, &data->cfg->qos, + &data->cfg->cc, bap_config, data); + g_assert(config_id); + + return true; +} + +static void bap_ready(struct bt_bap *bap, void *user_data) +{ + bt_bap_foreach_pac(bap, BT_BAP_SINK, pac_found, user_data); + bt_bap_foreach_pac(bap, BT_BAP_SOURCE, pac_found, user_data); +} + +static void test_client_config(struct test_data *data) +{ + if (!data->cfg) + return; + + if (data->cfg->src) { + if (data->cfg->vs) + data->snk = bt_bap_add_vendor_pac(data->db, + "test-bap-snk", + BT_BAP_SINK, 0x0ff, + 0x0001, 0x0001, + NULL, data->caps, NULL); + else + data->snk = bt_bap_add_pac(data->db, "test-bap-snk", + BT_BAP_SINK, LC3_ID, + NULL, data->caps, NULL); + g_assert(data->snk); + } + + if (data->cfg->snk) { + if (data->cfg->vs) + data->src = bt_bap_add_vendor_pac(data->db, + "test-bap-src", + BT_BAP_SOURCE, 0x0ff, + 0x0001, 0x0001, + NULL, data->caps, NULL); + else + data->src = bt_bap_add_pac(data->db, "test-bap-src", + BT_BAP_SOURCE, LC3_ID, + NULL, data->caps, NULL); + g_assert(data->src); + } +} + +static void test_client(const void *user_data) +{ + struct test_data *data = (void *)user_data; + struct io *io; + + io = tester_setup_io(data->iov, data->iovcnt); + g_assert(io); + + tester_io_set_complete_func(test_complete_cb); + + data->db = gatt_db_new(); + g_assert(data->db); + + test_client_config(data); + + data->bap = bt_bap_new(data->db, bt_gatt_client_get_db(data->client)); + g_assert(data->bap); + + bt_bap_set_debug(data->bap, print_debug, "bt_bap:", NULL); + + bt_bap_ready_register(data->bap, bap_ready, data, NULL); + + bt_bap_attach(data->bap, data->client); +} + +static void test_teardown(const void *user_data) +{ + struct test_data *data = (void *)user_data; + + bt_bap_unref(data->bap); + bt_gatt_client_unref(data->client); + util_iov_free(data->iov, data->iovcnt); + + bt_bap_remove_pac(data->snk); + bt_bap_remove_pac(data->src); + gatt_db_unref(data->db); + + tester_teardown_complete(); +} + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x0003 Type: Sink PAC (0x2bc9) + * ATT: Read Response (0x0b) len 24 + * Value: 010600000000100301ff0002020302030305041e00f00000 + * Handle: 0x0003 Type: Sink PAC (0x2bc9) + * Number of PAC(s): 1 + * PAC #0: + * Codec: LC3 (0x06) + * Codec Specific Capabilities #0: len 0x03 type 0x01 + * Sampling Frequencies: 0x00ff + * 8 Khz (0x0001) + * 11.25 Khz (0x0002) + * 16 Khz (0x0004) + * 22.05 Khz (0x0008) + * 24 Khz (0x0010) + * 32 Khz (0x0020) + * 44.1 Khz (0x0040) + * 48 Khz (0x0080) + * Codec Specific Capabilities #1: len 0x02 type 0x02 + * Frame Duration: 0x0003 + * 7.5 ms (0x01) + * 10 ms (0x02) + * Codec Specific Capabilities #2: len 0x02 type 0x03 + * Audio Channel Count: 0x03 + * 1 channel (0x01) + * 2 channels (0x02) + * Codec Specific Capabilities #3: len 0x05 type 0x04 + * Frame Length: 30 (0x001e) - 240 (0x00f0) + * ATT: Read Request (0x0a) len 2 + * Handle: 0x0006 Type: Sink Audio Location (0x2bca) + * ATT: Read Response (0x0b) len 4 + * Value: 03000000 + * Handle: 0x0006 Type: Sink Audio Locations (0x2bca) + * Location: 0x00000003 + * Front Left (0x00000001) + * Front Right (0x00000002) + */ +#define DISC_SNK_PAC(_caps...) \ + IOV_DATA(0x0a, 0x03, 0x00), \ + IOV_DATA(0x0b, 0x01, _caps), \ + IOV_DATA(0x0a, 0x06, 0x00), \ + IOV_DATA(0x0b, 0x03, 0x00, 0x00, 0x00) + +#define DISC_SNK_LC3 \ + DISC_SNK_PAC(0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x01, \ + 0xff, 0x00, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x05, 0x04, \ + 0x1e, 0x00, 0xf0, 0x00, 0x00) + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x0009 Type: Source PAC (0x2bcb) + * ATT: Read Response (0x0b) len 24 + * Value: 010600000000100301ff0002020302030305041e00f00000 + * Handle: 0x0009 Type: Source PAC (0x2bcb) + * Number of PAC(s): 1 + * PAC #0: + * Codec: LC3 (0x06) + * Codec Specific Capabilities #0: len 0x03 type 0x01 + * Sampling Frequencies: 0x00ff + * 8 Khz (0x0001) + * 11.25 Khz (0x0002) + * 16 Khz (0x0004) + * 22.05 Khz (0x0008) + * 24 Khz (0x0010) + * 32 Khz (0x0020) + * 44.1 Khz (0x0040) + * 48 Khz (0x0080) + * Codec Specific Capabilities #1: len 0x02 type 0x02 + * Frame Duration: 0x0003 + * 7.5 ms (0x01) + * 10 ms (0x02) + * Codec Specific Capabilities #2: len 0x02 type 0x03 + * Audio Channel Count: 0x03 + * 1 channel (0x01) + * 2 channels (0x02) + * Codec Specific Capabilities #3: len 0x05 type 0x04 + * Frame Length: 30 (0x001e) - 240 (0x00f0) + * ATT: Read Request (0x0a) len 2 + * Handle: 0x000c Type: Source Audio Location (0x2bcc) + * ATT: Read Response (0x0b) len 4 + * Value: 03000000 + * Handle: 0x000c Type: Source Audio Locations (0x2bcc) + * Location: 0x00000003 + * Front Left (0x00000001) + * Front Right (0x00000002) + */ +#define DISC_SRC_PAC(_caps...) \ + DISC_SNK_PAC(_caps), \ + IOV_DATA(0x0a, 0x09, 0x00), \ + IOV_DATA(0x0b, 0x01, _caps), \ + IOV_DATA(0x0a, 0x0c, 0x00), \ + IOV_DATA(0x0b, 0x03, 0x00, 0x00, 0x00) + +#define DISC_SRC_LC3 \ + DISC_SRC_PAC(0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x01, \ + 0xff, 0x00, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x05, 0x04, \ + 0x1e, 0x00, 0xf0, 0x00, 0x00) + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x000f Type: Available Audio Contexts (0x2bcd) + * ATT: Read Response (0x0b) len 4 + * Value: ff0f0e00 + * Handle: 0x000f Type: Available Audio Contexts (0x2bcd) + */ +#define DISC_CTX(_caps...) \ + DISC_SRC_PAC(_caps), \ + IOV_DATA(0x0a, 0x0f, 0x00), \ + IOV_DATA(0x0b, 0xff, 0x0f, 0x0e, 0x00) + +#define DISC_CTX_LC3 \ + DISC_CTX(0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x01, \ + 0xff, 0x00, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x05, 0x04, \ + 0x1e, 0x00, 0xf0, 0x00, 0x00) + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x0012 Type: Supported Audio Contexts (0x2bce) + * ATT: Read Response (0x0b) len 4 + * Value: ff0f0e00 + * Handle: 0x0012 Type: Supported Audio Contexts (0x2bce) + */ +#define DISC_SUP_CTX(_caps...) \ + DISC_CTX(_caps), \ + IOV_DATA(0x0a, 0x12, 0x00), \ + IOV_DATA(0x0b, 0xff, 0x0f, 0x0e, 0x00) + +#define DISC_SUP_CTX_LC3 \ + DISC_SUP_CTX(0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x01, \ + 0xff, 0x00, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x05, 0x04, \ + 0x1e, 0x00, 0xf0, 0x00, 0x00) + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x0016 Type: Sink ASE (0x2bc4) + * ATT: Read Response (0x0b) len 4 + * Value: 0100 + * Handle: 0x0016 Type: Sink ASE (0x2bc4) + * ATT: Write Request (0x12) len 4 + * Handle: 0x0017 Type: Client Characteristic Configuration (0x2902) + * Data: 0100 + * Notification (0x01) + * ATT: Write Response (0x13) len 0 + * ATT: Read Request (0x0a) len 2 + * Handle: 0x0019 Type: Sink ASE (0x2bc4) + * ATT: Read Response (0x0b) len 4 + * Value: 0200 + * Handle: 0x0019 Type: Sink ASE (0x2bc4) + * ATT: Write Request (0x12) len 4 + * Handle: 0x001a Type: Client Characteristic Configuration (0x2902) + * Data: 0100 + * Notification (0x01) + * ATT: Write Response (0x13) len 0 + */ +#define DISC_SNK_ASE(_caps...) \ + DISC_SUP_CTX(_caps), \ + IOV_DATA(0x0a, 0x16, 0x00), \ + IOV_DATA(0x0b, 0x01, 0x00), \ + IOV_DATA(0x12, 0x17, 0x00, 0x01, 0x00), \ + IOV_DATA(0x13), \ + IOV_DATA(0x0a, 0x19, 0x00), \ + IOV_DATA(0x0b, 0x02, 0x00), \ + IOV_DATA(0x12, 0x1a, 0x00, 0x01, 0x00), \ + IOV_DATA(0x13) + +#define DISC_SNK_ASE_LC3 \ + DISC_SNK_ASE(0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x01, \ + 0xff, 0x00, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x05, 0x04, \ + 0x1e, 0x00, 0xf0, 0x00, 0x00) + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x001c Type: Source ASE (0x2bc5) + * ATT: Read Response (0x0b) len 4 + * Value: 0300 + * Handle: 0x001c Type: Source ASE (0x2bc5) + * ATT: Write Request (0x12) len 4 + * Handle: 0x001d Type: Client Characteristic Configuration (0x2902) + * Data: 0100 + * Notification (0x01) + * ATT: Write Response (0x13) len 0 + * ATT: Read Request (0x0a) len 2 + * Handle: 0x001f Type: Source ASE (0x2bc5) + * ATT: Read Response (0x0b) len 4 + * Value: 0400 + * Handle: 0x001f Type: Source ASE (0x2bc5) + * ATT: Write Request (0x12) len 4 + * Handle: 0x0020 Type: Client Characteristic Configuration (0x2902) + * Data: 0100 + * Notification (0x01) + * ATT: Write Response (0x13) len 0 + * ATT: Write Request (0x12) len 4 + * Handle: 0x0023 Type: Client Characteristic Configuration (0x2902) + * Data: 0100 + * Notification (0x01) + * ATT: Write Response (0x13) len 0 + */ +#define DISC_SRC_ASE(_cfg...) \ + DISC_SNK_ASE(_cfg), \ + IOV_DATA(0x0a, 0x1c, 0x00), \ + IOV_DATA(0x0b, 0x03, 0x00), \ + IOV_DATA(0x12, 0x1d, 0x00, 0x01, 0x00), \ + IOV_DATA(0x13), \ + IOV_DATA(0x0a, 0x1f, 0x00), \ + IOV_DATA(0x0b, 0x04, 0x00), \ + IOV_DATA(0x12, 0x20, 0x00, 0x01, 0x00), \ + IOV_DATA(0x13), \ + IOV_DATA(0x12, 0x23, 0x00, 0x01, 0x00), \ + IOV_DATA(0x13) + +#define DISC_SRC_ASE_LC3 \ + DISC_SRC_ASE(0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x01, \ + 0xff, 0x00, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x05, 0x04, \ + 0x1e, 0x00, 0xf0, 0x00, 0x00) + +static void test_disc(void) +{ + /* The IUT discovers the characteristics specified in the PAC + * Characteristic and Location Characteristic columns in Table 4.4. + * The IUT reads the values of the characteristics specified in the PAC + * Characteristic and Location Characteristic columns. + */ + define_test("BAP/UCL/DISC/BV-01-C", test_client, NULL, DISC_SNK_LC3); + define_test("BAP/UCL/DISC/BV-02-C", test_client, NULL, DISC_SRC_LC3); + + /* BAP/UCL/DISC/BV-06-C [Discover Available Audio Contexts] + * + * The IUT successfully reads the value of the Available Audio Contexts + * characteristic on the LowerTester. + */ + define_test("BAP/UCL/DISC/BV-06-C", test_client, NULL, DISC_CTX_LC3); + + /* BAP/UCL/DISC/BV-05-C [Discover Supported Audio Contexts] + * + * The IUT successfully reads the value of the Supported Audio Contexts + * characteristic on the Lower Tester. + */ + define_test("BAP/UCL/DISC/BV-05-C", test_client, NULL, + DISC_SUP_CTX_LC3); + + /* BAP/UCL/DISC/BV-03-C [Discover Sink ASE_ID] + * BAP/UCL/DISC/BV-04-C [Discover Source ASE_ID] + * + * The IUT successfully reads the ASE_ID values of each discovered ASE + * characteristic on the LowerTester. + */ + define_test("BAP/UCL/DISC/BV-03-C", test_client, NULL, + DISC_SNK_ASE_LC3); + define_test("BAP/UCL/DISC/BV-04-C", test_client, NULL, + DISC_SRC_ASE_LC3); +} + +/* ATT: Write Command (0x52) len 23 + * Handle: 0x0022 + * Data: 0101010202_cfg + * ATT: Handle Value Notification (0x1b) len 7 + * Handle: 0x0022 + * Data: 0101010000 + * ATT: Handle Value Notification (0x1b) len 37 + * Handle: 0x0016 + * Data: 01010102010a00204e00409c00204e00409c00_cfg + */ +#define SCC_SNK(_cfg...) \ + IOV_DATA(0x52, 0x22, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, _cfg), \ + IOV_DATA(0x1b, 0x22, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00), \ + IOV_NULL, \ + IOV_DATA(0x1b, 0x16, 0x00, 0x01, 0x01, 0x01, 0x02, 0x01, 0x0a, 0x00, \ + 0x20, 0x4e, 0x00, 0x40, 0x9c, 0x00, 0x20, 0x4e, 0x00, \ + 0x40, 0x9c, 0x00, _cfg) + +#define SCC_SNK_LC3(_cc...) \ + DISC_SRC_ASE_LC3, \ + SCC_SNK(0x06, 0x00, 0x00, 0x00, 0x00, _cc) + +#define QOS_BALANCED_2M \ + { \ + .target_latency = BT_BAP_CONFIG_LATENCY_BALANCED, \ + .io_qos.phy = BT_BAP_CONFIG_PHY_2M, \ + } +#define QOS_UCAST \ +{\ + .ucast = QOS_BALANCED_2M, \ +} +static struct test_config cfg_snk_8_1 = { + .cc = LC3_CONFIG_8_1, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_8_1 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x1a, 0x00) + +static struct test_config cfg_snk_8_2 = { + .cc = LC3_CONFIG_8_2, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_8_2 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x1e, 0x00) + +static struct test_config cfg_snk_16_1 = { + .cc = LC3_CONFIG_16_1, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_16_1 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x03, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x1e, 0x00) + +static struct test_config cfg_snk_16_2 = { + .cc = LC3_CONFIG_16_2, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_16_2 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x03, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x28, 0x00) + +static struct test_config cfg_snk_24_1 = { + .cc = LC3_CONFIG_24_1, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_24_1 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x05, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x2d, 0x00) + +static struct test_config cfg_snk_24_2 = { + .cc = LC3_CONFIG_24_2, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_24_2 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x05, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x3c, 0x00) + +static struct test_config cfg_snk_32_1 = { + .cc = LC3_CONFIG_32_1, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_32_1 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x06, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x3c, 0x00) + +static struct test_config cfg_snk_32_2 = { + .cc = LC3_CONFIG_32_2, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_32_2 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x06, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x50, 0x00) + +static struct test_config cfg_snk_44_1 = { + .cc = LC3_CONFIG_44_1, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_44_1 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x07, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x62, 0x00) + +static struct test_config cfg_snk_44_2 = { + .cc = LC3_CONFIG_44_2, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_44_2 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x07, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x82, 0x00) + +static struct test_config cfg_snk_48_1 = { + .cc = LC3_CONFIG_48_1, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_48_1 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x4b, 0x00) + +static struct test_config cfg_snk_48_2 = { + .cc = LC3_CONFIG_48_2, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_48_2 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x64, 0x00) + +static struct test_config cfg_snk_48_3 = { + .cc = LC3_CONFIG_48_3, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_48_3 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x5a, 0x00) + +static struct test_config cfg_snk_48_4 = { + .cc = LC3_CONFIG_48_4, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_48_4 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x78, 0x00) + +static struct test_config cfg_snk_48_5 = { + .cc = LC3_CONFIG_48_5, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_48_5 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x75, 0x00) + +static struct test_config cfg_snk_48_6 = { + .cc = LC3_CONFIG_48_6, + .qos = QOS_UCAST, + .snk = true, +}; + +#define SCC_SNK_48_6 \ + SCC_SNK_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x9b, 0x00) + +/* ATT: Write Command (0x52) len 23 + * Handle: 0x0022 + * Data: 0101030202_cfg + * ATT: Handle Value Notification (0x1b) len 7 + * Handle: 0x0022 + * Data: 0101030000 + * ATT: Handle Value Notification (0x1b) len 37 + * Handle: 0x001c + * Data: 03010102010a00204e00409c00204e00409c00_cfg + */ +#define SCC_SRC(_cfg...) \ + IOV_DATA(0x52, 0x22, 0x00, 0x01, 0x01, 0x03, 0x02, 0x02, _cfg), \ + IOV_DATA(0x1b, 0x22, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00), \ + IOV_NULL, \ + IOV_DATA(0x1b, 0x1c, 0x00, 0x03, 0x01, 0x01, 0x02, 0x01, 0x0a, 0x00, \ + 0x20, 0x4e, 0x00, 0x40, 0x9c, 0x00, 0x20, 0x4e, 0x00, \ + 0x40, 0x9c, 0x00, _cfg) + +#define SCC_SRC_LC3(_cc...) \ + DISC_SRC_ASE_LC3, \ + SCC_SRC(0x06, 0x00, 0x00, 0x00, 0x00, _cc) + +static struct test_config cfg_src_8_1 = { + .cc = LC3_CONFIG_8_1, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_8_1 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x1a, 0x00) + +static struct test_config cfg_src_8_2 = { + .cc = LC3_CONFIG_8_2, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_8_2 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x1e, 0x00) + +static struct test_config cfg_src_16_1 = { + .cc = LC3_CONFIG_16_1, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_16_1 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x03, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x1e, 0x00) + +static struct test_config cfg_src_16_2 = { + .cc = LC3_CONFIG_16_2, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_16_2 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x03, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x28, 0x00) + +static struct test_config cfg_src_24_1 = { + .cc = LC3_CONFIG_24_1, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_24_1 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x05, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x2d, 0x00) + +static struct test_config cfg_src_24_2 = { + .cc = LC3_CONFIG_24_2, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_24_2 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x05, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x3c, 0x00) + +static struct test_config cfg_src_32_1 = { + .cc = LC3_CONFIG_32_1, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_32_1 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x06, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x3c, 0x00) + +static struct test_config cfg_src_32_2 = { + .cc = LC3_CONFIG_32_2, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_32_2 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x06, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x50, 0x00) + +static struct test_config cfg_src_44_1 = { + .cc = LC3_CONFIG_44_1, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_44_1 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x07, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x62, 0x00) + +static struct test_config cfg_src_44_2 = { + .cc = LC3_CONFIG_44_2, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_44_2 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x07, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x82, 0x00) + +static struct test_config cfg_src_48_1 = { + .cc = LC3_CONFIG_48_1, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_48_1 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x4b, 0x00) + +static struct test_config cfg_src_48_2 = { + .cc = LC3_CONFIG_48_2, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_48_2 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x64, 0x00) + +static struct test_config cfg_src_48_3 = { + .cc = LC3_CONFIG_48_3, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_48_3 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x5a, 0x00) + +static struct test_config cfg_src_48_4 = { + .cc = LC3_CONFIG_48_4, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_48_4 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x78, 0x00) + +static struct test_config cfg_src_48_5 = { + .cc = LC3_CONFIG_48_5, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_48_5 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x00, 0x03, 0x04, \ + 0x75, 0x00) + +static struct test_config cfg_src_48_6 = { + .cc = LC3_CONFIG_48_6, + .qos = QOS_UCAST, + .src = true, +}; + +#define SCC_SRC_48_6 \ + SCC_SRC_LC3(0x0a, 0x02, 0x01, 0x08, 0x02, 0x02, 0x01, 0x03, 0x04, \ + 0x9b, 0x00) + +/* Test Purpose: + * Verify that a Unicast Client IUT can initiate a Config Codec + * operation for an LC3 codec. + * + * Pass verdict: + * The IUT successfully writes to the ASE Control point with the opcode + * set to 0x01 (Config Codec) and correctly formatted parameter values + * from Table 4.9. The Codec_ID field is a 5-octet field with octet 0 + * set to the LC3 Coding_Format value defined in Bluetooth Assigned + * Numbers, octets 1–4 set to 0x0000. Each parameter (if present) + * included in the data sent in Codec_Specific_Configuration is + * formatted in an LTV structure with the length, type, and value + * specified in Table 4.10. + */ +static void test_scc_cc_lc3(void) +{ + define_test("BAP/UCL/SCC/BV-001-C [UCL SRC Config Codec, LC3 8_1]", + test_client, &cfg_snk_8_1, SCC_SNK_8_1); + define_test("BAP/UCL/SCC/BV-002-C [UCL SRC Config Codec, LC3 8_2]", + test_client, &cfg_snk_8_2, SCC_SNK_8_2); + define_test("BAP/UCL/SCC/BV-003-C [UCL SRC Config Codec, LC3 16_1]", + test_client, &cfg_snk_16_1, SCC_SNK_16_1); + define_test("BAP/UCL/SCC/BV-004-C [UCL SRC Config Codec, LC3 16_2]", + test_client, &cfg_snk_16_2, SCC_SNK_16_2); + define_test("BAP/UCL/SCC/BV-005-C [UCL SRC Config Codec, LC3 24_1]", + test_client, &cfg_snk_24_1, SCC_SNK_24_1); + define_test("BAP/UCL/SCC/BV-006-C [UCL SRC Config Codec, LC3 24_2]", + test_client, &cfg_snk_24_2, SCC_SNK_24_2); + define_test("BAP/UCL/SCC/BV-007-C [UCL SRC Config Codec, LC3 32_1]", + test_client, &cfg_snk_32_1, SCC_SNK_32_1); + define_test("BAP/UCL/SCC/BV-008-C [UCL SRC Config Codec, LC3 32_2]", + test_client, &cfg_snk_32_2, SCC_SNK_32_2); + define_test("BAP/UCL/SCC/BV-009-C [UCL SRC Config Codec, LC3 44.1_1]", + test_client, &cfg_snk_44_1, SCC_SNK_44_1); + define_test("BAP/UCL/SCC/BV-010-C [UCL SRC Config Codec, LC3 44.1_2]", + test_client, &cfg_snk_44_2, SCC_SNK_44_2); + define_test("BAP/UCL/SCC/BV-011-C [UCL SRC Config Codec, LC3 48_1]", + test_client, &cfg_snk_48_1, SCC_SNK_48_1); + define_test("BAP/UCL/SCC/BV-012-C [UCL SRC Config Codec, LC3 48_2]", + test_client, &cfg_snk_48_2, SCC_SNK_48_2); + define_test("BAP/UCL/SCC/BV-013-C [UCL SRC Config Codec, LC3 48_3]", + test_client, &cfg_snk_48_3, SCC_SNK_48_3); + define_test("BAP/UCL/SCC/BV-014-C [UCL SRC Config Codec, LC3 48_4]", + test_client, &cfg_snk_48_4, SCC_SNK_48_4); + define_test("BAP/UCL/SCC/BV-015-C [UCL SRC Config Codec, LC3 48_5]", + test_client, &cfg_snk_48_5, SCC_SNK_48_5); + define_test("BAP/UCL/SCC/BV-016-C [UCL SRC Config Codec, LC3 48_6]", + test_client, &cfg_snk_48_6, SCC_SNK_48_6); + define_test("BAP/UCL/SCC/BV-017-C [UCL SNK Config Codec, LC3 8_1]", + test_client, &cfg_src_8_1, SCC_SRC_8_1); + define_test("BAP/UCL/SCC/BV-018-C [UCL SNK Config Codec, LC3 8_2]", + test_client, &cfg_src_8_2, SCC_SRC_8_2); + define_test("BAP/UCL/SCC/BV-019-C [UCL SNK Config Codec, LC3 16_1]", + test_client, &cfg_src_16_1, SCC_SRC_16_1); + define_test("BAP/UCL/SCC/BV-020-C [UCL SNK Config Codec, LC3 16_2]", + test_client, &cfg_src_16_2, SCC_SRC_16_2); + define_test("BAP/UCL/SCC/BV-021-C [UCL SNK Config Codec, LC3 24_1]", + test_client, &cfg_src_24_1, SCC_SRC_24_1); + define_test("BAP/UCL/SCC/BV-022-C [UCL SNK Config Codec, LC3 24_2]", + test_client, &cfg_src_24_2, SCC_SRC_24_2); + define_test("BAP/UCL/SCC/BV-023-C [UCL SNK Config Codec, LC3 32_1]", + test_client, &cfg_src_32_1, SCC_SRC_32_1); + define_test("BAP/UCL/SCC/BV-024-C [UCL SNK Config Codec, LC3 32_2]", + test_client, &cfg_src_32_2, SCC_SRC_32_2); + define_test("BAP/UCL/SCC/BV-025-C [UCL SNK Config Codec, LC3 44.1_1]", + test_client, &cfg_src_44_1, SCC_SRC_44_1); + define_test("BAP/UCL/SCC/BV-026-C [UCL SNK Config Codec, LC3 44.1_2]", + test_client, &cfg_src_44_2, SCC_SRC_44_2); + define_test("BAP/UCL/SCC/BV-027-C [UCL SNK Config Codec, LC3 48_1]", + test_client, &cfg_src_48_1, SCC_SRC_48_1); + define_test("BAP/UCL/SCC/BV-028-C [UCL SNK Config Codec, LC3 48_2]", + test_client, &cfg_src_48_2, SCC_SRC_48_2); + define_test("BAP/UCL/SCC/BV-029-C [UCL SNK Config Codec, LC3 48_3]", + test_client, &cfg_src_48_3, SCC_SRC_48_3); + define_test("BAP/UCL/SCC/BV-030-C [UCL SNK Config Codec, LC3 48_4]", + test_client, &cfg_src_48_4, SCC_SRC_48_4); + define_test("BAP/UCL/SCC/BV-031-C [UCL SNK Config Codec, LC3 48_5]", + test_client, &cfg_src_48_5, SCC_SRC_48_5); + define_test("BAP/UCL/SCC/BV-032-C [UCL SNK Config Codec, LC3 48_6]", + test_client, &cfg_src_48_6, SCC_SRC_48_6); +} + +static struct test_config cfg_snk_vs = { + .cc = IOV_NULL, + .qos = QOS_UCAST, + .snk = true, + .vs = true, +}; + +#define DISC_SRC_ASE_VS \ + DISC_SRC_ASE(0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00) + +#define SCC_SNK_VS \ + DISC_SRC_ASE_VS, \ + SCC_SNK(0xff, 0x01, 0x00, 0x01, 0x00, 0x00) + +static struct test_config cfg_src_vs = { + .cc = IOV_NULL, + .qos = QOS_UCAST, + .src = true, + .vs = true, +}; + +#define SCC_SRC_VS \ + DISC_SRC_ASE_VS, \ + SCC_SRC(0xff, 0x01, 0x00, 0x01, 0x00, 0x00) + +/* Test Purpose: + * Verify that a Unicast Client IUT can initiate a Config Codec operation for a + * vendor-specific codec. + * + * Pass verdict: + * The IUT successfully writes to the ASE Control Point characteristic with the + * opcode set to 0x01 (Config Codec) and the specified parameters. The Codec_ID + * parameter is formatted with octet 0 set to 0xFF, octets 1–2 set to + * TSPX_VS_Company_ID, and octets 3–4 set to TSPX_VS_Codec_ID. + */ +static void test_scc_cc_vs(void) +{ + define_test("BAP/UCL/SCC/BV-033-C [UCL SRC Config Codec, VS]", + test_client, &cfg_snk_vs, SCC_SNK_VS); + define_test("BAP/UCL/SCC/BV-034-C [UCL SNK Config Codec, VS]", + test_client, &cfg_src_vs, SCC_SRC_VS); +} + +static void test_scc(void) +{ + test_scc_cc_lc3(); + test_scc_cc_vs(); +} + +int main(int argc, char *argv[]) +{ + tester_init(&argc, &argv); + + test_disc(); + test_scc(); + + return tester_run(); +} diff -Nru bluez-5.66/unit/test-bass.c bluez-5.68/unit/test-bass.c --- bluez-5.66/unit/test-bass.c 1970-01-01 00:00:00.000000000 +0000 +++ bluez-5.68/unit/test-bass.c 2023-06-30 08:10:20.000000000 +0000 @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright 2023 NXP + * + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/shared/util.h" +#include "src/shared/io.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/gatt-server.h" +#include "src/shared/bass.h" + +struct test_data { + struct gatt_db *db; + struct bt_bass *bass; + struct bt_gatt_server *server; + struct queue *ccc_states; + size_t iovcnt; + struct iovec *iov; +}; + +struct ccc_state { + uint16_t handle; + uint16_t value; +}; + +/* ATT: Exchange MTU Request (0x02) len 2 + * Client RX MTU: 64 + * ATT: Exchange MTU Response (0x03) len 2 + * Server RX MTU: 64 + */ +#define EXCHANGE_MTU \ + IOV_DATA(0x02, 0x40, 0x00), \ + IOV_DATA(0x03, 0x40, 0x00) + +/* ATT: Find By Type Value Request (0x06) len 8 + * Handle range: 0x0001-0xffff + * Attribute Type(UUID): Primary Service (0x2800) + * Value to find: Broadcast Audio Scan Service (0x184f) + * ATT: Find By Type Value Response (0x07) len 4 + * Handle range: 0x0001-0x0009 + * ATT: Find By Type Value Request (0x06) len 8 + * Handle range: 0x000a-0xffff + * Attribute Type(UUID): Primary Service (0x2800) + * Value to find: Broadcast Audio Scan Service (0x184f) + * ATT: Error Response (0x01) len 4 + * Find By Type Value Request (0x06) + * Handle: 0x000a + * Error: Attribute Not Found (0x0a) + */ +#define BASS_FIND_BY_TYPE_VALUE \ + IOV_DATA(0x06, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28, 0x4f, 0x18), \ + IOV_DATA(0x07, 0x01, 0x00, 0x09, 0x00), \ + IOV_DATA(0x06, 0x0a, 0x00, 0xff, 0xff, 0x00, 0x28, 0x4f, 0x18), \ + IOV_DATA(0x01, 0x06, 0x0a, 0x00, 0x0a) + +/* ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0001-0x0009 + * Attribute type: Characteristic (0x2803) + * ATT: Read By Type Response (0x09) len 22 + * Attribute data length: 7 + * Attribute data list: 3 entries + * Handle: 0x0002 + * Value: 120300c82b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0003 + * Value UUID: Broadcast Receive State (0x2bc8) + * Handle: 0x0005 + * Value: 120600c82b + * Properties: 0x12 + * Read (0x02) + * Notify (0x10) + * Value Handle: 0x0006 + * Value UUID: Broadcast Receive State (0x2bc8) + * Handle: 0x0008 + * Value: 0c0900c72b + * Properties: 0x0c + * Write (0x08) + * Write Without Response (0x04) + * Value Handle: 0x0009 + * Value UUID: Broadcast Audio Scan Control Point (0x2bc7) + * ATT: Read By Type Request (0x08) len 6 + * Handle range: 0x0009-0x0009 + * Attribute type: Characteristic (0x2803) + * ATT: Error Response (0x01) len 4 + * Read By Type Request (0x08) + * Handle: 0x0009 + * Error: Attribute Not Found (0x0a) + */ +#define DISC_BASS_CHAR \ + IOV_DATA(0x08, 0x01, 0x00, 0x09, 0x00, 0x03, 0x28), \ + IOV_DATA(0x09, 0x07, \ + 0x02, 0x00, 0x12, 0x03, 0x00, 0xc8, 0x2b, \ + 0x05, 0x00, 0x12, 0x06, 0x00, 0xc8, 0x2b, \ + 0x08, 0x00, 0x0c, 0x09, 0x00, 0xc7, 0x2b), \ + IOV_DATA(0x08, 0x09, 0x00, 0x09, 0x00, 0x03, 0x28), \ + IOV_DATA(0x01, 0x08, 0x09, 0x00, 0x0a) + +/* ATT: Read By Group Type Request (0x10) len 6 + * Handle range: 0x0001-0xffff + * Attribute group type: Primary Service (0x2800) + * ATT: Read By Group Type Response (0x11) len 7 + * Attribute data length: 6 + * Attribute group list: 1 entry + * Handle range: 0x0001-0x0009 + * UUID: Broadcast Audio Scan Service (0x184f) + * ATT: Read By Group Type Request (0x10) len 6 + * Handle range: 0x000a-0xffff + * Attribute group type: Primary Service (0x2800) + * ATT: Error Response (0x01) len 4 + * Read By Group Type Request (0x10) + * Handle: 0x000a + * Error: Attribute Not Found (0x0a) + */ +#define DISC_BASS_SER \ + EXCHANGE_MTU,\ + IOV_DATA(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \ + IOV_DATA(0x11, 0x06, 0x01, 0x00, 0x09, 0x00, 0x4f, 0x18), \ + IOV_DATA(0x10, 0x0a, 0x00, 0xff, 0xff, 0x00, 0x28), \ + IOV_DATA(0x01, 0x10, 0x0a, 0x00, 0x0a), \ + BASS_FIND_BY_TYPE_VALUE, \ + DISC_BASS_CHAR + +/* ATT: Find Information Request (0x04) len 4 + * Handle range: 0x0004-0x0004 + * ATT: Find Information Response (0x05) len 5 + * Format: Handle(s) and 16 bit bluetooth UUID(s) (0x01) + * Handle: 0x0004 + * Attribute: Client Characteristic Configuration (0x2902) + * ATT: Find Information Request (0x04) len 4 + * Handle range: 0x0007-0x0007 + * ATT: Find Information Response (0x05) len 5 + * Format: Handle(s) and 16 bit bluetooth UUID(s) (0x01) + * Handle: 0x0007 + * Attribute: Client Characteristic Configuration (0x2902) + */ +#define BASS_FIND_INFO \ + IOV_DATA(0x04, 0x04, 0x00, 0x04, 0x00), \ + IOV_DATA(0x05, 0x01, 0x04, 0x00, 0x02, 0x29), \ + IOV_DATA(0x04, 0x07, 0x00, 0x07, 0x00), \ + IOV_DATA(0x05, 0x01, 0x07, 0x00, 0x02, 0x29) + +#define DISC_BCAST_AUDIO_SCAN_CP \ + BASS_FIND_BY_TYPE_VALUE, \ + DISC_BASS_CHAR, \ + BASS_FIND_INFO + +/* ATT: Read Request (0x0a) len 2 + * Handle: 0x0004 Type: Client Characteristic Configuration (0x2902) + * ATT: Read Response (0x0b) len 2 + * Value: 0000 + * Handle: 0x0004 Type: Client Characteristic Configuration (0x2902) + * ATT: Read Request (0x0a) len 2 + * Handle: 0x0007 Type: Client Characteristic Configuration (0x2902) + * ATT: Read Response (0x0b) len 2 + * Value: 0000 + * Handle: 0x0007 Type: Client Characteristic Configuration (0x2902) + */ +#define BASS_READ_CHAR_DESC \ + IOV_DATA(0x0a, 0x04, 0x00), \ + IOV_DATA(0x0b, 0x00, 0x00), \ + IOV_DATA(0x0a, 0x07, 0x00), \ + IOV_DATA(0x0b, 0x00, 0x00) + +#define DISC_BCAST_RECV_STATE \ + DISC_BCAST_AUDIO_SCAN_CP, \ + BASS_READ_CHAR_DESC + +#define iov_data(args...) ((const struct iovec[]) { args }) + +#define define_test(name, function, _cfg, args...) \ + do { \ + const struct iovec iov[] = { args }; \ + static struct test_data data; \ + data.iovcnt = ARRAY_SIZE(iov_data(args)); \ + data.iov = util_iov_dup(iov, ARRAY_SIZE(iov_data(args))); \ + tester_add(name, &data, NULL, function, \ + test_teardown); \ + } while (0) + +static void test_complete_cb(const void *user_data) +{ + tester_test_passed(); +} + +static void print_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + if (tester_use_debug()) + tester_debug("%s%s", prefix, str); +} + +static void test_teardown(const void *user_data) +{ + struct test_data *data = (void *)user_data; + + bt_bass_unref(data->bass); + bt_gatt_server_unref(data->server); + util_iov_free(data->iov, data->iovcnt); + + gatt_db_unref(data->db); + + queue_destroy(data->ccc_states, free); + + tester_teardown_complete(); +} + +static bool ccc_state_match(const void *a, const void *b) +{ + const struct ccc_state *ccc = a; + uint16_t handle = PTR_TO_UINT(b); + + return ccc->handle == handle; +} + +static struct ccc_state *find_ccc_state(struct test_data *data, + uint16_t handle) +{ + return queue_find(data->ccc_states, ccc_state_match, + UINT_TO_PTR(handle)); +} + +static struct ccc_state *get_ccc_state(struct test_data *data, uint16_t handle) +{ + struct ccc_state *ccc; + + ccc = find_ccc_state(data, handle); + if (ccc) + return ccc; + + ccc = new0(struct ccc_state, 1); + ccc->handle = handle; + queue_push_tail(data->ccc_states, ccc); + + return ccc; +} + +static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct test_data *data = user_data; + struct ccc_state *ccc; + uint16_t handle; + uint8_t ecode = 0; + const uint8_t *value = NULL; + size_t len = 0; + + handle = gatt_db_attribute_get_handle(attrib); + + ccc = get_ccc_state(data, handle); + if (!ccc) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + len = sizeof(ccc->value); + value = (void *) &ccc->value; + +done: + gatt_db_attribute_read_result(attrib, id, ecode, value, len); +} + +static void test_server(const void *user_data) +{ + struct test_data *data = (void *)user_data; + struct bt_att *att; + struct io *io; + + io = tester_setup_io(data->iov, data->iovcnt); + g_assert(io); + + tester_io_set_complete_func(test_complete_cb); + + att = bt_att_new(io_get_fd(io), false); + g_assert(att); + + bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL); + + data->db = gatt_db_new(); + g_assert(data->db); + + gatt_db_ccc_register(data->db, gatt_ccc_read_cb, NULL, + NULL, data); + + data->bass = bt_bass_new(data->db, NULL); + g_assert(data->bass); + + data->server = bt_gatt_server_new(data->db, att, 64, 0); + g_assert(data->server); + + bt_gatt_server_set_debug(data->server, print_debug, "bt_gatt_server:", + NULL); + + data->ccc_states = queue_new(); + + tester_io_send(); + + bt_att_unref(att); +} + +static void test_sggit(void) +{ + /* BASS/SR/SGGIT/SER/BV-01-C [Service GGIT - Broadcast Scan] + * + * For each ATT_Read_By_Group_Type_Request, the IUT sends a correctly + * formatted ATT_Read_By_Group_Type_Response reporting BASS to the + * Lower Tester, or an ATT_Error_Response if there is no handle/UUID + * pair matching the request. + * + * For each ATT_Find_By_Type_Value_Request, the IUT sends one + * ATT_Find_By_Type_Value_Response reporting BASS to the Lower Tester, + * or an ATT_Error_Response when there are no more services matching + * the request. + * + * The IUT sends one ATT_Read_By_Type_Response to the Lower Tester for + * each received ATT_Read_By_Type_Request, if it has characteristic + * declarations within the handle range, or an ATT_Error_Response if + * there are no further characteristic declarations within the + * handle range of the request. The IUT reports all BASS + * characteristics. + */ + define_test("BASS/SR/SGGIT/SER/BV-01-C", test_server, NULL, + DISC_BASS_SER); + + /* BASS/SR/SGGIT/CHA/BV-01-C [Service GGIT - + * Broadcast Audio Scan Control Point] + * + * The IUT sends one ATT_Read_By_Type_Response to the Lower Tester for + * each received ATT_Read_By_Type_Request, if it has characteristic + * declarations within the handle range, or an ATT_Error_Response if + * there are no further characteristic declarations within the + * handle range of the request. The IUT reports one instance of the + * Broadcast Audio Scan Control Point characteristic. + */ + define_test("BASS/SR/SGGIT/CHA/BV-01-C", test_server, NULL, + DISC_BCAST_AUDIO_SCAN_CP); + + /* BASS/SR/SGGIT/CHA/BV-02-C [Service GGIT - + * Broadcast Receive State] + * + * The IUT sends one ATT_Read_By_Type_Response to the Lower Tester for + * each received ATT_Read_By_Type_Request, if it has characteristic + * declarations within the handle range, or an ATT_Error_Response if + * there are no further characteristic declarations within the + * handle range of the request. The IUT reports two instances of the + * Broadcast Receive State characteristic. + * + * The IUT sends one ATT_Find_Information_Response to the Lower Tester + * for each received ATT_Find_Information_Request, if it has + * characteristic descriptors within the handle range, or an + * ATT_Error_Response if there are no characteristic descriptors within + * the handle range of the request. For each Broadcast Receive State + * characteristic, the IUT reports one Client Characteristic + * Configuration descriptor. + * + * The IUT sends an ATT_Read_Response to the Lower Tester for each + * ATT_Read_Request. + */ + define_test("BASS/SR/SGGIT/CHA/BV-02-C", test_server, NULL, + DISC_BCAST_RECV_STATE); +} + +int main(int argc, char *argv[]) +{ + tester_init(&argc, &argv); + + test_sggit(); + + return tester_run(); +} diff -Nru bluez-5.66/unit/test-crypto.c bluez-5.68/unit/test-crypto.c --- bluez-5.66/unit/test-crypto.c 2021-02-22 20:27:00.000000000 +0000 +++ bluez-5.68/unit/test-crypto.c 2023-06-30 08:10:20.000000000 +0000 @@ -311,6 +311,78 @@ tester_test_passed(); } +static void test_sef(const void *data) +{ + const uint8_t sirk[16] = { + 0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce, + 0x22, 0xfd, 0xa1, 0x21, 0x09, 0x7d, 0x7d, 0x45 }; + const uint8_t k[16] = { + 0xd9, 0xce, 0xe5, 0x3c, 0x22, 0xc6, 0x1e, 0x06, + 0x6f, 0x69, 0x48, 0xd4, 0x9b, 0x1b, 0x6e, 0x67 }; + const uint8_t exp[16] = { + 0x46, 0xd3, 0x5f, 0xf2, 0xd5, 0x62, 0x25, 0x7e, + 0xa0, 0x24, 0x35, 0xe1, 0x35, 0x38, 0x0a, 0x17 }; + uint8_t res[16]; + + tester_debug("SIRK:"); + util_hexdump(' ', sirk, 16, print_debug, NULL); + + tester_debug("K:"); + util_hexdump(' ', k, 16, print_debug, NULL); + + if (!bt_crypto_sef(crypto, k, sirk, res)) { + tester_test_failed(); + return; + } + + tester_debug("Expected:"); + util_hexdump(' ', exp, 16, print_debug, NULL); + + tester_debug("Result:"); + util_hexdump(' ', res, 16, print_debug, NULL); + + if (memcmp(res, exp, 16)) { + tester_test_failed(); + return; + } + + tester_test_passed(); +} + +static void test_sih(const void *data) +{ + const uint8_t k[16] = { + 0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce, + 0x22, 0xfd, 0xa1, 0x21, 0x09, 0x7d, 0x7d, 0x45 }; + const uint8_t r[3] = { 0x63, 0xf5, 0x69 }; + const uint8_t exp[3] = { 0xda, 0x48, 0x19 }; + uint8_t hash[3]; + + tester_debug("K:"); + util_hexdump(' ', k, 16, print_debug, NULL); + + tester_debug("R:"); + util_hexdump(' ', r, 3, print_debug, NULL); + + if (!bt_crypto_sih(crypto, k, r, hash)) { + tester_test_failed(); + return; + } + + tester_debug("Expected:"); + util_hexdump(' ', exp, 3, print_debug, NULL); + + tester_debug("Result:"); + util_hexdump(' ', hash, 3, print_debug, NULL); + + if (memcmp(hash, exp, 3)) { + tester_test_failed(); + return; + } + + tester_test_passed(); +} + int main(int argc, char *argv[]) { int exit_status; @@ -337,6 +409,8 @@ NULL, test_verify_sign, NULL); tester_add("/crypto/verify_sign_too_short", &verify_sign_too_short_data, NULL, test_verify_sign, NULL); + tester_add("/crypto/sef", NULL, NULL, test_sef, NULL); + tester_add("/crypto/sih", NULL, NULL, test_sih, NULL); exit_status = tester_run(); diff -Nru bluez-5.66/unit/test-mesh-crypto.c bluez-5.68/unit/test-mesh-crypto.c --- bluez-5.66/unit/test-mesh-crypto.c 2021-10-13 18:38:35.000000000 +0000 +++ bluez-5.68/unit/test-mesh-crypto.c 2023-06-30 08:10:20.000000000 +0000 @@ -633,6 +633,36 @@ .beacon = "01003ecaff672f673370123456788ea261582f364f6f", }; +static const struct mesh_crypto_test s8_4_6_1 = { + .name = "8.4.6.1 Private Beacon IVU", + + .net_key = "f7a2a44f8e8a8029064f173ddc1e2b00", + .iv_index = 0x1010abcd, + + .enc_key = "6be76842460b2d3a5850d4698409f1bb", + .rand = "435f18f85cf78a3121f58478a5", + + .beacon_type = 0x02, + .beacon_flags = 0x02, + .beacon_cmac = "f3174f022a514741", + .beacon = "02435f18f85cf78a3121f58478a561e488e7cbf3174f022a514741", +}; + +static const struct mesh_crypto_test s8_4_6_2 = { + .name = "8.4.6.2 Private Beacon IVU Complete", + + .net_key = "3bbb6f1fbd53e157417f308ce7aec58f", + .iv_index = 0x00000000, + + .enc_key = "ca478cdac626b7a8522d7272dd124f26", + .rand = "1b998f82927535ea6f3076f422", + + .beacon_type = 0x02, + .beacon_flags = 0x00, + .beacon_cmac = "2f0ffb94cf97f881", + .beacon = "021b998f82927535ea6f3076f422ce827408ab2f0ffb94cf97f881", +}; + static const struct mesh_crypto_test s8_6_2 = { .name = "8.6.2 Service Data using Node Identity", @@ -683,6 +713,17 @@ l_free(str); } +static void verify_bool(const char *label, unsigned int indent, + bool sample, bool data) +{ + l_info("%-20s =%*c%s", label, 1 + (indent * 2), ' ', + sample ? "true" : "false"); + l_info("%-20s %*c%s => %s", "", 1 + (indent * 2), ' ', + data ? "true" : "false", + EVALNUM(sample, data)); + EXITNUM(sample, data); +} + static void verify_bool_not_both(const char *label, unsigned int indent, bool sample, bool data) { @@ -796,10 +837,11 @@ uint32_t hdr; uint64_t net_mic64, net_mic32; size_t net_msg_len; + bool status; uint8_t key_aid = keys->key_aid | (keys->akf ? KEY_ID_AKF : 0x00); if (keys->ctl) { - mesh_crypto_packet_build(keys->ctl, keys->net_ttl, + status = mesh_crypto_packet_build(keys->ctl, keys->net_ttl, keys->net_seq[0], keys->net_src, keys->net_dst, keys->opcode, @@ -809,7 +851,7 @@ enc_msg, len, packet, &packet_len); } else { - mesh_crypto_packet_build(keys->ctl, keys->net_ttl, + status = mesh_crypto_packet_build(keys->ctl, keys->net_ttl, keys->net_seq[0], keys->net_src, keys->net_dst, keys->opcode, @@ -822,6 +864,10 @@ l_info(COLOR_YELLOW "Segment-%d" COLOR_OFF, seg); + verify_bool("Crypto packet build", 0, true, status); + if (!status) + return; + hdr = l_get_be32(packet + 9); verify_uint8("SEG", 9, keys->segmented << (SEG_HDR_SHIFT % 8), packet[9] & (1 << (SEG_HDR_SHIFT % 8))); @@ -870,15 +916,20 @@ net_msg_len = len + 2; show_data("TransportPayload", 7, packet + 7, net_msg_len); - mesh_crypto_packet_encrypt(packet, packet_len, + status = mesh_crypto_packet_encrypt(packet, packet_len, enc_key, keys->iv_index, false, keys->ctl, keys->net_ttl, keys->net_seq[0], keys->net_src); + verify_bool("Crypto packet encrypt", 0, true, status); + if (!status) + return; + mesh_crypto_privacy_counter(keys->iv_index, packet + 7, priv_rand); + l_info(""); show_uint32("IVindex", 0, keys->iv_index); verify_data("NetworkNonce", 0, keys->net_nonce[0], net_nonce, 13); @@ -907,10 +958,15 @@ } show_data("PreObsPayload", 1, packet + 1, 6 + net_msg_len); - mesh_crypto_network_obfuscate(packet, priv_key, + status = mesh_crypto_network_obfuscate(packet, priv_key, keys->iv_index, keys->ctl, keys->net_ttl, keys->net_seq[0], keys->net_src); + + verify_bool("Crypto network obfuscate", 0, true, status); + if (!status) + return; + show_data("PostObsPayload", 1, packet + 1, 6 + net_msg_len); packet[0] = (keys->iv_index & 0x01) << 7 | nid; @@ -926,7 +982,7 @@ uint8_t *dev_key; uint8_t *app_key; uint8_t *net_key; - uint8_t nid; + uint8_t nid = 0; uint8_t enc_key[16]; uint8_t priv_key[16]; uint8_t net_nonce[13]; @@ -949,6 +1005,7 @@ uint8_t packet_len; uint16_t i, seg_max, seg_len = 0; uint32_t seqZero, hdr; + bool status; l_info(COLOR_BLUE "[Encrypt %s]" COLOR_OFF, keys->name); verify_bool_not_both("CTL && Segmented", 0, keys->ctl, keys->segmented); @@ -960,8 +1017,7 @@ show_data("NetworkKey", 0, net_key, 16); - if (keys->akf) { - mesh_crypto_k4(app_key, &key_aid); + if (keys->akf && mesh_crypto_k4(app_key, &key_aid)) { key_aid |= KEY_ID_AKF; } else { key_aid = 0; @@ -1034,7 +1090,7 @@ seg_max = SEG_MAX(keys->segmented, app_msg_len + 8); enc_msg = l_malloc(app_msg_len + 8); - mesh_crypto_payload_encrypt(aad, app_msg, + status = mesh_crypto_payload_encrypt(aad, app_msg, enc_msg, app_msg_len, keys->net_src, keys->net_dst, key_aid, keys->app_seq, keys->iv_index, @@ -1044,7 +1100,7 @@ seg_max = SEG_MAX(keys->segmented, app_msg_len + 4); enc_msg = l_malloc(app_msg_len + 4); - mesh_crypto_payload_encrypt(aad, app_msg, + status = mesh_crypto_payload_encrypt(aad, app_msg, enc_msg, app_msg_len, keys->net_src, keys->net_dst, key_aid, keys->app_seq, keys->iv_index, @@ -1052,6 +1108,10 @@ keys->akf ? app_key : dev_key); } + verify_bool("Crypto payload encrypt", 0, true, status); + if (!status) + return; + if (keys->dev_key && !keys->akf) show_data("DeviceKey", 0, dev_key, 16); @@ -1097,7 +1157,8 @@ } if (keys->ctl) { - mesh_crypto_packet_build(keys->ctl, keys->net_ttl, + status = mesh_crypto_packet_build(keys->ctl, + keys->net_ttl, keys->net_seq[i], keys->net_src, keys->net_dst, keys->opcode, @@ -1107,7 +1168,8 @@ enc_msg + 1, seg_len, packet, &packet_len); } else { - mesh_crypto_packet_build(keys->ctl, keys->net_ttl, + status = mesh_crypto_packet_build(keys->ctl, + keys->net_ttl, keys->net_seq[i], keys->net_src, keys->net_dst, keys->opcode, @@ -1120,6 +1182,10 @@ if (seg_max) l_info(COLOR_YELLOW "Segment-%d" COLOR_OFF, i); + verify_bool("Crypto packet build", 0, true, status); + if (!status) + return; + hdr = l_get_be32(packet + 9); verify_uint8("SEG", 9, keys->segmented << (SEG_HDR_SHIFT % 8), packet[9] & (1 << (SEG_HDR_SHIFT % 8))); @@ -1193,12 +1259,16 @@ net_msg_len = seg_len + 2; show_data("TransportPayload", 7, packet + 7, net_msg_len); - mesh_crypto_packet_encrypt(packet, packet_len, enc_key, + status = mesh_crypto_packet_encrypt(packet, packet_len, enc_key, keys->iv_index, false, keys->ctl, keys->net_ttl, keys->net_seq[i], keys->net_src); + verify_bool("Crypto packet encrypt", 0, true, status); + if (!status) + return; + mesh_crypto_privacy_counter(keys->iv_index, packet + 7, priv_rand); @@ -1232,11 +1302,15 @@ } show_data("PreObsPayload", 1, packet + 1, 6 + net_msg_len); - mesh_crypto_network_obfuscate(packet, priv_key, + status = mesh_crypto_network_obfuscate(packet, priv_key, keys->iv_index, keys->ctl, keys->net_ttl, keys->net_seq[i], keys->net_src); + verify_bool("Crypto network obfuscate", 0, true, status); + if (!status) + return; + show_data("PostObsPayload", 1, packet + 1, 6 + net_msg_len); packet[0] = (keys->iv_index & 0x01) << 7 | nid; @@ -1265,19 +1339,20 @@ uint8_t net_clr[29]; uint64_t net_mic64, calc_net_mic64; uint32_t hdr, net_mic32, calc_net_mic32; - bool ctl, segmented, relay, szmic, key_akf; + bool ctl, segmented, relay, szmic, key_akf, status; uint8_t ttl, opcode, key_aid, segO, segN; uint32_t seq; uint16_t src, dst, seqZero; memcpy(net_clr, pkt, pkt_len); show_data("NetworkMessage", 0, pkt, pkt_len); - mesh_crypto_packet_decode(pkt, pkt_len, + status = mesh_crypto_packet_decode(pkt, pkt_len, false, net_clr, keys->iv_index, enc_key, priv_key); show_data("Decoded", 0, net_clr, pkt_len); - mesh_crypto_packet_parse(net_clr, pkt_len, + if (status) + status = mesh_crypto_packet_parse(net_clr, pkt_len, &ctl, &ttl, &seq, &src, &dst, NULL, &opcode, @@ -1286,6 +1361,10 @@ &segO, &segN, &msg, &msg_len); + verify_bool("Crypto Decode-Parse", 0, true, status); + if (!status) + return; + if (ctl) { net_mic64 = l_get_be64(pkt + pkt_len - 8); show_data("EncryptedPayload", 7, pkt + 7, pkt_len - 7 - 8); @@ -1416,7 +1495,7 @@ uint16_t app_msg_len = 0; uint32_t calc_net_mic32, net_mic32 = 0; uint64_t calc_net_mic64, net_mic64 = 0; - bool net_ctl, net_segmented, net_rly, net_akf; + bool net_ctl, net_segmented, net_rly, net_akf, status; uint8_t net_aid, net_ttl, nid, net_segO, net_segN = 0; uint32_t net_seq, hdr, seqZero = 0; uint16_t net_src, net_dst; @@ -1501,8 +1580,14 @@ net_msg = packet + 7; net_msg_len = packet_len - 7; - mesh_crypto_network_clarify(packet, priv_key, keys->iv_index, - &net_ctl, &net_ttl, &net_seq, &net_src); + status = mesh_crypto_network_clarify(packet, priv_key, + keys->iv_index, &net_ctl, &net_ttl, &net_seq, + &net_src); + + verify_bool("Crypto Clarify", 0, true, status); + if (!status) + return; + show_str("Packet", 0, keys->packet[i]); @@ -1731,42 +1816,67 @@ { uint8_t *net_key; uint8_t *beacon_cmac; - uint8_t beacon[22]; + uint8_t *random = NULL; + uint8_t beacon[29]; uint8_t enc_key[16]; uint8_t net_id[8]; uint8_t cmac[8]; - uint64_t cmac_tmp; + uint64_t cmac_tmp = 0; + + if (keys->beacon_type < 1 || keys->beacon_type > 2) + verify_uint8("Unknown Beacon", 0, true, + (keys->beacon_type >= 1 || keys->beacon_type <= 2)); net_key = l_util_from_hexstring(keys->net_key, NULL); beacon_cmac = l_util_from_hexstring(keys->beacon_cmac, NULL); - mesh_crypto_nkbk(net_key, enc_key); + if (keys->beacon_type == 1) { + mesh_crypto_nkbk(net_key, enc_key); + } else { + mesh_crypto_nkpk(net_key, enc_key); + random = l_util_from_hexstring(keys->rand, NULL); + } + mesh_crypto_k3(net_key, net_id); l_info(COLOR_BLUE "[%s]" COLOR_OFF, keys->name); verify_data("NetworkKey", 0, keys->net_key, net_key, 16); + show_uint8("Beacon Flags", 0, keys->beacon_flags); show_uint32("IVindex", 0, keys->iv_index); verify_data("BeaconKey", 0, keys->enc_key, enc_key, 16); - verify_data("NetworkID", 0, keys->net_id, net_id, 8); beacon[0] = keys->beacon_type; - beacon[1] = keys->beacon_flags; - memcpy(beacon + 2, net_id, 8); - l_put_be32(keys->iv_index, beacon + 10); - mesh_crypto_beacon_cmac(enc_key, net_id, keys->iv_index, - !!(keys->beacon_flags & 0x01), - !!(keys->beacon_flags & 0x02), - &cmac_tmp); - - l_put_be64(cmac_tmp, cmac); - l_put_be64(cmac_tmp, beacon + 14); - verify_data("BeaconCMAC", 0, keys->beacon_cmac, cmac, 8); - verify_data("Beacon", 0, keys->beacon, beacon, sizeof(beacon)); + if (keys->beacon_type == 1) { + verify_data("NetworkID", 0, keys->net_id, net_id, 8); + beacon[1] = keys->beacon_flags; + memcpy(beacon + 2, net_id, 8); + l_put_be32(keys->iv_index, beacon + 10); + mesh_crypto_beacon_cmac(enc_key, net_id, keys->iv_index, + !!(keys->beacon_flags & 0x01), + !!(keys->beacon_flags & 0x02), + &cmac_tmp); + + l_put_be64(cmac_tmp, cmac); + l_put_be64(cmac_tmp, beacon + 14); + verify_data("BeaconCMAC", 0, keys->beacon_cmac, cmac, 8); + verify_data("SNBeacon", 0, keys->beacon, beacon, 22); + } else { + show_data("Random", 0, random, sizeof(random)); + memcpy(beacon + 1, random, 13); + beacon[14] = keys->beacon_flags; + l_put_be32(keys->iv_index, beacon + 15); + mesh_crypto_aes_ccm_encrypt(random, enc_key, NULL, 0, + beacon + 14, 5, + beacon + 14, NULL, 8); + verify_data("BeaconMIC", 0, keys->beacon_cmac, beacon + 19, 8); + verify_data("PrivBeacon", 0, keys->beacon, beacon, 27); + } l_info(""); + l_free(random); l_free(beacon_cmac); l_free(net_key); } @@ -2071,6 +2181,8 @@ /* Section 8.4 Beacon Sample Data */ check_beacon(&s8_4_3); + check_beacon(&s8_4_6_1); + check_beacon(&s8_4_6_2); /* Section 8.6 Mesh Proxy Service sample data */ check_id_beacon(&s8_6_2);