diff --git a/0001-main-Simplify-parse_config_string.patch b/0001-main-Simplify-parse_config_string.patch new file mode 100644 index 0000000..fc590ee --- /dev/null +++ b/0001-main-Simplify-parse_config_string.patch @@ -0,0 +1,81 @@ +From 3cf5bb59c3f82e1fcc8703e6bab956284f2c4566 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 10 May 2024 13:47:29 +0200 +Subject: [PATCH] main: Simplify parse_config_string() + +The memory management done by parse_config_string() was quite +complicated, as it expected to be able to free the value in the return +variable if it was already allocated. + +That particular behaviour was only used for a single variable which was +set to its default value during startup and might be overwritten after +this function call. + +Use an intermediate variable to check whether we need to free +btd_opts.name and simplify parse_config_string(). + +Error: RESOURCE_LEAK (CWE-772): [#def39] [important] +bluez-5.75/src/main.c:425:2: alloc_fn: Storage is returned from allocation function "g_key_file_get_string". +bluez-5.75/src/main.c:425:2: var_assign: Assigning: "tmp" = storage returned from "g_key_file_get_string(config, group, key, &err)". +bluez-5.75/src/main.c:433:2: noescape: Assuming resource "tmp" is not freed or pointed-to as ellipsis argument to "btd_debug". +bluez-5.75/src/main.c:440:2: leaked_storage: Variable "tmp" going out of scope leaks the storage it points to. +438| } +439| +440|-> return true; +441| } +442| +--- + src/main.c | 22 +++++++++++++--------- + 1 file changed, 13 insertions(+), 9 deletions(-) + +diff --git a/src/main.c b/src/main.c +index 62453bffaf57..178611e11ddd 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -420,9 +420,13 @@ 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 (val != NULL) { ++ warn("%s passed a NULL value", __func__); ++ return false; ++ } ++ ++ *val = 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); +@@ -430,12 +434,7 @@ static bool parse_config_string(GKeyFile *config, const char *group, + return false; + } + +- DBG("%s.%s = %s", group, key, tmp); +- +- if (val) { +- g_free(*val); +- *val = tmp; +- } ++ DBG("%s.%s = %s", group, key, *val); + + return true; + } +@@ -1004,7 +1003,12 @@ static void parse_secure_conns(GKeyFile *config) + + static void parse_general(GKeyFile *config) + { +- parse_config_string(config, "General", "Name", &btd_opts.name); ++ char *str = NULL; ++ ++ if (parse_config_string(config, "General", "Name", &str)) { ++ g_free(btd_opts.name); ++ btd_opts.name = str; ++ } + parse_config_hex(config, "General", "Class", &btd_opts.class); + parse_config_u32(config, "General", "DiscoverableTimeout", + &btd_opts.discovto, +-- +2.45.2 + diff --git a/0001-shared-shell-Free-memory-allocated-by-wordexp.patch b/0001-shared-shell-Free-memory-allocated-by-wordexp.patch new file mode 100644 index 0000000..2f87a2d --- /dev/null +++ b/0001-shared-shell-Free-memory-allocated-by-wordexp.patch @@ -0,0 +1,139 @@ +From 9c7ec707e88170adf3e117fe92ed74e311b2e859 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Tue, 2 Jul 2024 15:27:12 +0200 +Subject: [PATCH] shared/shell: Free memory allocated by wordexp() + +Error: RESOURCE_LEAK (CWE-772): [#def38] [important] +bluez-5.76/src/shared/shell.c:519:2: alloc_arg: "parse_args" allocates memory that is stored into "w.we_wordv". +bluez-5.76/src/shared/shell.c:523:3: leaked_storage: Variable "w" going out of scope leaks the storage "w.we_wordv" points to. +521| "Unable to parse mandatory command arguments: %s", man ); +522| free(man); +523|-> return -EINVAL; +524| } +525| + +Error: RESOURCE_LEAK (CWE-772): [#def40] [important] +bluez-5.76/src/shared/shell.c:1113:3: alloc_arg: "wordexp" allocates memory that is stored into "w.we_wordv". +bluez-5.76/src/shared/shell.c:1114:4: leaked_storage: Variable "w" going out of scope leaks the storage "w.we_wordv" points to. +1112| +1113| if (wordexp(rl_line_buffer, &w, WRDE_NOCMD)) +1114|-> return NULL; +1115| +1116| matches = menu_completion(default_menu, text, w.we_wordc, + +Error: RESOURCE_LEAK (CWE-772): [#def42] [important] +bluez-5.76/src/shared/shell.c:1412:2: alloc_arg: "wordexp" allocates memory that is stored into "w.we_wordv". +bluez-5.76/src/shared/shell.c:1415:3: leaked_storage: Variable "w" going out of scope leaks the storage "w.we_wordv" points to. +1413| switch (err) { +1414| case WRDE_BADCHAR: +1415|-> return -EBADMSG; +1416| case WRDE_BADVAL: +1417| case WRDE_SYNTAX: + +Error: RESOURCE_LEAK (CWE-772): [#def43] [important] +bluez-5.76/src/shared/shell.c:1412:2: alloc_arg: "wordexp" allocates memory that is stored into "w.we_wordv". +bluez-5.76/src/shared/shell.c:1418:3: leaked_storage: Variable "w" going out of scope leaks the storage "w.we_wordv" points to. +1416| case WRDE_BADVAL: +1417| case WRDE_SYNTAX: +1418|-> return -EINVAL; +1419| case WRDE_NOSPACE: +1420| return -ENOMEM; + +Error: RESOURCE_LEAK (CWE-772): [#def44] [important] +bluez-5.76/src/shared/shell.c:1412:2: alloc_arg: "wordexp" allocates memory that is stored into "w.we_wordv". +bluez-5.76/src/shared/shell.c:1420:3: leaked_storage: Variable "w" going out of scope leaks the storage "w.we_wordv" points to. +1418| return -EINVAL; +1419| case WRDE_NOSPACE: +1420|-> return -ENOMEM; +1421| case WRDE_CMDSUB: +1422| if (wordexp(input, &w, 0)) + +Error: RESOURCE_LEAK (CWE-772): [#def45] [important] +bluez-5.76/src/shared/shell.c:1422:3: alloc_arg: "wordexp" allocates memory that is stored into "w.we_wordv". +bluez-5.76/src/shared/shell.c:1423:4: leaked_storage: Variable "w" going out of scope leaks the storage "w.we_wordv" points to. +1421| case WRDE_CMDSUB: +1422| if (wordexp(input, &w, 0)) +1423|-> return -ENOEXEC; +1424| break; +1425| }; +--- + src/shared/shell.c | 22 ++++++++++++++++------ + 1 file changed, 16 insertions(+), 6 deletions(-) + +diff --git a/src/shared/shell.c b/src/shared/shell.c +index 88ecaa076adc..26c6a419af22 100644 +--- a/src/shared/shell.c ++++ b/src/shared/shell.c +@@ -452,13 +452,23 @@ static void shell_print_menu_zsh_complete(void) + } + } + ++static int _wordexp(const char *restrict s, wordexp_t *restrict p, int flags) ++{ ++ int ret; ++ ++ ret = wordexp(s, p, flags); ++ if (ret != 0) ++ wordfree(p); ++ return ret; ++} ++ + static int parse_args(char *arg, wordexp_t *w, char *del, int flags) + { + char *str; + + str = strdelimit(arg, del, '"'); + +- if (wordexp(str, w, flags)) { ++ if (_wordexp(str, w, flags) != 0) { + free(str); + return -EINVAL; + } +@@ -537,7 +547,7 @@ static int cmd_exec(const struct bt_shell_menu_entry *entry, + goto fail; + } + +- flags |= WRDE_APPEND; ++ flags |= WRDE_APPEND | WRDE_REUSE; + opt = strdup(entry->arg + len + 1); + + optional: +@@ -1043,7 +1053,7 @@ static char **args_completion(const struct bt_shell_menu_entry *entry, int argc, + args.we_offs = 0; + wordfree(&args); + +- if (wordexp(str, &args, WRDE_NOCMD)) ++ if (_wordexp(str, &args, WRDE_NOCMD)) + goto done; + + rl_completion_display_matches_hook = NULL; +@@ -1115,7 +1125,7 @@ static char **shell_completion(const char *text, int start, int end) + if (start > 0) { + wordexp_t w; + +- if (wordexp(rl_line_buffer, &w, WRDE_NOCMD)) ++ if (_wordexp(rl_line_buffer, &w, WRDE_NOCMD)) + return NULL; + + matches = menu_completion(default_menu, text, w.we_wordc, +@@ -1417,7 +1427,7 @@ int bt_shell_exec(const char *input) + if (data.monitor) + bt_log_printf(0xffff, data.name, LOG_INFO, "%s", input); + +- err = wordexp(input, &w, WRDE_NOCMD); ++ err = _wordexp(input, &w, WRDE_NOCMD); + switch (err) { + case WRDE_BADCHAR: + return -EBADMSG; +@@ -1427,7 +1437,7 @@ int bt_shell_exec(const char *input) + case WRDE_NOSPACE: + return -ENOMEM; + case WRDE_CMDSUB: +- if (wordexp(input, &w, 0)) ++ if (_wordexp(input, &w, 0)) + return -ENOEXEC; + break; + }; +-- +2.45.2 + diff --git a/5.77-devel.patch b/5.77-devel.patch new file mode 100644 index 0000000..e79bc77 --- /dev/null +++ b/5.77-devel.patch @@ -0,0 +1,3923 @@ +From f00d5546c9e989dd68ce0de0190cd0e043b0f1f5 Mon Sep 17 00:00:00 2001 +From: Arjan Opmeer +Date: Tue, 9 Jul 2024 13:55:41 +0200 +Subject: [PATCH 01/46] tools/btmgmt: Fix --index option for non-interactive + mode + +In non-interactive mode the --index option does not work because the +call to mgmt_set_index() is made after bt_shell_attach(). + +Fixes: https://github.com/bluez/bluez/issues/893 +--- + tools/btmgmt.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/btmgmt.c b/tools/btmgmt.c +index 9b7f851bd8c6..436c2bb21f10 100644 +--- a/tools/btmgmt.c ++++ b/tools/btmgmt.c +@@ -51,8 +51,8 @@ int main(int argc, char *argv[]) + return EXIT_FAILURE; + } + +- bt_shell_attach(fileno(stdin)); + mgmt_set_index(index_option); ++ bt_shell_attach(fileno(stdin)); + status = bt_shell_run(); + + mgmt_remove_submenu(); +-- +2.45.2 + + +From 66a76c268d05583c2396054e3f63a19c6f18bb9c Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Wed, 3 Jul 2024 17:58:39 +0300 +Subject: [PATCH 02/46] doc: Add initial MediaAssistant rst + +This adds initial documentation for the MediaAssistant D-Bus API, to +be used by a Broadcast Assistant application to interract with the +BlueZ implementation (BASS Client). +--- + Makefile.am | 6 +-- + doc/org.bluez.MediaAssistant.rst | 74 ++++++++++++++++++++++++++++++++ + 2 files changed, 77 insertions(+), 3 deletions(-) + create mode 100644 doc/org.bluez.MediaAssistant.rst + +diff --git a/Makefile.am b/Makefile.am +index 0ae72111179b..46a8cfb4966f 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -362,7 +362,7 @@ man_MANS += doc/org.bluez.Adapter.5 doc/org.bluez.Device.5 \ + man_MANS += doc/org.bluez.Media.5 doc/org.bluez.MediaControl.5 \ + doc/org.bluez.MediaPlayer.5 doc/org.bluez.MediaFolder.5 \ + doc/org.bluez.MediaItem.5 doc/org.bluez.MediaEndpoint.5 \ +- doc/org.bluez.MediaTransport.5 ++ doc/org.bluez.MediaTransport.5 doc/org.bluez.MediaAssistant.5 + man_MANS += doc/org.bluez.GattManager.5 doc/org.bluez.GattProfile.5 \ + doc/org.bluez.GattService.5 \ + doc/org.bluez.GattCharacteristic.5 \ +@@ -395,7 +395,7 @@ manual_pages += doc/org.bluez.Adapter.5 doc/org.bluez.Device.5 \ + manual_pages += doc/org.bluez.Media.5 doc/org.bluez.MediaControl.5 \ + doc/org.bluez.MediaPlayer.5 doc/org.bluez.MediaFolder.5 \ + doc/org.bluez.MediaItem.5 doc/org.bluez.MediaEndpoint.5 \ +- doc/org.bluez.MediaTransport.5 ++ doc/org.bluez.MediaTransport.5 doc/org.bluez.MediaAssistant.5 + manual_pages += doc/org.bluez.GattManager.5 doc/org.bluez.GattProfile.5 \ + doc/org.bluez.GattService.5 \ + doc/org.bluez.GattCharacteristic.5 \ +@@ -475,7 +475,7 @@ EXTRA_DIST += doc/org.bluez.Adapter.rst doc/org.bluez.Device.rst \ + EXTRA_DIST += doc/org.bluez.Media.rst doc/org.bluez.MediaControl.rst \ + doc/org.bluez.MediaPlayer.rst doc/org.bluez.MediaFolder.rst \ + doc/org.bluez.MediaItem.rst doc/org.bluez.MediaEndpoint.rst \ +- doc/org.bluez.MediaTransport.rst ++ doc/org.bluez.MediaTransport.rst doc/org.bluez.MediaAssistant.rst + + EXTRA_DIST += doc/org.bluez.GattManager.rst doc/org.bluez.GattProfile.rst\ + doc/org.bluez.GattService.rst \ +diff --git a/doc/org.bluez.MediaAssistant.rst b/doc/org.bluez.MediaAssistant.rst +new file mode 100644 +index 000000000000..4aac8953619d +--- /dev/null ++++ b/doc/org.bluez.MediaAssistant.rst +@@ -0,0 +1,74 @@ ++======================== ++org.bluez.MediaAssistant ++======================== ++ ++-------------------------------------------- ++BlueZ D-Bus MediaAssistant API documentation ++-------------------------------------------- ++ ++:Version: BlueZ ++:Date: June 2024 ++:Manual section: 5 ++:Manual group: Linux System Administration ++ ++Interface ++========= ++ ++:Service: org.bluez ++:Interface: org.bluez.MediaAssistant1 ++:Object path: /org/bluez/{hci0,hci1,...}/src_XX_XX_XX_XX_XX_XX/dev_YY_YY_YY_YY_YY_YY/bisZ ++ ++Methods ++------- ++ ++void Push(dict properties) ++```````````````````````````````````````````````````````` ++ ++ Send stream information to the remote device. ++ ++ :dict properties: ++ ++ Indicate stream properties that will be sent to the peer. ++ ++ Values: ++ ++ :array{byte} Metadata [ISO only]: ++ ++ See Metadata property. ++ ++ :dict QoS [ISO only]: ++ ++ See QoS property. ++ ++Properties ++---------- ++ ++string State [readonly] ++``````````````````````` ++ ++ Indicates the state of the assistant object. Possible values are: ++ ++ :"idle": assistant object was created for the stream ++ :"pending": assistant object was pushed (stream information was sent to the peer) ++ :"requesting": remote device requires Broadcast_Code ++ :"active": remote device started receiving stream ++ ++array{byte} Metadata [readwrite, ISO Only, experimental] ++```````````````````````````````````````````````````````` ++ ++ Indicates stream Metadata. ++ ++dict QoS [readwrite, ISO only, experimental] ++````````````````````````````````````````````````````` ++ ++ Indicates stream QoS capabilities. ++ ++ Values: ++ ++ :byte Encryption: ++ ++ Indicates whether the stream is encrypted. ++ ++ :array{byte} BCode ++ ++ Indicates Broadcast_Code to decrypt stream. +-- +2.45.2 + + +From 34aca9a4fbcf3d4da459f97cef4a863d7853f6e4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Antonio=20V=C3=A1zquez=20Blanco?= + +Date: Thu, 4 Jul 2024 12:11:23 +0200 +Subject: [PATCH 03/46] bdaddr: Add cypress manufacturer support + +--- + tools/bdaddr.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tools/bdaddr.c b/tools/bdaddr.c +index bc0478d461b2..de17416e9c6c 100644 +--- a/tools/bdaddr.c ++++ b/tools/bdaddr.c +@@ -303,6 +303,7 @@ static struct { + { 48, st_write_bd_addr, generic_reset_device }, + { 57, ericsson_write_bd_addr, generic_reset_device }, + { 72, mrvl_write_bd_addr, generic_reset_device }, ++ { 305, bcm_write_bd_addr, generic_reset_device }, + { 65535, NULL, NULL }, + }; + +-- +2.45.2 + + +From 2748c60a2c6b1b090a7507fdd23865a598129d61 Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Tue, 9 Jul 2024 11:59:03 +0300 +Subject: [PATCH 04/46] bap: Wait for BIG Info report event before creating + streams + +This makes it so that stream for each BIS is created after BIG +Info report is received. This ensures that when the stream is +created the encryption field is correctly set. +--- + profiles/audio/bap.c | 27 ++++++++++++++++++++++++--- + 1 file changed, 24 insertions(+), 3 deletions(-) + +diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c +index e82a253825f1..afa938091996 100644 +--- a/profiles/audio/bap.c ++++ b/profiles/audio/bap.c +@@ -134,6 +134,7 @@ struct bap_bcast_pa_req { + struct btd_service *service; + struct bap_setup *setup; + } data; ++ unsigned int io_id; /* io_id for BIG Info watch */ + }; + + static struct queue *sessions; +@@ -1220,7 +1221,8 @@ fail: + return ret; + } + +-static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) ++static gboolean big_info_report_cb(GIOChannel *io, GIOCondition cond, ++ gpointer user_data) + { + GError *err = NULL; + struct bap_bcast_pa_req *req = user_data; +@@ -1228,7 +1230,7 @@ static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) + struct bt_iso_base base; + struct bt_iso_qos qos; + +- DBG("PA Sync done"); ++ DBG("BIG Info received"); + + bt_io_get(io, &err, + BT_IO_OPT_BASE, &base, +@@ -1238,7 +1240,8 @@ static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) + error("%s", err->message); + g_error_free(err); + g_io_channel_shutdown(io, TRUE, NULL); +- return; ++ req->io_id = 0; ++ return FALSE; + } + + /* Close the io and remove the queue request for another PA Sync */ +@@ -1255,7 +1258,21 @@ static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) + service_set_connecting(req->data.service); + + queue_remove(data->adapter->bcast_pa_requests, req); ++ req->io_id = 0; + free(req); ++ ++ return FALSE; ++} ++ ++static void iso_pa_sync_confirm_cb(GIOChannel *io, void *user_data) ++{ ++ struct bap_bcast_pa_req *req = user_data; ++ /* PA Sync was established, wait for BIG Info report so that the ++ * encryption flag is also available. ++ */ ++ DBG("PA Sync done"); ++ req->io_id = g_io_add_watch(io, G_IO_OUT, big_info_report_cb, ++ user_data); + } + + static bool match_data_bap_data(const void *data, const void *match_data) +@@ -3177,6 +3194,10 @@ static void bap_bcast_remove(struct btd_service *service) + */ + req = queue_remove_if(data->adapter->bcast_pa_requests, + match_service, service); ++ if (req->io_id) { ++ g_source_remove(req->io_id); ++ req->io_id = 0; ++ } + free(req); + + bap_data_remove(data); +-- +2.45.2 + + +From aa6063aa66954ac8321211145d1ae6b434b2555c Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Tue, 9 Jul 2024 17:35:00 +0300 +Subject: [PATCH 05/46] health: mcap: add checks for NULL mcap_notify_error() + +It is necessary to prevent dereferencing of NULL pointers. + +Found with the SVACE static analysis tool. +--- + profiles/health/mcap.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/profiles/health/mcap.c b/profiles/health/mcap.c +index 7eceaa88a3a9..2e4214a6984f 100644 +--- a/profiles/health/mcap.c ++++ b/profiles/health/mcap.c +@@ -336,6 +336,9 @@ static void mcap_notify_error(struct mcap_mcl *mcl, GError *err) + case MCAP_MD_CREATE_MDL_REQ: + st = MDL_WAITING; + l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state); ++ if (!l) ++ return; ++ + mdl = l->data; + mcl->mdls = g_slist_remove(mcl->mdls, mdl); + mcap_mdl_unref(mdl); +@@ -345,6 +348,9 @@ static void mcap_notify_error(struct mcap_mcl *mcl, GError *err) + case MCAP_MD_ABORT_MDL_REQ: + st = MDL_WAITING; + l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state); ++ if (!l) ++ return; ++ + shutdown_mdl(l->data); + update_mcl_state(mcl); + con->cb.notify(err, con->user_data); +@@ -362,6 +368,9 @@ static void mcap_notify_error(struct mcap_mcl *mcl, GError *err) + case MCAP_MD_RECONNECT_MDL_REQ: + st = MDL_WAITING; + l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state); ++ if (!l) ++ return; ++ + shutdown_mdl(l->data); + update_mcl_state(mcl); + con->cb.op(NULL, err, con->user_data); +-- +2.45.2 + + +From 11dcc9bf0dba61c83269fb3cf234579d6f9ef192 Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Tue, 9 Jul 2024 17:35:01 +0300 +Subject: [PATCH 06/46] shared: prevent dereferencing of NULL pointers + +It is necessary to add checks for NULL before dereferencing pointers. + +Found with the SVACE static analysis tool. +--- + src/shared/micp.c | 4 ++++ + src/shared/vcp.c | 12 ++++++++++++ + 2 files changed, 16 insertions(+) + +diff --git a/src/shared/micp.c b/src/shared/micp.c +index b82bd92dedb8..1c34e9d0079f 100644 +--- a/src/shared/micp.c ++++ b/src/shared/micp.c +@@ -398,6 +398,10 @@ static void mics_mute_write(struct gatt_db_attribute *attrib, + } + + micp_op = iov_pull_mem(&iov, sizeof(*micp_op)); ++ if (!micp_op) { ++ DBG(micp, "iov_pull_mem() returned NULL"); ++ goto respond; ++ } + + if ((*micp_op == MICS_DISABLED) || (*micp_op != MICS_NOT_MUTED + && *micp_op != MICS_MUTED)) { +diff --git a/src/shared/vcp.c b/src/shared/vcp.c +index 06264a24146c..602d46dc1d1d 100644 +--- a/src/shared/vcp.c ++++ b/src/shared/vcp.c +@@ -925,6 +925,10 @@ static void vcs_cp_write(struct gatt_db_attribute *attrib, + } + + vcp_op = iov_pull_mem(&iov, sizeof(*vcp_op)); ++ if (!vcp_op) { ++ DBG(vcp, "iov_pull_mem() returned NULL"); ++ goto respond; ++ } + + for (handler = vcp_handlers; handler && handler->str; handler++) { + if (handler->op != *vcp_op) +@@ -985,6 +989,10 @@ static void vocs_cp_write(struct gatt_db_attribute *attrib, + } + + vcp_op = iov_pull_mem(&iov, sizeof(*vcp_op)); ++ if (!vcp_op) { ++ DBG(vcp, "iov_pull_mem() returned NULL"); ++ goto respond; ++ } + + for (handler = vocp_handlers; handler && handler->str; handler++) { + if (handler->op != *vcp_op) +@@ -1517,6 +1525,10 @@ static void aics_ip_cp_write(struct gatt_db_attribute *attrib, + } + + aics_op = iov_pull_mem(&iov, sizeof(*aics_op)); ++ if (!aics_op) { ++ DBG(vcp, "iov_pull_mem() returned NULL"); ++ goto respond; ++ } + + for (handler = aics_handlers; handler && handler->str; handler++) { + if (handler->op != *aics_op) +-- +2.45.2 + + +From 755091581336dd6b6a710e599da9e1e52037851a Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Tue, 9 Jul 2024 17:35:02 +0300 +Subject: [PATCH 07/46] settings: limit string size in load_service() + +It is necessary to prevent buffer overflow by limiting +the maximum string length. + +Found with the SVACE static analysis tool. +--- + src/settings.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/settings.c b/src/settings.c +index b61e694f1b85..643a083dbc84 100644 +--- a/src/settings.c ++++ b/src/settings.c +@@ -193,7 +193,7 @@ static int load_service(struct gatt_db *db, char *handle, char *value) + return -EIO; + } + +- if (sscanf(value, "%[^:]:%04hx:%36s", type, &end, uuid_str) != 3) { ++ if (sscanf(value, "%36[^:]:%04hx:%36s", type, &end, uuid_str) != 3) { + DBG("Failed to parse value: %s", value); + return -EIO; + } +-- +2.45.2 + + +From 4ca662fcea1604e937bde1bddd5de2c50bcb6e00 Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Tue, 9 Jul 2024 17:35:03 +0300 +Subject: [PATCH 08/46] settings: limit string size in gatt_db_load() + +It is necessary to prevent buffer overflow by limiting +the maximum string length. + +Found with the SVACE static analysis tool. +--- + src/settings.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/settings.c b/src/settings.c +index 643a083dbc84..37164939573f 100644 +--- a/src/settings.c ++++ b/src/settings.c +@@ -232,7 +232,7 @@ static int gatt_db_load(struct gatt_db *db, GKeyFile *key_file, char **keys) + value = g_key_file_get_string(key_file, "Attributes", *handle, + NULL); + +- if (!value || sscanf(value, "%[^:]:", type) != 1) { ++ if (!value || sscanf(value, "%36[^:]:", type) != 1) { + g_free(value); + return -EIO; + } +@@ -255,7 +255,7 @@ static int gatt_db_load(struct gatt_db *db, GKeyFile *key_file, char **keys) + value = g_key_file_get_string(key_file, "Attributes", *handle, + NULL); + +- if (!value || sscanf(value, "%[^:]:", type) != 1) { ++ if (!value || sscanf(value, "%36[^:]:", type) != 1) { + g_free(value); + return -EIO; + } +-- +2.45.2 + + +From e56fc72fc66765f407473e4cb903fdc80784a4ff Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Wed, 10 Jul 2024 14:31:44 +0300 +Subject: [PATCH 09/46] gatt: add return value check of io_get_fd() to + sock_io_send() + +It is necessary to add a return value check. + +Found with the SVACE static analysis tool. +--- + src/gatt-database.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/gatt-database.c b/src/gatt-database.c +index 8472aac5941d..6c84b085ca29 100644 +--- a/src/gatt-database.c ++++ b/src/gatt-database.c +@@ -2630,6 +2630,7 @@ static int sock_io_send(struct io *io, const void *data, size_t len) + { + struct msghdr msg; + struct iovec iov; ++ int fd; + + iov.iov_base = (void *) data; + iov.iov_len = len; +@@ -2638,7 +2639,13 @@ static int sock_io_send(struct io *io, const void *data, size_t len) + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + +- return sendmsg(io_get_fd(io), &msg, MSG_NOSIGNAL); ++ fd = io_get_fd(io); ++ if (fd < 0) { ++ error("io_get_fd() returned %d\n", fd); ++ return fd; ++ } ++ ++ return sendmsg(fd, &msg, MSG_NOSIGNAL); + } + + static void att_disconnect_cb(int err, void *user_data) +-- +2.45.2 + + +From ba70a116d97108f21a853f5549758a720fdbefb3 Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Wed, 10 Jul 2024 14:31:45 +0300 +Subject: [PATCH 10/46] shared/vcp: add NULL checks to foreach_aics_service() + +Make foreach_aics_service() safe for passing NULL pointers. + +Found with the SVACE static analysis tool. +--- + src/shared/vcp.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/shared/vcp.c b/src/shared/vcp.c +index 602d46dc1d1d..43ef1d18644d 100644 +--- a/src/shared/vcp.c ++++ b/src/shared/vcp.c +@@ -2729,6 +2729,9 @@ static void foreach_aics_service(struct gatt_db_attribute *attr, + struct bt_vcp *vcp = user_data; + struct bt_aics *aics = vcp_get_aics(vcp); + ++ if (!aics || !attr) ++ return; ++ + aics->service = attr; + + gatt_db_service_set_claimed(attr, true); +-- +2.45.2 + + +From 12525371ef082483d524447310da7d0f5866bf91 Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Wed, 10 Jul 2024 14:31:46 +0300 +Subject: [PATCH 11/46] client/player: add error code handling to + transport_recv() + +It is necessary to add return value check as in sock_send(). + +Found with the SVACE static analysis tool. +--- + client/player.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/client/player.c b/client/player.c +index 584fc5e8148a..de4491b534c2 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -4514,7 +4514,13 @@ static bool transport_recv(struct io *io, void *user_data) + uint8_t buf[1024]; + int ret, len; + +- ret = read(io_get_fd(io), buf, sizeof(buf)); ++ ret = io_get_fd(io); ++ if (ret < 0) { ++ bt_shell_printf("io_get_fd() returned %d\n", ret); ++ return true; ++ } ++ ++ ret = read(ret, buf, sizeof(buf)); + if (ret < 0) { + bt_shell_printf("Failed to read: %s (%d)\n", strerror(errno), + -errno); +-- +2.45.2 + + +From 7ffc08dd78d68eff15bb77e43efbc1b606fb4fd8 Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Wed, 10 Jul 2024 14:31:47 +0300 +Subject: [PATCH 12/46] shared/vcp: prevent dereferencing of NULL pointers + +util_memdup() will terminate the program if memory +allocation fails. + +Found with the SVACE static analysis tool. +--- + src/shared/vcp.c | 20 ++++---------------- + 1 file changed, 4 insertions(+), 16 deletions(-) + +diff --git a/src/shared/vcp.c b/src/shared/vcp.c +index 43ef1d18644d..cfc426624875 100644 +--- a/src/shared/vcp.c ++++ b/src/shared/vcp.c +@@ -2139,14 +2139,8 @@ static void read_vocs_audio_descriptor(struct bt_vcp *vcp, bool success, + return; + } + +- vocs_ao_dec_r = malloc(length+1); +- memset(vocs_ao_dec_r, 0, length+1); +- memcpy(vocs_ao_dec_r, value, length); +- +- if (!vocs_ao_dec_r) { +- DBG(vcp, "Unable to get VOCS Audio Descriptor"); +- return; +- } ++ vocs_ao_dec_r = util_memdup(value, length + 1); ++ memset(vocs_ao_dec_r + length, 0, 1); + + DBG(vcp, "VOCS Audio Descriptor: %s", vocs_ao_dec_r); + free(vocs_ao_dec_r); +@@ -2543,14 +2537,8 @@ static void read_aics_audio_ip_description(struct bt_vcp *vcp, bool success, + return; + } + +- ip_descrptn = malloc(length+1); +- memset(ip_descrptn, 0, length+1); +- memcpy(ip_descrptn, value, length); +- +- if (!ip_descrptn) { +- DBG(vcp, "Unable to get Audio Input Description"); +- return; +- } ++ ip_descrptn = util_memdup(value, length + 1); ++ memset(ip_descrptn + length, 0, 1); + + DBG(vcp, "Audio Input Description: %s", ip_descrptn); + free(ip_descrptn); +-- +2.45.2 + + +From cf3d80a01f1f21538148cb9a5569b678dad0848b Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Wed, 10 Jul 2024 14:31:48 +0300 +Subject: [PATCH 13/46] client/player: fix the order of args in + cmd_register_endpoint() + +Based on the function prototype, ep->cid and ep->vid should be swapped. + +Found with the SVACE static analysis tool. +--- + client/player.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/client/player.c b/client/player.c +index de4491b534c2..2480ed64b8e5 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -3388,7 +3388,7 @@ static void cmd_register_endpoint(int argc, char *argv[]) + + if (strrchr(argv[2], ':')) { + ep->codec = 0xff; +- parse_vendor_codec(argv[2], &ep->cid, &ep->vid); ++ parse_vendor_codec(argv[2], &ep->vid, &ep->cid); + ep->preset = new0(struct preset, 1); + ep->preset->custom.name = strdup("custom"); + ep->preset->default_preset = &ep->preset->custom; +-- +2.45.2 + + +From 7a45038dc1e505afbaa49f8dd64fd41dab627f23 Mon Sep 17 00:00:00 2001 +From: Roman Smirnov +Date: Wed, 10 Jul 2024 14:31:49 +0300 +Subject: [PATCH 14/46] shared/gatt-client: add NULL check to + discover_secondary_cb() + +It is necessary to prevent dereferencing of a NULL pointer. + +Found with the SVACE static analysis tool. +--- + src/shared/gatt-client.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c +index b48d739fc609..9db3f52117ff 100644 +--- a/src/shared/gatt-client.c ++++ b/src/shared/gatt-client.c +@@ -1276,7 +1276,9 @@ next: + + range = queue_peek_head(op->discov_ranges); + +- client->discovery_req = bt_gatt_discover_included_services(client->att, ++ if (range) ++ client->discovery_req = bt_gatt_discover_included_services( ++ client->att, + range->start, + range->end, + discover_incl_cb, +-- +2.45.2 + + +From 9cc587947b6ac56a4c94dcc880b273bc72af22a8 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 11 Jul 2024 15:11:56 -0400 +Subject: [PATCH 15/46] device: Fix overwritting current_flags + +MGMT Set Device Flags overwrites the current_flags so only the last +flags set this way would remain active which can be seem in the +following sequence when LL Privacy is enabled: + +@ MGMT Command: Set Device Flags (0x0050) plen 11 + LE Address: CF:AC:A6:79:3D:B9 (Static) + Current Flags: 0x00000001 + Remote Wakeup +@ MGMT Event: Command Complete (0x0001) plen 10 + Set Device Flags (0x0050) plen 7 + Status: Success (0x00) + LE Address: CF:AC:A6:79:3D:B9 (Static) +@ MGMT Command: Set Device Flags (0x0050) plen 11 + LE Address: CF:AC:A6:79:3D:B9 (Static) + Current Flags: 0x00000002 + Device Privacy Mode +@ MGMT Event: Command Complete (0x0001) plen 10 + Set Device Flags (0x0050) plen 7 + Status: Success (0x00) + LE Address: CF:AC:A6:79:3D:B9 (Static) + +In order to do this properly the code needs to track the pending_flags +being set and also call btd_device_flags_changed whenever a change is +complete since that event is not generated when MGMT_OP_SET_DEVICE_FLAGS +is sent by bluetoothd itself. +--- + src/adapter.c | 20 +++++++++++++++++--- + src/device.c | 20 +++++++++++++++++++- + src/device.h | 2 ++ + 3 files changed, 38 insertions(+), 4 deletions(-) + +diff --git a/src/adapter.c b/src/adapter.c +index bb49a1ecad23..85ddfc16568f 100644 +--- a/src/adapter.c ++++ b/src/adapter.c +@@ -5569,6 +5569,7 @@ void adapter_accept_list_remove(struct btd_adapter *adapter, + static void set_device_privacy_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) + { ++ struct btd_device *dev = user_data; + const struct mgmt_rp_set_device_flags *rp = param; + + if (status != MGMT_STATUS_SUCCESS) { +@@ -5581,6 +5582,9 @@ static void set_device_privacy_complete(uint8_t status, uint16_t length, + error("Too small Set Device Flags complete event: %d", length); + return; + } ++ ++ btd_device_flags_changed(dev, btd_device_get_supported_flags(dev), ++ btd_device_get_pending_flags(dev)); + } + + static void add_device_complete(uint8_t status, uint16_t length, +@@ -5626,7 +5630,7 @@ static void add_device_complete(uint8_t status, uint16_t length, + adapter_set_device_flags(adapter, dev, flags | + DEVICE_FLAG_DEVICE_PRIVACY, + set_device_privacy_complete, +- NULL); ++ dev); + } + } + } +@@ -5676,6 +5680,7 @@ void adapter_set_device_flags(struct btd_adapter *adapter, + { + struct mgmt_cp_set_device_flags cp; + uint32_t supported = btd_device_get_supported_flags(device); ++ uint32_t pending = btd_device_get_pending_flags(device); + const bdaddr_t *bdaddr; + uint8_t bdaddr_type; + +@@ -5683,6 +5688,14 @@ void adapter_set_device_flags(struct btd_adapter *adapter, + (supported | flags) != supported) + return; + ++ /* Check if changing flags are pending */ ++ if (flags == (flags & pending)) ++ return; ++ ++ /* Set Device Privacy Mode if it has not set the flag yet. */ ++ if (btd_opts.device_privacy && !(flags & DEVICE_FLAG_DEVICE_PRIVACY)) ++ flags |= DEVICE_FLAG_DEVICE_PRIVACY & supported & ~pending; ++ + bdaddr = device_get_address(device); + bdaddr_type = btd_device_get_bdaddr_type(device); + +@@ -5691,8 +5704,9 @@ void adapter_set_device_flags(struct btd_adapter *adapter, + cp.addr.type = bdaddr_type; + cp.current_flags = cpu_to_le32(flags); + +- mgmt_send(adapter->mgmt, MGMT_OP_SET_DEVICE_FLAGS, adapter->dev_id, +- sizeof(cp), &cp, func, user_data, NULL); ++ if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEVICE_FLAGS, adapter->dev_id, ++ sizeof(cp), &cp, func, user_data, NULL)) ++ btd_device_set_pending_flags(device, flags); + } + + static void device_flags_changed_callback(uint16_t index, uint16_t length, +diff --git a/src/device.c b/src/device.c +index 097b1fbba37d..a1dc0750ca41 100644 +--- a/src/device.c ++++ b/src/device.c +@@ -214,6 +214,7 @@ struct btd_device { + GDBusPendingPropertySet wake_id; + + uint32_t supported_flags; ++ uint32_t pending_flags; + uint32_t current_flags; + GSList *svc_callbacks; + GSList *eir_uuids; +@@ -1569,7 +1570,7 @@ static void set_wake_allowed_complete(uint8_t status, uint16_t length, + return; + } + +- device_set_wake_allowed_complete(dev); ++ btd_device_flags_changed(dev, dev->supported_flags, dev->pending_flags); + } + + void device_set_wake_allowed(struct btd_device *device, bool wake_allowed, +@@ -7243,6 +7244,22 @@ uint32_t btd_device_get_supported_flags(struct btd_device *dev) + return dev->supported_flags; + } + ++void btd_device_set_pending_flags(struct btd_device *dev, uint32_t flags) ++{ ++ if (!dev) ++ return; ++ ++ dev->pending_flags = flags; ++} ++ ++uint32_t btd_device_get_pending_flags(struct btd_device *dev) ++{ ++ if (!dev) ++ return 0; ++ ++ return dev->pending_flags; ++} ++ + /* This event is sent immediately after add device on all mgmt sockets. + * Afterwards, it is only sent to mgmt sockets other than the one which called + * set_device_flags. +@@ -7255,6 +7272,7 @@ void btd_device_flags_changed(struct btd_device *dev, uint32_t supported_flags, + + dev->supported_flags = supported_flags; + dev->current_flags = current_flags; ++ dev->pending_flags = 0; + + if (!changed_flags) + return; +diff --git a/src/device.h b/src/device.h +index 0794f92d0178..3742f6028040 100644 +--- a/src/device.h ++++ b/src/device.h +@@ -191,6 +191,8 @@ int btd_device_connect_services(struct btd_device *dev, GSList *services); + + uint32_t btd_device_get_current_flags(struct btd_device *dev); + uint32_t btd_device_get_supported_flags(struct btd_device *dev); ++uint32_t btd_device_get_pending_flags(struct btd_device *dev); ++void btd_device_set_pending_flags(struct btd_device *dev, uint32_t flags); + void btd_device_flags_changed(struct btd_device *dev, uint32_t supported_flags, + uint32_t current_flags); + +-- +2.45.2 + + +From 73266377b0185c56c921b8cece257df428612d73 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 12 Jul 2024 15:27:12 -0400 +Subject: [PATCH 16/46] shared/bap: Fix ASE notification order + +When processing a CP operation the CP shall be notified ahead of +the ASE itself: + + 'If the server successfully completes a client-initiated ASE Control + operation for an ASE, the server shall send a notification of the ASE + Control Point characteristic value formatted as defined in Table 4.7. + The server shall then perform the behavior defined in Section 5.1 + through Section 5.8 for that ASE Control operation and send + notifications of any ASE characteristic values written during that + ASE Control operation.' + +So this delays the processing of notifications of ASE states so the CP +responses always appears first in the notification e.g: + +> ACL Data RX: Handle 42 flags 0x02 dlen 59 + ATT: Handle Multiple Value Notification (0x23) len 54 + Length: 0x0008 + Handle: 0x0036 Type: ASE Control Point (0x2bc6) + Data[8]: 0202030000010000 + Opcode: QoS Configuration (0x02) + Number of ASE(s): 2 + ASE: #0 + ASE ID: 0x03 + ASE Response Code: Success (0x00) + ASE Response Reason: None (0x00) + ASE: #1 + ASE ID: 0x01 + ASE Response Code: Success (0x00) + ASE Response Reason: None (0x00) + Length: 0x0011 + Handle: 0x0030 Type: Source ASE (0x2bc5) + Data[17]: 0302000010270000022800020a00409c00 + ASE ID: 3 + State: QoS Configured (0x02) + CIG ID: 0x00 + CIS ID: 0x00 + SDU Interval: 10000 usec + Framing: Unframed (0x00) + PHY: 0x02 + LE 2M PHY (0x02) + Max SDU: 40 + RTN: 2 + Max Transport Latency: 10 + Presentation Delay: 40000 us + Length: 0x0011 + Handle: 0x002a Type: Sink ASE (0x2bc4) + Data[17]: 0102000010270000025000020a00409c00 + ASE ID: 1 + State: QoS Configured (0x02) + CIG ID: 0x00 + CIS ID: 0x00 + SDU Interval: 10000 usec + Framing: Unframed (0x00) + PHY: 0x02 + LE 2M PHY (0x02) + Max SDU: 80 + RTN: 2 + Max Transport Latency: 10 + Presentation Delay: 40000 us +--- + src/shared/bap.c | 53 +++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 43 insertions(+), 10 deletions(-) + +diff --git a/src/shared/bap.c b/src/shared/bap.c +index 3a4c1f9d3a98..d59eac8cca16 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -169,6 +169,7 @@ struct bt_bap { + unsigned int process_id; + unsigned int disconn_id; + unsigned int idle_id; ++ bool in_cp_write; + + struct queue *reqs; + struct queue *notify; +@@ -266,6 +267,7 @@ struct bt_bap_stream { + const struct bt_bap_stream_ops *ops; + uint8_t old_state; + uint8_t state; ++ unsigned int state_id; + bool client; + void *user_data; + }; +@@ -1102,6 +1104,8 @@ static void bap_stream_free(void *data) + { + struct bt_bap_stream *stream = data; + ++ timeout_remove(stream->state_id); ++ + if (stream->ep) + stream->ep->stream = NULL; + +@@ -1579,20 +1583,17 @@ static bool bap_queue_req(struct bt_bap *bap, struct bt_bap_req *req) + return true; + } + +-static void bap_ucast_set_state(struct bt_bap_stream *stream, uint8_t state) ++static bool stream_notify_state(void *data) + { ++ struct bt_bap_stream *stream = data; + struct bt_bap_endpoint *ep = stream->ep; + +- ep->old_state = ep->state; +- ep->state = state; +- +- DBG(stream->bap, "stream %p dir 0x%02x: %s -> %s", stream, +- bt_bap_stream_get_dir(stream), +- bt_bap_stream_statestr(stream->ep->old_state), +- bt_bap_stream_statestr(stream->ep->state)); ++ DBG(stream->bap, "stream %p", stream); + +- if (stream->lpac->type == BT_BAP_BCAST_SINK || stream->client) +- goto done; ++ if (stream->state_id) { ++ timeout_remove(stream->state_id); ++ stream->state_id = 0; ++ } + + switch (ep->state) { + case BT_ASCS_ASE_STATE_IDLE: +@@ -1610,6 +1611,31 @@ static void bap_ucast_set_state(struct bt_bap_stream *stream, uint8_t state) + break; + } + ++ return false; ++} ++ ++static void bap_ucast_set_state(struct bt_bap_stream *stream, uint8_t state) ++{ ++ struct bt_bap_endpoint *ep = stream->ep; ++ ++ ep->old_state = ep->state; ++ ep->state = state; ++ ++ DBG(stream->bap, "stream %p dir 0x%02x: %s -> %s", stream, ++ bt_bap_stream_get_dir(stream), ++ bt_bap_stream_statestr(stream->ep->old_state), ++ bt_bap_stream_statestr(stream->ep->state)); ++ ++ if (stream->client) ++ goto done; ++ ++ if (!stream->bap->in_cp_write) ++ stream_notify_state(stream); ++ else if (!stream->state_id) ++ stream->state_id = timeout_add(BAP_PROCESS_TIMEOUT, ++ stream_notify_state, ++ stream, NULL); ++ + done: + bap_stream_state_changed(stream); + } +@@ -3069,8 +3095,15 @@ static void ascs_ase_cp_write(struct gatt_db_attribute *attrib, + + DBG(bap, "%s", handler->str); + ++ /* Set in_cp_write so ASE notification are not sent ahead of ++ * CP notifcation. ++ */ ++ bap->in_cp_write = true; ++ + for (i = 0; i < hdr->num; i++) + ret = handler->func(ascs, bap, &iov, rsp); ++ ++ bap->in_cp_write = false; + } else { + DBG(bap, "Unknown opcode 0x%02x", hdr->op); + ascs_ase_rsp_add_errno(rsp, 0x00, -ENOTSUP); +-- +2.45.2 + + +From 025f07ec0d0ebfb5e83c07d2918a6c01b0ae49a6 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 12 Jul 2024 10:52:03 -0400 +Subject: [PATCH 17/46] client/player: Add support for name custom presets + +This adds support for naming custom presets instead of always having +just one "custom" codec preset which needs to be overwriten everytime +a new set of settings needs to be entered. +--- + client/player.c | 130 ++++++++++++++++++++++++++++++++---------------- + 1 file changed, 87 insertions(+), 43 deletions(-) + +diff --git a/client/player.c b/client/player.c +index 2480ed64b8e5..26190fef7bc1 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -1232,6 +1232,7 @@ struct codec_preset { + const struct iovec data; + struct bt_bap_qos qos; + uint8_t target_latency; ++ bool custom; + }; + + #define SBC_PRESET(_name, _data) \ +@@ -1448,7 +1449,6 @@ static void print_lc3_meta(void *data, int len) + { \ + .uuid = _uuid, \ + .codec = _codec, \ +- .custom = { .name = "custom" }, \ + .default_preset = &_presets[_default_index], \ + .presets = _presets, \ + .num_presets = ARRAY_SIZE(_presets), \ +@@ -1459,7 +1459,7 @@ static struct preset { + uint8_t codec; + uint16_t cid; + uint16_t vid; +- struct codec_preset custom; ++ struct queue *custom; + struct codec_preset *default_preset; + struct codec_preset *presets; + size_t num_presets; +@@ -1557,6 +1557,14 @@ static struct preset *find_presets_name(const char *uuid, const char *codec) + return find_presets(uuid, id, 0x0000, 0x0000); + } + ++static bool match_custom_name(const void *data, const void *match_data) ++{ ++ const struct codec_preset *preset = data; ++ const char *name = match_data; ++ ++ return !strcmp(preset->name, name); ++} ++ + static struct codec_preset *preset_find_name(struct preset *preset, + const char *name) + { +@@ -1567,8 +1575,6 @@ static struct codec_preset *preset_find_name(struct preset *preset, + + 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; +@@ -1579,19 +1585,7 @@ static struct codec_preset *preset_find_name(struct preset *preset, + 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); ++ return queue_find(preset->custom, match_custom_name, name); + } + + static DBusMessage *endpoint_select_config_reply(DBusMessage *msg, +@@ -2816,10 +2810,11 @@ static void endpoint_free(void *data) + if (ep->msg) + dbus_message_unref(ep->msg); + +- if (ep->codec == 0xff) { +- free(ep->preset->custom.name); ++ queue_destroy(ep->preset->custom, free); ++ ep->preset->custom = NULL; ++ ++ if (ep->codec == 0xff) + free(ep->preset); +- } + + queue_destroy(ep->acquiring, NULL); + queue_destroy(ep->transports, free); +@@ -3365,6 +3360,36 @@ static const struct capabilities *find_capabilities(const char *uuid, + return NULL; + } + ++static struct codec_preset *codec_preset_new(const char *name) ++{ ++ struct codec_preset *codec; ++ ++ codec = new0(struct codec_preset, 1); ++ codec->name = strdup(name); ++ codec->custom = true; ++ ++ return codec; ++} ++ ++static struct codec_preset *codec_preset_add(struct preset *preset, ++ const char *name) ++{ ++ struct codec_preset *codec; ++ ++ codec = preset_find_name(preset, name); ++ if (codec) ++ return codec; ++ ++ codec = codec_preset_new(name); ++ ++ if (!preset->custom) ++ preset->custom = queue_new(); ++ ++ queue_push_tail(preset->custom, codec); ++ ++ return codec; ++} ++ + static void cmd_register_endpoint(int argc, char *argv[]) + { + struct endpoint *ep; +@@ -3390,8 +3415,8 @@ static void cmd_register_endpoint(int argc, char *argv[]) + ep->codec = 0xff; + parse_vendor_codec(argv[2], &ep->vid, &ep->cid); + ep->preset = new0(struct preset, 1); +- ep->preset->custom.name = strdup("custom"); +- ep->preset->default_preset = &ep->preset->custom; ++ ep->preset->default_preset = codec_preset_add(ep->preset, ++ "custom"); + } else { + ep->preset = find_presets_name(ep->uuid, argv[2]); + } +@@ -4060,21 +4085,27 @@ static void custom_frequency(const char *input, void *user_data) + custom_duration, user_data); + } + ++static void foreach_custom_preset_print(void *data, void *user_data) ++{ ++ struct codec_preset *p = data; ++ struct preset *preset = user_data; ++ ++ bt_shell_printf("%s%s\n", p == preset->default_preset ? "*" : "", ++ p->name); ++} ++ + static void print_presets(struct preset *preset) + { + size_t i; + struct codec_preset *p; + +- 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); + } ++ ++ queue_foreach(preset->custom, foreach_custom_preset_print, preset); + } + + static void cmd_presets_endpoint(int argc, char *argv[]) +@@ -4082,29 +4113,42 @@ static void cmd_presets_endpoint(int argc, char *argv[]) + struct preset *preset; + struct codec_preset *default_preset = NULL; + +- 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) { ++ if (argc > 3) { ++ default_preset = codec_preset_add(preset, argv[3]); ++ if (!default_preset) { ++ bt_shell_printf("Preset %s not found\n", argv[3]); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } + preset->default_preset = default_preset; +- goto done; +- } + +- print_presets(preset); ++ if (argc > 4) { ++ struct iovec *iov = (void *)&default_preset->data; + +-done: +- if (default_preset && !strcmp(default_preset->name, "custom")) { ++ iov->iov_base = str2bytearray(argv[4], &iov->iov_len); ++ if (!iov->iov_base) { ++ bt_shell_printf("Invalid configuration %s\n", ++ argv[4]); ++ return bt_shell_noninteractive_quit( ++ EXIT_FAILURE); ++ } ++ ++ bt_shell_prompt_input("QoS", "Enter Target Latency " ++ "(Low, Balance, High):", ++ custom_target_latency, ++ default_preset); ++ ++ return; ++ } ++ } else ++ print_presets(preset); ++ ++ if (default_preset && default_preset->custom) { + bt_shell_prompt_input("Codec", "Enter frequency (Khz):", + custom_frequency, default_preset); + return; +@@ -4133,9 +4177,9 @@ static const struct bt_shell_menu endpoint_menu = { + cmd_config_endpoint, + "Configure Endpoint", + endpoint_generator }, +- { "presets", " [default]", ++ { "presets", " [preset] [config]", + cmd_presets_endpoint, +- "List available presets", ++ "List or add presets", + uuid_generator }, + {} }, + }; +-- +2.45.2 + + +From 957c956112cc2bba528fe8df4a0a21d221a617ca Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 18 Jul 2024 13:58:52 -0400 +Subject: [PATCH 18/46] client/player: Fix printing errors when + transport->filename is not set + +If transport->filename is not set don't attempt to write to the +transport->fd. +--- + client/player.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/client/player.c b/client/player.c +index 26190fef7bc1..5b0b918fb8d7 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -4575,10 +4575,10 @@ static bool transport_recv(struct io *io, void *user_data) + + transport->seq++; + +- if (transport->fd >= 0) { ++ if (transport->filename) { + len = write(transport->fd, buf, ret); + if (len < 0) +- bt_shell_printf("Unable to write: %s (%d)", ++ bt_shell_printf("Unable to write: %s (%d)\n", + strerror(errno), -errno); + } + +-- +2.45.2 + + +From 0bad3d5cbea84b24d53e86de7c419e893bb19a93 Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 11:11:31 +0300 +Subject: [PATCH 19/46] bap: Fix crash in bap_bcast_remove + +This adds a check for the PA request dequeued in bap_bcast_remove, +to avoid accessing a member within a NULL pointer. +--- + profiles/audio/bap.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c +index afa938091996..16c02524f2d0 100644 +--- a/profiles/audio/bap.c ++++ b/profiles/audio/bap.c +@@ -3194,7 +3194,7 @@ static void bap_bcast_remove(struct btd_service *service) + */ + req = queue_remove_if(data->adapter->bcast_pa_requests, + match_service, service); +- if (req->io_id) { ++ if (req && req->io_id) { + g_source_remove(req->io_id); + req->io_id = 0; + } +-- +2.45.2 + + +From caa4202a7ee3423211733f7883641c77666dfbbf Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:21:58 +0300 +Subject: [PATCH 20/46] shared/bap: Add separate API to merge caps + +This moves the logic to merge L2 and L3 capabilities discovered +inside a BASE structure in a public API. +--- + src/shared/bap.c | 40 ++++++++++++++++++++++++---------------- + src/shared/bap.h | 2 ++ + 2 files changed, 26 insertions(+), 16 deletions(-) + +diff --git a/src/shared/bap.c b/src/shared/bap.c +index d59eac8cca16..1259ee3c90ef 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -6607,29 +6607,21 @@ static struct bt_ltv_match bap_check_bis(struct bt_bap_db *ldb, + return compare_data; + } + +-void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, +- struct bt_bap_codec *codec, +- struct iovec *l2_caps, +- struct iovec *l3_caps, +- struct bt_bap_pac **lpac, +- struct iovec **caps) ++struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps) + { + struct bt_ltv_extract merge_data = {0}; +- struct bt_ltv_match match_data; + + if (!l2_caps) + /* Codec_Specific_Configuration parameters shall + * be present at Level 2. + */ +- return; ++ return NULL; + +- if (!l3_caps) { ++ if (!l3_caps) + /* Codec_Specific_Configuration parameters may + * be present at Level 3. + */ +- merge_data.result = util_iov_dup(l2_caps, 1); +- goto done; +- } ++ return util_iov_dup(l2_caps, 1); + + merge_data.src = l3_caps; + merge_data.result = new0(struct iovec, 1); +@@ -6642,17 +6634,33 @@ void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, + NULL, + bap_sink_check_level2_ltv, &merge_data); + +-done: ++ return merge_data.result; ++} ++ ++void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, ++ struct bt_bap_codec *codec, ++ struct iovec *l2_caps, ++ struct iovec *l3_caps, ++ struct bt_bap_pac **lpac, ++ struct iovec **caps) ++{ ++ struct iovec *merged_caps; ++ struct bt_ltv_match match_data; ++ ++ merged_caps = bt_bap_merge_caps(l2_caps, l3_caps); ++ if (!merged_caps) ++ return; ++ + /* Check each BIS Codec Specific Configuration LTVs against our Codec + * Specific Capabilities and if the BIS matches create a PAC with it + */ +- match_data = bap_check_bis(bap->ldb, merge_data.result); ++ match_data = bap_check_bis(bap->ldb, merged_caps); + if (match_data.found == true) { +- *caps = merge_data.result; ++ *caps = merged_caps; + *lpac = match_data.data; + DBG(bap, "Matching BIS %i", bis_index); + } else { +- util_iov_free(merge_data.result, 1); ++ util_iov_free(merged_caps, 1); + *caps = NULL; + *lpac = NULL; + } +diff --git a/src/shared/bap.h b/src/shared/bap.h +index b35b2711edb9..e63161dca4e8 100644 +--- a/src/shared/bap.h ++++ b/src/shared/bap.h +@@ -251,6 +251,8 @@ bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac); + + struct iovec *bt_bap_stream_get_base(struct bt_bap_stream *stream); + ++struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps); ++ + void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, + struct bt_bap_codec *codec, + struct iovec *l2_caps, +-- +2.45.2 + + +From 679349fbc9f2eaf4216cca0ca45f25e4d2829c9d Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:21:59 +0300 +Subject: [PATCH 21/46] shared/bap: Update bt_bap_verify_bis to receive caps + +This updates bt_bap_verify_bis to receive the already merged L2 and L3 +capabilities, instead of computing it internally. +--- + profiles/audio/bap.c | 11 ++++++++--- + src/shared/bap.c | 15 ++++----------- + src/shared/bap.h | 6 ++---- + unit/test-bap.c | 7 ++++--- + 4 files changed, 18 insertions(+), 21 deletions(-) + +diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c +index 16c02524f2d0..81b5051f089a 100644 +--- a/profiles/audio/bap.c ++++ b/profiles/audio/bap.c +@@ -1191,12 +1191,17 @@ static bool parse_base(struct bap_data *bap_data, struct bt_iso_base *base, + l3_caps->iov_len, NULL, print_ltv, + func); + ++ merged_caps = bt_bap_merge_caps(l2_caps, l3_caps); ++ if (!merged_caps) { ++ free(path); ++ continue; ++ } ++ + /* Check if this BIS matches any local PAC */ + bt_bap_verify_bis(bap_data->bap, bis_index, &codec, +- l2_caps, l3_caps, &matched_lpac, +- &merged_caps); ++ merged_caps, &matched_lpac); + +- if (matched_lpac == NULL || merged_caps == NULL) { ++ if (matched_lpac == NULL) { + free(path); + continue; + } +diff --git a/src/shared/bap.c b/src/shared/bap.c +index 1259ee3c90ef..3381ffdd43ee 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -6639,29 +6639,22 @@ struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps) + + void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, + struct bt_bap_codec *codec, +- struct iovec *l2_caps, +- struct iovec *l3_caps, +- struct bt_bap_pac **lpac, +- struct iovec **caps) ++ struct iovec *caps, ++ struct bt_bap_pac **lpac) + { +- struct iovec *merged_caps; + struct bt_ltv_match match_data; + +- merged_caps = bt_bap_merge_caps(l2_caps, l3_caps); +- if (!merged_caps) ++ if (!caps) + return; + + /* Check each BIS Codec Specific Configuration LTVs against our Codec + * Specific Capabilities and if the BIS matches create a PAC with it + */ +- match_data = bap_check_bis(bap->ldb, merged_caps); ++ match_data = bap_check_bis(bap->ldb, caps); + if (match_data.found == true) { +- *caps = merged_caps; + *lpac = match_data.data; + DBG(bap, "Matching BIS %i", bis_index); + } else { +- util_iov_free(merged_caps, 1); +- *caps = NULL; + *lpac = NULL; + } + +diff --git a/src/shared/bap.h b/src/shared/bap.h +index e63161dca4e8..3e68f00e2e29 100644 +--- a/src/shared/bap.h ++++ b/src/shared/bap.h +@@ -255,8 +255,6 @@ struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps); + + void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, + struct bt_bap_codec *codec, +- struct iovec *l2_caps, +- struct iovec *l3_caps, +- struct bt_bap_pac **lpac, +- struct iovec **caps); ++ struct iovec *caps, ++ struct bt_bap_pac **lpac); + +diff --git a/unit/test-bap.c b/unit/test-bap.c +index 9dd7a45e89c1..4b47d6363a80 100644 +--- a/unit/test-bap.c ++++ b/unit/test-bap.c +@@ -587,12 +587,13 @@ static void bsnk_pac_added(struct bt_bap_pac *pac, void *user_data) + codec.id = LC3_ID; + + for (uint8_t i = 0; i < data->cfg->streams; i++) { +- bt_bap_verify_bis(data->bap, bis_idx++, &codec, +- &data->cfg->cc, NULL, &lpac, &cc); ++ cc = bt_bap_merge_caps(&data->cfg->cc, NULL); ++ g_assert(cc); ++ ++ bt_bap_verify_bis(data->bap, bis_idx++, &codec, cc, &lpac); + + g_assert(lpac); + g_assert(pac == lpac); +- g_assert(cc); + + stream = bt_bap_stream_new(data->bap, + pac, NULL, &data->cfg->qos, cc); +-- +2.45.2 + + +From bbcf4891cd46f53e35761db808155dc0fb89b175 Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:00 +0300 +Subject: [PATCH 22/46] shared/bap: Remove unused param from bt_bap_verify_bis + +This removes the codec parameter from bt_bap_verify_bis, +since it is not used. +--- + profiles/audio/bap.c | 2 +- + src/shared/bap.c | 1 - + src/shared/bap.h | 1 - + unit/test-bap.c | 8 +------- + 4 files changed, 2 insertions(+), 10 deletions(-) + +diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c +index 81b5051f089a..b76d5385fa10 100644 +--- a/profiles/audio/bap.c ++++ b/profiles/audio/bap.c +@@ -1198,7 +1198,7 @@ static bool parse_base(struct bap_data *bap_data, struct bt_iso_base *base, + } + + /* Check if this BIS matches any local PAC */ +- bt_bap_verify_bis(bap_data->bap, bis_index, &codec, ++ bt_bap_verify_bis(bap_data->bap, bis_index, + merged_caps, &matched_lpac); + + if (matched_lpac == NULL) { +diff --git a/src/shared/bap.c b/src/shared/bap.c +index 3381ffdd43ee..d2a500e486ed 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -6638,7 +6638,6 @@ struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps) + } + + void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, +- struct bt_bap_codec *codec, + struct iovec *caps, + struct bt_bap_pac **lpac) + { +diff --git a/src/shared/bap.h b/src/shared/bap.h +index 3e68f00e2e29..bf928bc2d577 100644 +--- a/src/shared/bap.h ++++ b/src/shared/bap.h +@@ -254,7 +254,6 @@ struct iovec *bt_bap_stream_get_base(struct bt_bap_stream *stream); + struct iovec *bt_bap_merge_caps(struct iovec *l2_caps, struct iovec *l3_caps); + + void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, +- struct bt_bap_codec *codec, + struct iovec *caps, + struct bt_bap_pac **lpac); + +diff --git a/unit/test-bap.c b/unit/test-bap.c +index 4b47d6363a80..9cfc8c403ef0 100644 +--- a/unit/test-bap.c ++++ b/unit/test-bap.c +@@ -575,22 +575,16 @@ static void bsnk_pac_added(struct bt_bap_pac *pac, void *user_data) + struct test_data *data = user_data; + struct bt_bap_pac *lpac; + struct iovec *cc; +- struct bt_bap_codec codec = {0}; + struct bt_bap_stream *stream; + uint8_t bis_idx = 1; + + bt_bap_pac_set_ops(pac, &bcast_pac_ops, NULL); + +- if (data->cfg->vs) +- codec.id = 0xff; +- else +- codec.id = LC3_ID; +- + for (uint8_t i = 0; i < data->cfg->streams; i++) { + cc = bt_bap_merge_caps(&data->cfg->cc, NULL); + g_assert(cc); + +- bt_bap_verify_bis(data->bap, bis_idx++, &codec, cc, &lpac); ++ bt_bap_verify_bis(data->bap, bis_idx++, cc, &lpac); + + g_assert(lpac); + g_assert(pac == lpac); +-- +2.45.2 + + +From 662aee4357f8975763280fec0e6cd35b2082200d Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:01 +0300 +Subject: [PATCH 23/46] shared/bap: Allow checking bis caps against peer caps + +A BAP Broadcast Assistant needs to match stream capabilities with +capabilities discovered in the Sink PAC characteristic on the peer. + +This updates bt_bap_verify_bis to check the provided stream capabilities +against local or remote capabilities, depending on the bap structure +provided: + +If the device is acting as a BAP Broadcast Sink and the bap session was +created after scanning a Broadcast Source, the stream caps will be matched +with the local broadcast sink PAC. + +If the device is acting as a Broadcast Assistant and the bap session is a +client session with a BAP Scan Delegator, the stream caps will be matched +with the PAC records populated in the rdb at service discovery. +--- + src/shared/bap.c | 26 ++++++++++++++++++++------ + 1 file changed, 20 insertions(+), 6 deletions(-) + +diff --git a/src/shared/bap.c b/src/shared/bap.c +index d2a500e486ed..44fb06169e4e 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -6577,7 +6577,7 @@ static void bap_sink_match_allocation(size_t i, uint8_t l, uint8_t t, + data->found = false; + } + +-static struct bt_ltv_match bap_check_bis(struct bt_bap_db *ldb, ++static struct bt_ltv_match bap_check_bis(uint32_t sink_loc, struct queue *pacs, + struct iovec *bis_data) + { + struct bt_ltv_match compare_data = {}; +@@ -6588,10 +6588,10 @@ static struct bt_ltv_match bap_check_bis(struct bt_bap_db *ldb, + */ + compare_data.found = true; + +- if (ldb->pacs->sink_loc_value) { ++ if (sink_loc) { + uint8_t type = BAP_CHANNEL_ALLOCATION_LTV_TYPE; + +- compare_data.data32 = ldb->pacs->sink_loc_value; ++ compare_data.data32 = sink_loc; + util_ltv_foreach(bis_data->iov_base, bis_data->iov_len, &type, + bap_sink_match_allocation, &compare_data); + } +@@ -6600,8 +6600,7 @@ static struct bt_ltv_match bap_check_bis(struct bt_bap_db *ldb, + if (compare_data.found) { + compare_data.data = bis_data; + compare_data.found = false; +- queue_foreach(ldb->broadcast_sinks, check_local_pac, +- &compare_data); ++ queue_foreach(pacs, check_local_pac, &compare_data); + } + + return compare_data; +@@ -6642,14 +6641,29 @@ void bt_bap_verify_bis(struct bt_bap *bap, uint8_t bis_index, + struct bt_bap_pac **lpac) + { + struct bt_ltv_match match_data; ++ uint32_t sink_loc; ++ struct queue *pacs; + + if (!caps) + return; + ++ /* If the bap session corresponds to a client connection with ++ * a BAP Server, bis caps should be checked against peer caps. ++ * If the bap session corresponds to a scanned broadcast source, ++ * bis caps should be checked against local broadcast sink caps. ++ */ ++ if (bap->client) { ++ sink_loc = bap->rdb->pacs->sink_loc_value; ++ pacs = bap->rdb->sinks; ++ } else { ++ sink_loc = bap->ldb->pacs->sink_loc_value; ++ pacs = bap->ldb->broadcast_sinks; ++ } ++ + /* Check each BIS Codec Specific Configuration LTVs against our Codec + * Specific Capabilities and if the BIS matches create a PAC with it + */ +- match_data = bap_check_bis(bap->ldb, caps); ++ match_data = bap_check_bis(sink_loc, pacs, caps); + if (match_data.found == true) { + *lpac = match_data.data; + DBG(bap, "Matching BIS %i", bis_index); +-- +2.45.2 + + +From 2c98c478863ee9e213a4129f0f4fee2b16b678da Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:02 +0300 +Subject: [PATCH 24/46] shared/bap: Append bcast sink pacs to Sink PAC char + +It is mandatory for a BAP Broadcast Sink to support the PACS Server role. +The Sink PAC characteristic should contain PAC records that expose +supported audio capabilities for receiving both unicast and broadcast +streams. + +A BAP Broadcast Assistant acting as a GATT Client needs to discover the +Sink PAC characteristic on the BAP Scan Delegator peer (BAP Broadcast +Sink), in order to discover supported capabilities for receiving streams. + +This commit updates the callback for handling read requests for the Sink +PAC characteristic, to also append Broadcast Sink pac structures to the +read response. +--- + src/shared/bap.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/shared/bap.c b/src/shared/bap.c +index 44fb06169e4e..0aa89c2781ba 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -441,6 +441,7 @@ static void pacs_sink_read(struct gatt_db_attribute *attrib, + iov.iov_len = 0; + + queue_foreach(bdb->sinks, pac_foreach, &iov); ++ queue_foreach(bdb->broadcast_sinks, pac_foreach, &iov); + + gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base, + iov.iov_len); +-- +2.45.2 + + +From f163913488106929081026c56c236800aa6f8269 Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:03 +0300 +Subject: [PATCH 25/46] bap: Add API to get bt_bap matching device + +This adds a public BAP API to obtain a reference to the bt_bap session +with a peer device. +--- + Makefile.plugins | 2 +- + profiles/audio/bap.c | 21 +++++++++++++++++++++ + profiles/audio/bap.h | 10 ++++++++++ + 3 files changed, 32 insertions(+), 1 deletion(-) + create mode 100644 profiles/audio/bap.h + +diff --git a/Makefile.plugins b/Makefile.plugins +index 44fda45367d9..9dd8134b42c5 100644 +--- a/Makefile.plugins ++++ b/Makefile.plugins +@@ -115,7 +115,7 @@ endif + + if BAP + builtin_modules += bap +-builtin_sources += profiles/audio/bap.c ++builtin_sources += profiles/audio/bap.h profiles/audio/bap.c + endif + + if BASS +diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c +index b76d5385fa10..79e9cc52791e 100644 +--- a/profiles/audio/bap.c ++++ b/profiles/audio/bap.c +@@ -56,6 +56,8 @@ + #include "src/log.h" + #include "src/error.h" + ++#include "bap.h" ++ + #define ISO_SOCKET_UUID "6fbaf188-05e0-496a-9885-d6ddfdb4e03e" + #define PACS_UUID_STR "00001850-0000-1000-8000-00805f9b34fb" + #define BCAAS_UUID_STR "00001852-0000-1000-8000-00805f9b34fb" +@@ -2751,6 +2753,25 @@ static void pac_removed_broadcast(struct bt_bap_pac *pac, void *user_data) + ep_unregister(ep); + } + ++static bool match_device(const void *data, const void *match_data) ++{ ++ const struct bap_data *bdata = data; ++ const struct btd_device *device = match_data; ++ ++ return bdata->device == device; ++} ++ ++struct bt_bap *bap_get_session(struct btd_device *device) ++{ ++ struct bap_data *data; ++ ++ data = queue_find(sessions, match_device, device); ++ if (!data) ++ return NULL; ++ ++ return data->bap; ++} ++ + static struct bap_data *bap_data_new(struct btd_device *device) + { + struct bap_data *data; +diff --git a/profiles/audio/bap.h b/profiles/audio/bap.h +new file mode 100644 +index 000000000000..66f8db88713a +--- /dev/null ++++ b/profiles/audio/bap.h +@@ -0,0 +1,10 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * ++ * BlueZ - Bluetooth protocol stack for Linux ++ * ++ * Copyright 2024 NXP ++ * ++ */ ++ ++struct bt_bap *bap_get_session(struct btd_device *device); +-- +2.45.2 + + +From 88bf423eb525655e15890bdca84d9acb5afab122 Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:04 +0300 +Subject: [PATCH 26/46] shared/bass: Add API to get GATT client reference + +Some use cases require the BASS plugin to differentiate between client and +server BASS sessions - for example, the BAP Broadcast Assistant role only +considers client BASS sessions. + +This adds a BASS API to obtain a reference to the bt_gatt_client structure +attached to the bt_bass session. +--- + src/shared/bass.c | 8 ++++++++ + src/shared/bass.h | 1 + + 2 files changed, 9 insertions(+) + +diff --git a/src/shared/bass.c b/src/shared/bass.c +index d82c043ac0db..268e3bd8613e 100644 +--- a/src/shared/bass.c ++++ b/src/shared/bass.c +@@ -1683,6 +1683,14 @@ struct bt_att *bt_bass_get_att(struct bt_bass *bass) + return bt_gatt_client_get_att(bass->client); + } + ++struct bt_gatt_client *bt_bass_get_client(struct bt_bass *bass) ++{ ++ if (!bass) ++ return NULL; ++ ++ return 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) + { +diff --git a/src/shared/bass.h b/src/shared/bass.h +index c4b5b76baa53..1674146bca10 100644 +--- a/src/shared/bass.h ++++ b/src/shared/bass.h +@@ -121,6 +121,7 @@ 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); ++struct bt_gatt_client *bt_bass_get_client(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); +-- +2.45.2 + + +From 77e4c0976c0d342e45f0ad1b485efd7e60863e30 Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:05 +0300 +Subject: [PATCH 27/46] bass: Register MediaAssistant objects + +This adds an initial implementation of the BAP Broadcast Assistant role +in the BASS plugin, by introducing the MediaAssistant DBus object. + +The BAP plugin implements the callback to probe Broadcast Sources and +parse the BASE. This commit adds 2 BASS APIs, that will be called by the +BAP plugin to notify BISes discovered in the BASE of a broadcaster to +BASS, or to inform the BASS plugin that a broadcaster has been removed. + +For each BASS client session, the BASS plugin checks BIS caps against +the peer caps, and registers a MediaAssistant object for each match. +--- + Makefile.plugins | 2 +- + profiles/audio/bass.c | 257 ++++++++++++++++++++++++++++++++++++++++++ + profiles/audio/bass.h | 13 +++ + 3 files changed, 271 insertions(+), 1 deletion(-) + create mode 100644 profiles/audio/bass.h + +diff --git a/Makefile.plugins b/Makefile.plugins +index 9dd8134b42c5..9da29a3ce43a 100644 +--- a/Makefile.plugins ++++ b/Makefile.plugins +@@ -120,7 +120,7 @@ endif + + if BASS + builtin_modules += bass +-builtin_sources += profiles/audio/bass.c ++builtin_sources += profiles/audio/bass.h profiles/audio/bass.c + endif + + if MCP +diff --git a/profiles/audio/bass.c b/profiles/audio/bass.c +index 7952105c51bb..083988358735 100644 +--- a/profiles/audio/bass.c ++++ b/profiles/audio/bass.c +@@ -39,6 +39,7 @@ + #include "src/shared/gatt-server.h" + #include "src/adapter.h" + #include "src/shared/bass.h" ++#include "src/shared/bap.h" + + #include "src/plugin.h" + #include "src/gatt-database.h" +@@ -48,21 +49,265 @@ + #include "src/log.h" + #include "src/error.h" + ++#include "bass.h" ++#include "bap.h" ++ + #define BASS_UUID_STR "0000184f-0000-1000-8000-00805f9b34fb" + ++#define MEDIA_ASSISTANT_INTERFACE "org.bluez.MediaAssistant1" ++ ++enum assistant_state { ++ ASSISTANT_STATE_IDLE, /* Assistant object was created for ++ * the stream ++ */ ++ ASSISTANT_STATE_PENDING, /* Assistant object was pushed */ ++ ASSISTANT_STATE_REQUESTING, /* Remote device requires ++ * Broadcast_Code ++ */ ++ ASSISTANT_STATE_ACTIVE, /* Remote device started receiving ++ * stream ++ */ ++}; ++ + struct bass_data { + struct btd_device *device; + struct btd_service *service; + struct bt_bass *bass; + }; + ++struct bass_assistant { ++ struct btd_device *device; /* Broadcast source device */ ++ struct bass_data *data; /* BASS session with peer device */ ++ uint8_t sgrp; ++ uint8_t bis; ++ struct bt_iso_qos qos; ++ struct iovec *meta; ++ struct iovec *caps; ++ enum assistant_state state; ++ char *path; ++}; ++ + static struct queue *sessions; ++static struct queue *assistants; + + static void bass_debug(const char *str, void *user_data) + { + DBG_IDX(0xffff, "%s", str); + } + ++static DBusMessage *push(DBusConnection *conn, DBusMessage *msg, ++ void *user_data) ++{ ++ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); ++} ++ ++static const GDBusMethodTable assistant_methods[] = { ++ {GDBUS_EXPERIMENTAL_ASYNC_METHOD("Push", ++ GDBUS_ARGS({ "Props", "a{sv}" }), ++ NULL, push)}, ++ {}, ++}; ++ ++static const char *state2str(enum assistant_state state) ++{ ++ switch (state) { ++ case ASSISTANT_STATE_IDLE: ++ return "idle"; ++ case ASSISTANT_STATE_PENDING: ++ return "pending"; ++ case ASSISTANT_STATE_REQUESTING: ++ return "requesting"; ++ case ASSISTANT_STATE_ACTIVE: ++ return "active"; ++ } ++ ++ return NULL; ++} ++ ++static gboolean get_state(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct bass_assistant *assistant = data; ++ const char *state = state2str(assistant->state); ++ ++ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &state); ++ ++ return TRUE; ++} ++ ++static gboolean get_metadata(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct bass_assistant *assistant = data; ++ DBusMessageIter array; ++ ++ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_BYTE_AS_STRING, &array); ++ ++ if (assistant->meta) ++ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, ++ &assistant->meta->iov_base, ++ assistant->meta->iov_len); ++ ++ dbus_message_iter_close_container(iter, &array); ++ ++ return TRUE; ++} ++ ++static gboolean get_qos(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct bass_assistant *assistant = data; ++ DBusMessageIter dict; ++ uint8_t *bcode = assistant->qos.bcast.bcode; ++ ++ dbus_message_iter_open_container(iter, 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); ++ ++ dict_append_entry(&dict, "Encryption", DBUS_TYPE_BYTE, ++ &assistant->qos.bcast.encryption); ++ dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE, ++ &bcode, BT_BASS_BCAST_CODE_SIZE); ++ ++ dbus_message_iter_close_container(iter, &dict); ++ ++ return TRUE; ++} ++ ++static const GDBusPropertyTable assistant_properties[] = { ++ { "State", "s", get_state }, ++ { "Metadata", "ay", get_metadata, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { "QoS", "a{sv}", get_qos, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { } ++}; ++ ++static void assistant_free(void *data) ++{ ++ struct bass_assistant *assistant = data; ++ ++ g_free(assistant->path); ++ util_iov_free(assistant->meta, 1); ++ util_iov_free(assistant->caps, 1); ++ ++ free(assistant); ++} ++ ++static struct bass_assistant *assistant_new(struct btd_adapter *adapter, ++ struct btd_device *device, struct bass_data *data, ++ uint8_t sgrp, uint8_t bis, struct bt_iso_qos *qos, ++ struct iovec *meta, struct iovec *caps) ++{ ++ struct bass_assistant *assistant; ++ char src_addr[18]; ++ char dev_addr[18]; ++ ++ assistant = new0(struct bass_assistant, 1); ++ if (!assistant) ++ return NULL; ++ ++ DBG("assistant %p", assistant); ++ ++ assistant->device = device; ++ assistant->data = data; ++ assistant->sgrp = sgrp; ++ assistant->bis = bis; ++ assistant->qos = *qos; ++ assistant->meta = util_iov_dup(meta, 1); ++ assistant->caps = util_iov_dup(caps, 1); ++ ++ ba2str(device_get_address(device), src_addr); ++ ba2str(device_get_address(data->device), dev_addr); ++ ++ assistant->path = g_strdup_printf("%s/src_%s/dev_%s/bis%d", ++ adapter_get_path(adapter), src_addr, dev_addr, bis); ++ ++ g_strdelimit(assistant->path, ":", '_'); ++ ++ if (!assistants) ++ assistants = queue_new(); ++ ++ queue_push_tail(assistants, assistant); ++ ++ return assistant; ++} ++ ++void bass_add_stream(struct btd_device *device, struct iovec *meta, ++ struct iovec *caps, struct bt_iso_qos *qos, ++ uint8_t sgrp, uint8_t bis) ++{ ++ const struct queue_entry *entry; ++ struct bt_bap *bap; ++ struct bt_bap_pac *pac; ++ struct bass_assistant *assistant; ++ char addr[18]; ++ ++ for (entry = queue_get_entries(sessions); entry; entry = entry->next) { ++ struct bass_data *data = entry->data; ++ struct btd_adapter *adapter = device_get_adapter(data->device); ++ ++ if (!bt_bass_get_client(data->bass)) ++ /* Only client sessions must be handled */ ++ continue; ++ ++ bap = bap_get_session(data->device); ++ if (!bap) ++ continue; ++ ++ /* Check stream capabilities against peer caps. */ ++ bt_bap_verify_bis(bap, bis, caps, &pac); ++ ++ if (!pac) ++ /* Capabilities did not match. */ ++ continue; ++ ++ ba2str(device_get_address(device), addr); ++ ++ DBG("%s data %p BIS %d", addr, data, bis); ++ ++ assistant = assistant_new(adapter, device, data, sgrp, ++ bis, qos, meta, caps); ++ ++ if (g_dbus_register_interface(btd_get_dbus_connection(), ++ assistant->path, ++ MEDIA_ASSISTANT_INTERFACE, ++ assistant_methods, NULL, ++ assistant_properties, ++ assistant, ++ assistant_free) == FALSE) ++ DBG("Could not register path %s", assistant->path); ++ } ++} ++ ++static bool assistant_match_device(const void *data, const void *match_data) ++{ ++ const struct bass_assistant *assistant = data; ++ const struct btd_device *device = match_data; ++ ++ return (assistant->device == device); ++} ++ ++static void unregister_assistant(void *data) ++{ ++ struct bass_assistant *assistant = data; ++ ++ DBG("%p", assistant); ++ ++ g_dbus_unregister_interface(btd_get_dbus_connection(), ++ assistant->path, MEDIA_ASSISTANT_INTERFACE); ++} ++ ++void bass_remove_stream(struct btd_device *device) ++{ ++ queue_remove_all(assistants, assistant_match_device, ++ device, unregister_assistant); ++} ++ + static struct bass_data *bass_data_new(struct btd_device *device) + { + struct bass_data *data; +@@ -101,6 +346,14 @@ static bool match_data(const void *data, const void *match_data) + return bdata->bass == bass; + } + ++static bool assistant_match_data(const void *data, const void *match_data) ++{ ++ const struct bass_assistant *assistant = data; ++ const struct bass_data *bdata = match_data; ++ ++ return (assistant->data == bdata); ++} ++ + static void bass_data_free(struct bass_data *data) + { + if (data->service) { +@@ -109,6 +362,10 @@ static void bass_data_free(struct bass_data *data) + } + + bt_bass_unref(data->bass); ++ ++ queue_remove_all(assistants, assistant_match_data, ++ data, unregister_assistant); ++ + free(data); + } + +diff --git a/profiles/audio/bass.h b/profiles/audio/bass.h +new file mode 100644 +index 000000000000..5bef92946c46 +--- /dev/null ++++ b/profiles/audio/bass.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * ++ * BlueZ - Bluetooth protocol stack for Linux ++ * ++ * Copyright 2024 NXP ++ * ++ */ ++ ++void bass_add_stream(struct btd_device *device, struct iovec *meta, ++ struct iovec *caps, struct bt_iso_qos *qos, ++ uint8_t sgrp, uint8_t bis); ++void bass_remove_stream(struct btd_device *device); +-- +2.45.2 + + +From 22779f0bce61cfd5cd72f4e2c199aaa385067248 Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:06 +0300 +Subject: [PATCH 28/46] bap: Notify scanned BISes to BASS + +This updates the BAP implementation to also notify the BASS plugin about +scanned broadcast streams, or when a scanned broadcaster is removed. This +is needed for the BAP Broadcast Assistant role - the BASS plugin registers +MediaAssistant objects for each detected stream that matches the audio +capabilities of peer Scan Delegator devices. +--- + profiles/audio/bap.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c +index 79e9cc52791e..53f430d66171 100644 +--- a/profiles/audio/bap.c ++++ b/profiles/audio/bap.c +@@ -57,6 +57,7 @@ + #include "src/error.h" + + #include "bap.h" ++#include "bass.h" + + #define ISO_SOCKET_UUID "6fbaf188-05e0-496a-9885-d6ddfdb4e03e" + #define PACS_UUID_STR "00001850-0000-1000-8000-00805f9b34fb" +@@ -1199,6 +1200,9 @@ static bool parse_base(struct bap_data *bap_data, struct bt_iso_base *base, + continue; + } + ++ bass_add_stream(bap_data->device, meta, merged_caps, ++ qos, idx, bis_index); ++ + /* Check if this BIS matches any local PAC */ + bt_bap_verify_bis(bap_data->bap, bis_index, + merged_caps, &matched_lpac); +@@ -3227,6 +3231,8 @@ static void bap_bcast_remove(struct btd_service *service) + free(req); + + bap_data_remove(data); ++ ++ bass_remove_stream(device); + } + + static int bap_probe(struct btd_service *service) +-- +2.45.2 + + +From a3f9970f7a8b62b426e7a00303ddb66acb79aadd Mon Sep 17 00:00:00 2001 +From: Iulia Tanasescu +Date: Tue, 16 Jul 2024 17:22:07 +0300 +Subject: [PATCH 29/46] client: Add assistant submenu + +This adds the initial implementation for the assistant menu in +bluetoothctl, to detect and print MediaAssistant objects. + +The current BAP Broadcast Assistant implementation can be tested +by running bluetoothctl, connecting to a BASS Server, scanning +a Broadcast Source that is streaming a number of BISes with +audio capabilities matching the capabilities of the peer device, +and noticing the MediaAssistant objects being created: + +client/bluetoothctl +[bluetooth]# [CHG] Controller 00:60:37:31:7E:3F Pairable: yes +[bluetooth]# AdvertisementMonitor path registered +[bluetooth]# scan on +[bluetooth]# [NEW] Device 00:60:37:94:A6:A3 00-60-37-94-A6-A3 +[bluetooth]# connect 00:60:37:94:A6:A3 +Attempting to connect to 00:60:37:94:A6:A3 +[CHG] Device 00:60:37:94:A6:A3 Connected: yes +[00-60-37-94-A6-A3]# Connection successful +[00-60-37-94-A6-A3]# [NEW] Device 15:65:78:B6:52:F6 15-65-78-B6-52-F6 +[00-60-37-94-A6-A3]# [NEW] Assistant + /org/bluez/hci0/src_15_65_78_B6_52_F6/dev_00_60_37_94_A6_A3/bis1 +[00-60-37-94-A6-A3]# [NEW] Assistant + /org/bluez/hci0/src_15_65_78_B6_52_F6/dev_00_60_37_94_A6_A3/bis2 +[00-60-37-94-A6-A3]# scan off +[00-60-37-94-A6-A3]# Diovery stopped +[00-60-37-94-A6-A3]# disconnect +Attempting to disconnect from 00:60:37:94:A6:A3 +[00-60-37-94-A6-A3]# Successful disconnected +[CHG] Device 00:60:37:94:A6:A3 Connected: no +[bluetooth]# [DEL] Assistant + /org/bluez/hci0/src_15_65_78_B6_52_F6/dev_00_60_37_94_A6_A3/bis1 +[bluetooth]# [DEL] Assistant + /org/bluez/hci0/src_15_65_78_B6_52_F6/dev_00_60_37_94_A6_A3/bis2 +--- + Makefile.tools | 3 +- + client/assistant.c | 164 +++++++++++++++++++++++++++++++++++++++++++++ + client/assistant.h | 13 ++++ + client/main.c | 5 +- + 4 files changed, 183 insertions(+), 2 deletions(-) + create mode 100644 client/assistant.c + create mode 100644 client/assistant.h + +diff --git a/Makefile.tools b/Makefile.tools +index 679c914bf8cd..f4f9e82dc7c4 100644 +--- a/Makefile.tools ++++ b/Makefile.tools +@@ -13,7 +13,8 @@ client_bluetoothctl_SOURCES = client/main.c \ + client/gatt.h client/gatt.c \ + client/admin.h client/admin.c \ + client/player.h client/player.c \ +- client/mgmt.h client/mgmt.c ++ client/mgmt.h client/mgmt.c \ ++ client/assistant.h client/assistant.c + client_bluetoothctl_LDADD = lib/libbluetooth-internal.la \ + gdbus/libgdbus-internal.la src/libshared-glib.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -lreadline +diff --git a/client/assistant.c b/client/assistant.c +new file mode 100644 +index 000000000000..69a955c18655 +--- /dev/null ++++ b/client/assistant.c +@@ -0,0 +1,164 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * ++ * BlueZ - Bluetooth protocol stack for Linux ++ * ++ * Copyright 2024 NXP ++ * ++ * ++ */ ++ ++#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/uuid.h" ++ ++#include "src/shared/util.h" ++#include "src/shared/shell.h" ++#include "src/shared/io.h" ++#include "src/shared/queue.h" ++#include "print.h" ++#include "assistant.h" ++ ++/* String display constants */ ++#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF ++#define COLORED_CHG COLOR_YELLOW "CHG" COLOR_OFF ++#define COLORED_DEL COLOR_RED "DEL" COLOR_OFF ++ ++#define MEDIA_ASSISTANT_INTERFACE "org.bluez.MediaAssistant1" ++ ++static DBusConnection *dbus_conn; ++ ++static GList *assistants; ++ ++static char *proxy_description(GDBusProxy *proxy, const char *title, ++ const char *description) ++{ ++ const char *path; ++ ++ path = g_dbus_proxy_get_path(proxy); ++ ++ return g_strdup_printf("%s%s%s%s %s ", ++ description ? "[" : "", ++ description ? : "", ++ description ? "] " : "", ++ title, path); ++} ++ ++static void print_assistant(GDBusProxy *proxy, const char *description) ++{ ++ char *str; ++ ++ str = proxy_description(proxy, "Assistant", description); ++ ++ bt_shell_printf("%s\n", str); ++ ++ g_free(str); ++} ++ ++static void assistant_added(GDBusProxy *proxy) ++{ ++ assistants = g_list_append(assistants, proxy); ++ ++ print_assistant(proxy, COLORED_NEW); ++} ++ ++static void proxy_added(GDBusProxy *proxy, void *user_data) ++{ ++ const char *interface; ++ ++ interface = g_dbus_proxy_get_interface(proxy); ++ ++ if (!strcmp(interface, MEDIA_ASSISTANT_INTERFACE)) ++ assistant_added(proxy); ++} ++ ++static void assistant_removed(GDBusProxy *proxy) ++{ ++ assistants = g_list_remove(assistants, proxy); ++ ++ print_assistant(proxy, COLORED_DEL); ++} ++ ++static void proxy_removed(GDBusProxy *proxy, void *user_data) ++{ ++ const char *interface; ++ ++ interface = g_dbus_proxy_get_interface(proxy); ++ ++ if (!strcmp(interface, MEDIA_ASSISTANT_INTERFACE)) ++ assistant_removed(proxy); ++} ++ ++static void assistant_property_changed(GDBusProxy *proxy, const char *name, ++ DBusMessageIter *iter) ++{ ++ char *str; ++ ++ str = proxy_description(proxy, "Assistant", COLORED_CHG); ++ print_iter(str, name, iter); ++ g_free(str); ++} ++ ++static void property_changed(GDBusProxy *proxy, const char *name, ++ DBusMessageIter *iter, void *user_data) ++{ ++ const char *interface; ++ ++ interface = g_dbus_proxy_get_interface(proxy); ++ ++ if (!strcmp(interface, MEDIA_ASSISTANT_INTERFACE)) ++ assistant_property_changed(proxy, name, iter); ++} ++ ++static void assistant_unregister(void *data) ++{ ++ GDBusProxy *proxy = data; ++ ++ bt_shell_printf("Assistant %s unregistered\n", ++ g_dbus_proxy_get_path(proxy)); ++} ++ ++static void disconnect_handler(DBusConnection *connection, void *user_data) ++{ ++ g_list_free_full(assistants, assistant_unregister); ++ assistants = NULL; ++} ++ ++static GDBusClient * client; ++ ++void assistant_add_submenu(void) ++{ ++ dbus_conn = bt_shell_get_env("DBUS_CONNECTION"); ++ if (!dbus_conn || client) ++ return; ++ ++ client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); ++ ++ g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, ++ property_changed, NULL); ++ g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL); ++} ++ ++void assistant_remove_submenu(void) ++{ ++ g_dbus_client_unref(client); ++ client = NULL; ++} ++ +diff --git a/client/assistant.h b/client/assistant.h +new file mode 100644 +index 000000000000..418b0b84031f +--- /dev/null ++++ b/client/assistant.h +@@ -0,0 +1,13 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * ++ * BlueZ - Bluetooth protocol stack for Linux ++ * ++ * Copyright 2024 NXP ++ * ++ * ++ */ ++ ++void assistant_add_submenu(void); ++void assistant_remove_submenu(void); ++ +diff --git a/client/main.c b/client/main.c +index f012ddd436ad..a96a4263849d 100644 +--- a/client/main.c ++++ b/client/main.c +@@ -4,7 +4,7 @@ + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. +- * ++ * Copyright 2024 NXP + * + */ + +@@ -34,6 +34,7 @@ + #include "admin.h" + #include "player.h" + #include "mgmt.h" ++#include "assistant.h" + + /* String display constants */ + #define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF +@@ -3205,6 +3206,7 @@ int main(int argc, char *argv[]) + admin_add_submenu(); + player_add_submenu(); + mgmt_add_submenu(); ++ assistant_add_submenu(); + + client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); + +@@ -3222,6 +3224,7 @@ int main(int argc, char *argv[]) + admin_remove_submenu(); + player_remove_submenu(); + mgmt_remove_submenu(); ++ assistant_remove_submenu(); + + g_dbus_client_unref(client); + +-- +2.45.2 + + +From 4c9d4ed059b539c696e1ebcc92b0cb2522e48a2d Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Mon, 22 Jul 2024 11:58:39 -0400 +Subject: [PATCH 30/46] client/player: Set number of channels based on + locations + +This sets the number of channels based on the locations set rather than +always hardcoding it to 3 which in certain case is incorrect and can +lead for the same location to be configured multiple times. +--- + client/player.c | 19 ++++++++++++------- + 1 file changed, 12 insertions(+), 7 deletions(-) + +diff --git a/client/player.c b/client/player.c +index 5b0b918fb8d7..9334a053d34d 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -1140,10 +1140,9 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, + .meta = _meta, \ + } + +-#define LC3_DATA(_freq, _duration, _chan_count, _len_min, _len_max) \ ++#define LC3_DATA(_freq, _duration, _len_min, _len_max) \ + UTIL_IOV_INIT(0x03, 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) + +@@ -1182,11 +1181,10 @@ static const struct capabilities { + * + * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz + * Duration: 7.5 ms 10 ms +- * Channel count: 3 + * Frame length: 26-240 + */ + CODEC_CAPABILITIES("pac_snk/lc3", PAC_SINK_UUID, LC3_ID, +- LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26, ++ LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26, + 240), + UTIL_IOV_INIT()), + +@@ -1198,7 +1196,7 @@ static const struct capabilities { + * Frame length: 26-240 + */ + CODEC_CAPABILITIES("pac_src/lc3", PAC_SOURCE_UUID, LC3_ID, +- LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26, ++ LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26, + 240), + UTIL_IOV_INIT()), + +@@ -1210,7 +1208,7 @@ static const struct capabilities { + * Frame length: 26-240 + */ + CODEC_CAPABILITIES("bcaa/lc3", BCAA_SERVICE_UUID, LC3_ID, +- LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26, ++ LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26, + 240), + UTIL_IOV_INIT()), + +@@ -1222,7 +1220,7 @@ static const struct capabilities { + * Frame length: 26-240 + */ + CODEC_CAPABILITIES("baa/lc3", BAA_SERVICE_UUID, LC3_ID, +- LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26, ++ LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26, + 240), + UTIL_IOV_INIT()), + }; +@@ -3220,6 +3218,7 @@ static void endpoint_locations(const char *input, void *user_data) + struct endpoint *ep = user_data; + char *endptr = NULL; + int value; ++ uint8_t channels; + + value = strtol(input, &endptr, 0); + +@@ -3230,6 +3229,12 @@ static void endpoint_locations(const char *input, void *user_data) + + ep->locations = value; + ++ channels = __builtin_popcount(value); ++ /* Automatically set LC3_CHAN_COUNT if only 1 location is supported */ ++ if (channels == 1) ++ util_ltv_push(ep->caps, sizeof(channels), LC3_CHAN_COUNT, ++ &channels); ++ + bt_shell_prompt_input(ep->path, "Supported Context (value):", + endpoint_supported_context, ep); + } +-- +2.45.2 + + +From c2312ebe318413f9e72df505fa236024b57429d4 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 19 Jul 2024 15:52:29 -0400 +Subject: [PATCH 31/46] client/player: Add support to enter alternative preset + +This adds support for alternative preset to be entered so when auto +accepting configuration a different preset can be selected following the +order given to endpoint.presets. +--- + client/player.c | 120 ++++++++++++++++++++++++++++++++++++++++-------- + 1 file changed, 101 insertions(+), 19 deletions(-) + +diff --git a/client/player.c b/client/player.c +index 9334a053d34d..3c3587f2ca3a 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -1230,7 +1230,10 @@ struct codec_preset { + const struct iovec data; + struct bt_bap_qos qos; + uint8_t target_latency; ++ uint32_t chan_alloc; + bool custom; ++ bool alt; ++ struct codec_preset *alt_preset; + }; + + #define SBC_PRESET(_name, _data) \ +@@ -1969,12 +1972,31 @@ static int parse_chan_alloc(DBusMessageIter *iter, uint32_t *location, + if (*channels) + *channels = __builtin_popcount(*location); + return 0; ++ } else if (!strcasecmp(key, "Locations")) { ++ uint32_t tmp; ++ ++ if (var != DBUS_TYPE_UINT32) ++ return -EINVAL; ++ ++ dbus_message_iter_get_basic(&value, &tmp); ++ *location &= tmp; ++ ++ if (*channels) ++ *channels = __builtin_popcount(*location); + } + + dbus_message_iter_next(iter); + } + +- return -EINVAL; ++ return *location ? 0 : -EINVAL; ++} ++ ++static void ltv_find(size_t i, uint8_t l, uint8_t t, uint8_t *v, ++ void *user_data) ++{ ++ bool *found = user_data; ++ ++ *found = true; + } + + static DBusMessage *endpoint_select_properties_reply(struct endpoint *ep, +@@ -1985,7 +2007,7 @@ static DBusMessage *endpoint_select_properties_reply(struct endpoint *ep, + DBusMessageIter iter, props; + struct endpoint_config *cfg; + struct bt_bap_io_qos *qos; +- uint32_t location = 0; ++ uint32_t location = ep->locations; + uint8_t channels = 1; + + if (!preset) +@@ -2006,13 +2028,44 @@ static DBusMessage *endpoint_select_properties_reply(struct endpoint *ep, + dbus_message_iter_recurse(&iter, &props); + + if (!parse_chan_alloc(&props, &location, &channels)) { +- uint8_t chan_alloc_ltv[] = { +- 0x05, LC3_CONFIG_CHAN_ALLOC, location & 0xff, +- location >> 8, location >> 16, location >> 24 +- }; ++ uint32_t chan_alloc = 0; ++ uint8_t type = LC3_CONFIG_CHAN_ALLOC; ++ bool found = false; ++ ++ if (preset->chan_alloc & location) ++ chan_alloc = preset->chan_alloc & location; ++ else if (preset->alt_preset && ++ preset->alt_preset->chan_alloc & ++ location) { ++ chan_alloc = preset->alt_preset->chan_alloc & location; ++ preset = preset->alt_preset; ++ ++ /* Copy alternate capabilities */ ++ util_iov_free(cfg->caps, 1); ++ cfg->caps = util_iov_dup(&preset->data, 1); ++ cfg->target_latency = preset->target_latency; ++ } else ++ chan_alloc = location; ++ ++ /* Check if Channel Allocation is present in caps */ ++ util_ltv_foreach(cfg->caps->iov_base, cfg->caps->iov_len, ++ &type, ltv_find, &found); + +- util_iov_append(cfg->caps, &chan_alloc_ltv, ++ /* If Channel Allocation has not been set directly via ++ * preset->data then attempt to set it if chan_alloc has been ++ * set. ++ */ ++ if (!found && chan_alloc) { ++ uint8_t chan_alloc_ltv[] = { ++ 0x05, LC3_CONFIG_CHAN_ALLOC, chan_alloc & 0xff, ++ chan_alloc >> 8, chan_alloc >> 16, ++ chan_alloc >> 24 ++ }; ++ ++ put_le32(chan_alloc, &chan_alloc_ltv[2]); ++ util_iov_append(cfg->caps, &chan_alloc_ltv, + sizeof(chan_alloc_ltv)); ++ } + } + + /* Copy metadata */ +@@ -2035,6 +2088,8 @@ static DBusMessage *endpoint_select_properties_reply(struct endpoint *ep, + + dbus_message_iter_init_append(reply, &iter); + ++ bt_shell_printf("selecting %s...\n", preset->name); ++ + append_properties(&iter, cfg); + + free(cfg); +@@ -2098,8 +2153,6 @@ static DBusMessage *endpoint_select_properties(DBusConnection *conn, + if (!reply) + return NULL; + +- bt_shell_printf("Auto Accepting using %s...\n", p->name); +- + return reply; + } + +@@ -3621,14 +3674,6 @@ add_meta: + endpoint_set_metadata_cfg, cfg); + } + +-static void ltv_find(size_t i, uint8_t l, uint8_t t, uint8_t *v, +- void *user_data) +-{ +- bool *found = user_data; +- +- *found = true; +-} +- + static void config_endpoint_iso_group(const char *input, void *user_data) + { + struct endpoint_config *cfg = user_data; +@@ -4106,13 +4151,38 @@ static void print_presets(struct preset *preset) + + for (i = 0; i < preset->num_presets; i++) { + p = &preset->presets[i]; +- bt_shell_printf("%s%s\n", p == preset->default_preset ? +- "*" : "", p->name); ++ ++ if (p == preset->default_preset) ++ bt_shell_printf("*%s\n", p->name); ++ else if (preset->default_preset && ++ p == preset->default_preset->alt_preset) ++ bt_shell_printf("**%s\n", p->name); ++ else ++ bt_shell_printf("%s\n", p->name); + } + + queue_foreach(preset->custom, foreach_custom_preset_print, preset); + } + ++static void custom_chan_alloc(const char *input, void *user_data) ++{ ++ struct codec_preset *p = user_data; ++ char *endptr = NULL; ++ ++ p->chan_alloc = strtol(input, &endptr, 0); ++ if (!endptr || *endptr != '\0') { ++ bt_shell_printf("Invalid argument: %s\n", input); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } ++ ++ if (p->alt_preset) ++ bt_shell_prompt_input(p->alt_preset->name, ++ "Enter Channel Allocation: ", ++ custom_chan_alloc, p->alt_preset); ++ else ++ return bt_shell_noninteractive_quit(EXIT_SUCCESS); ++} ++ + static void cmd_presets_endpoint(int argc, char *argv[]) + { + struct preset *preset; +@@ -4133,8 +4203,20 @@ static void cmd_presets_endpoint(int argc, char *argv[]) + preset->default_preset = default_preset; + + if (argc > 4) { ++ struct codec_preset *alt_preset; + struct iovec *iov = (void *)&default_preset->data; + ++ /* Check if and alternative preset was given */ ++ alt_preset = preset_find_name(preset, argv[4]); ++ if (alt_preset) { ++ default_preset->alt_preset = alt_preset; ++ bt_shell_prompt_input(default_preset->name, ++ "Enter Channel Allocation: ", ++ custom_chan_alloc, ++ default_preset); ++ return; ++ } ++ + iov->iov_base = str2bytearray(argv[4], &iov->iov_len); + if (!iov->iov_base) { + bt_shell_printf("Invalid configuration %s\n", +-- +2.45.2 + + +From fcf39175e35ef086ffbe4e84ed6dcd3bf4c0aeea Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Tue, 23 Jul 2024 15:49:58 -0400 +Subject: [PATCH 32/46] shared/bap: Fix bt_bap_select with multiple lpacs + +When there are multiple local PAC records of the same codec with +different locations only the first was consider, also bt_bap_select +would stop doing location matching early if the location don't match +without considering there could be more remote channels. +--- + src/shared/bap.c | 35 ++++++++++++++++++++++++++--------- + 1 file changed, 26 insertions(+), 9 deletions(-) + +diff --git a/src/shared/bap.c b/src/shared/bap.c +index 0aa89c2781ba..499e740c9162 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -3249,25 +3249,32 @@ static void *ltv_merge(struct iovec *data, struct iovec *cont) + return util_iov_append(data, cont->iov_base, cont->iov_len); + } + +-static void bap_pac_foreach_channel(size_t i, uint8_t l, uint8_t t, uint8_t *v, +- void *user_data) ++static void bap_pac_chan_add(struct bt_bap_pac *pac, uint8_t count, ++ uint32_t location) + { +- struct bt_bap_pac *pac = user_data; + struct bt_bap_chan *chan; + +- if (!v) +- return; +- + if (!pac->channels) + pac->channels = queue_new(); + + chan = new0(struct bt_bap_chan, 1); +- chan->count = *v; +- chan->location = bt_bap_pac_get_locations(pac) ? : pac->qos.location; ++ chan->count = count; ++ chan->location = location; + + queue_push_tail(pac->channels, chan); + } + ++static void bap_pac_foreach_channel(size_t i, uint8_t l, uint8_t t, uint8_t *v, ++ void *user_data) ++{ ++ struct bt_bap_pac *pac = user_data; ++ ++ if (!v) ++ return; ++ ++ bap_pac_chan_add(pac, *v, bt_bap_pac_get_locations(pac)); ++} ++ + static void bap_pac_update_channels(struct bt_bap_pac *pac, struct iovec *data) + { + uint8_t type = 0x03; +@@ -3277,6 +3284,13 @@ static void bap_pac_update_channels(struct bt_bap_pac *pac, struct iovec *data) + + util_ltv_foreach(data->iov_base, data->iov_len, &type, + bap_pac_foreach_channel, pac); ++ ++ /* If record didn't set a channel count but set a location use that as ++ * channel count. ++ */ ++ if (queue_isempty(pac->channels) && pac->qos.location) ++ bap_pac_chan_add(pac, pac->qos.location, pac->qos.location); ++ + } + + static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data, +@@ -3607,6 +3621,9 @@ uint32_t bt_bap_pac_get_locations(struct bt_bap_pac *pac) + if (!pac) + return 0; + ++ if (pac->qos.location) ++ return pac->qos.location; ++ + pacs = pac->bdb->pacs; + + switch (pac->type) { +@@ -5411,7 +5428,7 @@ int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac, + + /* Try matching the channel location */ + if (!(map.location & rc->location)) +- break; ++ continue; + + lpac->ops->select(lpac, rpac, map.location & + rc->location, &rpac->qos, +-- +2.45.2 + + +From 549d38852f665d8251b2c0c0e8c9f3d5574ac99b Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Mon, 29 Jul 2024 12:37:36 +0100 +Subject: [PATCH 33/46] client/player: Fix not setting config target_latency + with edpoint.config + +This fixes not setting target_latency with endpoint.config command. +--- + client/player.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/client/player.c b/client/player.c +index 3c3587f2ca3a..93d6b62e24bf 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -1790,9 +1790,9 @@ static void append_ucast_qos(DBusMessageIter *iter, struct endpoint_config *cfg) + DBUS_TYPE_UINT32, &qos->delay); + + if (cfg->target_latency) { +- bt_shell_printf("TargetLatency 0x%02x\n", qos->target_latency); ++ bt_shell_printf("TargetLatency 0x%02x\n", cfg->target_latency); + g_dbus_dict_append_entry(iter, "TargetLatency", DBUS_TYPE_BYTE, +- &qos->target_latency); ++ &cfg->target_latency); + } + + append_io_qos(iter, &qos->io_qos); +@@ -3765,6 +3765,7 @@ static void cmd_config_endpoint(int argc, char *argv[]) + /* Copy capabilities */ + util_iov_append(cfg->caps, preset->data.iov_base, + preset->data.iov_len); ++ cfg->target_latency = preset->target_latency; + + /* Set QoS parameters */ + cfg->qos = preset->qos; +@@ -3960,7 +3961,7 @@ static void custom_target_latency(const char *input, void *user_data) + else if (!strcasecmp(input, "Balance")) + p->target_latency = 0x02; + else if (!strcasecmp(input, "High")) +- p->target_latency = 0x02; ++ p->target_latency = 0x03; + else { + char *endptr = NULL; + +-- +2.45.2 + + +From d7b7f3a39562ca6341254a711ce079b6b8185cd1 Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:06 +0300 +Subject: [PATCH 34/46] doc/media: Add 'broadcasting' state and 'Select' method + +This adds a new state for transports created by the Broadcast +Sink. Such transports will remain in the 'idle' state until the +user calls 'Select' on them, at which point they will be moved to +'broadcasting'. This allows the user to select the desired BIS as +the audio server automatically acquires transports that are in this +state. +--- + doc/org.bluez.MediaTransport.rst | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/doc/org.bluez.MediaTransport.rst b/doc/org.bluez.MediaTransport.rst +index 6e95df8f2ee8..c8aca0223d24 100644 +--- a/doc/org.bluez.MediaTransport.rst ++++ b/doc/org.bluez.MediaTransport.rst +@@ -7,7 +7,7 @@ BlueZ D-Bus MediaTransport API documentation + -------------------------------------------- + + :Version: BlueZ +-:Date: September 2023 ++:Date: July 2024 + :Manual section: 5 + :Manual group: Linux System Administration + +@@ -51,6 +51,20 @@ void Release() + + Releases file descriptor. + ++void Select() ++````````````` ++ ++ Applicable only for transports created by a broadcast sink. This moves ++ the transport from 'idle' to 'broadcasting'. This allows the user to ++ select which BISes he wishes to sync to via a 2 step process: ++ 1) the user calls this method, changing the transport's state to idle ++ 2) the audio server detects that the transport is in the 'broadcasting' ++ state and automatically acquires it ++ ++ Possible Errors: ++ ++ :org.bluez.Error.NotAuthorized: ++ + Properties + ---------- + +@@ -84,6 +98,8 @@ string State [readonly] + + :"idle": not streaming + :"pending": streaming but not acquired ++ :"broadcasting": streaming but not acquired, applicable only for transports ++ created by a broadcast sink + :"active": streaming and acquired + + uint16 Delay [readwrite, optional] +-- +2.45.2 + + +From 9357edb87bb98c74677f4c5548a4fe2d589230f8 Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:07 +0300 +Subject: [PATCH 35/46] transport: Add 'broadcasting' state + +This adds a new state for transports created by the Broadcast +Sink device as a result of scanning a Broadcast Source. Such +transports will remain in the 'idle' state until the user +selects them using 'transport.select', at which point they will +be moved to 'broadcasting'. +--- + profiles/audio/transport.c | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c +index 922911cf3fbc..0a890c0ac5f7 100644 +--- a/profiles/audio/transport.c ++++ b/profiles/audio/transport.c +@@ -51,6 +51,10 @@ + typedef enum { + TRANSPORT_STATE_IDLE, /* Not acquired and suspended */ + TRANSPORT_STATE_PENDING, /* Playing but not acquired */ ++ /* Playing but not acquired, applicable only for transports ++ * created by a broadcast sink ++ */ ++ TRANSPORT_STATE_BROADCASTING, + TRANSPORT_STATE_REQUESTING, /* Acquire in progress */ + TRANSPORT_STATE_ACTIVE, /* Acquired and playing */ + TRANSPORT_STATE_SUSPENDING, /* Release in progress */ +@@ -59,6 +63,7 @@ typedef enum { + static const char *str_state[] = { + "TRANSPORT_STATE_IDLE", + "TRANSPORT_STATE_PENDING", ++ "TRANSPORT_STATE_BROADCASTING", + "TRANSPORT_STATE_REQUESTING", + "TRANSPORT_STATE_ACTIVE", + "TRANSPORT_STATE_SUSPENDING", +@@ -139,6 +144,8 @@ static const char *state2str(transport_state_t state) + return "idle"; + case TRANSPORT_STATE_PENDING: + return "pending"; ++ case TRANSPORT_STATE_BROADCASTING: ++ return "broadcasting"; + case TRANSPORT_STATE_ACTIVE: + case TRANSPORT_STATE_SUSPENDING: + return "active"; +@@ -152,6 +159,7 @@ static gboolean state_in_use(transport_state_t state) + switch (state) { + case TRANSPORT_STATE_IDLE: + case TRANSPORT_STATE_PENDING: ++ case TRANSPORT_STATE_BROADCASTING: + return FALSE; + case TRANSPORT_STATE_REQUESTING: + case TRANSPORT_STATE_ACTIVE: +@@ -679,7 +687,8 @@ static DBusMessage *try_acquire(DBusConnection *conn, DBusMessage *msg, + if (transport->state >= TRANSPORT_STATE_REQUESTING) + return btd_error_not_authorized(msg); + +- if (transport->state != TRANSPORT_STATE_PENDING) ++ if ((transport->state != TRANSPORT_STATE_PENDING) && ++ (transport->state != TRANSPORT_STATE_BROADCASTING)) + return btd_error_not_available(msg); + + owner = media_owner_create(msg); +@@ -1281,7 +1290,8 @@ static void transport_update_playing(struct media_transport *transport, + str_state[transport->state], playing); + + if (playing == FALSE) { +- if (transport->state == TRANSPORT_STATE_PENDING) ++ if ((transport->state == TRANSPORT_STATE_PENDING) || ++ (transport->state == TRANSPORT_STATE_BROADCASTING)) + transport_set_state(transport, TRANSPORT_STATE_IDLE); + else if (transport->state == TRANSPORT_STATE_ACTIVE) { + /* Remove owner */ +-- +2.45.2 + + +From 083d1a7b66b5c495d2545670d5d255aef340dbf9 Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:08 +0300 +Subject: [PATCH 36/46] transport: Add 'Select' method + +This adds the 'Select' method for Broadcast transports. It's role +is to change the transport's state from idle to broadcasting. This +allows the user to select the desired stream when running the setup +with PipeWire since it acquires any transport that is broadcasting. +--- + profiles/audio/transport.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c +index 0a890c0ac5f7..bf2215a0f725 100644 +--- a/profiles/audio/transport.c ++++ b/profiles/audio/transport.c +@@ -972,6 +972,9 @@ static gboolean get_endpoint(const GDBusPropertyTable *property, + return TRUE; + } + ++static DBusMessage *select_transport(DBusConnection *conn, DBusMessage *msg, ++ void *data); ++ + static const GDBusMethodTable transport_methods[] = { + { GDBUS_ASYNC_METHOD("Acquire", + NULL, +@@ -984,6 +987,8 @@ static const GDBusMethodTable transport_methods[] = { + { "mtu_w", "q" }), + try_acquire) }, + { GDBUS_ASYNC_METHOD("Release", NULL, NULL, release) }, ++ { GDBUS_ASYNC_METHOD("Select", ++ NULL, NULL, select_transport) }, + { }, + }; + +@@ -1302,6 +1307,25 @@ static void transport_update_playing(struct media_transport *transport, + transport_set_state(transport, TRANSPORT_STATE_PENDING); + } + ++static DBusMessage *select_transport(DBusConnection *conn, DBusMessage *msg, ++ void *data) ++{ ++ struct media_transport *transport = data; ++ ++ if (transport->owner != NULL) ++ return btd_error_not_authorized(msg); ++ ++ if (transport->state >= TRANSPORT_STATE_REQUESTING) ++ return btd_error_not_authorized(msg); ++ ++ if (!strcmp(media_endpoint_get_uuid(transport->endpoint), ++ BAA_SERVICE_UUID)) { ++ transport_update_playing(transport, TRUE); ++ } ++ ++ return NULL; ++} ++ + static void sink_state_changed(struct btd_service *service, + sink_state_t old_state, + sink_state_t new_state, +-- +2.45.2 + + +From 61e16e3b831754368599fc619ddac31f0db48571 Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:09 +0300 +Subject: [PATCH 37/46] client/player: Expose transport 'Select' method to the + user + +This exposes the 'Select' method for Broadcast transports. This +allows the user to select the desired stream when running the setup +with PipeWire since it acquires any transport that is broadcasting. +--- + client/player.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 52 insertions(+) + +diff --git a/client/player.c b/client/player.c +index 93d6b62e24bf..c36e7ff4851b 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -4766,6 +4766,23 @@ static void acquire_reply(DBusMessage *message, void *user_data) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + ++static void select_reply(DBusMessage *message, void *user_data) ++{ ++ DBusError error; ++ ++ dbus_error_init(&error); ++ ++ if (dbus_set_error_from_message(&error, message) == TRUE) { ++ bt_shell_printf("Failed to select: %s\n", error.name); ++ dbus_error_free(&error); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } ++ ++ bt_shell_printf("Select successful"); ++ ++ return bt_shell_noninteractive_quit(EXIT_SUCCESS); ++} ++ + static void prompt_acquire(const char *input, void *user_data) + { + GDBusProxy *proxy = user_data; +@@ -4987,6 +5004,38 @@ static void cmd_acquire_transport(int argc, char *argv[]) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + ++static void transport_select(GDBusProxy *proxy, bool prompt) ++{ ++ if (!g_dbus_proxy_method_call(proxy, "Select", NULL, ++ select_reply, proxy, NULL)) { ++ bt_shell_printf("Failed select transport\n"); ++ return; ++ } ++} ++ ++static void cmd_select_transport(int argc, char *argv[]) ++{ ++ GDBusProxy *proxy; ++ 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); ++ } ++ ++ if (find_transport(proxy)) { ++ bt_shell_printf("Transport %s already acquired\n", ++ argv[i]); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } ++ ++ transport_select(proxy, false); ++ } ++} ++ + static void release_reply(DBusMessage *message, void *user_data) + { + struct transport *transport = user_data; +@@ -5415,6 +5464,9 @@ static const struct bt_shell_menu transport_menu = { + { "volume", " [value]", cmd_volume_transport, + "Get/Set transport volume", + transport_generator }, ++ { "select", " [transport1...]", cmd_select_transport, ++ "Select Transport", ++ transport_generator }, + {} }, + }; + +-- +2.45.2 + + +From 53a4078cb350f630b19f7fe6ea32dd4e1c01b7bb Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:10 +0300 +Subject: [PATCH 38/46] transport: Broadcast sink: wait for user to select + transport + +This changes the flow for transports created on broadcast sink side. +Transports are not automatically changed to pending anymore, instead +the user must first run transport.select on them which updates the +state to 'broadcasting'. This allows for the selection of the desired +stream when running the setup with PipeWire, which acquires any transport +that is broadcasting. +--- + profiles/audio/transport.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c +index bf2215a0f725..80e4f564c861 100644 +--- a/profiles/audio/transport.c ++++ b/profiles/audio/transport.c +@@ -1303,8 +1303,14 @@ static void transport_update_playing(struct media_transport *transport, + if (transport->owner != NULL) + media_transport_remove_owner(transport); + } +- } else if (transport->state == TRANSPORT_STATE_IDLE) +- transport_set_state(transport, TRANSPORT_STATE_PENDING); ++ } else if (transport->state == TRANSPORT_STATE_IDLE) { ++ if (!strcmp(media_endpoint_get_uuid(transport->endpoint), ++ BAA_SERVICE_UUID)) ++ transport_set_state(transport, ++ TRANSPORT_STATE_BROADCASTING); ++ else ++ transport_set_state(transport, TRANSPORT_STATE_PENDING); ++ } + } + + static DBusMessage *select_transport(DBusConnection *conn, DBusMessage *msg, +@@ -1686,10 +1692,7 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, + bap_update_qos(transport); + else if (bt_bap_stream_io_dir(stream) != BT_BAP_BCAST_SOURCE) + bap_update_bcast_qos(transport); +- if (bt_bap_stream_io_dir(stream) == BT_BAP_BCAST_SOURCE) +- transport_update_playing(transport, TRUE); +- else +- transport_update_playing(transport, FALSE); ++ transport_update_playing(transport, FALSE); + return; + case BT_BAP_STREAM_STATE_DISABLING: + return; +-- +2.45.2 + + +From c7e79fa8bfffff1c7b76cd32ff925ab4613ceb45 Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:11 +0300 +Subject: [PATCH 39/46] doc/media: Add 'Unselect' method + +This adds the documentation for a new method, exclusive to transports +created by the Broadcast Sink. It would allow the user to terminate the +sync to a BIS, via a 2 step process. The first step is the call to this +method, which changes the transport's state to idle, with the second step +being done by the audio server which detects this change and releases +the transport. +--- + doc/org.bluez.MediaTransport.rst | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/doc/org.bluez.MediaTransport.rst b/doc/org.bluez.MediaTransport.rst +index c8aca0223d24..eb3e04ae25c6 100644 +--- a/doc/org.bluez.MediaTransport.rst ++++ b/doc/org.bluez.MediaTransport.rst +@@ -57,7 +57,7 @@ void Select() + Applicable only for transports created by a broadcast sink. This moves + the transport from 'idle' to 'broadcasting'. This allows the user to + select which BISes he wishes to sync to via a 2 step process: +- 1) the user calls this method, changing the transport's state to idle ++ 1) the user calls the method, changing the transport's state to broadcasting + 2) the audio server detects that the transport is in the 'broadcasting' + state and automatically acquires it + +@@ -65,6 +65,19 @@ void Select() + + :org.bluez.Error.NotAuthorized: + ++void Unselect() ++``````````````` ++ ++ Applicable only for transports created by a broadcast sink. This moves ++ the transport from 'broadcasting' or 'active' to 'idle'. This allows the ++ user to terminate the sync to a BIS to via a 2 step process: ++ 1) the user calls this method, changing the transport's state to idle ++ 2) the audio server detects this event and releases the transport ++ ++ Possible Errors: ++ ++ :org.bluez.Error.NotAuthorized: ++ + Properties + ---------- + +-- +2.45.2 + + +From 6ee75c3ec383c664cd7e7be02e951999758a6c4f Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:12 +0300 +Subject: [PATCH 40/46] transport: Add 'Unselect' method + +This adds a new method, exclusive to transports created by the Broadcast +Sink. It allows the user to terminate the sync to a BIS, via a 2 step +process. The first step is the call to this method, which changes the +transport's state to idle, with the second step being done by the audio +server which detects this change and releases the transport. +--- + profiles/audio/transport.c | 41 +++++++++++++++++++++++++++++++------- + 1 file changed, 34 insertions(+), 7 deletions(-) + +diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c +index 80e4f564c861..3001457943ac 100644 +--- a/profiles/audio/transport.c ++++ b/profiles/audio/transport.c +@@ -975,6 +975,9 @@ static gboolean get_endpoint(const GDBusPropertyTable *property, + static DBusMessage *select_transport(DBusConnection *conn, DBusMessage *msg, + void *data); + ++static DBusMessage *unselect_transport(DBusConnection *conn, DBusMessage *msg, ++ void *data); ++ + static const GDBusMethodTable transport_methods[] = { + { GDBUS_ASYNC_METHOD("Acquire", + NULL, +@@ -989,6 +992,8 @@ static const GDBusMethodTable transport_methods[] = { + { GDBUS_ASYNC_METHOD("Release", NULL, NULL, release) }, + { GDBUS_ASYNC_METHOD("Select", + NULL, NULL, select_transport) }, ++ { GDBUS_ASYNC_METHOD("Unselect", ++ NULL, NULL, unselect_transport) }, + { }, + }; + +@@ -1295,13 +1300,22 @@ static void transport_update_playing(struct media_transport *transport, + str_state[transport->state], playing); + + if (playing == FALSE) { +- if ((transport->state == TRANSPORT_STATE_PENDING) || +- (transport->state == TRANSPORT_STATE_BROADCASTING)) +- transport_set_state(transport, TRANSPORT_STATE_IDLE); +- else if (transport->state == TRANSPORT_STATE_ACTIVE) { +- /* Remove owner */ +- if (transport->owner != NULL) +- media_transport_remove_owner(transport); ++ if (!strcmp(media_endpoint_get_uuid(transport->endpoint), ++ BCAA_SERVICE_UUID)) { ++ if ((transport->state == ++ TRANSPORT_STATE_BROADCASTING) || ++ (transport->state == TRANSPORT_STATE_ACTIVE)) ++ transport_set_state(transport, ++ TRANSPORT_STATE_IDLE); ++ } else { ++ if (transport->state == TRANSPORT_STATE_PENDING) ++ transport_set_state(transport, ++ TRANSPORT_STATE_IDLE); ++ else if (transport->state == TRANSPORT_STATE_ACTIVE) { ++ /* Remove owner */ ++ if (transport->owner != NULL) ++ media_transport_remove_owner(transport); ++ } + } + } else if (transport->state == TRANSPORT_STATE_IDLE) { + if (!strcmp(media_endpoint_get_uuid(transport->endpoint), +@@ -1332,6 +1346,19 @@ static DBusMessage *select_transport(DBusConnection *conn, DBusMessage *msg, + return NULL; + } + ++static DBusMessage *unselect_transport(DBusConnection *conn, DBusMessage *msg, ++ void *data) ++{ ++ struct media_transport *transport = data; ++ ++ if (!strcmp(media_endpoint_get_uuid(transport->endpoint), ++ BAA_SERVICE_UUID)) { ++ transport_update_playing(transport, FALSE); ++ } ++ ++ return NULL; ++} ++ + static void sink_state_changed(struct btd_service *service, + sink_state_t old_state, + sink_state_t new_state, +-- +2.45.2 + + +From 827416638289d901fe5b2bc747fc33cff8b7db99 Mon Sep 17 00:00:00 2001 +From: Vlad Pruteanu +Date: Wed, 31 Jul 2024 09:17:13 +0300 +Subject: [PATCH 41/46] client/player: Expose transport 'Unselect' method to + the user + +This exposes the 'Unselect' method for Broadcast transports. This +allows the user to terminate the sync to a specific BIS, via a 2 +step process. The first step is the call to this method, which +changes the transport's state to idle, with the second step being +done by the audio server which detects this change and releases +the transport. +--- + client/player.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 48 insertions(+) + +diff --git a/client/player.c b/client/player.c +index c36e7ff4851b..f1cd909663eb 100644 +--- a/client/player.c ++++ b/client/player.c +@@ -4783,6 +4783,24 @@ static void select_reply(DBusMessage *message, void *user_data) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + ++static void unselect_reply(DBusMessage *message, void *user_data) ++{ ++ DBusError error; ++ ++ dbus_error_init(&error); ++ ++ if (dbus_set_error_from_message(&error, message) == TRUE) { ++ bt_shell_printf("Failed to unselect: %s\n", error.name); ++ dbus_error_free(&error); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } ++ ++ bt_shell_printf("Select successful"); ++ ++ return bt_shell_noninteractive_quit(EXIT_SUCCESS); ++} ++ ++ + static void prompt_acquire(const char *input, void *user_data) + { + GDBusProxy *proxy = user_data; +@@ -5013,6 +5031,16 @@ static void transport_select(GDBusProxy *proxy, bool prompt) + } + } + ++static void transport_unselect(GDBusProxy *proxy, bool prompt) ++{ ++ if (!g_dbus_proxy_method_call(proxy, "Unselect", NULL, ++ unselect_reply, proxy, NULL)) { ++ bt_shell_printf("Failed unselect transport\n"); ++ return; ++ } ++} ++ ++ + static void cmd_select_transport(int argc, char *argv[]) + { + GDBusProxy *proxy; +@@ -5036,6 +5064,23 @@ static void cmd_select_transport(int argc, char *argv[]) + } + } + ++static void cmd_unselect_transport(int argc, char *argv[]) ++{ ++ GDBusProxy *proxy; ++ 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_unselect(proxy, false); ++ } ++} ++ + static void release_reply(DBusMessage *message, void *user_data) + { + struct transport *transport = user_data; +@@ -5467,6 +5512,9 @@ static const struct bt_shell_menu transport_menu = { + { "select", " [transport1...]", cmd_select_transport, + "Select Transport", + transport_generator }, ++ { "unselect", " [transport1...]", cmd_unselect_transport, ++ "Unselect Transport", ++ transport_generator }, + {} }, + }; + +-- +2.45.2 + + +From 720e8ec9760b8d8bfb565e535bd311bbc8273a76 Mon Sep 17 00:00:00 2001 +From: Alexander Ganslandt +Date: Wed, 31 Jul 2024 12:23:21 +0200 +Subject: [PATCH 42/46] client/gatt: Set handle before calling print functions + +The print functions (print_service, print_chrc and print_desc) all print +the handle, but the handle is never set in the struct object. This +results in the handle always printing as 0x0000. Set the handle before +calling the print function. +--- + client/gatt.c | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + +diff --git a/client/gatt.c b/client/gatt.c +index e1d2b545d691..4dac8859060b 100644 +--- a/client/gatt.c ++++ b/client/gatt.c +@@ -165,6 +165,7 @@ static void print_service_proxy(GDBusProxy *proxy, const char *description) + DBusMessageIter iter; + const char *uuid; + dbus_bool_t primary; ++ uint16_t handle; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; +@@ -176,10 +177,16 @@ static void print_service_proxy(GDBusProxy *proxy, const char *description) + + dbus_message_iter_get_basic(&iter, &primary); + ++ if (g_dbus_proxy_get_property(proxy, "Handle", &iter) == FALSE) ++ return; ++ ++ dbus_message_iter_get_basic(&iter, &handle); ++ + memset(&service, 0, sizeof(service)); + service.path = (char *) g_dbus_proxy_get_path(proxy); + service.uuid = (char *) uuid; + service.primary = primary; ++ service.handle = handle; + + print_service(&service, description); + } +@@ -253,15 +260,22 @@ static void print_characteristic(GDBusProxy *proxy, const char *description) + struct chrc chrc; + DBusMessageIter iter; + const char *uuid; ++ uint16_t handle; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &uuid); + ++ if (g_dbus_proxy_get_property(proxy, "Handle", &iter) == FALSE) ++ return; ++ ++ dbus_message_iter_get_basic(&iter, &handle); ++ + memset(&chrc, 0, sizeof(chrc)); + chrc.path = (char *) g_dbus_proxy_get_path(proxy); + chrc.uuid = (char *) uuid; ++ chrc.handle = handle; + + print_chrc(&chrc, description); + } +@@ -347,15 +361,22 @@ static void print_descriptor(GDBusProxy *proxy, const char *description) + struct desc desc; + DBusMessageIter iter; + const char *uuid; ++ uint16_t handle; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &uuid); + ++ if (g_dbus_proxy_get_property(proxy, "Handle", &iter) == FALSE) ++ return; ++ ++ dbus_message_iter_get_basic(&iter, &handle); ++ + memset(&desc, 0, sizeof(desc)); + desc.path = (char *) g_dbus_proxy_get_path(proxy); + desc.uuid = (char *) uuid; ++ desc.handle = handle; + + print_desc(&desc, description); + } +-- +2.45.2 + + +From 8a708aa5f04613768e903d243a7261efd202ea88 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 31 Jul 2024 12:15:47 +0100 +Subject: [PATCH 43/46] monitor: Fix crash parsing notification + +This fixes the following crash caused by notify callback being NULL: + +Jump to the invalid address stated on the next line + at 0x0: ??? + by 0x1E8375: print_notify (att.c:5420) + by 0x1E9464: att_multiple_vl_rsp (att.c:5463) + by 0x20D39E: att_packet (att.c:5637) + by 0x1B2054: l2cap_frame (l2cap.c:2567) + by 0x1B4A4D: l2cap_packet (l2cap.c:2708) + by 0x19AD43: packet_hci_acldata (packet.c:12522) + by 0x19CF07: packet_monitor (packet.c:4249) + by 0x152405: data_callback (control.c:973) + by 0x2204F6: mainloop_run (mainloop.c:106) + by 0x221017: mainloop_run_with_signal (mainloop-notify.c:189) + by 0x14F387: main (main.c:298) + Address 0x0 is not stack'd, malloc'd or (recently) free'd +--- + monitor/att.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/monitor/att.c b/monitor/att.c +index a23347ef7ede..73a61658454f 100644 +--- a/monitor/att.c ++++ b/monitor/att.c +@@ -4646,7 +4646,8 @@ static void print_notify(const struct l2cap_frame *frame, uint16_t handle, + frame = &clone; + } + +- handler->notify(frame); ++ if (handler->notify) ++ handler->notify(frame); + } + + static void att_handle_value_notify(const struct l2cap_frame *frame) +-- +2.45.2 + + +From fe703a0058d8271a259885d3da4e886400cf4245 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 31 Jul 2024 12:17:07 +0100 +Subject: [PATCH 44/46] shared/bap: Fix not setting metadata + +bt_bap_stream_metatada shall not send Update Metadata if the states +don't allow it, instead it shall store it so it can be send later when +enabling the stream. +--- + src/shared/bap.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/src/shared/bap.c b/src/shared/bap.c +index 499e740c9162..a7217b4177e7 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -1971,8 +1971,17 @@ static unsigned int bap_ucast_metadata(struct bt_bap_stream *stream, + return 0; + } + +- return bap_stream_metadata(stream, BT_ASCS_METADATA, data, func, +- user_data); ++ switch (bt_bap_stream_get_state(stream)) { ++ /* Valid only if ASE_State field = 0x03 (Enabling) */ ++ case BT_BAP_STREAM_STATE_ENABLING: ++ /* or 0x04 (Streaming) */ ++ case BT_BAP_STREAM_STATE_STREAMING: ++ return bap_stream_metadata(stream, BT_ASCS_METADATA, data, func, ++ user_data); ++ } ++ ++ stream_metadata(stream, data, NULL); ++ return 0; + } + + static uint8_t stream_release(struct bt_bap_stream *stream, struct iovec *rsp) +-- +2.45.2 + + +From 998104507ba103ae0c83641d381794bf11dd46e0 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 31 Jul 2024 12:19:04 +0100 +Subject: [PATCH 45/46] bap: Fix not setting metatada + +Fix not using bt_bap_stream_metadata when configuring a new stream as +the endpoint/client may have set it. +--- + profiles/audio/bap.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c +index 53f430d66171..a2c5a546db47 100644 +--- a/profiles/audio/bap.c ++++ b/profiles/audio/bap.c +@@ -1523,6 +1523,10 @@ static void setup_config(void *data, void *user_data) + return; + } + ++ if (setup->metadata && setup->metadata->iov_len) ++ bt_bap_stream_metadata(setup->stream, setup->metadata, NULL, ++ NULL); ++ + bt_bap_stream_set_user_data(setup->stream, ep->path); + } + +-- +2.45.2 + + +From 100c845b2d20e7f4f96b371e044b8b59944230ab Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 25 Jul 2024 15:17:39 -0400 +Subject: [PATCH 46/46] shared/bap: Fix overwriting sink attribute + +When allocating the sink and sink_ccc attribute they were being +overwriten by source and source_ccc attributes. +--- + src/shared/bap.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/shared/bap.c b/src/shared/bap.c +index a7217b4177e7..9381ebb39a7c 100644 +--- a/src/shared/bap.c ++++ b/src/shared/bap.c +@@ -559,14 +559,14 @@ static struct bt_pacs *pacs_new(struct gatt_db *db) + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, PAC_SOURCE_CHRC_UUID); +- pacs->sink = gatt_db_service_add_characteristic(pacs->service, &uuid, ++ pacs->source = gatt_db_service_add_characteristic(pacs->service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_NOTIFY, + pacs_source_read, NULL, + pacs); + +- pacs->sink_ccc = gatt_db_service_add_ccc(pacs->service, ++ pacs->source_ccc = gatt_db_service_add_ccc(pacs->service, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); + + bt_uuid16_create(&uuid, PAC_SOURCE_LOC_CHRC_UUID); +-- +2.45.2 + diff --git a/bluez.spec b/bluez.spec index a233670..95a720c 100644 --- a/bluez.spec +++ b/bluez.spec @@ -13,6 +13,17 @@ URL: http://www.bluez.org/ Source0: https://www.kernel.org/pub/linux/bluetooth/%{name}-%{version}.tar.xz +# Upstream patches +Patch0: 5.77-devel.patch +# https://patchwork.kernel.org/project/bluetooth/patch/20240702084900.773620-2-hadess@hadess.net/ +Patch1: 0001-main-Simplify-parse_config_string.patch +# https://patchwork.kernel.org/project/bluetooth/patch/20240704102617.1132337-4-hadess@hadess.net/ +Patch2: 0001-shared-shell-Free-memory-allocated-by-wordexp.patch +# https://patchwork.kernel.org/project/bluetooth/list/?series=876731 +Patch3: static-analysis-issues-6.patch +# Coverity downstream patches +Patch4: coverity-workarounds.patch + BuildRequires: dbus-devel >= 1.6 BuildRequires: glib2-devel BuildRequires: libell-devel >= 0.37 @@ -330,6 +341,8 @@ install emulator/btvirt ${RPM_BUILD_ROOT}/%{_libexecdir}/bluetooth/ %changelog * Mon Aug 05 2024 Bastien Nocera - 5.77-2 - Use git to apply patches +- Fix coverity issues +- Related: Jira:RHEL-34536 * Mon Aug 05 2024 Bastien Nocera - 5.77-1 - Update to 5.77 diff --git a/coverity-workarounds.patch b/coverity-workarounds.patch new file mode 100644 index 0000000..096978f --- /dev/null +++ b/coverity-workarounds.patch @@ -0,0 +1,481 @@ +From ad622447efc5429a5dc3f84c722a81cc41658e7e Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Mon, 5 Aug 2024 12:17:29 +0200 +Subject: [PATCH 1/8] monitor: Work-around overflow_sink Case #01164573 + +Coverity thinks "len" can be negative, even though we check its value, +and exit the function if it is. +--- + monitor/control.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/monitor/control.c b/monitor/control.c +index 62857b4b84de..40e8a3a90c05 100644 +--- a/monitor/control.c ++++ b/monitor/control.c +@@ -1102,6 +1102,7 @@ static void client_callback(int fd, uint32_t events, void *user_data) + UINT16_MAX - data->offset > len) + return; + ++ /* coverity[overflow] : FALSE */ + data->offset += len; + + while (data->offset >= MGMT_HDR_SIZE) { +-- +2.45.2 + + +From c2a1630f0e484c4330c565c56e9a26f8f1ae2664 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Tue, 30 Jul 2024 15:45:18 +0200 +Subject: [PATCH 2/8] mesh/net: Work-around memory overallocation warning + +Coverity doesn't realise that the "payload" struct was allocated past +its structure size, so quiet that warning. + +Error: OVERRUN (CWE-119): [#def1] [important] +bluez-5.77/mesh/net.c:3276:2: cond_at_most: Checking "msg_len > 384" implies that "msg_len" may be up to 384 on the false branch. +bluez-5.77/mesh/net.c:3290:2: cond_at_most: Checking "msg_len <= 15" implies that "msg_len" may be up to 15 on the true branch. +bluez-5.77/mesh/net.c:3316:2: overrun-buffer-arg: Overrunning array "payload->buf" of 4 bytes by passing it to a function which accesses it at byte offset 14 using argument "msg_len" (which evaluates to 15). [Note: The source code implementation of the function has been overridden by a builtin model.] +3314| /* Setup OTA Network send */ +3315| payload = mesh_sar_new(msg_len); +3316|-> memcpy(payload->buf, msg, msg_len); +3317| payload->len = msg_len; +3318| payload->src = src; +--- + mesh/net.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/mesh/net.c b/mesh/net.c +index ef6a3133859a..ca2cda8ec948 100644 +--- a/mesh/net.c ++++ b/mesh/net.c +@@ -3306,6 +3306,7 @@ bool mesh_net_app_send(struct mesh_net *net, bool frnd_cred, uint16_t src, + + /* Setup OTA Network send */ + payload = mesh_sar_new(msg_len); ++ /* coverity[overrun-buffer-arg] : FALSE */ + memcpy(payload->buf, msg, msg_len); + payload->len = msg_len; + payload->src = src; +-- +2.45.2 + + +From 6494fc8665f89b70b8e9d80b829eabc71a22278f Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Wed, 17 Jul 2024 12:51:56 +0200 +Subject: [PATCH 3/8] shared/shell: Work-around SAT-45980 with wordexp() + +Coverity sees a leak when one doesn't exist yet. + +Error: RESOURCE_LEAK (CWE-772): [#def23] [important] +bluez-5.77/src/shared/shell.c:534:2: alloc_arg: "parse_args" allocates memory that is stored into "w.we_wordv". +bluez-5.77/src/shared/shell.c:558:3: leaked_storage: Variable "w" going out of scope leaks the storage "w.we_wordv" points to. +556| "Unable to parse optional command arguments: %s", opt); +557| free(opt); +558|-> return -EINVAL; +559| } +560| +--- + src/shared/shell.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/shared/shell.c b/src/shared/shell.c +index 26c6a419af22..9d2b50b260f9 100644 +--- a/src/shared/shell.c ++++ b/src/shared/shell.c +@@ -555,6 +555,7 @@ optional: + print_text(COLOR_HIGHLIGHT, + "Unable to parse optional command arguments: %s", opt); + free(opt); ++ /* coverity[leaked_storage : FALSE] */ + return -EINVAL; + } + +-- +2.45.2 + + +From 99c12a3e56129361ed50934054876126b1e55881 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Wed, 17 Jul 2024 11:28:17 +0200 +Subject: [PATCH 4/8] sdp: Work-around #01163325 with single-linked list + +Error: RESOURCE_LEAK (CWE-772): [#def2] [important] +bluez-5.77/lib/sdp.c:1896:4: alloc_fn: Storage is returned from allocation function "sdp_list_append". +bluez-5.77/lib/sdp.c:1896:4: var_assign: Assigning: "pds" = storage returned from "sdp_list_append(pds, curr->val.dataseq)". +bluez-5.77/lib/sdp.c:1896:4: identity_transfer: Passing "pds" as argument 1 to function "sdp_list_append", which returns that argument. +bluez-5.77/lib/sdp.c:1896:4: noescape: Resource "pds" is not freed or pointed-to in "sdp_list_append". +bluez-5.77/lib/sdp.c:1896:4: overwrite_var: Overwriting "pds" in "pds = sdp_list_append(pds, curr->val.dataseq)". +bluez-5.77/lib/sdp.c:1896:4: var_assign: Assigning: "pds" = storage returned from "sdp_list_append(pds, curr->val.dataseq)". +bluez-5.77/lib/sdp.c:1896:4: noescape: Resource "pds" is not freed or pointed-to in "sdp_list_append". +bluez-5.77/lib/sdp.c:1896:4: overwrite_var: Overwriting "pds" in "pds = sdp_list_append(pds, curr->val.dataseq)" leaks the storage that "pds" points to. +1894| goto failed; +1895| } +1896|-> pds = sdp_list_append(pds, curr->val.dataseq); +1897| } +1898| + +Error: RESOURCE_LEAK (CWE-772): [#def3] [important] +bluez-5.77/lib/sdp.c:1899:3: alloc_fn: Storage is returned from allocation function "sdp_list_append". +bluez-5.77/lib/sdp.c:1899:3: var_assign: Assigning: "ap" = storage returned from "sdp_list_append(ap, pds)". +bluez-5.77/lib/sdp.c:1899:3: identity_transfer: Passing "ap" as argument 1 to function "sdp_list_append", which returns that argument. +bluez-5.77/lib/sdp.c:1899:3: noescape: Resource "ap" is not freed or pointed-to in "sdp_list_append". +bluez-5.77/lib/sdp.c:1899:3: overwrite_var: Overwriting "ap" in "ap = sdp_list_append(ap, pds)". +bluez-5.77/lib/sdp.c:1899:3: var_assign: Assigning: "ap" = storage returned from "sdp_list_append(ap, pds)". +bluez-5.77/lib/sdp.c:1899:3: noescape: Resource "ap" is not freed or pointed-to in "sdp_list_append". +bluez-5.77/lib/sdp.c:1899:3: overwrite_var: Overwriting "ap" in "ap = sdp_list_append(ap, pds)" leaks the storage that "ap" points to. +1897| } +1898| +1899|-> ap = sdp_list_append(ap, pds); +1900| } +1901| + +Error: RESOURCE_LEAK (CWE-772): [#def17] [important] +bluez-5.77/src/sdp-client.c:197:3: alloc_fn: Storage is returned from allocation function "sdp_list_append". +bluez-5.77/src/sdp-client.c:197:3: var_assign: Assigning: "recs" = storage returned from "sdp_list_append(recs, rec)". +bluez-5.77/src/sdp-client.c:197:3: identity_transfer: Passing "recs" as argument 1 to function "sdp_list_append", which returns that argument. +bluez-5.77/src/sdp-client.c:197:3: noescape: Resource "recs" is not freed or pointed-to in "sdp_list_append". +bluez-5.77/src/sdp-client.c:197:3: overwrite_var: Overwriting "recs" in "recs = sdp_list_append(recs, rec)". +bluez-5.77/src/sdp-client.c:197:3: var_assign: Assigning: "recs" = storage returned from "sdp_list_append(recs, rec)". +bluez-5.77/src/sdp-client.c:197:3: noescape: Resource "recs" is not freed or pointed-to in "sdp_list_append". +bluez-5.77/src/sdp-client.c:197:3: overwrite_var: Overwriting "recs" in "recs = sdp_list_append(recs, rec)" leaks the storage that "recs" points to. +195| } +196| +197|-> recs = sdp_list_append(recs, rec); +198| } while (scanned < (ssize_t) size && bytesleft > 0); +199| +--- + lib/sdp.c | 2 ++ + src/sdp-client.c | 1 + + 2 files changed, 3 insertions(+) + +diff --git a/lib/sdp.c b/lib/sdp.c +index 8a15ad803db1..99efbc19c299 100644 +--- a/lib/sdp.c ++++ b/lib/sdp.c +@@ -1893,9 +1893,11 @@ static int sdp_get_proto_descs(uint16_t attr_id, const sdp_record_t *rec, + sdp_list_free(pds, NULL); + goto failed; + } ++ /* coverity[overwrite_var] : FALSE */ + pds = sdp_list_append(pds, curr->val.dataseq); + } + ++ /* coverity[overwrite_var] : FALSE */ + ap = sdp_list_append(ap, pds); + } + +diff --git a/src/sdp-client.c b/src/sdp-client.c +index 71d3d9e95044..2f043cb7f010 100644 +--- a/src/sdp-client.c ++++ b/src/sdp-client.c +@@ -194,6 +194,7 @@ static void search_completed_cb(uint8_t type, uint16_t status, + continue; + } + ++ /* coverity[overwrite_var] : FALSE */ + recs = sdp_list_append(recs, rec); + } while (scanned < (ssize_t) size && bytesleft > 0); + +-- +2.45.2 + + +From 6fcbf34a02133628a1a8afeabb093270ca89dbb8 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 18 Jul 2024 15:05:07 +0200 +Subject: [PATCH 5/8] mesh: Quiet imprecise "overrun-buffer-val" #01163326 + +Error: OVERRUN (CWE-119): [#def1] [important] +bluez-5.77/mesh/friend.c:326:2: overrun-buffer-val: Overrunning array "msg" of 5 bytes by passing it to a function which accesses it at byte offset 12. +324| l_put_be16(neg->lp_addr, msg + 1); +325| l_put_be16(neg->lp_cnt, msg + 3); +326|-> mesh_net_transport_send(neg->net, 0, 0, +327| mesh_net_get_iv_index(neg->net), DEFAULT_TTL, +328| 0, 0, neg->old_friend, + +Error: OVERRUN (CWE-119): [#def2] [important] +bluez-5.77/mesh/net.c:276:2: overrun-buffer-val: Overrunning array "msg" of 4 bytes by passing it to a function which accesses it at byte offset 12. +274| n += 2; +275| +276|-> mesh_net_transport_send(net, 0, 0, mesh_net_get_iv_index(net), +277| pub->ttl, 0, 0, pub->dst, msg, n); +278| } + +Error: OVERRUN (CWE-119): [#def3] [important] +bluez-5.77/mesh/net.c:1463:3: overrun-buffer-val: Overrunning array "msg" of 7 bytes by passing it to a function which accesses it at byte offset 12. +1461| mesh_net_next_seq_num(net), 0, dst, msg); +1462| } else { +1463|-> mesh_net_transport_send(net, 0, 0, +1464| mesh_net_get_iv_index(net), DEFAULT_TTL, +1465| 0, 0, dst, msg, sizeof(msg)); + +Error: OVERRUN (CWE-119): [#def4] [important] +bluez-5.77/mesh/net.c:1498:2: overrun-buffer-val: Overrunning array "msg" of 7 bytes by passing it to a function which accesses it at byte offset 12. +1496| } +1497| +1498|-> mesh_net_transport_send(net, 0, sar->net_idx, +1499| mesh_net_get_iv_index(net), DEFAULT_TTL, +1500| 0, src, dst, msg, + +Error: OVERRUN (CWE-119): [#def6] [important] +bluez-5.77/mesh/net.c:2053:3: overrun-buffer-val: Overrunning array "sar_in->buf" of 4 bytes by passing it to a function which accesses it at byte offset 11. +2051| send_net_ack(net, sar_in, expected); +2052| +2053|-> msg_rxed(net, frnd, iv_index, ttl, seq, net_idx, +2054| sar_in->remote, dst, key_aid, true, szmic, +2055| sar_in->seqZero, sar_in->buf, sar_in->len); +--- + mesh/friend.c | 1 + + mesh/net.c | 4 ++++ + 2 files changed, 5 insertions(+) + +diff --git a/mesh/friend.c b/mesh/friend.c +index 5b73da68916f..bb8f62e9f57f 100644 +--- a/mesh/friend.c ++++ b/mesh/friend.c +@@ -323,6 +323,7 @@ static void clear_retry(struct l_timeout *timeout, void *user_data) + + l_put_be16(neg->lp_addr, msg + 1); + l_put_be16(neg->lp_cnt, msg + 3); ++ /* coverity[overrun-buffer-val] : FALSE */ + mesh_net_transport_send(neg->net, 0, 0, + mesh_net_get_iv_index(neg->net), DEFAULT_TTL, + 0, 0, neg->old_friend, +diff --git a/mesh/net.c b/mesh/net.c +index ca2cda8ec948..9d6c2ae5142f 100644 +--- a/mesh/net.c ++++ b/mesh/net.c +@@ -273,6 +273,7 @@ static void send_hb_publication(void *data) + l_put_be16(net->features, msg + n); + n += 2; + ++ /* coverity[overrun-buffer-val] : FALSE */ + mesh_net_transport_send(net, 0, 0, mesh_net_get_iv_index(net), + pub->ttl, 0, 0, pub->dst, msg, n); + } +@@ -1460,6 +1461,7 @@ static void send_frnd_ack(struct mesh_net *net, uint16_t src, uint16_t dst, + friend_ack_rxed(net, mesh_net_get_iv_index(net), + mesh_net_next_seq_num(net), 0, dst, msg); + } else { ++ /* coverity[overrun-buffer-val] : FALSE */ + mesh_net_transport_send(net, 0, 0, + mesh_net_get_iv_index(net), DEFAULT_TTL, + 0, 0, dst, msg, sizeof(msg)); +@@ -1495,6 +1497,7 @@ static void send_net_ack(struct mesh_net *net, struct mesh_sar *sar, + return; + } + ++ /* coverity[overrun-buffer-val] : FALSE */ + mesh_net_transport_send(net, 0, sar->net_idx, + mesh_net_get_iv_index(net), DEFAULT_TTL, + 0, src, dst, msg, +@@ -2050,6 +2053,7 @@ static bool seg_rxed(struct mesh_net *net, bool frnd, uint32_t iv_index, + /* Got it all */ + send_net_ack(net, sar_in, expected); + ++ /* coverity[overrun-buffer-val] : FALSE */ + msg_rxed(net, frnd, iv_index, ttl, seq, net_idx, + sar_in->remote, dst, key_aid, true, szmic, + sar_in->seqZero, sar_in->buf, sar_in->len); +-- +2.45.2 + + +From 91066706378840f28146e51702e3ed8c1780dcd9 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 18 Jul 2024 15:37:58 +0200 +Subject: [PATCH 6/8] mesh: Quiet imprecise "overrun-buffer-val" #01163327 + +Those errors are incorrect, as just before the flagged function calls, +the packet is modified to flag for a "segmented" packet, which is +handled differently, so nothing is accessed past the array size. + +Error: OVERRUN (CWE-119): [#def5] [important] +bluez-5.77/mesh/net.c:1769:3: cond_at_least: Checking "size > 15" implies that "size" is at least 16 on the true branch. +bluez-5.77/mesh/net.c:1776:3: overrun-call: Overrunning callee's array of size 15 by passing argument "size" (which evaluates to 16) in call to "friend_packet_queue". +1774| } +1775| +1776|-> if (friend_packet_queue(net, iv_index, false, frnd_ttl, +1777| seq, src, dst, +1778| hdr, data, size)) + +Error: OVERRUN (CWE-119): [#def2] [important] +bluez-5.77/mesh/net.c:2016:3: cond_at_least: Checking "segN" implies that "segN" is at least 1 on the true branch. +bluez-5.77/mesh/net.c:2016:3: assignment: Assigning: "len" = "segN ? (segN + 1) * 12 : 15". The value of "len" is now at least 24. +bluez-5.77/mesh/net.c:2028:3: assignment: Assigning: "sar_in->len" = "len". The value of "sar_in->len" is now at least 24. +bluez-5.77/mesh/net.c:2058:3: overrun-call: Overrunning callee's array of size 15 by passing argument "sar_in->len" (which evaluates to 24) in call to "msg_rxed". +2056| +2057| /* coverity[overrun-buffer-val] : FALSE */ +2058|-> msg_rxed(net, frnd, iv_index, ttl, seq, net_idx, +2059| sar_in->remote, dst, key_aid, true, szmic, +2060| sar_in->seqZero, sar_in->buf, sar_in->len); + +Error: OVERRUN (CWE-119): [#def4] [important] +bluez-5.77/mesh/net.c:3266:2: cond_at_most: Checking "msg_len > 384" implies that "msg_len" may be up to 384 on the false branch. +bluez-5.77/mesh/net.c:3280:2: cond_between: Checking "msg_len <= 15" implies that "msg_len" is between 16 and 384 (inclusive) on the false branch. +bluez-5.77/mesh/net.c:3284:2: overrun-call: Overrunning callee's array of size 15 by passing argument "msg_len" (which evaluates to 384) in call to "msg_rxed". +3282| +3283| /* First enqueue to any Friends and internal models */ +3284|-> result = msg_rxed(net, false, iv_index, ttl, seq, net_idx, src, dst, +3285| key_aid, segmented, szmic, seq & SEQ_ZERO_MASK, +3286| msg, msg_len); +--- + mesh/net.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/mesh/net.c b/mesh/net.c +index 9d6c2ae5142f..30dcdb2fe517 100644 +--- a/mesh/net.c ++++ b/mesh/net.c +@@ -1776,6 +1776,7 @@ static bool msg_rxed(struct mesh_net *net, bool frnd, uint32_t iv_index, + hdr |= SEG_MAX(true, size) << SEGN_HDR_SHIFT; + } + ++ /* coverity[overrun-call] : FALSE */ + if (friend_packet_queue(net, iv_index, false, frnd_ttl, + seq, src, dst, + hdr, data, size)) +@@ -2054,6 +2055,7 @@ static bool seg_rxed(struct mesh_net *net, bool frnd, uint32_t iv_index, + send_net_ack(net, sar_in, expected); + + /* coverity[overrun-buffer-val] : FALSE */ ++ /* coverity[overrun-call] : FALSE */ + msg_rxed(net, frnd, iv_index, ttl, seq, net_idx, + sar_in->remote, dst, key_aid, true, szmic, + sar_in->seqZero, sar_in->buf, sar_in->len); +@@ -3289,6 +3291,7 @@ bool mesh_net_app_send(struct mesh_net *net, bool frnd_cred, uint16_t src, + segmented |= !!(seg_max); + + /* First enqueue to any Friends and internal models */ ++ /* coverity[overrun-call] : FALSE */ + result = msg_rxed(net, false, iv_index, ttl, seq, net_idx, src, dst, + key_aid, segmented, szmic, seq & SEQ_ZERO_MASK, + msg, msg_len); +-- +2.45.2 + + +From 1a1239f998ca15dd233e2adaa2ce12f4ae97e5d1 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 19 Jul 2024 15:06:24 +0200 +Subject: [PATCH 7/8] shared/gatt-db: Work-around overrun-buffer-arg case + #01163328 + +Despite the checks added, Coverity still thinks that uuid_to_le() can +return more than 16 (for UUID128 / 8), so quiet those. + +Error: OVERRUN (CWE-119): [#def6] [important] +bluez-5.77/src/shared/gatt-db.c:612:2: assignment: Assigning: "len" = "uuid_to_le(uuid, value)". The value of "len" is now between 0 and 31 (inclusive). +bluez-5.77/src/shared/gatt-db.c:614:2: overrun-buffer-arg: Overrunning array "value" of 16 bytes by passing it to a function which accesses it at byte offset 30 using argument "len" (which evaluates to 31). +612| len = uuid_to_le(uuid, value); +613| +614|-> service->attributes[0] = new_attribute(service, handle, type, value, +615| len); +616| if (!service->attributes[0]) { + +Error: OVERRUN (CWE-119): [#def7] [important] +bluez-5.77/src/shared/gatt-db.c:947:2: assignment: Assigning: "len" = "0". +bluez-5.77/src/shared/gatt-db.c:971:2: assignment: Assigning: "len" += "1UL". The value of "len" is now 1. +bluez-5.77/src/shared/gatt-db.c:975:2: assignment: Assigning: "len" += "2UL". The value of "len" is now 3. +bluez-5.77/src/shared/gatt-db.c:976:2: assignment: Assigning: "len" += "uuid_to_le(uuid, &value[3])". The value of "len" is now between 3 and 34 (inclusive). +bluez-5.77/src/shared/gatt-db.c:978:2: overrun-buffer-arg: Overrunning array "value" of 19 bytes by passing it to a function which accesses it at byte offset 33 using argument "len" (which evaluates to 34). +976| len += uuid_to_le(uuid, &value[3]); +977| +978|-> service->attributes[i] = new_attribute(service, handle, +979| &characteristic_uuid, +980| value, len); + +Error: OVERRUN (CWE-119): [#def8] [important] +bluez-5.77/src/shared/gatt-db.c:947:2: assignment: Assigning: "len" = "0". +bluez-5.77/src/shared/gatt-db.c:971:2: assignment: Assigning: "len" += "1UL". The value of "len" is now 1. +bluez-5.77/src/shared/gatt-db.c:975:2: assignment: Assigning: "len" += "2UL". The value of "len" is now 3. +bluez-5.77/src/shared/gatt-db.c:976:2: assignment: Assigning: "len" += "uuid_to_le(uuid, &value[3])". The value of "len" is now between 3 and 34 (inclusive). +bluez-5.77/src/shared/gatt-db.c:1005:2: overrun-buffer-arg: Overrunning array "value" of 19 bytes by passing it to a function which accesses it at byte offset 33 using argument "len" (which evaluates to 34). +1003| /* Update handle of characteristic value_handle if it has changed */ +1004| put_le16(value_handle, &value[1]); +1005|-> if (memcmp((*chrc)->value, value, len)) +1006| memcpy((*chrc)->value, value, len); +1007| + +Error: OVERRUN (CWE-119): [#def9] [important] +bluez-5.77/src/shared/gatt-db.c:947:2: assignment: Assigning: "len" = "0". +bluez-5.77/src/shared/gatt-db.c:971:2: assignment: Assigning: "len" += "1UL". The value of "len" is now 1. +bluez-5.77/src/shared/gatt-db.c:975:2: assignment: Assigning: "len" += "2UL". The value of "len" is now 3. +bluez-5.77/src/shared/gatt-db.c:976:2: assignment: Assigning: "len" += "uuid_to_le(uuid, &value[3])". The value of "len" is now between 3 and 34 (inclusive). +bluez-5.77/src/shared/gatt-db.c:1006:3: overrun-buffer-arg: Overrunning array "value" of 19 bytes by passing it to a function which accesses it at byte offset 33 using argument "len" (which evaluates to 34). [Note: The source code implementation of the function has been overridden by a builtin model.] +1004| put_le16(value_handle, &value[1]); +1005| if (memcmp((*chrc)->value, value, len)) +1006|-> memcpy((*chrc)->value, value, len); +1007| +1008| set_attribute_data(service->attributes[i], read_func, write_func, +--- + src/shared/gatt-db.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c +index cd0eba6bf1d0..9045a53c6dfe 100644 +--- a/src/shared/gatt-db.c ++++ b/src/shared/gatt-db.c +@@ -616,6 +616,7 @@ static struct gatt_db_service *gatt_db_service_create(const bt_uuid_t *uuid, + + len = uuid_to_le(uuid, value); + ++ /* coverity[overrun-buffer-arg] : FALSE */ + service->attributes[0] = new_attribute(service, handle, type, value, + len); + if (!service->attributes[0]) { +@@ -980,6 +981,7 @@ service_insert_characteristic(struct gatt_db_service *service, + len += sizeof(uint16_t); + len += uuid_to_le(uuid, &value[3]); + ++ /* coverity[overrun-buffer-arg] : FALSE */ + service->attributes[i] = new_attribute(service, handle, + &characteristic_uuid, + value, len); +@@ -1007,8 +1009,11 @@ service_insert_characteristic(struct gatt_db_service *service, + + /* Update handle of characteristic value_handle if it has changed */ + put_le16(value_handle, &value[1]); +- if (memcmp((*chrc)->value, value, len)) ++ /* coverity[overrun-buffer-arg] : FALSE */ ++ if (memcmp((*chrc)->value, value, len)) { ++ /* coverity[overrun-buffer-arg] : FALSE */ + memcpy((*chrc)->value, value, len); ++ } + + set_attribute_data(service->attributes[i], read_func, write_func, + permissions, user_data); +-- +2.45.2 + + +From cddd78cb6d2a780b352e27ea5e7e44378f8a8ef4 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Tue, 30 Jul 2024 15:27:49 +0200 +Subject: [PATCH 8/8] shared/btsnoop: Work-around underflow case #01163329 + +It should be impossible to have toread underflow, as we check that it +has a value of at least 1 when decremented, and that we check for it +have a non-zero value before using it. +--- + src/shared/btsnoop.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/shared/btsnoop.c b/src/shared/btsnoop.c +index bb0bccf0dd01..12f960ec353d 100644 +--- a/src/shared/btsnoop.c ++++ b/src/shared/btsnoop.c +@@ -553,6 +553,7 @@ bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv, + btsnoop->aborted = true; + return false; + } ++ /* coverity[underflow] : FALSE */ + toread--; + + *index = 0; +-- +2.45.2 + diff --git a/static-analysis-issues-6.patch b/static-analysis-issues-6.patch new file mode 100644 index 0000000..68d033c --- /dev/null +++ b/static-analysis-issues-6.patch @@ -0,0 +1,353 @@ +From bdf5fd2a0156e9070e1e55777b4a71033160fbf1 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Wed, 17 Jul 2024 12:37:16 +0200 +Subject: [PATCH 1/8] sdp: Ensure size doesn't overflow + +Error: INTEGER_OVERFLOW (CWE-190): [#def1] [important] +bluez-5.77/lib/sdp.c:1685:2: tainted_data_argument: The check "sent < size" contains the tainted expression "sent" which causes "size" to be considered tainted. +bluez-5.77/lib/sdp.c:1686:3: overflow: The expression "size - sent" is deemed overflowed because at least one of its arguments has overflowed. +bluez-5.77/lib/sdp.c:1686:3: overflow_sink: "size - sent", which might have underflowed, is passed to "send(session->sock, buf + sent, size - sent, 0)". +1684| +1685| while (sent < size) { +1686|-> int n = send(session->sock, buf + sent, size - sent, 0); +1687| if (n < 0) +1688| return -1; +--- + lib/sdp.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/lib/sdp.c b/lib/sdp.c +index 411a95b8a7d3..8a15ad803db1 100644 +--- a/lib/sdp.c ++++ b/lib/sdp.c +@@ -1678,13 +1678,13 @@ sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attrId) + return NULL; + } + +-static int sdp_send_req(sdp_session_t *session, uint8_t *buf, uint32_t size) ++static int sdp_send_req(sdp_session_t *session, uint8_t *buf, size_t size) + { +- uint32_t sent = 0; ++ size_t sent = 0; + + while (sent < size) { + int n = send(session->sock, buf + sent, size - sent, 0); +- if (n < 0) ++ if (n < 0 || sent > SIZE_MAX - n) + return -1; + sent += n; + } +-- +2.45.2 + + +From 062c998fb5c407bc09d6124324b1bd393997bfee Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 18 Jul 2024 15:43:35 +0200 +Subject: [PATCH 2/8] tools/isotest: Ensure ret doesn't overflow + +Error: INTEGER_OVERFLOW (CWE-190): [#def20] [important] +bluez-5.77/tools/isotest.c:778:2: tainted_data_argument: The check "ret < count" contains the tainted expression "ret" which causes "count" to be considered tainted. +bluez-5.77/tools/isotest.c:779:3: overflow: The expression "count - ret" is deemed overflowed because at least one of its arguments has overflowed. +bluez-5.77/tools/isotest.c:779:3: overflow_sink: "count - ret", which might have underflowed, is passed to "read(fd, buf + ret, count - ret)". [Note: The source code implementation of the function has been overridden by a builtin model.] +777| +778| while (ret < count) { +779|-> len = read(fd, buf + ret, count - ret); +780| if (len < 0) +781| return -errno; +--- + tools/isotest.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/tools/isotest.c b/tools/isotest.c +index 2cac0e49cc39..0805faa66e47 100644 +--- a/tools/isotest.c ++++ b/tools/isotest.c +@@ -779,6 +779,8 @@ static int read_stream(int fd, ssize_t count) + len = read(fd, buf + ret, count - ret); + if (len < 0) + return -errno; ++ if (len > SSIZE_MAX - ret) ++ return -EOVERFLOW; + + ret += len; + usleep(1000); +-- +2.45.2 + + +From 122a888962765010162306f19fccf77333e1bc1b Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 18 Jul 2024 15:45:47 +0200 +Subject: [PATCH 3/8] health: mcap: Ensure sent doesn't overflow + +Error: INTEGER_OVERFLOW (CWE-190): [#def13] [important] +bluez-5.77/profiles/health/mcap.c:390:2: tainted_data_argument: The check "sent < size" contains the tainted expression "sent" which causes "size" to be considered tainted. +bluez-5.77/profiles/health/mcap.c:391:3: overflow: The expression "size - sent" is deemed overflowed because at least one of its arguments has overflowed. +bluez-5.77/profiles/health/mcap.c:391:3: overflow_sink: "size - sent", which might have underflowed, is passed to "write(sock, buf_b + sent, size - sent)". +389| +390| while (sent < size) { +391|-> int n = write(sock, buf_b + sent, size - sent); +392| if (n < 0) +393| return -1; +--- + profiles/health/mcap.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/profiles/health/mcap.c b/profiles/health/mcap.c +index 2e4214a6984f..b3bf403e74d2 100644 +--- a/profiles/health/mcap.c ++++ b/profiles/health/mcap.c +@@ -389,7 +389,7 @@ int mcap_send_data(int sock, const void *buf, uint32_t size) + + while (sent < size) { + int n = write(sock, buf_b + sent, size - sent); +- if (n < 0) ++ if (n < 0 || n > SSIZE_MAX - sent) + return -1; + sent += n; + } +-- +2.45.2 + + +From fce37c2100a043fce99fbe2e8c8171406b841fae Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 19 Jul 2024 11:26:45 +0200 +Subject: [PATCH 4/8] shared/tester: Add early failure check + +Add a similar assertion to the other tests to avoid passing negative len +to tester_monitor() which might result in crashes. + +Error: OVERRUN (CWE-119): [#def13] [important] +bluez-5.77/src/shared/tester.c:946:2: return_constant: Function call "io_send(io, iov, 1)" may return -107. +bluez-5.77/src/shared/tester.c:946:2: assignment: Assigning: "len" = "io_send(io, iov, 1)". The value of "len" is now -107. +bluez-5.77/src/shared/tester.c:948:2: overrun-buffer-arg: Calling "tester_monitor" with "iov->iov_base" and "len" is suspicious because of the very large index, 18446744073709551509. The index may be due to a negative parameter being interpreted as unsigned. +946| len = io_send(io, iov, 1); +947| +948|-> tester_monitor('<', 0x0004, 0x0000, iov->iov_base, len); +949| +950| g_assert_cmpint(len, ==, iov->iov_len); +--- + src/shared/tester.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/shared/tester.c b/src/shared/tester.c +index 56c8cba6f578..3053025d7945 100644 +--- a/src/shared/tester.c ++++ b/src/shared/tester.c +@@ -945,6 +945,8 @@ static bool test_io_send(struct io *io, void *user_data) + + len = io_send(io, iov, 1); + ++ g_assert(len > 0); ++ + tester_monitor('<', 0x0004, 0x0000, iov->iov_base, len); + + g_assert_cmpint(len, ==, iov->iov_len); +-- +2.45.2 + + +From 5078e205d5892048cb1243ce2977dcf8eb0c02fc Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Mon, 29 Jul 2024 13:53:41 +0200 +Subject: [PATCH 5/8] mesh: Fix possible integer overflow + +Error: INTEGER_OVERFLOW (CWE-190): [#def1] [important] +bluez-5.77/mesh/net.c:3164:4: cast_overflow: Truncation due to cast operation on "msg->len - seg_off" from 32 to 8 bits. +bluez-5.77/mesh/net.c:3164:4: overflow_assign: "seg_len" is assigned from "msg->len - seg_off". +bluez-5.77/mesh/net.c:3178:2: overflow_sink: "seg_len", which might have overflowed, is passed to "mesh_crypto_packet_build(false, msg->ttl, seq_num, msg->src, msg->remote, 0, msg->segmented, msg->key_aid, msg->szmic, false, msg->seqZero, segO, segN, msg->buf + seg_off, seg_len, packet + 1, &packet_len)". +3176| +3177| /* TODO: Are we RXing on an LPN's behalf? Then set RLY bit */ +3178|-> if (!mesh_crypto_packet_build(false, msg->ttl, seq_num, msg->src, +3179| msg->remote, 0, msg->segmented, +3180| msg->key_aid, msg->szmic, false, + +X +--- + mesh/net.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/mesh/net.c b/mesh/net.c +index 05ca48326fc5..ef6a3133859a 100644 +--- a/mesh/net.c ++++ b/mesh/net.c +@@ -3149,13 +3149,22 @@ static bool send_seg(struct mesh_net *net, uint8_t cnt, uint16_t interval, + uint32_t seq_num; + + if (msg->segmented) { ++ if (msg->len < seg_off) { ++ l_error("Failed to build packet"); ++ return false; ++ } + /* Send each segment on unique seq_num */ + seq_num = mesh_net_next_seq_num(net); + +- if (msg->len - seg_off > SEG_OFF(1)) ++ if (msg->len - seg_off > SEG_OFF(1)) { + seg_len = SEG_OFF(1); +- else ++ } else { ++ if (msg->len - seg_off > UINT8_MAX) { ++ l_error("Failed to build packet"); ++ return false; ++ } + seg_len = msg->len - seg_off; ++ } + } else { + /* Send on same seq_num used for Access Layer */ + seq_num = msg->seqAuth; +-- +2.45.2 + + +From c37f2cdd4b8fa66fc97d423c4c980865b4793ef2 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 19 Jul 2024 14:27:54 +0200 +Subject: [PATCH 6/8] shared/gatt-db: Fix possible buffer overrun + +uuid_to_le() returns one of the possible values from bt_uuid_len(). +bt_uuid_len() returns "type / 8". +type is a value between 0 and 128, but could be something else +depending on the validity of the UUID that's parsed. So an invalid +value of type between 128 and 256 would trigger an overrun. + +Add a check to make sure that an invalid type isn't used to calculate +the length. + +Error: OVERRUN (CWE-119): [#def6] [important] +bluez-5.77/src/shared/gatt-db.c:612:2: assignment: Assigning: "len" = "uuid_to_le(uuid, value)". The value of "len" is now between 0 and 31 (inclusive). +bluez-5.77/src/shared/gatt-db.c:614:2: overrun-buffer-arg: Overrunning array "value" of 16 bytes by passing it to a function which accesses it at byte offset 30 using argument "len" (which evaluates to 31). +612| len = uuid_to_le(uuid, value); +613| +614|-> service->attributes[0] = new_attribute(service, handle, type, value, +615| len); +616| if (!service->attributes[0]) { +--- + src/shared/gatt-db.c | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c +index b35763410d17..cd0eba6bf1d0 100644 +--- a/src/shared/gatt-db.c ++++ b/src/shared/gatt-db.c +@@ -560,9 +560,14 @@ static int uuid_to_le(const bt_uuid_t *uuid, uint8_t *dst) + return bt_uuid_len(uuid); + } + +- bt_uuid_to_uuid128(uuid, &uuid128); +- bswap_128(&uuid128.value.u128, dst); +- return bt_uuid_len(&uuid128); ++ if (uuid->type == BT_UUID32 || ++ uuid->type == BT_UUID128) { ++ bt_uuid_to_uuid128(uuid, &uuid128); ++ bswap_128(&uuid128.value.u128, dst); ++ return bt_uuid_len(&uuid128); ++ } ++ ++ return 0; + } + + static bool le_to_uuid(const uint8_t *src, size_t len, bt_uuid_t *uuid) +-- +2.45.2 + + +From b7cb9a4bc9b94ded15be812d1d444d0ace4a886d Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 19 Jul 2024 11:29:15 +0200 +Subject: [PATCH 7/8] shared/btsnoop: Avoid underflowing toread variable + +Error: INTEGER_OVERFLOW (CWE-190): [#def8] [important] +bluez-5.77/src/shared/btsnoop.c:556:3: underflow: The decrement operator on the unsigned variable "toread" might result in an underflow. +bluez-5.77/src/shared/btsnoop.c:572:2: overflow_sink: "toread", which might have underflowed, is passed to "read(btsnoop->fd, data, toread)". [Note: The source code implementation of the function has been overridden by a builtin model.] +570| } +571| +572|-> len = read(btsnoop->fd, data, toread); +573| if (len < 0) { +574| btsnoop->aborted = true; +--- + src/shared/btsnoop.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/shared/btsnoop.c b/src/shared/btsnoop.c +index bc5f7fcbe84c..bb0bccf0dd01 100644 +--- a/src/shared/btsnoop.c ++++ b/src/shared/btsnoop.c +@@ -530,7 +530,7 @@ bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv, + } + + toread = be32toh(pkt.len); +- if (toread > BTSNOOP_MAX_PACKET_SIZE) { ++ if (toread > BTSNOOP_MAX_PACKET_SIZE || toread < 1) { + btsnoop->aborted = true; + return false; + } +@@ -569,6 +569,11 @@ bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv, + return false; + } + ++ if (toread == 0) { ++ btsnoop->aborted = true; ++ return false; ++ } ++ + len = read(btsnoop->fd, data, toread); + if (len < 0) { + btsnoop->aborted = true; +-- +2.45.2 + + +From 354babc88eb98970a9f59056b41854b0f0f87859 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Fri, 19 Jul 2024 15:14:26 +0200 +Subject: [PATCH 8/8] monitor: Check for possible integer underflow + +Error: INTEGER_OVERFLOW (CWE-190): [#def4] [important] +bluez-5.77/monitor/control.c:1094:2: tainted_data_return: Called function "recv(data->fd, data->buf + data->offset, 1490UL - data->offset, MSG_DONTWAIT)", and a possible return value may be less than zero. +bluez-5.77/monitor/control.c:1094:2: assign: Assigning: "len" = "recv(data->fd, data->buf + data->offset, 1490UL - data->offset, MSG_DONTWAIT)". +bluez-5.77/monitor/control.c:1099:2: overflow: The expression "data->offset" is considered to have possibly overflowed. +bluez-5.77/monitor/control.c:1115:3: overflow: The expression "data->offset -= pktlen + 6" is deemed overflowed because at least one of its arguments has overflowed. +bluez-5.77/monitor/control.c:1118:4: overflow_sink: "data->offset", which might have underflowed, is passed to "memmove(data->buf, data->buf + 6 + pktlen, data->offset)". [Note: The source code implementation of the function has been overridden by a builtin model.] +1116| +1117| if (data->offset > 0) +1118|-> memmove(data->buf, data->buf + MGMT_HDR_SIZE + pktlen, +1119| data->offset); +1120| } +--- + monitor/control.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/monitor/control.c b/monitor/control.c +index 009cf15209f0..62857b4b84de 100644 +--- a/monitor/control.c ++++ b/monitor/control.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1091,9 +1092,14 @@ static void client_callback(int fd, uint32_t events, void *user_data) + return; + } + ++ if (sizeof(data->buf) <= data->offset) ++ return; ++ + len = recv(data->fd, data->buf + data->offset, + sizeof(data->buf) - data->offset, MSG_DONTWAIT); +- if (len < 0) ++ if (len < 0 || ++ len > UINT16_MAX || ++ UINT16_MAX - data->offset > len) + return; + + data->offset += len; +-- +2.45.2 +