All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu
@ 2022-04-13 21:16 Luiz Augusto von Dentz
  2022-04-13 21:16 ` [PATCH v2 BlueZ 2/2] client/player: Add transport menu Luiz Augusto von Dentz
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2022-04-13 21:16 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds endpoint menu:

[bluetooth]# menu endpoint
Menu endpoint:
Available commands:
-------------------
list [local]                                      List available endpoints
show <endpoint>                                   Endpoint information
register <UUID> <codec> [capabilities...]         Register Endpoint
unregister <UUID/object>                          Register Endpoint
config <endpoint> <local endpoint> [preset]       Configure Endpoint
presets <UUID> [default]                          List available presets
---
 client/player.c | 1188 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 1183 insertions(+), 5 deletions(-)

diff --git a/client/player.c b/client/player.c
index 1095482d4..5db35cfeb 100644
--- a/client/player.c
+++ b/client/player.c
@@ -21,7 +21,9 @@
 #include <stdlib.h>
 #include <fcntl.h>
 #include <string.h>
+#include <sys/ioctl.h>
 #include <sys/uio.h>
+#include <wordexp.h>
 
 #include <glib.h>
 
@@ -30,8 +32,12 @@
 #include "lib/bluetooth.h"
 #include "lib/uuid.h"
 
+#include "profiles/audio/a2dp-codecs.h"
+
 #include "src/shared/util.h"
 #include "src/shared/shell.h"
+#include "src/shared/io.h"
+#include "src/shared/queue.h"
 #include "player.h"
 
 /* String display constants */
@@ -39,15 +45,51 @@
 #define COLORED_CHG	COLOR_YELLOW "CHG" COLOR_OFF
 #define COLORED_DEL	COLOR_RED "DEL" COLOR_OFF
 
+#define BLUEZ_MEDIA_INTERFACE "org.bluez.Media1"
 #define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
 #define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
 #define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
+#define BLUEZ_MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
+
+#define BLUEZ_MEDIA_ENDPOINT_PATH "/local/endpoint"
+
+#define NSEC_USEC(_t) (_t / 1000L)
+#define SEC_USEC(_t)  (_t  * 1000000L)
+#define TS_USEC(_ts)  (SEC_USEC((_ts)->tv_sec) + NSEC_USEC((_ts)->tv_nsec))
+
+struct endpoint {
+	char *path;
+	char *uuid;
+	uint8_t codec;
+	struct iovec *caps;
+	bool auto_accept;
+	char *transport;
+	DBusMessage *msg;
+};
 
 static DBusConnection *dbus_conn;
 static GDBusProxy *default_player;
+static GList *medias = NULL;
 static GList *players = NULL;
 static GList *folders = NULL;
 static GList *items = NULL;
+static GList *endpoints = NULL;
+static GList *local_endpoints = NULL;
+
+static void endpoint_unregister(void *data)
+{
+	struct endpoint *ep = data;
+
+	bt_shell_printf("Endpoint %s unregistered\n", ep->path);
+	g_dbus_unregister_interface(dbus_conn, ep->path,
+						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+}
+
+static void disconnect_handler(DBusConnection *connection, void *user_data)
+{
+	g_list_free_full(local_endpoints, endpoint_unregister);
+	local_endpoints = NULL;
+}
 
 static bool check_default_player(void)
 {
@@ -63,6 +105,9 @@ static char *generic_generator(const char *text, int state, GList *source)
 {
 	static int index = 0;
 
+	if (!source)
+		return NULL;
+
 	if (!state)
 		index = 0;
 
@@ -319,11 +364,10 @@ static void generic_callback(const DBusError *error, void *user_data)
 	if (dbus_error_is_set(error)) {
 		bt_shell_printf("Failed to set %s: %s\n", str, error->name);
 		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	} else {
+		bt_shell_printf("Changing %s succeeded\n", str);
+		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
 	}
-
-	bt_shell_printf("Changing %s succeeded\n", str);
-
-	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
 }
 
 static void cmd_equalizer(int argc, char *argv[])
@@ -450,6 +494,17 @@ static char *proxy_description(GDBusProxy *proxy, const char *title,
 					title, path);
 }
 
+static void print_media(GDBusProxy *proxy, const char *description)
+{
+	char *str;
+
+	str = proxy_description(proxy, "Media", description);
+
+	bt_shell_printf("%s\n", str);
+
+	g_free(str);
+}
+
 static void print_player(GDBusProxy *proxy, const char *description)
 {
 	char *str;
@@ -481,6 +536,7 @@ static void print_iter(const char *label, const char *name,
 	dbus_uint32_t valu32;
 	dbus_uint16_t valu16;
 	dbus_int16_t vals16;
+	unsigned char byte;
 	const char *valstr;
 	DBusMessageIter subiter;
 
@@ -515,6 +571,10 @@ static void print_iter(const char *label, const char *name,
 		dbus_message_iter_get_basic(iter, &vals16);
 		bt_shell_printf("%s%s: %d\n", label, name, vals16);
 		break;
+	case DBUS_TYPE_BYTE:
+		dbus_message_iter_get_basic(iter, &byte);
+		bt_shell_printf("%s%s: 0x%02x (%d)\n", label, name, byte, byte);
+		break;
 	case DBUS_TYPE_VARIANT:
 		dbus_message_iter_recurse(iter, &subiter);
 		print_iter(label, name, &subiter);
@@ -948,6 +1008,1081 @@ static const struct bt_shell_menu player_menu = {
 	{} },
 };
 
+static char *endpoint_generator(const char *text, int state)
+{
+	return generic_generator(text, state, endpoints);
+}
+
+static char *local_endpoint_generator(const char *text, int state)
+{
+	int len = strlen(text);
+	GList *l;
+	static int index = 0;
+
+	if (!state)
+		index = 0;
+
+	for (l = g_list_nth(local_endpoints, index); l; l = g_list_next(l)) {
+		struct endpoint *ep = l->data;
+
+		index++;
+
+		if (!strncasecmp(ep->path, text, len))
+			return strdup(ep->path);
+	}
+
+	return NULL;
+}
+
+static void print_endpoint(void *data, void *user_data)
+{
+	GDBusProxy *proxy = data;
+	const char *description = user_data;
+	char *str;
+
+	str = proxy_description(proxy, "Endpoint", description);
+
+	bt_shell_printf("%s\n", str);
+
+	g_free(str);
+}
+
+static void cmd_list_endpoints(int argc, char *argv[])
+{
+	GList *l;
+
+	if (argc > 1) {
+		if (strcmp("local", argv[1])) {
+			bt_shell_printf("Endpoint list %s not available\n",
+					argv[1]);
+			return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+		}
+
+		for (l = local_endpoints; l; l = g_list_next(l)) {
+			struct endpoint *ep = l->data;
+
+			bt_shell_printf("%s\n", ep->path);
+		}
+
+		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+	}
+
+	for (l = endpoints; l; l = g_list_next(l)) {
+		GDBusProxy *proxy = l->data;
+		print_endpoint(proxy, NULL);
+	}
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void confirm_response(const char *input, void *user_data)
+{
+	DBusMessage *msg = user_data;
+
+	if (!strcasecmp(input, "y") || !strcasecmp(input, "yes"))
+		g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID);
+	else if (!strcasecmp(input, "n") || !strcmp(input, "no"))
+		g_dbus_send_error(dbus_conn, msg, "org.bluez.Error.Rejected",
+									NULL);
+	else
+		g_dbus_send_error(dbus_conn, msg, "org.bluez.Error.Canceled",
+									NULL);
+}
+
+static DBusMessage *endpoint_set_configuration(DBusConnection *conn,
+					DBusMessage *msg, void *user_data)
+{
+	struct endpoint *ep = user_data;
+	DBusMessageIter args, props;
+	const char *path;
+
+	dbus_message_iter_init(msg, &args);
+
+	dbus_message_iter_get_basic(&args, &path);
+	dbus_message_iter_next(&args);
+
+	dbus_message_iter_recurse(&args, &props);
+	if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+		return g_dbus_create_error(msg,
+					 "org.bluez.Error.InvalidArguments",
+					 NULL);
+
+	bt_shell_printf("Endpoint: SetConfiguration\n");
+	bt_shell_printf("\tTransport %s\n", path);
+	print_iter("\t", "Properties", &props);
+
+	free(ep->transport);
+	ep->transport = strdup(path);
+
+	if (ep->auto_accept) {
+		bt_shell_printf("Auto Accepting...\n");
+		return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+	}
+
+	bt_shell_prompt_input("Endpoint", "Accept (yes/no):", confirm_response,
+							dbus_message_ref(msg));
+
+	return NULL;
+}
+
+struct codec_capabilities {
+	uint8_t len;
+	uint8_t type;
+	uint8_t data[UINT8_MAX];
+};
+
+#define data(args...) ((const unsigned char[]) { args })
+
+#define SBC_DATA(args...) \
+	{ \
+		.iov_base = (void *)data(args), \
+		.iov_len = sizeof(data(args)), \
+	}
+
+#define CODEC_CAPABILITIES(_uuid, _codec_id, _data) \
+	{ \
+		.uuid = _uuid, \
+		.codec_id = _codec_id, \
+		.data = _data, \
+	}
+
+static const struct capabilities {
+	const char *uuid;
+	uint8_t codec_id;
+	struct iovec data;
+} caps[] = {
+	/* A2DP SBC Source:
+	 *
+	 * Channel Modes: Mono DualChannel Stereo JointStereo
+	 * Frequencies: 16Khz 32Khz 44.1Khz 48Khz
+	 * Subbands: 4 8
+	 * Blocks: 4 8 12 16
+	 * Bitpool Range: 2-64
+	 */
+	CODEC_CAPABILITIES(A2DP_SOURCE_UUID, A2DP_CODEC_SBC,
+					SBC_DATA(0xff, 0xff, 2, 64)),
+	/* A2DP SBC Sink:
+	 *
+	 * Channel Modes: Mono DualChannel Stereo JointStereo
+	 * Frequencies: 16Khz 32Khz 44.1Khz 48Khz
+	 * Subbands: 4 8
+	 * Blocks: 4 8 12 16
+	 * Bitpool Range: 2-64
+	 */
+	CODEC_CAPABILITIES(A2DP_SINK_UUID, A2DP_CODEC_SBC,
+					SBC_DATA(0xff, 0xff, 2, 64)),
+};
+
+struct codec_preset {
+	const char *name;
+	const struct iovec data;
+	bool is_default;
+};
+
+#define SBC_PRESET(_name, _data) \
+	{ \
+		.name = _name, \
+		.data = _data, \
+	}
+
+#define SBC_DEFAULT_PRESET(_name, _data) \
+	{ \
+		.name = _name, \
+		.data = _data, \
+		.is_default = true, \
+	}
+
+static struct codec_preset sbc_presets[] = {
+	/* Table 4.7: Recommended sets of SBC parameters in the SRC device
+	 * Other settings: Block length = 16, Allocation method = Loudness,
+	 * Subbands = 8.
+	 * A2DP spec sets maximum bitrates as follows:
+	 * This profile limits the available maximum bit rate to 320kb/s for
+	 * mono, and 512kb/s for two-channel modes.
+	 */
+	SBC_PRESET("MQ_MONO_44_1",
+		SBC_DATA(0x28, 0x15, 2, SBC_BITPOOL_MQ_MONO_44100)),
+	SBC_PRESET("MQ_MONO_48",
+		SBC_DATA(0x18, 0x15, 2, SBC_BITPOOL_MQ_MONO_48000)),
+	SBC_PRESET("MQ_STEREO_44_1",
+		SBC_DATA(0x21, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_44100)),
+	SBC_PRESET("MQ_STEREO_48",
+		SBC_DATA(0x11, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_48000)),
+	SBC_PRESET("HQ_MONO_44_1",
+		SBC_DATA(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)),
+	SBC_PRESET("HQ_MONO_48",
+		SBC_DATA(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)),
+	SBC_DEFAULT_PRESET("HQ_STEREO_44_1",
+		SBC_DATA(0x21, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_44100)),
+	SBC_PRESET("HQ_STEREO_48",
+		SBC_DATA(0x11, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_48000)),
+	/* Higher bitrates not recommended by A2DP spec, it dual channel to
+	 * avoid going above 53 bitpool:
+	 *
+	 * https://habr.com/en/post/456476/
+	 * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/1092
+	 */
+	SBC_PRESET("XQ_DUAL_44_1", SBC_DATA(0x24, 0x15, 2, 43)),
+	SBC_PRESET("XQ_DUAL_48", SBC_DATA(0x14, 0x15, 2, 39)),
+	/* Ultra high bitpool that fits in 512 kbps mandatory bitrate */
+	SBC_PRESET("UQ_STEREO_44_1", SBC_DATA(0x21, 0x15, 2, 64)),
+	SBC_PRESET("UQ_STEREO_48", SBC_DATA(0x11, 0x15, 2, 58)),
+};
+
+#define PRESET(_uuid, _presets) \
+	{ \
+		.uuid = _uuid, \
+		.presets = _presets, \
+		.num_presets = ARRAY_SIZE(_presets), \
+	}
+
+static const struct preset {
+	const char *uuid;
+	struct codec_preset *presets;
+	size_t num_presets;
+} presets[] = {
+	PRESET(A2DP_SOURCE_UUID, sbc_presets),
+	PRESET(A2DP_SINK_UUID, sbc_presets),
+};
+
+static struct codec_preset *find_preset(const char *uuid, const char *name)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(presets); i++) {
+		const struct preset *preset = &presets[i];
+
+		if (!strcasecmp(preset->uuid, uuid)) {
+			size_t j;
+
+			for (j = 0; j < preset->num_presets; j++) {
+				struct codec_preset *p;
+
+				p = &preset->presets[j];
+
+				if (!name) {
+					if (p->is_default)
+						return p;
+				} else if (!strcmp(p->name, name))
+					return p;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static DBusMessage *endpoint_select_config_reply(DBusMessage *msg,
+						 uint8_t *data, size_t len)
+{
+	DBusMessage *reply;
+	DBusMessageIter args, array;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &args);
+
+	dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+						DBUS_TYPE_BYTE_AS_STRING,
+						&array);
+
+	dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &data,
+								len);
+
+	dbus_message_iter_close_container(&args, &array);
+
+	return reply;
+}
+
+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 g_memdup(value, i);
+}
+
+static void select_config_response(const char *input, void *user_data)
+{
+	struct endpoint *ep = user_data;
+	struct codec_preset *p;
+	DBusMessage *reply;
+	uint8_t *data;
+	size_t len;
+
+	p = find_preset(ep->uuid, input);
+	if (p) {
+		data = p->data.iov_base;
+		len = p->data.iov_len;
+		goto done;
+	}
+
+	data = str2bytearray((void *) input, &len);
+	if (!data) {
+		g_dbus_send_error(dbus_conn, ep->msg,
+				  "org.bluez.Error.Rejected", NULL);
+		ep->msg = NULL;
+		return;
+	}
+
+done:
+	reply = endpoint_select_config_reply(ep->msg, data, len);
+	if (!reply)
+		return;
+
+	if (!p)
+		free(data);
+
+	g_dbus_send_message(dbus_conn, reply);
+	dbus_message_unref(ep->msg);
+	ep->msg = NULL;
+}
+
+static DBusMessage *endpoint_select_configuration(DBusConnection *conn,
+					DBusMessage *msg, void *user_data)
+{
+	struct endpoint *ep = user_data;
+	struct codec_preset *p;
+	DBusMessageIter args;
+	DBusMessage *reply;
+
+	dbus_message_iter_init(msg, &args);
+
+	bt_shell_printf("Endpoint: SelectConfiguration\n");
+	print_iter("\t", "Capabilities", &args);
+
+	if (!ep->auto_accept) {
+		ep->msg = dbus_message_ref(msg);
+		bt_shell_prompt_input("Endpoint", "Enter preset/configuration:",
+					select_config_response, ep);
+		return NULL;
+	}
+
+	p = find_preset(ep->uuid, NULL);
+	if (!p)
+		NULL;
+
+	reply = endpoint_select_config_reply(msg, p->data.iov_base,
+						p->data.iov_len);
+	if (!reply)
+		return NULL;
+
+	bt_shell_printf("Auto Accepting using %s...\n", p->name);
+
+	return reply;
+}
+
+static DBusMessage *endpoint_clear_configuration(DBusConnection *conn,
+					DBusMessage *msg, void *user_data)
+{
+	struct endpoint *ep = user_data;
+
+	free(ep->transport);
+	ep->transport = NULL;
+
+	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static struct endpoint *endpoint_find(const char *pattern)
+{
+	GList *l;
+
+	for (l = local_endpoints; l; l = g_list_next(l)) {
+		struct endpoint *ep = l->data;
+
+		/* match object path */
+		if (!strcmp(ep->path, pattern))
+			return ep;
+
+		/* match UUID */
+		if (!strcmp(ep->uuid, pattern))
+			return ep;
+	}
+
+	return NULL;
+}
+
+static void cmd_show_endpoint(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_lookup(endpoints, NULL, argv[1],
+						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+	if (!proxy) {
+		bt_shell_printf("Endpoint %s not found\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+	}
+
+	bt_shell_printf("Endpoint %s\n", g_dbus_proxy_get_path(proxy));
+
+	print_property(proxy, "UUID");
+	print_property(proxy, "Codec");
+	print_property(proxy, "Capabilities");
+	print_property(proxy, "Device");
+	print_property(proxy, "DelayReporting");
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static const GDBusMethodTable endpoint_methods[] = {
+	{ GDBUS_ASYNC_METHOD("SetConfiguration",
+					GDBUS_ARGS({ "endpoint", "o" },
+						{ "properties", "a{sv}" } ),
+					NULL, endpoint_set_configuration) },
+	{ GDBUS_ASYNC_METHOD("SelectConfiguration",
+					GDBUS_ARGS({ "caps", "ay" } ),
+					NULL, endpoint_select_configuration) },
+	{ GDBUS_ASYNC_METHOD("ClearConfiguration",
+					GDBUS_ARGS({ "transport", "o" } ),
+					NULL, endpoint_clear_configuration) },
+	{ },
+};
+
+static void endpoint_free(void *data)
+{
+	struct endpoint *ep = data;
+
+	if (ep->caps) {
+		g_free(ep->caps->iov_base);
+		g_free(ep->caps);
+	}
+
+	if (ep->msg)
+		dbus_message_unref(ep->msg);
+
+	g_free(ep->path);
+	g_free(ep->uuid);
+	g_free(ep);
+}
+
+static gboolean endpoint_get_uuid(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	struct endpoint *ep = data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ep->uuid);
+
+	return TRUE;
+}
+
+static gboolean endpoint_get_codec(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	struct endpoint *ep = data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &ep->codec);
+
+	return TRUE;
+}
+
+static gboolean endpoint_get_capabilities(const GDBusPropertyTable *property,
+					DBusMessageIter *iter, void *data)
+{
+	struct endpoint *ep = data;
+	DBusMessageIter array;
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+					DBUS_TYPE_BYTE_AS_STRING, &array);
+
+	dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
+					     &ep->caps->iov_base,
+					     ep->caps->iov_len);
+
+	dbus_message_iter_close_container(iter, &array);
+
+	return TRUE;
+}
+
+static const GDBusPropertyTable endpoint_properties[] = {
+	{ "UUID", "s", endpoint_get_uuid, NULL, NULL },
+	{ "Codec", "y", endpoint_get_codec, NULL, NULL },
+	{ "Capabilities", "ay", endpoint_get_capabilities, NULL, NULL },
+	{ }
+};
+
+static void register_endpoint_setup(DBusMessageIter *iter, void *user_data)
+{
+	struct endpoint *ep = user_data;
+	DBusMessageIter dict;
+	const char *key = "Capabilities";
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &ep->path);
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
+
+	g_dbus_dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &ep->uuid);
+
+	g_dbus_dict_append_entry(&dict, "Codec", DBUS_TYPE_BYTE, &ep->codec);
+
+	g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key,
+					DBUS_TYPE_BYTE, &ep->caps->iov_base,
+					ep->caps->iov_len);
+
+	bt_shell_printf("Capabilities:\n");
+	bt_shell_hexdump(ep->caps->iov_base, ep->caps->iov_len);
+
+	dbus_message_iter_close_container(iter, &dict);
+}
+
+static void register_endpoint_reply(DBusMessage *message, void *user_data)
+{
+	struct endpoint *ep = user_data;
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message)) {
+		bt_shell_printf("Failed to register endpoint: %s\n",
+				error.name);
+		dbus_error_free(&error);
+		local_endpoints = g_list_remove(local_endpoints, ep);
+		g_dbus_unregister_interface(dbus_conn, ep->path,
+						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Endpoint %s registered\n", ep->path);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void endpoint_register(struct endpoint *ep)
+{
+	GList *l;
+
+	if (!g_dbus_register_interface(dbus_conn, ep->path,
+					BLUEZ_MEDIA_ENDPOINT_INTERFACE,
+					endpoint_methods, NULL,
+					endpoint_properties, ep,
+					endpoint_free)) {
+		goto fail;
+	}
+
+	for (l = medias; l; l = g_list_next(l)) {
+		if (!g_dbus_proxy_method_call(l->data, "RegisterEndpoint",
+						register_endpoint_setup,
+						register_endpoint_reply,
+						ep, NULL)) {
+			g_dbus_unregister_interface(dbus_conn, ep->path,
+						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+			goto fail;
+		}
+	}
+
+	return;
+
+fail:
+	bt_shell_printf("Failed register endpoint\n");
+	local_endpoints = g_list_remove(local_endpoints, ep);
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+}
+
+static void endpoint_auto_accept(const char *input, void *user_data)
+{
+	struct endpoint *ep = user_data;
+
+	if (!strcasecmp(input, "y") || !strcasecmp(input, "yes"))
+		ep->auto_accept = true;
+	else if (!strcasecmp(input, "n") || !strcasecmp(input, "no"))
+		ep->auto_accept = false;
+	else
+		bt_shell_printf("Invalid input for Auto Accept\n");
+
+	endpoint_register(ep);
+}
+
+static void endpoint_set_capabilities(const char *input, void *user_data)
+{
+	struct endpoint *ep = user_data;
+
+	if (ep->caps)
+		g_free(ep->caps->iov_base);
+	else
+		ep->caps = g_new0(struct iovec, 1);
+
+	ep->caps->iov_base = str2bytearray((char *) input, &ep->caps->iov_len);
+
+	bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):",
+						endpoint_auto_accept, ep);
+}
+
+static char *uuid_generator(const char *text, int state)
+{
+	int len = strlen(text);
+	static int index = 0;
+	size_t i;
+
+	if (!state)
+		index = 0;
+
+	for (i = index; i < ARRAY_SIZE(caps); i++) {
+		const struct capabilities *cap = &caps[i];
+
+		index++;
+
+		if (!strncasecmp(cap->uuid, text, len))
+			return strdup(cap->uuid);
+	}
+
+	return NULL;
+}
+
+static const struct capabilities *find_capabilities(const char *uuid,
+							uint8_t codec_id)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(caps); i++) {
+		const struct capabilities *cap = &caps[i];
+
+		if (strcasecmp(cap->uuid, uuid))
+			continue;
+
+		if (cap->codec_id == codec_id)
+			return cap;
+	}
+
+	return NULL;
+}
+
+static struct iovec *iov_append(struct iovec **iov, const void *data,
+							size_t len)
+{
+	if (!*iov) {
+		*iov = new0(struct iovec, 1);
+		(*iov)->iov_base = new0(uint8_t, UINT8_MAX);
+	}
+
+	if (data && len) {
+		memcpy((*iov)->iov_base + (*iov)->iov_len, data, len);
+		(*iov)->iov_len += len;
+	}
+
+	return *iov;
+}
+
+static void cmd_register_endpoint(int argc, char *argv[])
+{
+	struct endpoint *ep;
+	char *endptr = NULL;
+
+	ep = g_new0(struct endpoint, 1);
+	ep->uuid = g_strdup(argv[1]);
+	ep->codec = strtol(argv[2], &endptr, 0);
+	ep->path = g_strdup_printf("%s/ep%u", BLUEZ_MEDIA_ENDPOINT_PATH,
+					g_list_length(local_endpoints));
+	local_endpoints = g_list_append(local_endpoints, ep);
+
+	if (argc > 3)
+		endpoint_set_capabilities(argv[3], ep);
+	else {
+		const struct capabilities *cap;
+
+		cap = find_capabilities(ep->uuid, ep->codec);
+		if (cap) {
+			if (ep->caps)
+				ep->caps->iov_len = 0;
+
+			/* Copy capabilities */
+			iov_append(&ep->caps, cap->data.iov_base,
+							cap->data.iov_len);
+
+			bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):",
+						endpoint_auto_accept, ep);
+		} else
+			bt_shell_prompt_input(ep->path, "Enter capabilities:",
+						endpoint_set_capabilities, ep);
+	}
+}
+
+static void unregister_endpoint_setup(DBusMessageIter *iter, void *user_data)
+{
+	struct endpoint *ep = user_data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &ep->path);
+}
+
+static void unregister_endpoint_reply(DBusMessage *message, void *user_data)
+{
+	struct endpoint *ep = user_data;
+	DBusError error;
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message)) {
+		bt_shell_printf("Failed to unregister endpoint: %s\n",
+				error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Endpoint %s unregistered\n", ep->path);
+
+	local_endpoints = g_list_remove(local_endpoints, ep);
+	g_dbus_unregister_interface(dbus_conn, ep->path,
+					BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_unregister_endpoint(int argc, char *argv[])
+{
+	struct endpoint *ep;
+	GList *l;
+
+	ep = endpoint_find(argv[1]);
+	if (!ep) {
+		bt_shell_printf("Unable to find endpoint object: %s\n",
+								argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	for (l = medias; l; l = g_list_next(l)) {
+		if (!g_dbus_proxy_method_call(l->data, "UnregisterEndpoint",
+						unregister_endpoint_setup,
+						unregister_endpoint_reply,
+						ep, NULL)) {
+			bt_shell_printf("Failed unregister endpoint\n");
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+	}
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+struct codec_qos {
+	uint32_t interval;
+	uint8_t  framing;
+	char *phy;
+	uint16_t sdu;
+	uint8_t  rtn;
+	uint16_t latency;
+	uint32_t delay;
+};
+
+struct endpoint_config {
+	GDBusProxy *proxy;
+	struct endpoint *ep;
+	struct iovec *caps;
+	struct codec_qos qos;
+};
+
+static void config_endpoint_setup(DBusMessageIter *iter, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+	DBusMessageIter dict;
+	const char *key = "Capabilities";
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
+					&cfg->ep->path);
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
+
+	bt_shell_printf("Capabilities: ");
+	bt_shell_hexdump(cfg->caps->iov_base, cfg->caps->iov_len);
+
+	g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key,
+					DBUS_TYPE_BYTE, &cfg->caps->iov_base,
+					cfg->caps->iov_len);
+
+	bt_shell_printf("Interval %u\n", cfg->qos.interval);
+
+	g_dbus_dict_append_entry(&dict, "Interval", DBUS_TYPE_UINT32,
+						&cfg->qos.interval);
+
+	bt_shell_printf("Framing %s\n", cfg->qos.framing ? "true" : "false");
+
+	g_dbus_dict_append_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN,
+						&cfg->qos.framing);
+
+	bt_shell_printf("PHY %s\n", cfg->qos.phy);
+
+	g_dbus_dict_append_entry(&dict, "PHY", DBUS_TYPE_STRING, &cfg->qos.phy);
+
+	bt_shell_printf("SDU %u\n", cfg->qos.sdu);
+
+	g_dbus_dict_append_entry(&dict, "SDU", DBUS_TYPE_UINT16,
+						&cfg->qos.sdu);
+
+	bt_shell_printf("Retransmissions %u\n", cfg->qos.rtn);
+
+	g_dbus_dict_append_entry(&dict, "Retransmissions", DBUS_TYPE_BYTE,
+						&cfg->qos.rtn);
+
+	bt_shell_printf("Latency %u\n", cfg->qos.latency);
+
+	g_dbus_dict_append_entry(&dict, "Latency", DBUS_TYPE_UINT16,
+						&cfg->qos.latency);
+
+	bt_shell_printf("Delay %u\n", cfg->qos.delay);
+
+	g_dbus_dict_append_entry(&dict, "Delay", DBUS_TYPE_UINT32,
+						&cfg->qos.delay);
+
+	dbus_message_iter_close_container(iter, &dict);
+}
+
+static void config_endpoint_reply(DBusMessage *message, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+	struct endpoint *ep = cfg->ep;
+	DBusError error;
+
+	free(cfg->caps->iov_base);
+	free(cfg->caps);
+	free(cfg);
+
+	dbus_error_init(&error);
+
+	if (dbus_set_error_from_message(&error, message)) {
+		bt_shell_printf("Failed to config endpoint: %s\n",
+				error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Endpoint %s configured\n", ep->path);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void endpoint_set_config(struct endpoint_config *cfg)
+{
+	if (!g_dbus_proxy_method_call(cfg->proxy, "SetConfiguration",
+						config_endpoint_setup,
+						config_endpoint_reply,
+						cfg, NULL)) {
+		bt_shell_printf("Failed to config endpoint\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+}
+
+static void qos_delay(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+
+	cfg->qos.delay = strtol(input, NULL, 0);
+
+	endpoint_set_config(cfg);
+}
+
+static void qos_latency(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+
+	cfg->qos.latency = strtol(input, NULL, 0);
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter Delay:", qos_delay, cfg);
+}
+
+static void qos_rtn(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+
+	cfg->qos.rtn = strtol(input, NULL, 0);
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter Latency:",
+							qos_latency, cfg);
+}
+
+static void qos_sdu(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+
+	cfg->qos.sdu = strtol(input, NULL, 0);
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter Retransmissions:",
+							qos_rtn, cfg);
+}
+
+static void qos_phy(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+
+	cfg->qos.phy = strdup(input);
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter SDU:", qos_sdu, cfg);
+}
+
+static void qos_framing(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+
+	cfg->qos.framing = strtol(input, NULL, 0);
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter PHY:", qos_phy, cfg);
+}
+
+static void qos_interval(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+
+	cfg->qos.interval = strtol(input, NULL, 0);
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter Framing:", qos_framing,
+									cfg);
+}
+
+static void endpoint_config(const char *input, void *user_data)
+{
+	struct endpoint_config *cfg = user_data;
+	uint8_t *data;
+	size_t len;
+
+	data = str2bytearray((char *) input, &len);
+
+	iov_append(&cfg->caps, data, len);
+	free(data);
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter Interval:", qos_interval,
+									cfg);
+}
+
+static void cmd_config_endpoint(int argc, char *argv[])
+{
+	struct endpoint_config *cfg;
+	const struct codec_preset *preset;
+
+	cfg = new0(struct endpoint_config, 1);
+
+	cfg->proxy = g_dbus_proxy_lookup(endpoints, NULL, argv[1],
+						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+	if (!cfg->proxy) {
+		bt_shell_printf("Endpoint %s not found\n", argv[1]);
+		goto fail;
+	}
+
+	cfg->ep = endpoint_find(argv[2]);
+	if (!cfg->ep) {
+		bt_shell_printf("Local Endpoint %s not found\n", argv[2]);
+		goto fail;
+	}
+
+	if (argc > 3) {
+		preset = find_preset(cfg->ep->uuid, argv[3]);
+		if (!preset) {
+			bt_shell_printf("Preset %s not found\n", argv[3]);
+			goto fail;
+		}
+
+		/* Copy capabilities */
+		iov_append(&cfg->caps, preset->data.iov_base,
+						preset->data.iov_len);
+
+		endpoint_set_config(cfg);
+		return;
+	}
+
+	bt_shell_prompt_input(cfg->ep->path, "Enter configuration:",
+					endpoint_config, cfg);
+
+	return;
+
+fail:
+	g_free(cfg);
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_presets_endpoint(int argc, char *argv[])
+{
+	size_t i;
+	struct codec_preset *default_preset = NULL;
+
+	if (argc > 2) {
+		default_preset = find_preset(argv[1], argv[2]);
+		if (!default_preset) {
+			bt_shell_printf("Preset %s not found\n", argv[2]);
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+		}
+
+		default_preset->is_default = true;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(presets); i++) {
+		const struct preset *preset = &presets[i];
+
+		if (!strcasecmp(preset->uuid, argv[1])) {
+			size_t j;
+
+			for (j = 0; j < preset->num_presets; j++) {
+				struct codec_preset *p;
+
+				p = &preset->presets[j];
+
+				if (default_preset && p != default_preset)
+					p->is_default = false;
+
+				if (p->is_default)
+					bt_shell_printf("*%s\n", p->name);
+				else
+					bt_shell_printf("%s\n", p->name);
+			}
+		}
+	}
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static const struct bt_shell_menu endpoint_menu = {
+	.name = "endpoint",
+	.desc = "Media Endpoint Submenu",
+	.entries = {
+	{ "list",         "[local]",    cmd_list_endpoints,
+						"List available endpoints" },
+	{ "show",         "<endpoint>", cmd_show_endpoint,
+						"Endpoint information",
+						endpoint_generator },
+	{ "register",     "<UUID> <codec> [capabilities...]",
+						cmd_register_endpoint,
+						"Register Endpoint",
+						uuid_generator },
+	{ "unregister",   "<UUID/object>", cmd_unregister_endpoint,
+						"Register Endpoint",
+						local_endpoint_generator },
+	{ "config",       "<endpoint> <local endpoint> [preset]",
+						cmd_config_endpoint,
+						"Configure Endpoint",
+						endpoint_generator },
+	{ "presets",      "<UUID> [default]", cmd_presets_endpoint,
+						"List available presets",
+						uuid_generator },
+	{} },
+};
+
+static void media_added(GDBusProxy *proxy)
+{
+	medias = g_list_append(medias, proxy);
+
+	print_media(proxy, COLORED_NEW);
+}
+
 static void player_added(GDBusProxy *proxy)
 {
 	players = g_list_append(players, proxy);
@@ -1002,18 +2137,36 @@ static void item_added(GDBusProxy *proxy)
 	print_item(proxy, COLORED_NEW);
 }
 
+static void endpoint_added(GDBusProxy *proxy)
+{
+	endpoints = g_list_append(endpoints, proxy);
+
+	print_endpoint(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, BLUEZ_MEDIA_PLAYER_INTERFACE))
+	if (!strcmp(interface, BLUEZ_MEDIA_INTERFACE))
+		media_added(proxy);
+	else if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE))
 		player_added(proxy);
 	else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE))
 		folder_added(proxy);
 	else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE))
 		item_added(proxy);
+	else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+		endpoint_added(proxy);
+}
+
+static void media_removed(GDBusProxy *proxy)
+{
+	print_media(proxy, COLORED_DEL);
+
+	medias = g_list_remove(medias, proxy);
 }
 
 static void player_removed(GDBusProxy *proxy)
@@ -1040,18 +2193,29 @@ static void item_removed(GDBusProxy *proxy)
 	print_item(proxy, COLORED_DEL);
 }
 
+static void endpoint_removed(GDBusProxy *proxy)
+{
+	endpoints = g_list_remove(endpoints, proxy);
+
+	print_endpoint(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, BLUEZ_MEDIA_INTERFACE))
+		media_removed(proxy);
 	if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE))
 		player_removed(proxy);
 	if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE))
 		folder_removed(proxy);
 	if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE))
 		item_removed(proxy);
+	if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+		endpoint_removed(proxy);
 }
 
 static void player_property_changed(GDBusProxy *proxy, const char *name,
@@ -1084,6 +2248,16 @@ static void item_property_changed(GDBusProxy *proxy, const char *name,
 	g_free(str);
 }
 
+static void endpoint_property_changed(GDBusProxy *proxy, const char *name,
+						DBusMessageIter *iter)
+{
+	char *str;
+
+	str = proxy_description(proxy, "Endpoint", COLORED_CHG);
+	print_iter(str, name, iter);
+	g_free(str);
+}
+
 static void property_changed(GDBusProxy *proxy, const char *name,
 					DBusMessageIter *iter, void *user_data)
 {
@@ -1097,6 +2271,8 @@ static void property_changed(GDBusProxy *proxy, const char *name,
 		folder_property_changed(proxy, name, iter);
 	else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE))
 		item_property_changed(proxy, name, iter);
+	else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+		endpoint_property_changed(proxy, name, iter);
 }
 
 static GDBusClient *client;
@@ -1104,6 +2280,7 @@ static GDBusClient *client;
 void player_add_submenu(void)
 {
 	bt_shell_add_submenu(&player_menu);
+	bt_shell_add_submenu(&endpoint_menu);
 
 	dbus_conn = bt_shell_get_env("DBUS_CONNECTION");
 	if (!dbus_conn || client)
@@ -1113,6 +2290,7 @@ void player_add_submenu(void)
 
 	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 player_remove_submenu(void)
-- 
2.35.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v2 BlueZ 2/2] client/player: Add transport menu
  2022-04-13 21:16 [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu Luiz Augusto von Dentz
@ 2022-04-13 21:16 ` Luiz Augusto von Dentz
  2022-04-15  7:05 ` [v2,BlueZ,1/2] client/player: Add endpoint menu bluez.test.bot
  2022-04-19  0:00 ` [PATCH v2 BlueZ 1/2] " patchwork-bot+bluetooth
  2 siblings, 0 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2022-04-13 21:16 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds transport menu:

