bluez/client/assistant.c
Iulia Tanasescu 2ec7799354 client/assistant: Enter Broadcast Code as string
Currently, the user sets the Broadcast Code as an array of bytes
when prompted from the assistant submenu. However, the Bluetooth
Core Specification requires that, on the UI level, the Broadcast
Code shall be represented as a string (Vol 3, Part C, 3.2.6).

This commit makes the Broadcast Code be parsed as a string from
the assistant prompt. The bluetoothctl log below shows a Broadcast
Assistant pushing an encrypted stream to a peer:

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:31:7E:3F 00-60-37-31-7E-3F
[bluetooth]# connect 00:60:37:31:7E:3F
Attempting to connect to 00:60:37:31:7E:3F
[CHG] Device 00:60:37:31:7E:3F Connected: yes
[00-60-37-31-7E-3F]# Connection successful
[00-60-37-31-7E-3F]# [NEW] Device 19:9A:7A:71:E5:8B 19-9A-7A-71-E5-8B
[00-60-37-31-7E-3F]# [NEW] Assistant
    /org/bluez/hci0/src_19_9A_7A_71_E5_8B/dev_00_60_37_31_7E_3F/bis1
[00-60-37-31-7E-3F]# assistant.push
    /org/bluez/hci0/src_19_9A_7A_71_E5_8B/dev_00_60_37_31_7E_3F/bis1
[Assistant] Enter Metadata (auto/value): a
[Assistant] Enter Broadcast Code (auto/value): Borne House
[00-60-37-31-7E-3F]# [CHG] Assistant
    /org/bluez/hci0/src_19_9A_7A_71_E5_8B/dev_00_60_37_31_7E_3F/bis1
    State: pending
[00-60-37-31-7E-3F]# Assistant
    /org/bluez/hci0/src_19_9A_7A_71_E5_8B/dev_00_60_37_31_7E_3F/bis1
    pushed
[00-60-37-31-7E-3F]# [CHG] Assistant
    /org/bluez/hci0/src_19_9A_7A_71_E5_8B/dev_00_60_37_31_7E_3F/bis1
    State: requesting
[00-60-37-31-7E-3F]# [CHG] Assistant
    /org/bluez/hci0/src_19_9A_7A_71_E5_8B/dev_00_60_37_31_7E_3F/bis1
    State: active

The btmon log below shows the way the Broadcast Code string is converted
into a byte array and sent to the peer via GATT:

bluetoothd[6013]: < ACL Data TX: Handle 0 flags 0x00 dlen 28
      ATT: Write Command (0x52) len 23
        Handle: 0x0040 Type: Broadcast Audio Scan Control Point (0x2bc7)
          Data[21]: 02018be5717a9a1900db5e3a02ffff010100000000
            Opcode: Add Source (0x02)
            Source_Address_Type: 1
            Source_Address: 19:9A:7A:71:E5:8B
            Source_Adv_SID: 0
            Broadcast_ID: 0x3a5edb
            PA_Sync_State: Synchronize to PA - PAST not available
            PA_Interval: 0xffff
            Num_Subgroups: 1
            Subgroup #0:
              BIS_Sync State: 0x00000001
> ACL Data RX: Handle 0 flags 0x01 dlen 2
      ATT: Handle Multiple Value Notification (0x23) len 24
        Length: 0x0014
        Handle: 0x003a Type: Broadcast Receive State (0x2bc8)
          Data[20]: 01018be5717a9a1900db5e3a0201010000000000
          Source_ID: 1
          Source_Address_Type: 1
          Source_Address: 19:9A:7A:71:E5:8B
          Source_Adv_SID: 0
          Broadcast_ID: 0x3a5edb
          PA_Sync_State: Synchronized to PA
          BIG_Encryption: Broadcast_Code required
          Num_Subgroups: 1
          Subgroup #0:
            BIS_Sync State: 0x00000000
bluetoothd[6013]: < ACL Data TX: Handle 0 flags 0x00 dlen 25
      ATT: Write Command (0x52) len 20
        Handle: 0x0040 Type: Broadcast Audio Scan Control Point (0x2bc7)
          Data[18]: 040142c3b8726e6520486f75736500000000
            Opcode: Set Broadcast_Code (0x04)
            Source_ID: 1
            Broadcast_Code[16]: 426f726e6520486f7573650000000000
> ACL Data RX: Handle 0 flags 0x01 dlen 2
      ATT: Handle Multiple Value Notification (0x23) len 24
        Length: 0x0014
        Handle: 0x003a Type: Broadcast Receive State (0x2bc8)
          Data[20]: 01018be5717a9a1900db5e3a0202010100000000
          Source_ID: 1
          Source_Address_Type: 1
          Source_Address: 19:9A:7A:71:E5:8B
          Source_Adv_SID: 0
          Broadcast_ID: 0x3a5edb
          PA_Sync_State: Synchronized to PA
          BIG_Encryption: Decrypting
          Num_Subgroups: 1
          Subgroup #0:
            BIS_Sync State: 0x00000001
2024-09-09 12:58:05 -04:00