[bluetooth]# menu transport
Menu transport:
Available commands:
-------------------
list                                              List available transports
show <transport>                                  Transport information
acquire <transport>                               Acquire Transport
release <transport>                               Release Transport
send <filename>                                   Send contents of a file
---
 client/player.c | 385 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 384 insertions(+), 1 deletion(-)

diff --git a/client/player.c b/client/player.c
index 5db35cfeb..bcd12d594 100644
--- a/client/player.c
+++ b/client/player.c
@@ -50,6 +50,7 @@
 #define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
 #define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
 #define BLUEZ_MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
+#define BLUEZ_MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1"
 
 #define BLUEZ_MEDIA_ENDPOINT_PATH "/local/endpoint"
 
@@ -75,6 +76,16 @@ static GList *folders = NULL;
 static GList *items = NULL;
 static GList *endpoints = NULL;
 static GList *local_endpoints = NULL;
+static GList *transports = NULL;
+
+struct transport {
+	int sk;
+	int mtu[2];
+	struct io *io;
+	uint32_t seq;
+} transport = {
+	.sk = -1,
+};
 
 static void endpoint_unregister(void *data)
 {
@@ -1639,8 +1650,9 @@ static char *uuid_generator(const char *text, int state)
 	static int index = 0;
 	size_t i;
 
-	if (!state)
+	if (!state) {
 		index = 0;
+	}
 
 	for (i = index; i < ARRAY_SIZE(caps); i++) {
 		const struct capabilities *cap = &caps[i];
@@ -2144,6 +2156,26 @@ static void endpoint_added(GDBusProxy *proxy)
 	print_endpoint(proxy, COLORED_NEW);
 }
 
+static void print_transport(void *data, void *user_data)
+{
+	GDBusProxy *proxy = data;
+	const char *description = user_data;
+	char *str;
+
+	str = proxy_description(proxy, "Transport", description);
+
+	bt_shell_printf("%s\n", str);
+
+	g_free(str);
+}
+
+static void transport_added(GDBusProxy *proxy)
+{
+	transports = g_list_append(transports, proxy);
+
+	print_transport(proxy, COLORED_NEW);
+}
+
 static void proxy_added(GDBusProxy *proxy, void *user_data)
 {
 	const char *interface;
@@ -2160,6 +2192,8 @@ static void proxy_added(GDBusProxy *proxy, void *user_data)
 		item_added(proxy);
 	else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
 		endpoint_added(proxy);
+	else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
+		transport_added(proxy);
 }
 
 static void media_removed(GDBusProxy *proxy)
@@ -2200,6 +2234,13 @@ static void endpoint_removed(GDBusProxy *proxy)
 	print_endpoint(proxy, COLORED_DEL);
 }
 
+static void transport_removed(GDBusProxy *proxy)
+{
+	transports = g_list_remove(transports, proxy);
+
+	print_transport(proxy, COLORED_DEL);
+}
+
 static void proxy_removed(GDBusProxy *proxy, void *user_data)
 {
 	const char *interface;
@@ -2216,6 +2257,8 @@ static void proxy_removed(GDBusProxy *proxy, void *user_data)
 		item_removed(proxy);
 	if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
 		endpoint_removed(proxy);
+	if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
+		transport_removed(proxy);
 }
 
 static void player_property_changed(GDBusProxy *proxy, const char *name,
@@ -2258,6 +2301,134 @@ static void endpoint_property_changed(GDBusProxy *proxy, const char *name,
 	g_free(str);
 }
 
+static struct endpoint *find_ep_by_transport(const char *path)
+{
+	GList *l;
+
+	for (l = local_endpoints; l; l = g_list_next(l)) {
+		struct endpoint *ep = l->data;
+
+		if (ep->transport && !strcmp(ep->transport, path))
+			return ep;
+	}
+
+	return NULL;
+}
+
+static bool transport_disconnected(struct io *io, void *user_data)
+{
+	bt_shell_printf("Transport fd disconnected\n");
+
+	io_destroy(transport.io);
+	transport.io = NULL;
+	transport.sk = -1;
+
+	return false;
+}
+
+static bool transport_recv(struct io *io, void *user_data)
+{
+	uint8_t buf[1024];
+	int ret;
+
+	ret = read(io_get_fd(io), buf, sizeof(buf));
+	if (ret < 0) {
+		bt_shell_printf("Failed to read: %s (%d)\n", strerror(errno),
+								-errno);
+		return true;
+	}
+
+	bt_shell_printf("[seq %d] recv: %u bytes\n", transport.seq, ret);
+
+	transport.seq++;
+
+	return true;
+}
+
+static void acquire_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 acquire: %s\n", error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	if (!dbus_message_get_args(message, &error,
+				   DBUS_TYPE_UNIX_FD, &transport.sk,
+				   DBUS_TYPE_UINT16, &transport.mtu[0],
+				   DBUS_TYPE_UINT16, &transport.mtu[1],
+				   DBUS_TYPE_INVALID)) {
+		bt_shell_printf("Failed to parse Acquire() reply: %s",
+							error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Acquire successful: fd %d MTU %d:%d\n", transport.sk,
+					transport.mtu[0], transport.mtu[1]);
+
+	io_destroy(transport.io);
+	transport.io = io_new(transport.sk);
+
+	io_set_disconnect_handler(transport.io, transport_disconnected, NULL,
+								NULL);
+	io_set_read_handler(transport.io, transport_recv, NULL, NULL);
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void transport_acquire(const char *input, void *user_data)
+{
+	GDBusProxy *proxy = user_data;
+
+	if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) {
+		if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
+						acquire_reply, NULL, NULL))
+			bt_shell_printf("Failed acquire transport\n");
+	}
+}
+
+static void transport_property_changed(GDBusProxy *proxy, const char *name,
+						DBusMessageIter *iter)
+{
+	char *str;
+	struct endpoint *ep;
+
+	str = proxy_description(proxy, "Transport", COLORED_CHG);
+	print_iter(str, name, iter);
+	g_free(str);
+
+	if (strcmp(name, "State"))
+		return;
+
+	dbus_message_iter_get_basic(iter, &str);
+
+	if (strcmp(str, "pending"))
+		return;
+
+	/* Only attempt to acquire if transport is configured with a local
+	 * endpoint.
+	 */
+	ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy));
+	if (!ep)
+		return;
+
+	if (ep->auto_accept) {
+		bt_shell_printf("Auto Accepting...\n");
+		if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
+						acquire_reply, NULL, NULL))
+			bt_shell_printf("Failed acquire transport\n");
+		return;
+	}
+
+	bt_shell_prompt_input(g_dbus_proxy_get_path(proxy), "Acquire (yes/no):",
+					transport_acquire, proxy);
+}
+
 static void property_changed(GDBusProxy *proxy, const char *name,
 					DBusMessageIter *iter, void *user_data)
 {
@@ -2273,14 +2444,226 @@ static void property_changed(GDBusProxy *proxy, const char *name,
 		item_property_changed(proxy, name, iter);
 	else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
 		endpoint_property_changed(proxy, name, iter);
+	else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
+		transport_property_changed(proxy, name, iter);
 }
 
+static char *transport_generator(const char *text, int state)
+{
+	return generic_generator(text, state, transports);
+}
+
+static void cmd_list_transport(int argc, char *argv[])
+{
+	GList *l;
+
+	for (l = transports; l; l = g_list_next(l)) {
+		GDBusProxy *proxy = l->data;
+		print_transport(proxy, NULL);
+	}
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_show_transport(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_lookup(transports, NULL, argv[1],
+					BLUEZ_MEDIA_TRANSPORT_INTERFACE);
+	if (!proxy) {
+		bt_shell_printf("Transport %s not found\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_printf("Transport %s\n", g_dbus_proxy_get_path(proxy));
+
+	print_property(proxy, "UUID");
+	print_property(proxy, "Codec");
+	print_property(proxy, "Configuration");
+	print_property(proxy, "Device");
+	print_property(proxy, "State");
+	print_property(proxy, "Delay");
+	print_property(proxy, "Volume");
+	print_property(proxy, "Endpoint");
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_acquire_transport(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (transport.sk >= 0) {
+		bt_shell_printf("Transport socked %d already acquired\n",
+							transport.sk);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	proxy = g_dbus_proxy_lookup(transports, NULL, argv[1],
+					BLUEZ_MEDIA_TRANSPORT_INTERFACE);
+	if (!proxy) {
+		bt_shell_printf("Transport %s not found\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
+					acquire_reply, NULL, NULL)) {
+		bt_shell_printf("Failed acquire transport\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void release_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 release: %s\n", error.name);
+		dbus_error_free(&error);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	close(transport.sk);
+	transport.sk = -1;
+
+	bt_shell_printf("Release successful\n");
+
+	return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_release_transport(int argc, char *argv[])
+{
+	GDBusProxy *proxy;
+
+	if (transport.sk < 0) {
+		bt_shell_printf("No Transport Socked found\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	proxy = g_dbus_proxy_lookup(transports, NULL, argv[1],
+					BLUEZ_MEDIA_TRANSPORT_INTERFACE);
+	if (!proxy) {
+		bt_shell_printf("Transport %s not found\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	if (!g_dbus_proxy_method_call(proxy, "Release", NULL,
+					release_reply, NULL, NULL)) {
+		bt_shell_printf("Failed release transport\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static int open_file(const char *filename)
+{
+	int fd = -1;
+
+	bt_shell_printf("Opening %s ...\n", filename);
+
+	fd = open(filename, O_RDONLY);
+	if (fd <= 0)
+		bt_shell_printf("Can't open file %s: %s\n", filename,
+						strerror(errno));
+
+	return fd;
+}
+
+static int transport_send(int fd)
+{
+	uint8_t *buf;
+
+	buf = malloc(transport.mtu[1]);
+	if (!buf) {
+		bt_shell_printf("malloc: %s (%d)", strerror(errno), errno);
+		return -ENOMEM;
+	}
+
+	for (transport.seq = 0; ; transport.seq++) {
+		ssize_t ret;
+		int queued;
+
+		ret = read(fd, buf, transport.mtu[1]);
+		if (ret <= 0) {
+			if (ret < 0)
+				bt_shell_printf("read failed: %s (%d)",
+						strerror(errno), errno);
+			close(fd);
+			return ret;
+		}
+
+		ret = send(transport.sk, buf, ret, 0);
+		if (ret <= 0) {
+			bt_shell_printf("Send failed: %s (%d)",
+							strerror(errno), errno);
+			return -errno;
+		}
+
+		ioctl(transport.sk, TIOCOUTQ, &queued);
+
+		bt_shell_printf("[seq %d] send: %zd bytes "
+				"(TIOCOUTQ %d bytes)\n",
+				transport.seq, ret, queued);
+	}
+
+	free(buf);
+}
+
+static void cmd_send_transport(int argc, char *argv[])
+{
+	int fd, err;
+
+	if (transport.sk < 0) {
+		bt_shell_printf("No Transport Socked found\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	fd = open_file(argv[1]);
+
+	bt_shell_printf("Sending ...\n");
+	err = transport_send(fd);
+
+	close(fd);
+
+	if (err < 0)
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static const struct bt_shell_menu transport_menu = {
+	.name = "transport",
+	.desc = "Media Transport Submenu",
+	.entries = {
+	{ "list",         NULL,    cmd_list_transport,
+						"List available transports" },
+	{ "show",        "<transport>", cmd_show_transport,
+						"Transport information",
+						transport_generator },
+	{ "acquire",     "<transport>",	cmd_acquire_transport,
+						"Acquire Transport",
+						transport_generator },
+	{ "release",     "<transport>",	cmd_release_transport,
+						"Release Transport",
+						transport_generator },
+	{ "send",        "<filename>",	cmd_send_transport,
+						"Send contents of a file" },
+	{} },
+};
+
 static GDBusClient *client;
 
 void player_add_submenu(void)
 {
 	bt_shell_add_submenu(&player_menu);
 	bt_shell_add_submenu(&endpoint_menu);
+	bt_shell_add_submenu(&transport_menu);
 
 	dbus_conn = bt_shell_get_env("DBUS_CONNECTION");
 	if (!dbus_conn || client)
-- 
2.35.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* RE: [v2,BlueZ,1/2] client/player: Add endpoint menu
  2022-04-13 21:16 [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu Luiz Augusto von Dentz
  2022-04-13 21:16 ` [PATCH v2 BlueZ 2/2] client/player: Add transport menu Luiz Augusto von Dentz
@ 2022-04-15  7:05 ` bluez.test.bot
  2022-04-19  0:00 ` [PATCH v2 BlueZ 1/2] " patchwork-bot+bluetooth
  2 siblings, 0 replies; 4+ messages in thread
From: bluez.test.bot @ 2022-04-15  7:05 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz

[-- Attachment #1: Type: text/plain, Size: 3899 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=632031

---Test result---

Test Summary:
CheckPatch                    FAIL      2.97 seconds
GitLint                       PASS      0.91 seconds
Prep - Setup ELL              PASS      41.43 seconds
Build - Prep                  PASS      0.45 seconds
Build - Configure             PASS      8.12 seconds
Build - Make                  PASS      1247.20 seconds
Make Check                    PASS      11.23 seconds
Make Check w/Valgrind         PASS      435.67 seconds
Make Distcheck                PASS      223.18 seconds
Build w/ext ELL - Configure   PASS      8.22 seconds
Build w/ext ELL - Make        PASS      1229.19 seconds
Incremental Build with patchesPASS      2507.39 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script with rule in .checkpatch.conf
Output:
[v2,BlueZ,1/2] client/player: Add endpoint menu
ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#152: FILE: client/player.c:72:
+static GList *medias = NULL;

ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#156: FILE: client/player.c:76:
+static GList *endpoints = NULL;

ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#157: FILE: client/player.c:77:
+static GList *local_endpoints = NULL;

ERROR:INITIALISED_STATIC: do not initialise statics to 0
#251: FILE: client/player.c:1020:
+	static int index = 0;

WARNING:LINE_SPACING: Missing a blank line after declarations
#303: FILE: client/player.c:1072:
+		GDBusProxy *proxy = l->data;
+		print_endpoint(proxy, NULL);

ERROR:SPACING: space prohibited before that close parenthesis ')'
#687: FILE: client/player.c:1456:
+						{ "properties", "a{sv}" } ),

ERROR:SPACING: space prohibited before that close parenthesis ')'
#690: FILE: client/player.c:1459:
+					GDBUS_ARGS({ "caps", "ay" } ),

ERROR:SPACING: space prohibited before that close parenthesis ')'
#693: FILE: client/player.c:1462:
+					GDBUS_ARGS({ "transport", "o" } ),

ERROR:INITIALISED_STATIC: do not initialise statics to 0
#870: FILE: client/player.c:1639:
+	static int index = 0;

/github/workspace/src/12812703.patch total: 8 errors, 1 warnings, 1314 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/12812703.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.

[v2,BlueZ,2/2] client/player: Add transport menu
ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#113: FILE: client/player.c:79:
+static GList *transports = NULL;

WARNING:LINE_SPACING: Missing a blank line after declarations
#350: FILE: client/player.c:2462:
+		GDBusProxy *proxy = l->data;
+		print_transport(proxy, NULL);

/github/workspace/src/12812704.patch total: 1 errors, 1 warnings, 448 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/12812704.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.




---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu
  2022-04-13 21:16 [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu Luiz Augusto von Dentz
  2022-04-13 21:16 ` [PATCH v2 BlueZ 2/2] client/player: Add transport menu Luiz Augusto von Dentz
  2022-04-15  7:05 ` [v2,BlueZ,1/2] client/player: Add endpoint menu bluez.test.bot
@ 2022-04-19  0:00 ` patchwork-bot+bluetooth
  2 siblings, 0 replies; 4+ messages in thread
From: patchwork-bot+bluetooth @ 2022-04-19  0:00 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hello:

This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Wed, 13 Apr 2022 14:16:29 -0700 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> 
> This adds endpoint menu:
> 
> [bluetooth]# menu endpoint
> Menu endpoint:
> Available commands:
> 
> [...]

Here is the summary with links:
  - [v2,BlueZ,1/2] client/player: Add endpoint menu
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=46f171a86c17
  - [v2,BlueZ,2/2] client/player: Add transport menu
    (no matching commit)

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2022-04-19  0:00 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-13 21:16 [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu Luiz Augusto von Dentz
2022-04-13 21:16 ` [PATCH v2 BlueZ 2/2] client/player: Add transport menu Luiz Augusto von Dentz
2022-04-15  7:05 ` [v2,BlueZ,1/2] client/player: Add endpoint menu bluez.test.bot
2022-04-19  0:00 ` [PATCH v2 BlueZ 1/2] " patchwork-bot+bluetooth

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.