416 lines
9.1 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright 2024 NXP
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <glib.h>
#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"
#define BCODE_LEN 16
struct assistant_config {
GDBusProxy *proxy; /* DBus object reference */
struct iovec *meta; /* Stream metadata LTVs */
struct bt_iso_qos qos; /* Stream QoS parameters */
};
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 uint8_t *str2bytearray(char *arg, size_t *val_len)
{
uint8_t value[UINT8_MAX];
char *entry;
unsigned int i;
for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++) {
long val;
char *endptr = NULL;
if (*entry == '\0')
continue;
if (i >= G_N_ELEMENTS(value)) {
bt_shell_printf("Too much data\n");
return NULL;
}
val = strtol(entry, &endptr, 0);
if (!endptr || *endptr != '\0' || val > UINT8_MAX) {
bt_shell_printf("Invalid value at index %d\n", i);
return NULL;
}
value[i] = val;
}
*val_len = i;
return util_memdup(value, i);
}
static void append_qos(DBusMessageIter *iter, struct assistant_config *cfg)
{
DBusMessageIter entry, var, dict;
const char *key = "QoS";
const char *bcode_key = "BCode";
uint8_t *bcode = cfg->qos.bcast.bcode;
dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY,
NULL, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
"a{sv}", &var);
dbus_message_iter_open_container(&var, DBUS_TYPE_ARRAY, "{sv}",
&dict);
g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING,
&bcode_key, DBUS_TYPE_BYTE,
&bcode, BCODE_LEN);
dbus_message_iter_close_container(&var, &dict);
dbus_message_iter_close_container(&entry, &var);
dbus_message_iter_close_container(iter, &entry);
}
static void push_setup(DBusMessageIter *iter, void *user_data)
{
struct assistant_config *cfg = user_data;
DBusMessageIter dict;
const char *meta = "Metadata";
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
if (cfg->meta)
g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &meta,
DBUS_TYPE_BYTE, &cfg->meta->iov_base,
cfg->meta->iov_len);
if (cfg->qos.bcast.encryption)
append_qos(&dict, cfg);
dbus_message_iter_close_container(iter, &dict);
}
static void push_reply(DBusMessage *message, void *user_data)
{
struct assistant_config *cfg = user_data;
DBusError error;
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, message)) {
bt_shell_printf("Failed to push assistant: %s\n",
error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Assistant %s pushed\n",
g_dbus_proxy_get_path(cfg->proxy));
free(cfg->meta);
g_free(cfg);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void assistant_set_bcode_cfg(const char *input, void *user_data)
{
struct assistant_config *cfg = user_data;
if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
memset(cfg->qos.bcast.bcode, 0, BCODE_LEN);
} else {
if (strlen(input) > BCODE_LEN) {
bt_shell_printf("Input string too long %s\n", input);
goto fail;
}
memcpy(cfg->qos.bcast.bcode, input, strlen(input));
}
if (!g_dbus_proxy_method_call(cfg->proxy, "Push",
push_setup, push_reply,
cfg, NULL)) {
bt_shell_printf("Failed to push assistant\n");
goto fail;
}
return;
fail:
free(cfg->meta);
g_free(cfg);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void assistant_set_metadata_cfg(const char *input, void *user_data)
{
struct assistant_config *cfg = user_data;
DBusMessageIter iter, dict, entry, value;
const char *key;
if (!strcasecmp(input, "a") || !strcasecmp(input, "auto"))
goto done;
if (!cfg->meta)
cfg->meta = g_new0(struct iovec, 1);
cfg->meta->iov_base = str2bytearray((char *) input,
&cfg->meta->iov_len);
if (!cfg->meta->iov_base) {
free(cfg->meta);
cfg->meta = NULL;
}
done:
/* Get QoS property to check if the stream is encrypted */
if (!g_dbus_proxy_get_property(cfg->proxy, "QoS", &iter))
goto fail;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
goto fail;
dbus_message_iter_recurse(&iter, &dict);
if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY)
goto fail;
dbus_message_iter_recurse(&dict, &entry);
dbus_message_iter_get_basic(&entry, &key);
if (strcasecmp(key, "Encryption") != 0)
goto fail;
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_BYTE)
goto fail;
dbus_message_iter_get_basic(&value, &cfg->qos.bcast.encryption);
if (cfg->qos.bcast.encryption)
/* Prompt user to enter the Broadcast Code to decrypt
* the stream
*/
bt_shell_prompt_input("Assistant",
"Enter Broadcast Code (auto/value):",
assistant_set_bcode_cfg, cfg);
else
if (!g_dbus_proxy_method_call(cfg->proxy, "Push",
push_setup, push_reply,
cfg, NULL)) {
bt_shell_printf("Failed to push assistant\n");
goto fail;
}
return;
fail:
free(cfg->meta);
g_free(cfg);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void cmd_push_assistant(int argc, char *argv[])
{
struct assistant_config *cfg;
cfg = new0(struct assistant_config, 1);
if (!cfg)
goto fail;
/* Search for DBus object */
cfg->proxy = g_dbus_proxy_lookup(assistants, NULL, argv[1],
MEDIA_ASSISTANT_INTERFACE);
if (!cfg->proxy) {
bt_shell_printf("Assistant %s not found\n", argv[1]);
goto fail;
}
/* Prompt user to enter metadata */
bt_shell_prompt_input("Assistant",
"Enter Metadata (auto/value):",
assistant_set_metadata_cfg, cfg);
return;
fail:
g_free(cfg);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static const struct bt_shell_menu assistant_menu = {
.name = "assistant",
.desc = "Media Assistant Submenu",
.entries = {
{ "push", "<assistant>", cmd_push_assistant,
"Send stream information to peer" },
{} },
};
static GDBusClient * client;
void assistant_add_submenu(void)
{
bt_shell_add_submenu(&assistant_menu);
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;
}