// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2020  Intel Corporation. All rights reserved.
 *  Copyright 2023-2025 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 <sys/ioctl.h>
#include <sys/uio.h>
#include <wordexp.h>
#include <sys/timerfd.h>
#include <sys/stat.h>

#include <glib.h>

#include "gdbus/gdbus.h"

#include "bluetooth/bluetooth.h"
#include "bluetooth/uuid.h"
#include "bluetooth/iso.h"

#include "profiles/audio/a2dp-codecs.h"
#include "src/shared/lc3.h"

#include "src/shared/util.h"
#include "src/shared/shell.h"
#include "src/shared/io.h"
#include "src/shared/queue.h"
#include "src/shared/bap-debug.h"
#include "print.h"
#include "player.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 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_TRANSPORT_INTERFACE "org.bluez.MediaTransport1"

#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))
#define ROUND_CLOSEST(_x, _y) (((_x) + (_y / 2)) / (_y))

#define EP_SRC_LOCATIONS 0x00000003
#define EP_SNK_LOCATIONS 0x00000003

#define EP_SRC_CTXT 0x000f
#define EP_SUPPORTED_SRC_CTXT EP_SRC_CTXT
#define EP_SNK_CTXT 0x0fff
#define EP_SUPPORTED_SNK_CTXT EP_SNK_CTXT

#if __BYTE_ORDER == __LITTLE_ENDIAN

struct avdtp_media_codec_capability {
	uint8_t rfa0:4;
	uint8_t media_type:4;
	uint8_t media_codec_type;
	uint8_t data[0];
} __attribute__ ((packed));

#elif __BYTE_ORDER == __BIG_ENDIAN

struct avdtp_media_codec_capability {
	uint8_t media_type:4;
	uint8_t rfa0:4;
	uint8_t media_codec_type;
	uint8_t data[0];
} __attribute__ ((packed));

#else
#error "Unknown byte order"
#endif

#define BCAST_CODE {0x01, 0x02, 0x68, 0x05, 0x53, 0xf1, 0x41, 0x5a, \
				0xa2, 0x65, 0xbb, 0xaf, 0xc6, 0xea, 0x03, 0xb8}

struct endpoint {
	char *path;
	char *uuid;
	uint8_t codec;
	uint16_t cid;
	uint16_t vid;
	struct iovec *caps;
	struct iovec *meta;
	uint32_t locations;
	uint16_t supported_context;
	uint16_t context;
	bool auto_accept;
	uint8_t max_transports;
	uint8_t iso_group;
	uint8_t iso_stream;
	struct queue *acquiring;
	struct queue *transports;
	DBusMessage *msg;
	struct preset *preset;
	struct codec_preset *codec_preset;
	bool broadcast;
	struct iovec *bcode;
};

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 GList *transports = NULL;
static struct queue *ios = NULL;
static uint8_t bcast_code[] = BCAST_CODE;

struct transport {
	GDBusProxy *proxy;
	int sk;
	uint16_t mtu[2];
	char *filename;
	int fd;
	struct stat stat;
	struct io *io;
	uint32_t seq;
	struct io *timer_io;
	int num;
};

struct transport_select_args {
	GDBusProxy *proxy;
	struct queue *links;
	struct queue *selecting;
};

static void player_menu_pre_run(const struct bt_shell_menu *menu);
static void transport_set_links(struct transport_select_args *args);
static void transport_select(struct transport_select_args *args);

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)
{
	if (!default_player) {
		bt_shell_printf("No default player available\n");
		return FALSE;
	}

	return TRUE;
}

static char *generic_generator(const char *text, int state, GList *source)
{
	static int index = 0;

	if (!source)
		return NULL;

	if (!state)
		index = 0;

	return g_dbus_proxy_path_lookup(source, &index, text);
}

static char *player_generator(const char *text, int state)
{
	return generic_generator(text, state, players);
}

static char *item_generator(const char *text, int state)
{
	return generic_generator(text, state, items);
}

static void play_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 play: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Play successful\n");

	return bt_shell_noninteractive_quit(EXIT_FAILURE);
}

static void cmd_play(int argc, char *argv[])
{
	GDBusProxy *proxy;

	if (argc > 1) {
		proxy = g_dbus_proxy_lookup(items, NULL, argv[1],
						BLUEZ_MEDIA_ITEM_INTERFACE);
		if (proxy == NULL) {
			bt_shell_printf("Item %s not available\n", argv[1]);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}
	} else {
		if (!check_default_player())
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		proxy = default_player;
	}

	if (g_dbus_proxy_method_call(proxy, "Play", NULL, play_reply,
							NULL, NULL) == FALSE) {
		bt_shell_printf("Failed to play\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to play %s\n", argv[1] ? : "");
}

static void pause_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 pause: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Pause successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_pause(int argc, char *argv[])
{
	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (g_dbus_proxy_method_call(default_player, "Pause", NULL,
					pause_reply, NULL, NULL) == FALSE) {
		bt_shell_printf("Failed to play\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to pause\n");
}

static void stop_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 stop: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Stop successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_stop(int argc, char *argv[])
{
	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (g_dbus_proxy_method_call(default_player, "Stop", NULL, stop_reply,
							NULL, NULL) == FALSE) {
		bt_shell_printf("Failed to stop\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to stop\n");
}

static void next_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 jump to next: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Next successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_next(int argc, char *argv[])
{
	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (g_dbus_proxy_method_call(default_player, "Next", NULL, next_reply,
							NULL, NULL) == FALSE) {
		bt_shell_printf("Failed to jump to next\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to jump to next\n");
}

static void previous_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 jump to previous: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Previous successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_previous(int argc, char *argv[])
{
	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (g_dbus_proxy_method_call(default_player, "Previous", NULL,
					previous_reply, NULL, NULL) == FALSE) {
		bt_shell_printf("Failed to jump to previous\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to jump to previous\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void fast_forward_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 fast forward: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("FastForward successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_fast_forward(int argc, char *argv[])
{
	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (g_dbus_proxy_method_call(default_player, "FastForward", NULL,
				fast_forward_reply, NULL, NULL) == FALSE) {
		bt_shell_printf("Failed to jump to previous\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Fast forward playback\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void rewind_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 rewind: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Rewind successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_rewind(int argc, char *argv[])
{
	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (g_dbus_proxy_method_call(default_player, "Rewind", NULL,
					rewind_reply, NULL, NULL) == FALSE) {
		bt_shell_printf("Failed to rewind\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Rewind playback\n");
}

static void generic_callback(const DBusError *error, void *user_data)
{
	char *str = 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);
	}
}

static void cmd_equalizer(int argc, char *argv[])
{
	char *value;
	DBusMessageIter iter;

	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (!g_dbus_proxy_get_property(default_player, "Equalizer", &iter)) {
		bt_shell_printf("Operation not supported\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	value = g_strdup(argv[1]);

	if (g_dbus_proxy_set_property_basic(default_player, "Equalizer",
						DBUS_TYPE_STRING, &value,
						generic_callback, value,
						g_free) == FALSE) {
		bt_shell_printf("Failed to setting equalizer\n");
		g_free(value);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to set equalizer\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_repeat(int argc, char *argv[])
{
	char *value;
	DBusMessageIter iter;

	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (!g_dbus_proxy_get_property(default_player, "Repeat", &iter)) {
		bt_shell_printf("Operation not supported\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	value = g_strdup(argv[1]);

	if (g_dbus_proxy_set_property_basic(default_player, "Repeat",
						DBUS_TYPE_STRING, &value,
						generic_callback, value,
						g_free) == FALSE) {
		bt_shell_printf("Failed to set repeat\n");
		g_free(value);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to set repeat\n");
}

static void cmd_shuffle(int argc, char *argv[])
{
	char *value;
	DBusMessageIter iter;

	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (!g_dbus_proxy_get_property(default_player, "Shuffle", &iter)) {
		bt_shell_printf("Operation not supported\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	value = g_strdup(argv[1]);

	if (g_dbus_proxy_set_property_basic(default_player, "Shuffle",
						DBUS_TYPE_STRING, &value,
						generic_callback, value,
						g_free) == FALSE) {
		bt_shell_printf("Failed to set shuffle\n");
		g_free(value);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to set shuffle\n");
}

static void cmd_scan(int argc, char *argv[])
{
	char *value;
	DBusMessageIter iter;

	if (!check_default_player())
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	if (!g_dbus_proxy_get_property(default_player, "Shuffle", &iter)) {
		bt_shell_printf("Operation not supported\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	value = g_strdup(argv[1]);

	if (g_dbus_proxy_set_property_basic(default_player, "Shuffle",
						DBUS_TYPE_STRING, &value,
						generic_callback, value,
						g_free) == FALSE) {
		bt_shell_printf("Failed to set scan\n");
		g_free(value);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to set scan\n");
}

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_media(GDBusProxy *proxy, const char *description)
{
	char *str;

	str = proxy_description(proxy, "Media", description);

	bt_shell_printf("%s\n", str);
	print_property(proxy, "SupportedUUIDs");

	g_free(str);
}

static void print_player(void *data, void *user_data)
{
	GDBusProxy *proxy = data;
	const char *description = user_data;
	char *str;

	str = proxy_description(proxy, "Player", description);

	bt_shell_printf("%s%s\n", str,
			default_player == proxy ? "[default]" : "");

	g_free(str);
}

static void cmd_list(int argc, char *arg[])
{
	g_list_foreach(players, print_player, NULL);

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_show_item(int argc, char *argv[])
{
	GDBusProxy *proxy;

	proxy = g_dbus_proxy_lookup(items, NULL, argv[1],
						BLUEZ_MEDIA_ITEM_INTERFACE);
	if (!proxy) {
		bt_shell_printf("Item %s not available\n", argv[1]);
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
	}

	bt_shell_printf("Item %s\n", g_dbus_proxy_get_path(proxy));

	print_property(proxy, "Player");
	print_property(proxy, "Name");
	print_property(proxy, "Type");
	print_property(proxy, "FolderType");
	print_property(proxy, "Playable");
	print_property(proxy, "Metadata");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_show(int argc, char *argv[])
{
	GDBusProxy *proxy;
	GDBusProxy *folder;
	GDBusProxy *item;
	DBusMessageIter iter;
	const char *path;

	if (argc < 2) {
		if (check_default_player() == FALSE)
			return bt_shell_noninteractive_quit(EXIT_FAILURE);

		proxy = default_player;
	} else {
		proxy = g_dbus_proxy_lookup(players, NULL, argv[1],
						BLUEZ_MEDIA_PLAYER_INTERFACE);
		if (!proxy) {
			bt_shell_printf("Player %s not available\n", argv[1]);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}
	}

	bt_shell_printf("Player %s\n", g_dbus_proxy_get_path(proxy));

	print_property(proxy, "Name");
	print_property(proxy, "Repeat");
	print_property(proxy, "Equalizer");
	print_property(proxy, "Shuffle");
	print_property(proxy, "Scan");
	print_property(proxy, "Status");
	print_property(proxy, "Position");
	print_property(proxy, "Track");

	folder = g_dbus_proxy_lookup(folders, NULL,
					g_dbus_proxy_get_path(proxy),
					BLUEZ_MEDIA_FOLDER_INTERFACE);
	if (folder == NULL)
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);

	bt_shell_printf("Folder %s\n", g_dbus_proxy_get_path(proxy));

	print_property(folder, "Name");
	print_property(folder, "NumberOfItems");

	if (!g_dbus_proxy_get_property(proxy, "Playlist", &iter))
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);

	dbus_message_iter_get_basic(&iter, &path);

	item = g_dbus_proxy_lookup(items, NULL, path,
					BLUEZ_MEDIA_ITEM_INTERFACE);
	if (item == NULL)
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);

	bt_shell_printf("Playlist %s\n", path);

	print_property(item, "Name");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_select(int argc, char *argv[])
{
	GDBusProxy *proxy;

	proxy = g_dbus_proxy_lookup(players, NULL, argv[1],
						BLUEZ_MEDIA_PLAYER_INTERFACE);
	if (proxy == NULL) {
		bt_shell_printf("Player %s not available\n", argv[1]);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (default_player == proxy)
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);

	default_player = proxy;
	print_player(proxy, NULL);

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void change_folder_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 change folder: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("ChangeFolder successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void change_folder_setup(DBusMessageIter *iter, void *user_data)
{
	const char *path = user_data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
}

static void cmd_change_folder(int argc, char *argv[])
{
	GDBusProxy *proxy;

	if (dbus_validate_path(argv[1], NULL) == FALSE) {
		bt_shell_printf("Not a valid path\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (check_default_player() == FALSE)
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	proxy = g_dbus_proxy_lookup(folders, NULL,
					g_dbus_proxy_get_path(default_player),
					BLUEZ_MEDIA_FOLDER_INTERFACE);
	if (proxy == NULL) {
		bt_shell_printf("Operation not supported\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (g_dbus_proxy_method_call(proxy, "ChangeFolder", change_folder_setup,
				change_folder_reply, argv[1], NULL) == FALSE) {
		bt_shell_printf("Failed to change current folder\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to change folder\n");
}

struct list_items_args {
	int start;
	int end;
};

static void list_items_setup(DBusMessageIter *iter, void *user_data)
{
	struct list_items_args *args = user_data;
	DBusMessageIter dict;

	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);

	if (args->start < 0)
		goto done;

	g_dbus_dict_append_entry(&dict, "Start",
					DBUS_TYPE_UINT32, &args->start);

	if (args->end < 0)
		goto done;

	g_dbus_dict_append_entry(&dict, "End", DBUS_TYPE_UINT32, &args->end);

done:
	dbus_message_iter_close_container(iter, &dict);
}

static void list_items_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 list items: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("ListItems successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_list_items(int argc, char *argv[])
{
	GDBusProxy *proxy;
	struct list_items_args *args;

	if (check_default_player() == FALSE)
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	proxy = g_dbus_proxy_lookup(folders, NULL,
					g_dbus_proxy_get_path(default_player),
					BLUEZ_MEDIA_FOLDER_INTERFACE);
	if (proxy == NULL) {
		bt_shell_printf("Operation not supported\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	args = g_new0(struct list_items_args, 1);
	args->start = -1;
	args->end = -1;

	if (argc < 2)
		goto done;

	errno = 0;
	args->start = strtol(argv[1], NULL, 10);
	if (errno != 0) {
		bt_shell_printf("%s(%d)\n", strerror(errno), errno);
		g_free(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (argc < 3)
		goto done;

	errno = 0;
	args->end = strtol(argv[2], NULL, 10);
	if (errno != 0) {
		bt_shell_printf("%s(%d)\n", strerror(errno), errno);
		g_free(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

done:
	if (g_dbus_proxy_method_call(proxy, "ListItems", list_items_setup,
				list_items_reply, args, g_free) == FALSE) {
		bt_shell_printf("Failed to change current folder\n");
		g_free(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to list items\n");
}

static void search_setup(DBusMessageIter *iter, void *user_data)
{
	char *string = user_data;
	DBusMessageIter dict;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);

	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);

	dbus_message_iter_close_container(iter, &dict);
}

static void search_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 search: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Search successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_search(int argc, char *argv[])
{
	GDBusProxy *proxy;
	char *string;

	if (check_default_player() == FALSE)
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	proxy = g_dbus_proxy_lookup(folders, NULL,
					g_dbus_proxy_get_path(default_player),
					BLUEZ_MEDIA_FOLDER_INTERFACE);
	if (proxy == NULL) {
		bt_shell_printf("Operation not supported\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	string = g_strdup(argv[1]);

	if (g_dbus_proxy_method_call(proxy, "Search", search_setup,
				search_reply, string, g_free) == FALSE) {
		bt_shell_printf("Failed to search\n");
		g_free(string);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to search\n");
}

static void add_to_nowplaying_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 queue: %s\n", error.name);
		dbus_error_free(&error);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("AddToNowPlaying successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_queue(int argc, char *argv[])
{
	GDBusProxy *proxy;

	proxy = g_dbus_proxy_lookup(items, NULL, argv[1],
						BLUEZ_MEDIA_ITEM_INTERFACE);
	if (proxy == NULL) {
		bt_shell_printf("Item %s not available\n", argv[1]);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (g_dbus_proxy_method_call(proxy, "AddtoNowPlaying", NULL,
					add_to_nowplaying_reply, NULL,
					NULL) == FALSE) {
		bt_shell_printf("Failed to play\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Attempting to queue %s\n", argv[1]);
}

static const struct bt_shell_menu player_menu = {
	.name = "player",
	.desc = "Media Player Submenu",
	.entries = {
	{ "list",         NULL,       cmd_list, "List available players" },
	{ "show",         "[player]", cmd_show, "Player information",
							player_generator},
	{ "select",       "<player>", cmd_select, "Select default player",
							player_generator},
	{ "play",         "[item]",   cmd_play, "Start playback",
							item_generator},
	{ "pause",        NULL,       cmd_pause, "Pause playback" },
	{ "stop",         NULL,       cmd_stop, "Stop playback" },
	{ "next",         NULL,       cmd_next, "Jump to next item" },
	{ "previous",     NULL,       cmd_previous, "Jump to previous item" },
	{ "fast-forward", NULL,       cmd_fast_forward,
						"Fast forward playback" },
	{ "rewind",       NULL,       cmd_rewind, "Rewind playback" },
	{ "equalizer",    "<on/off>", cmd_equalizer,
						"Enable/Disable equalizer"},
	{ "repeat",       "<singletrack/alltrack/group/off>", cmd_repeat,
						"Set repeat mode"},
	{ "shuffle",      "<alltracks/group/off>", cmd_shuffle,
						"Set shuffle mode"},
	{ "scan",         "<alltracks/group/off>", cmd_scan,
						"Set scan mode"},
	{ "change-folder", "<item>",  cmd_change_folder,
						"Change current folder",
							item_generator},
	{ "list-items", "[start] [end]",  cmd_list_items,
					"List items of current folder" },
	{ "search",     "<string>",   cmd_search,
					"Search items containing string" },
	{ "queue",       "<item>",    cmd_queue, "Add item to playlist queue",
							item_generator},
	{ "show-item",   "<item>",    cmd_show_item, "Show item information",
							item_generator},
	{} },
};

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 char *endpoint_generator(const char *text, int state)
{
	char *ret;

	ret = generic_generator(text, state, endpoints);
	if (ret)
		return ret;

	return local_endpoint_generator(text, state);
}

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);

	if (!ep->max_transports) {
		bt_shell_printf("Maximum transports reached: rejecting\n");
		return g_dbus_create_error(msg,
					 "org.bluez.Error.Rejected",
					 "Maximum transports reached");
	}

	ep->max_transports--;

	if (!ep->transports)
		ep->transports = queue_new();

	queue_push_tail(ep->transports, strdup(path));

	if (ep->auto_accept) {
		bt_shell_printf("Auto Accepting...\n");
		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;
}

#define CODEC_CAPABILITIES(_name, _uuid, _codec_id, _data, _meta) \
	{ \
		.name = _name, \
		.uuid = _uuid, \
		.codec_id = _codec_id, \
		.data = _data, \
		.meta = _meta, \
	}

#define LC3_DATA(_freq, _duration, _len_min, _len_max) \
	UTIL_IOV_INIT(0x03, LC3_FREQ, _freq, _freq >> 8, \
			0x02, LC3_DURATION, _duration, \
			0x05, LC3_FRAME_LEN, _len_min, _len_min >> 8, \
			_len_max, _len_max >> 8)

static const struct capabilities {
	const char *name;
	const char *uuid;
	uint8_t codec_id;
	struct iovec data;
	struct iovec meta;
} 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_src/sbc", A2DP_SOURCE_UUID, A2DP_CODEC_SBC,
				UTIL_IOV_INIT(0xff, 0xff, 2, 64),
				UTIL_IOV_INIT()),

	/* 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_snk/sbc", A2DP_SINK_UUID, A2DP_CODEC_SBC,
				UTIL_IOV_INIT(0xff, 0xff, 2, 64),
				UTIL_IOV_INIT()),

	/* PAC LC3 Sink:
	 *
	 * Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
	 * Duration: 7.5 ms 10 ms
	 * Frame length: 26-240
	 */
	CODEC_CAPABILITIES("pac_snk/lc3", PAC_SINK_UUID, LC3_ID,
				LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26,
					240),
				UTIL_IOV_INIT()),

	/* PAC LC3 Source:
	 *
	 * 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_src/lc3", PAC_SOURCE_UUID, LC3_ID,
				LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26,
					240),
				UTIL_IOV_INIT()),

	/* Broadcast LC3 Source:
	 *
	 * 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("bcaa/lc3", BCAA_SERVICE_UUID, LC3_ID,
				LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26,
					240),
				UTIL_IOV_INIT()),

	/* Broadcast LC3 Sink:
	 *
	 * 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("baa/lc3", BAA_SERVICE_UUID, LC3_ID,
				LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 26,
					240),
				UTIL_IOV_INIT()),
};

struct codec_preset {
	char *name;
	const struct iovec data;
	const struct iovec meta;
	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) \
	{ \
		.name = _name, \
		.data = _data, \
	}

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",
		UTIL_IOV_INIT(0x28, 0x15, 2, SBC_BITPOOL_MQ_MONO_44100)),
	SBC_PRESET("MQ_MONO_48",
		UTIL_IOV_INIT(0x18, 0x15, 2, SBC_BITPOOL_MQ_MONO_48000)),
	SBC_PRESET("MQ_STEREO_44_1",
		UTIL_IOV_INIT(0x21, 0x15, 2,
				SBC_BITPOOL_MQ_JOINT_STEREO_44100)),
	SBC_PRESET("MQ_STEREO_48",
		UTIL_IOV_INIT(0x11, 0x15, 2,
				SBC_BITPOOL_MQ_JOINT_STEREO_48000)),
	SBC_PRESET("HQ_MONO_44_1",
		UTIL_IOV_INIT(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)),
	SBC_PRESET("HQ_MONO_48",
		UTIL_IOV_INIT(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)),
	SBC_PRESET("HQ_STEREO_44_1",
		UTIL_IOV_INIT(0x21, 0x15, 2,
				SBC_BITPOOL_HQ_JOINT_STEREO_44100)),
	SBC_PRESET("HQ_STEREO_48",
		UTIL_IOV_INIT(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", UTIL_IOV_INIT(0x24, 0x15, 2, 43)),
	SBC_PRESET("XQ_DUAL_48", UTIL_IOV_INIT(0x14, 0x15, 2, 39)),
	/* Ultra high bitpool that fits in 512 kbps mandatory bitrate */
	SBC_PRESET("UQ_STEREO_44_1", UTIL_IOV_INIT(0x21, 0x15, 2, 64)),
	SBC_PRESET("UQ_STEREO_48", UTIL_IOV_INIT(0x11, 0x15, 2, 58)),
};

#define LC3_PRESET_LL(_name, _data, _qos) \
	{ \
		.name = _name, \
		.data = _data, \
		.qos = _qos, \
		.target_latency = 0x01, \
	}

#define LC3_PRESET(_name, _data, _qos) \
	{ \
		.name = _name, \
		.data = _data, \
		.qos = _qos, \
		.target_latency = 0x02, \
	}

#define LC3_PRESET_HR(_name, _data, _qos) \
	{ \
		.name = _name, \
		.data = _data, \
		.qos = _qos, \
		.target_latency = 0x03, \
	}

#define LC3_PRESET_B(_name, _data, _qos) \
	{ \
		.name = _name, \
		.data = _data, \
		.qos = _qos, \
		.target_latency = 0x00, \
	}

static struct codec_preset lc3_ucast_presets[] = {
	/* Table 4.43: QoS configuration support setting requirements */
	LC3_PRESET("8_1_1", LC3_CONFIG_8_1, LC3_QOS_8_1_1),
	LC3_PRESET("8_2_1", LC3_CONFIG_8_2, LC3_QOS_8_2_1),
	LC3_PRESET("16_1_1", LC3_CONFIG_16_1, LC3_QOS_16_1_1),
	LC3_PRESET("16_2_1", LC3_CONFIG_16_2, LC3_QOS_16_2_1),
	LC3_PRESET("24_1_1", LC3_CONFIG_24_1, LC3_QOS_24_1_1),
	LC3_PRESET("24_2_1", LC3_CONFIG_24_2, LC3_QOS_24_2_1),
	LC3_PRESET("32_1_1", LC3_CONFIG_32_1, LC3_QOS_32_1_1),
	LC3_PRESET("32_2_1", LC3_CONFIG_32_2, LC3_QOS_32_1_1),
	LC3_PRESET("44_1_1", LC3_CONFIG_44_1, LC3_QOS_44_1_1),
	LC3_PRESET("44_2_1", LC3_CONFIG_44_2, LC3_QOS_44_2_1),
	LC3_PRESET("48_1_1", LC3_CONFIG_48_1, LC3_QOS_48_1_1),
	LC3_PRESET("48_2_1", LC3_CONFIG_48_2, LC3_QOS_48_2_1),
	LC3_PRESET("48_3_1", LC3_CONFIG_48_3, LC3_QOS_48_3_1),
	LC3_PRESET("48_4_1", LC3_CONFIG_48_4, LC3_QOS_48_4_1),
	LC3_PRESET("48_5_1", LC3_CONFIG_48_5, LC3_QOS_48_5_1),
	LC3_PRESET("48_6_1", LC3_CONFIG_48_6, LC3_QOS_48_6_1),
	/* QoS Configuration settings for high reliability audio data */
	LC3_PRESET_HR("8_1_2", LC3_CONFIG_8_1, LC3_QOS_8_1_2),
	LC3_PRESET_HR("8_2_2", LC3_CONFIG_8_2, LC3_QOS_8_2_2),
	LC3_PRESET_HR("16_1_2", LC3_CONFIG_16_1, LC3_QOS_16_1_2),
	LC3_PRESET_HR("16_2_2", LC3_CONFIG_16_2, LC3_QOS_16_2_2),
	LC3_PRESET_HR("24_1_2", LC3_CONFIG_24_1, LC3_QOS_24_1_2),
	LC3_PRESET_HR("24_2_2", LC3_CONFIG_24_2, LC3_QOS_24_2_2),
	LC3_PRESET_HR("32_1_2", LC3_CONFIG_32_1, LC3_QOS_32_1_2),
	LC3_PRESET_HR("32_2_2", LC3_CONFIG_32_2, LC3_QOS_32_2_2),
	LC3_PRESET_HR("44_1_2", LC3_CONFIG_44_1, LC3_QOS_44_1_2),
	LC3_PRESET_HR("44_2_2", LC3_CONFIG_44_2, LC3_QOS_44_2_2),
	LC3_PRESET_HR("48_1_2", LC3_CONFIG_48_1, LC3_QOS_48_1_2),
	LC3_PRESET_HR("48_2_2", LC3_CONFIG_48_2, LC3_QOS_48_2_2),
	LC3_PRESET_HR("48_3_2", LC3_CONFIG_48_3, LC3_QOS_48_3_2),
	LC3_PRESET_HR("48_4_2", LC3_CONFIG_48_4, LC3_QOS_48_4_2),
	LC3_PRESET_HR("48_5_2", LC3_CONFIG_48_5, LC3_QOS_48_5_2),
	LC3_PRESET_HR("48_6_2", LC3_CONFIG_48_6, LC3_QOS_48_6_2),
	/* QoS configuration support setting requirements for the UGG and UGT */
	LC3_PRESET_LL("16_1_gs", LC3_CONFIG_16_1, LC3_QOS_16_1_GS),
	LC3_PRESET_LL("16_2_gs", LC3_CONFIG_16_2, LC3_QOS_16_2_GS),
	LC3_PRESET_LL("32_1_gs", LC3_CONFIG_32_1, LC3_QOS_32_1_GS),
	LC3_PRESET_LL("32_2_gs", LC3_CONFIG_32_2, LC3_QOS_32_2_GS),
	LC3_PRESET_LL("48_1_gs", LC3_CONFIG_48_1, LC3_QOS_48_1_GS),
	LC3_PRESET_LL("48_2_gs", LC3_CONFIG_48_2, LC3_QOS_48_2_GS),
	LC3_PRESET_LL("32_1_gr", LC3_CONFIG_32_1, LC3_QOS_32_1_GR),
	LC3_PRESET_LL("32_2_gr", LC3_CONFIG_32_2, LC3_QOS_32_2_GR),
	LC3_PRESET_LL("48_1_gr", LC3_CONFIG_48_1, LC3_QOS_48_1_GR),
	LC3_PRESET_LL("48_2_gr", LC3_CONFIG_48_2, LC3_QOS_48_2_GR),
	LC3_PRESET_LL("48_3_gr", LC3_CONFIG_48_3, LC3_QOS_48_3_GR),
	LC3_PRESET_LL("48_4_gr", LC3_CONFIG_48_4, LC3_QOS_48_4_GR),
	LC3_PRESET_LL("32_1_gr_l+r", LC3_CONFIG_32_1_AC(2),
				LC3_QOS_32_1_GR_AC(2)),
	LC3_PRESET_LL("32_2_gr_l+r", LC3_CONFIG_32_2_AC(2),
				LC3_QOS_32_2_GR_AC(2)),
	LC3_PRESET_LL("48_1_gr_l+r", LC3_CONFIG_48_1_AC(2),
				LC3_QOS_48_1_GR_AC(2)),
	LC3_PRESET_LL("48_2_gr_l+r", LC3_CONFIG_48_2_AC(2),
				LC3_QOS_48_2_GR_AC(2)),
	LC3_PRESET_LL("48_3_gr_l+r", LC3_CONFIG_48_3_AC(2),
				LC3_QOS_48_3_GR_AC(2)),
	LC3_PRESET_LL("48_4_gr_l+r", LC3_CONFIG_48_4_AC(2),
				LC3_QOS_48_4_GR_AC(2)),
};

static struct codec_preset lc3_bcast_presets[] = {
	/* Table 6.4: Broadcast Audio Stream configuration support requirements
	 * for the Broadcast Source and Broadcast Sink
	 */
	LC3_PRESET_B("8_1_1", LC3_CONFIG_8_1, LC3_QOS_8_1_1_B),
	LC3_PRESET_B("8_2_1", LC3_CONFIG_8_2, LC3_QOS_8_2_1_B),
	LC3_PRESET_B("16_1_1", LC3_CONFIG_16_1, LC3_QOS_16_1_1_B),
	LC3_PRESET_B("16_2_1", LC3_CONFIG_16_2, LC3_QOS_16_2_1_B),
	LC3_PRESET_B("24_1_1", LC3_CONFIG_24_1, LC3_QOS_24_1_1_B),
	LC3_PRESET_B("24_2_1", LC3_CONFIG_24_2, LC3_QOS_24_2_1_B),
	LC3_PRESET_B("32_1_1", LC3_CONFIG_32_1, LC3_QOS_32_1_1_B),
	LC3_PRESET_B("32_2_1", LC3_CONFIG_32_2, LC3_QOS_32_2_1_B),
	LC3_PRESET_B("44_1_1", LC3_CONFIG_44_1, LC3_QOS_44_1_1_B),
	LC3_PRESET_B("44_2_1", LC3_CONFIG_44_2, LC3_QOS_44_2_1_B),
	LC3_PRESET_B("48_1_1", LC3_CONFIG_48_1, LC3_QOS_48_1_1_B),
	LC3_PRESET_B("48_2_1", LC3_CONFIG_48_2, LC3_QOS_48_2_1_B),
	LC3_PRESET_B("48_3_1", LC3_CONFIG_48_3, LC3_QOS_48_3_1_B),
	LC3_PRESET_B("48_4_1", LC3_CONFIG_48_4, LC3_QOS_48_4_1_B),
	LC3_PRESET_B("48_5_1", LC3_CONFIG_48_5, LC3_QOS_48_5_1_B),
	LC3_PRESET_B("48_6_1", LC3_CONFIG_48_6, LC3_QOS_48_6_1_B),
	/* Broadcast Audio Stream configuration settings for high-reliability
	 * audio data.
	 */
	LC3_PRESET_B("8_1_2", LC3_CONFIG_8_1, LC3_QOS_8_1_1_B),
	LC3_PRESET_B("8_2_2", LC3_CONFIG_8_2, LC3_QOS_8_2_2_B),
	LC3_PRESET_B("16_1_2", LC3_CONFIG_16_1, LC3_QOS_16_1_2_B),
	LC3_PRESET_B("16_2_2", LC3_CONFIG_16_2, LC3_QOS_16_2_2_B),
	LC3_PRESET_B("24_1_2", LC3_CONFIG_24_1, LC3_QOS_24_1_2_B),
	LC3_PRESET_B("24_2_2", LC3_CONFIG_24_2, LC3_QOS_24_2_2_B),
	LC3_PRESET_B("32_1_2", LC3_CONFIG_32_1, LC3_QOS_32_1_2_B),
	LC3_PRESET_B("32_2_2", LC3_CONFIG_32_2, LC3_QOS_32_2_2_B),
	LC3_PRESET_B("44_1_2", LC3_CONFIG_44_1, LC3_QOS_44_1_2_B),
	LC3_PRESET_B("44_2_2", LC3_CONFIG_44_2, LC3_QOS_44_2_2_B),
	LC3_PRESET_B("48_1_2", LC3_CONFIG_48_1, LC3_QOS_48_1_2_B),
	LC3_PRESET_B("48_2_2", LC3_CONFIG_48_2, LC3_QOS_48_2_2_B),
	LC3_PRESET_B("48_3_2", LC3_CONFIG_48_3, LC3_QOS_48_3_2_B),
	LC3_PRESET_B("48_4_2", LC3_CONFIG_48_4, LC3_QOS_48_4_2_B),
	LC3_PRESET_B("48_5_2", LC3_CONFIG_48_5, LC3_QOS_48_5_2_B),
	LC3_PRESET_B("48_6_2", LC3_CONFIG_48_6, LC3_QOS_48_6_2_B),
};

static void print_ltv(const char *str, void *user_data)
{
	const char *label = user_data;

	bt_shell_printf("\t%s.%s\n", label, str);
}

static void print_lc3_caps(uint8_t *data, int len)
{
	const char *label = "Capabilities";

	bt_bap_debug_caps(data, len, print_ltv, (void *)label);
}

static void print_lc3_cfg(void *data, int len)
{
	const char *label = "Configuration";

	bt_bap_debug_config(data, len, print_ltv, (void *)label);
}

static void print_lc3_meta(void *data, int len)
{
	const char *label = "Metadata";

	bt_bap_debug_metadata(data, len, print_ltv, (void *)label);
}

#define PRESET(_uuid, _codec, _presets, _default_index) \
	{ \
		.uuid = _uuid, \
		.codec = _codec, \
		.default_preset = &_presets[_default_index], \
		.presets = _presets, \
		.num_presets = ARRAY_SIZE(_presets), \
	}

static struct preset {
	const char *uuid;
	uint8_t codec;
	uint16_t cid;
	uint16_t vid;
	struct queue *custom;
	struct codec_preset *default_preset;
	struct codec_preset *presets;
	size_t num_presets;
} presets[] = {
	PRESET(A2DP_SOURCE_UUID, A2DP_CODEC_SBC, sbc_presets, 6),
	PRESET(A2DP_SINK_UUID, A2DP_CODEC_SBC, sbc_presets, 6),
	PRESET(PAC_SINK_UUID, LC3_ID, lc3_ucast_presets, 3),
	PRESET(PAC_SOURCE_UUID, LC3_ID, lc3_ucast_presets, 3),
	PRESET(BCAA_SERVICE_UUID,  LC3_ID, lc3_bcast_presets, 3),
	PRESET(BAA_SERVICE_UUID,  LC3_ID, lc3_bcast_presets, 3),
};

static void parse_vendor_codec(const char *codec, uint16_t *vid, uint16_t *cid)
{
	char **list;
	char *endptr = NULL;

	if (!codec)
		return;

	list = g_strsplit(codec, ":", 2);

	if (vid)
		*vid = strtol(list[0], &endptr, 0);

	if (cid)
		*cid = strtol(list[1], &endptr, 0);

	g_strfreev(list);
}

static struct preset *find_presets(const char *uuid, uint8_t codec,
					uint16_t vid, uint16_t cid)
{
	size_t i;

	if (codec == 0xff) {
		GList *l;

		for (l = local_endpoints; l; l = g_list_next(l)) {
			struct endpoint *ep = l->data;

			if (strcasecmp(ep->uuid, uuid) || ep->codec != codec)
				continue;

			if (ep->codec == 0xff && (ep->vid != vid ||
							ep->cid != cid))
				continue;

			return ep->preset;
		}

		return NULL;
	}

	for (i = 0; i < ARRAY_SIZE(presets); i++) {
		struct preset *preset = &presets[i];

		if (preset->codec != codec)
			continue;

		if (!strcasecmp(preset->uuid, uuid))
			return preset;
	}

	return NULL;
}

static struct preset *find_vendor_presets(const char *uuid, const char *codec)
{
	uint16_t cid;
	uint16_t vid;

	if (!uuid || !codec)
		return NULL;

	parse_vendor_codec(codec, &vid, &cid);

	return find_presets(uuid, 0xff, vid, cid);
}

static struct preset *find_presets_name(const char *uuid, const char *codec)
{
	uint8_t id;
	char *endptr = NULL;

	if (!uuid || !codec)
		return NULL;

	if (strrchr(codec, ':'))
		return find_vendor_presets(uuid, codec);

	id = strtol(codec, &endptr, 0);

	return find_presets(uuid, id, 0x0000, 0x0000);
}

static 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)
{
	size_t i;

	if (!preset)
		return NULL;

	if (!name)
		return preset->default_preset;

	for (i = 0; i < preset->num_presets; i++) {
		struct codec_preset *p;

		p = &preset->presets[i];

		if (!strcmp(p->name, name))
			return p;
	}

	return queue_find(preset->custom, match_custom_name, name);
}

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 util_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 = preset_find_name(ep->preset, 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->max_transports) {
		bt_shell_printf("Maximum transports reached: rejecting\n");
		return g_dbus_create_error(msg,
					 "org.bluez.Error.Rejected",
					 "Maximum transports reached");
	}

	if (!ep->auto_accept) {
		ep->msg = dbus_message_ref(msg);
		bt_shell_prompt_input("Endpoint", "Enter preset/configuration:",
					select_config_response, ep);
		return NULL;
	}

	p = preset_find_name(ep->preset, NULL);
	if (!p) {
		reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected",
								NULL);
		return reply;
	}

	reply = endpoint_select_config_reply(msg, p->data.iov_base,
						p->data.iov_len);
	if (!reply) {
		reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected",
								NULL);
		return reply;
	}

	bt_shell_printf("Auto Accepting using %s...\n", p->name);

	return reply;
}

struct endpoint_config {
	GDBusProxy *proxy;
	struct endpoint *ep;
	struct iovec *caps;		/* Codec Specific Configuration LTVs */
	struct iovec *meta;		/* Metadata LTVs*/
	uint8_t target_latency;
	struct bt_bap_qos qos;		/* BAP QOS configuration parameters */
};

static void append_io_qos(DBusMessageIter *iter, struct bt_bap_io_qos *qos)
{
	bt_shell_printf("Interval %u\n", qos->interval);

	g_dbus_dict_append_entry(iter, "Interval", DBUS_TYPE_UINT32,
						&qos->interval);

	bt_shell_printf("PHY 0x%02x\n", qos->phy);

	g_dbus_dict_append_entry(iter, "PHY", DBUS_TYPE_BYTE, &qos->phy);

	bt_shell_printf("SDU %u\n", qos->sdu);

	g_dbus_dict_append_entry(iter, "SDU", DBUS_TYPE_UINT16, &qos->sdu);

	bt_shell_printf("Retransmissions %u\n", qos->rtn);

	g_dbus_dict_append_entry(iter, "Retransmissions", DBUS_TYPE_BYTE,
						&qos->rtn);

	bt_shell_printf("Latency %u\n", qos->latency);

	g_dbus_dict_append_entry(iter, "Latency", DBUS_TYPE_UINT16,
						&qos->latency);
}

static void append_ucast_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
{
	struct bt_bap_ucast_qos *qos = &cfg->qos.ucast;

	if (cfg->ep->iso_group != BT_ISO_QOS_GROUP_UNSET) {
		bt_shell_printf("CIG 0x%2.2x\n", cfg->ep->iso_group);
		g_dbus_dict_append_entry(iter, "CIG", DBUS_TYPE_BYTE,
							&cfg->ep->iso_group);
	}

	if (cfg->ep->iso_stream != BT_ISO_QOS_STREAM_UNSET) {
		bt_shell_printf("CIS 0x%2.2x\n", cfg->ep->iso_stream);
		g_dbus_dict_append_entry(iter, "CIS", DBUS_TYPE_BYTE,
							&cfg->ep->iso_stream);
	}

	bt_shell_printf("Framing 0x%02x\n", qos->framing);

	g_dbus_dict_append_entry(iter, "Framing", DBUS_TYPE_BYTE,
					&qos->framing);

	bt_shell_printf("PresentationDelay %u\n", qos->delay);

	g_dbus_dict_append_entry(iter, "PresentationDelay",
					DBUS_TYPE_UINT32, &qos->delay);

	if (cfg->target_latency) {
		bt_shell_printf("TargetLatency 0x%02x\n", cfg->target_latency);
		g_dbus_dict_append_entry(iter, "TargetLatency", DBUS_TYPE_BYTE,
					&cfg->target_latency);
	}

	append_io_qos(iter, &qos->io_qos);
}

static void append_bcast_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
{
	struct bt_bap_bcast_qos *qos = &cfg->qos.bcast;

	if (cfg->ep->iso_group != BT_ISO_QOS_BIG_UNSET) {
		bt_shell_printf("BIG 0x%2.2x\n", cfg->ep->iso_group);
		g_dbus_dict_append_entry(iter, "BIG", DBUS_TYPE_BYTE,
							&cfg->ep->iso_group);
	}

	if (cfg->ep->iso_stream != BT_ISO_QOS_BIS_UNSET) {
		bt_shell_printf("BIS 0x%2.2x\n", cfg->ep->iso_stream);
		g_dbus_dict_append_entry(iter, "BIS", DBUS_TYPE_BYTE,
							&cfg->ep->iso_stream);
	}

	if (qos->sync_factor) {
		bt_shell_printf("SyncFactor %u\n", qos->sync_factor);
		g_dbus_dict_append_entry(iter, "SyncFactor", DBUS_TYPE_BYTE,
						&qos->sync_factor);
	}

	if (qos->options) {
		bt_shell_printf("Options %u\n", qos->options);
		g_dbus_dict_append_entry(iter, "Options", DBUS_TYPE_BYTE,
						&qos->options);
	}

	if (qos->skip) {
		bt_shell_printf("Skip %u\n", qos->skip);
		g_dbus_dict_append_entry(iter, "Skip", DBUS_TYPE_UINT16,
						&qos->skip);
	}

	if (qos->sync_timeout) {
		bt_shell_printf("SyncTimeout %u\n", qos->sync_timeout);
		g_dbus_dict_append_entry(iter, "SyncTimeout", DBUS_TYPE_UINT16,
						&qos->sync_timeout);
	}

	if (qos->sync_cte_type) {
		bt_shell_printf("SyncCteType %u\n", qos->sync_cte_type);
		g_dbus_dict_append_entry(iter, "SyncCteType", DBUS_TYPE_BYTE,
					&qos->sync_cte_type);
	}

	if (qos->mse) {
		bt_shell_printf("MSE %u\n", qos->mse);
		g_dbus_dict_append_entry(iter, "MSE", DBUS_TYPE_BYTE,
						&qos->mse);
	}

	if (qos->timeout) {
		bt_shell_printf("Timeout %u\n", qos->timeout);
		g_dbus_dict_append_entry(iter, "Timeout", DBUS_TYPE_UINT16,
						&qos->timeout);
	}

	if (cfg->ep->bcode->iov_len != 0) {
		const char *key = "BCode";

		bt_shell_printf("BCode:\n");
		bt_shell_hexdump(cfg->ep->bcode->iov_base,
				cfg->ep->bcode->iov_len);

		g_dbus_dict_append_basic_array(iter, DBUS_TYPE_STRING,
						&key, DBUS_TYPE_BYTE,
						&cfg->ep->bcode->iov_base,
						cfg->ep->bcode->iov_len);
	}

	bt_shell_printf("Framing 0x%02x\n", qos->framing);

	g_dbus_dict_append_entry(iter, "Framing", DBUS_TYPE_BYTE,
					&qos->framing);

	bt_shell_printf("PresentationDelay %u\n", qos->delay);

	g_dbus_dict_append_entry(iter, "PresentationDelay",
					DBUS_TYPE_UINT32, &qos->delay);

	/* Add BAP codec QOS configuration */
	append_io_qos(iter, &qos->io_qos);
}

static void append_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
{
	DBusMessageIter entry, var, dict;
	const char *key = "QoS";

	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);

	if (cfg->ep->broadcast)
		append_bcast_qos(&dict, cfg);
	else
		append_ucast_qos(&dict, cfg);

	dbus_message_iter_close_container(&var, &dict);
	dbus_message_iter_close_container(&entry, &var);
	dbus_message_iter_close_container(iter, &entry);
}

static void append_properties(DBusMessageIter *iter,
						struct endpoint_config *cfg)
{
	DBusMessageIter dict;
	const char *key = "Capabilities";

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);

	if (cfg->ep->codec == LC3_ID) {
		print_lc3_cfg(cfg->caps->iov_base, cfg->caps->iov_len);
	} else {
		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);

	if (cfg->meta && cfg->meta->iov_len) {
		const char *meta = "Metadata";

		g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &meta,
				DBUS_TYPE_BYTE, &cfg->meta->iov_base,
				cfg->meta->iov_len);

		if (cfg->ep->codec == LC3_ID) {
			print_lc3_meta(cfg->meta->iov_base, cfg->meta->iov_len);
		} else {
			bt_shell_printf("Metadata:\n");
			bt_shell_hexdump(cfg->meta->iov_base,
						cfg->meta->iov_len);
		}
	}

	append_qos(&dict, cfg);

	dbus_message_iter_close_container(iter, &dict);
}

static int parse_chan_alloc(DBusMessageIter *iter, uint32_t *location,
						uint8_t *channels)
{
	while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) {
		const char *key;
		DBusMessageIter value, entry;
		int var;

		dbus_message_iter_recurse(iter, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		var = dbus_message_iter_get_arg_type(&value);

		if (!strcasecmp(key, "ChannelAllocation")) {
			if (var != DBUS_TYPE_UINT32)
				return -EINVAL;
			dbus_message_iter_get_basic(&value, 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 *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,
						DBusMessage *msg,
						struct codec_preset *preset)
{
	DBusMessage *reply;
	DBusMessageIter iter, props;
	struct endpoint_config *cfg;
	struct bt_bap_io_qos *qos;
	uint32_t location = ep->locations;
	uint8_t channels = 1;

	if (!preset)
		return NULL;

	reply = dbus_message_new_method_return(msg);
	if (!reply)
		return NULL;

	cfg = new0(struct endpoint_config, 1);
	cfg->ep = ep;

	/* Copy capabilities */
	cfg->caps = util_iov_dup(&preset->data, 1);
	cfg->target_latency = preset->target_latency;

	dbus_message_iter_init(msg, &iter);
	dbus_message_iter_recurse(&iter, &props);

	if (!parse_chan_alloc(&props, &location, &channels)) {
		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);

		/* 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 */
	if (preset->meta.iov_len)
		cfg->meta = util_iov_dup(&preset->meta, 1);
	else
		cfg->meta = util_iov_dup(ep->meta, 1);

	if (ep->broadcast)
		qos = &preset->qos.bcast.io_qos;
	else
		qos = &preset->qos.ucast.io_qos;

	if (qos->phy) {
		/* Set QoS parameters */
		cfg->qos = preset->qos;
		/* Adjust the SDU size based on the number of
		 * locations/channels that is being requested.
		 */
		if (channels > 1) {
			if (ep->broadcast)
				cfg->qos.bcast.io_qos.sdu *= channels;
			else
				cfg->qos.ucast.io_qos.sdu *= channels;
		}
	}

	dbus_message_iter_init_append(reply, &iter);

	bt_shell_printf("selecting %s...\n", preset->name);

	append_properties(&iter, cfg);

	free(cfg);

	return reply;
}

static struct codec_preset *endpoint_find_codec_preset(struct endpoint *ep,
							const char *name)
{
	if (ep->codec_preset &&
			(!name || !strcmp(ep->codec_preset->name, name)))
		return ep->codec_preset;

	return preset_find_name(ep->preset, name);
}

static void select_properties_response(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	struct codec_preset *p;
	DBusMessage *reply;

	p = endpoint_find_codec_preset(ep, input);
	if (p) {
		reply = endpoint_select_properties_reply(ep, ep->msg, p);
		goto done;
	}

	bt_shell_printf("Preset %s not found\n", input);
	reply = g_dbus_create_error(ep->msg, "org.bluez.Error.Rejected", NULL);

done:
	g_dbus_send_message(dbus_conn, reply);
	dbus_message_unref(ep->msg);
	ep->msg = NULL;
}

static DBusMessage *endpoint_select_properties(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: SelectProperties\n");
	print_iter("\t", "Properties", &args);

	if (!ep->max_transports) {
		bt_shell_printf("Maximum transports reached: rejecting\n");
		return g_dbus_create_error(msg,
					 "org.bluez.Error.Rejected",
					 "Maximum transports reached");
	}

	if (!ep->auto_accept) {
		ep->msg = dbus_message_ref(msg);
		bt_shell_prompt_input("Endpoint", "Enter preset/configuration:",
					select_properties_response, ep);
		return NULL;
	}

	p = endpoint_find_codec_preset(ep, NULL);
	if (!p)
		return NULL;

	reply = endpoint_select_properties_reply(ep, msg, p);
	if (!reply)
		return NULL;

	return reply;
}

static bool match_str(const void *data, const void *user_data)
{
	return !strcmp(data, user_data);
}

static DBusMessage *endpoint_clear_configuration(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct endpoint *ep = user_data;
	DBusMessageIter args;
	const char *path;

	dbus_message_iter_init(msg, &args);

	dbus_message_iter_get_basic(&args, &path);

	if (ep->max_transports != UINT8_MAX)
		ep->max_transports++;

	queue_remove_if(ep->transports, match_str, (void *)path);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

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 print_aptx_common(a2dp_aptx_t *aptx)
{
	bt_shell_printf("\n\t\tFrequencies: ");
	if (aptx->frequency & APTX_SAMPLING_FREQ_16000)
		bt_shell_printf("16kHz ");
	if (aptx->frequency & APTX_SAMPLING_FREQ_32000)
		bt_shell_printf("32kHz ");
	if (aptx->frequency & APTX_SAMPLING_FREQ_44100)
		bt_shell_printf("44.1kHz ");
	if (aptx->frequency & APTX_SAMPLING_FREQ_48000)
		bt_shell_printf("48kHz ");

	bt_shell_printf("\n\t\tChannel modes: ");
	if (aptx->channel_mode & APTX_CHANNEL_MODE_MONO)
		bt_shell_printf("Mono ");
	if (aptx->channel_mode & APTX_CHANNEL_MODE_STEREO)
		bt_shell_printf("Stereo ");
}

static void print_aptx(a2dp_aptx_t *aptx, uint8_t size)
{
	bt_shell_printf("\t\tVendor Specific Value (aptX)");

	if (size < sizeof(*aptx)) {
		bt_shell_printf(" (broken)\n");
		return;
	}

	print_aptx_common(aptx);

	bt_shell_printf("\n");
}

static void print_faststream(a2dp_faststream_t *faststream, uint8_t size)
{
	bt_shell_printf("\t\tVendor Specific Value (FastStream)");

	if (size < sizeof(*faststream)) {
		bt_shell_printf(" (broken)\n");
		return;
	}

	bt_shell_printf("\n\t\tDirections: ");
	if (faststream->direction & FASTSTREAM_DIRECTION_SINK)
		bt_shell_printf("sink ");
	if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE)
		bt_shell_printf("source ");

	if (faststream->direction & FASTSTREAM_DIRECTION_SINK) {
		bt_shell_printf("\n\t\tSink Frequencies: ");
		if (faststream->sink_frequency &
				FASTSTREAM_SINK_SAMPLING_FREQ_44100)
			bt_shell_printf("44.1kHz ");
		if (faststream->sink_frequency &
				FASTSTREAM_SINK_SAMPLING_FREQ_48000)
			bt_shell_printf("48kHz ");
	}

	if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE) {
		bt_shell_printf("\n\t\tSource Frequencies: ");
		if (faststream->source_frequency &
				FASTSTREAM_SOURCE_SAMPLING_FREQ_16000)
			bt_shell_printf("16kHz ");
	}

	bt_shell_printf("\n");
}

static void print_aptx_ll(a2dp_aptx_ll_t *aptx_ll, uint8_t size)
{
	a2dp_aptx_ll_new_caps_t *aptx_ll_new;

	bt_shell_printf("\t\tVendor Specific Value (aptX Low Latency)");

	if (size < sizeof(*aptx_ll)) {
		bt_shell_printf(" (broken)\n");
		return;
	}

	print_aptx_common(&aptx_ll->aptx);

	bt_shell_printf("\n\tBidirectional link: %s",
			aptx_ll->bidirect_link ? "Yes" : "No");

	aptx_ll_new = &aptx_ll->new_caps[0];
	if (aptx_ll->has_new_caps &&
	    size >= sizeof(*aptx_ll) + sizeof(*aptx_ll_new)) {
		bt_shell_printf("\n\tTarget codec buffer level: %u",
			(unsigned int)aptx_ll_new->target_level2 |
			((unsigned int)(aptx_ll_new->target_level1) << 8));
		bt_shell_printf("\n\tInitial codec buffer level: %u",
			(unsigned int)aptx_ll_new->initial_level2 |
			((unsigned int)(aptx_ll_new->initial_level1) << 8));
		bt_shell_printf("\n\tSRA max rate: %g",
			aptx_ll_new->sra_max_rate / 10000.0);
		bt_shell_printf("\n\tSRA averaging time: %us",
			(unsigned int)aptx_ll_new->sra_avg_time);
		bt_shell_printf("\n\tGood working codec buffer level: %u",
			(unsigned int)aptx_ll_new->good_working_level2 |
			((unsigned int)(aptx_ll_new->good_working_level1) << 8)
			);
	}

	bt_shell_printf("\n");
}

static void print_aptx_hd(a2dp_aptx_hd_t *aptx_hd, uint8_t size)
{
	bt_shell_printf("\t\tVendor Specific Value (aptX HD)");

	if (size < sizeof(*aptx_hd)) {
		bt_shell_printf(" (broken)\n");
		return;
	}

	print_aptx_common(&aptx_hd->aptx);

	bt_shell_printf("\n");
}

static void print_ldac(a2dp_ldac_t *ldac, uint8_t size)
{
	bt_shell_printf("\t\tVendor Specific Value (LDAC)");

	if (size < sizeof(*ldac)) {
		bt_shell_printf(" (broken)\n");
		return;
	}

	bt_shell_printf("\n\t\tFrequencies: ");
	if (ldac->frequency & LDAC_SAMPLING_FREQ_44100)
		bt_shell_printf("44.1kHz ");
	if (ldac->frequency & LDAC_SAMPLING_FREQ_48000)
		bt_shell_printf("48kHz ");
	if (ldac->frequency & LDAC_SAMPLING_FREQ_88200)
		bt_shell_printf("88.2kHz ");
	if (ldac->frequency & LDAC_SAMPLING_FREQ_96000)
		bt_shell_printf("96kHz ");
	if (ldac->frequency & LDAC_SAMPLING_FREQ_176400)
		bt_shell_printf("176.4kHz ");
	if (ldac->frequency & LDAC_SAMPLING_FREQ_192000)
		bt_shell_printf("192kHz ");

	bt_shell_printf("\n\t\tChannel modes: ");
	if (ldac->channel_mode & LDAC_CHANNEL_MODE_MONO)
		bt_shell_printf("Mono ");
	if (ldac->channel_mode & LDAC_CHANNEL_MODE_DUAL)
		bt_shell_printf("Dual ");
	if (ldac->channel_mode & LDAC_CHANNEL_MODE_STEREO)
		bt_shell_printf("Stereo ");

	bt_shell_printf("\n");
}

static void print_opus_g(a2dp_opus_g_t *opus, uint8_t size)
{
	bt_shell_printf("\t\tVendor Specific Value (Opus [Google])");

	if (size < sizeof(*opus)) {
		bt_shell_printf(" (broken)\n");
		return;
	}

	bt_shell_printf("\n\t\tFrequencies: ");
	if (opus->data & OPUS_G_FREQUENCY_48000)
		bt_shell_printf("48kHz ");

	bt_shell_printf("\n\t\tChannel modes: ");
	if (opus->data & OPUS_G_CHANNELS_MONO)
		bt_shell_printf("Mono ");
	if (opus->data & OPUS_G_CHANNELS_STEREO)
		bt_shell_printf("Stereo ");
	if (opus->data & OPUS_G_CHANNELS_DUAL)
		bt_shell_printf("Dual Mono ");

	bt_shell_printf("\n\t\tFrame durations: ");
	if (opus->data & OPUS_G_DURATION_100)
		bt_shell_printf("10 ms ");
	if (opus->data & OPUS_G_DURATION_200)
		bt_shell_printf("20 ms ");

	bt_shell_printf("\n");
}

static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size)
{
	uint32_t vendor_id;
	uint16_t codec_id;
	int i;

	if (size < sizeof(*vendor)) {
		bt_shell_printf("\tMedia Codec: Vendor Specific A2DP Codec "
				"(broken)");
		return;
	}

	vendor_id = A2DP_GET_VENDOR_ID(*vendor);
	codec_id = A2DP_GET_CODEC_ID(*vendor);

	bt_shell_printf("\tMedia Codec: Vendor Specific A2DP Codec");

	bt_shell_printf("\n\tVendor ID 0x%08x", vendor_id);

	bt_shell_printf("\n\tVendor Specific Codec ID 0x%04x", codec_id);

	bt_shell_printf("\n\tVendor Specific Data:");
	for (i = 6; i < size; ++i)
		bt_shell_printf(" 0x%.02x", ((unsigned char *)vendor)[i]);
	bt_shell_printf("\n");

	if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID)
		print_aptx((void *) vendor, size);
	else if (vendor_id == FASTSTREAM_VENDOR_ID &&
			codec_id == FASTSTREAM_CODEC_ID)
		print_faststream((void *) vendor, size);
	else if (vendor_id == APTX_LL_VENDOR_ID && codec_id == APTX_LL_CODEC_ID)
		print_aptx_ll((void *) vendor, size);
	else if (vendor_id == APTX_HD_VENDOR_ID && codec_id == APTX_HD_CODEC_ID)
		print_aptx_hd((void *) vendor, size);
	else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID)
		print_ldac((void *) vendor, size);
	else if (vendor_id == OPUS_G_VENDOR_ID && codec_id == OPUS_G_CODEC_ID)
		print_opus_g((void *) vendor, size);
}

static void print_mpeg24(a2dp_aac_t *aac, uint8_t size)
{
	unsigned int freq, bitrate;

	if (size < sizeof(*aac)) {
		bt_shell_printf("\tMedia Codec: MPEG24 (broken)\n");
		return;
	}

	freq = AAC_GET_FREQUENCY(*aac);
	bitrate = AAC_GET_BITRATE(*aac);

	bt_shell_printf("\tMedia Codec: MPEG24\n\tObject Types: ");

	if (aac->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC)
		bt_shell_printf("MPEG-2 AAC LC ");
	if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC)
		bt_shell_printf("MPEG-4 AAC LC ");
	if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LTP)
		bt_shell_printf("MPEG-4 AAC LTP ");
	if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_SCA)
		bt_shell_printf("MPEG-4 AAC scalable ");

	bt_shell_printf("\n\tFrequencies: ");
	if (freq & AAC_SAMPLING_FREQ_8000)
		bt_shell_printf("8kHz ");
	if (freq & AAC_SAMPLING_FREQ_11025)
		bt_shell_printf("11.025kHz ");
	if (freq & AAC_SAMPLING_FREQ_12000)
		bt_shell_printf("12kHz ");
	if (freq & AAC_SAMPLING_FREQ_16000)
		bt_shell_printf("16kHz ");
	if (freq & AAC_SAMPLING_FREQ_22050)
		bt_shell_printf("22.05kHz ");
	if (freq & AAC_SAMPLING_FREQ_24000)
		bt_shell_printf("24kHz ");
	if (freq & AAC_SAMPLING_FREQ_32000)
		bt_shell_printf("32kHz ");
	if (freq & AAC_SAMPLING_FREQ_44100)
		bt_shell_printf("44.1kHz ");
	if (freq & AAC_SAMPLING_FREQ_48000)
		bt_shell_printf("48kHz ");
	if (freq & AAC_SAMPLING_FREQ_64000)
		bt_shell_printf("64kHz ");
	if (freq & AAC_SAMPLING_FREQ_88200)
		bt_shell_printf("88.2kHz ");
	if (freq & AAC_SAMPLING_FREQ_96000)
		bt_shell_printf("96kHz ");

	bt_shell_printf("\n\tChannels: ");
	if (aac->channels & AAC_CHANNELS_1)
		bt_shell_printf("1 ");
	if (aac->channels & AAC_CHANNELS_2)
		bt_shell_printf("2 ");

	bt_shell_printf("\n\tBitrate: %u", bitrate);

	bt_shell_printf("\n\tVBR: %s", aac->vbr ? "Yes\n" : "No\n");
}

static void print_mpeg12(a2dp_mpeg_t *mpeg, uint8_t size)
{
	uint16_t bitrate;

	if (size < sizeof(*mpeg)) {
		bt_shell_printf("\tMedia Codec: MPEG12 (broken)\n");
		return;
	}

	bitrate = MPEG_GET_BITRATE(*mpeg);

	bt_shell_printf("\tMedia Codec: MPEG12\n\tChannel Modes: ");

	if (mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO)
		bt_shell_printf("Mono ");
	if (mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL)
		bt_shell_printf("DualChannel ");
	if (mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO)
		bt_shell_printf("Stereo ");
	if (mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO)
		bt_shell_printf("JointStereo");

	bt_shell_printf("\n\tFrequencies: ");
	if (mpeg->frequency & MPEG_SAMPLING_FREQ_16000)
		bt_shell_printf("16Khz ");
	if (mpeg->frequency & MPEG_SAMPLING_FREQ_22050)
		bt_shell_printf("22.05Khz ");
	if (mpeg->frequency & MPEG_SAMPLING_FREQ_24000)
		bt_shell_printf("24Khz ");
	if (mpeg->frequency & MPEG_SAMPLING_FREQ_32000)
		bt_shell_printf("32Khz ");
	if (mpeg->frequency & MPEG_SAMPLING_FREQ_44100)
		bt_shell_printf("44.1Khz ");
	if (mpeg->frequency & MPEG_SAMPLING_FREQ_48000)
		bt_shell_printf("48Khz ");

	bt_shell_printf("\n\tCRC: %s", mpeg->crc ? "Yes" : "No");

	bt_shell_printf("\n\tLayer: ");
	if (mpeg->layer & MPEG_LAYER_MP1)
		bt_shell_printf("1 ");
	if (mpeg->layer & MPEG_LAYER_MP2)
		bt_shell_printf("2 ");
	if (mpeg->layer & MPEG_LAYER_MP3)
		bt_shell_printf("3 ");

	if (bitrate & MPEG_BIT_RATE_FREE) {
		bt_shell_printf("\n\tBit Rate: Free format");
	} else {
		if (mpeg->layer & MPEG_LAYER_MP1) {
			bt_shell_printf("\n\tLayer 1 Bit Rate: ");
			if (bitrate & MPEG_MP1_BIT_RATE_32000)
				bt_shell_printf("32kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_64000)
				bt_shell_printf("64kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_96000)
				bt_shell_printf("96kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_128000)
				bt_shell_printf("128kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_160000)
				bt_shell_printf("160kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_192000)
				bt_shell_printf("192kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_224000)
				bt_shell_printf("224kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_256000)
				bt_shell_printf("256kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_320000)
				bt_shell_printf("320kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_352000)
				bt_shell_printf("352kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_384000)
				bt_shell_printf("384kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_416000)
				bt_shell_printf("416kbps ");
			if (bitrate & MPEG_MP1_BIT_RATE_448000)
				bt_shell_printf("448kbps ");
		}

		if (mpeg->layer & MPEG_LAYER_MP2) {
			bt_shell_printf("\n\tLayer 2 Bit Rate: ");
			if (bitrate & MPEG_MP2_BIT_RATE_32000)
				bt_shell_printf("32kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_48000)
				bt_shell_printf("48kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_56000)
				bt_shell_printf("56kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_64000)
				bt_shell_printf("64kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_80000)
				bt_shell_printf("80kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_96000)
				bt_shell_printf("96kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_112000)
				bt_shell_printf("112kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_128000)
				bt_shell_printf("128kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_160000)
				bt_shell_printf("160kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_192000)
				bt_shell_printf("192kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_224000)
				bt_shell_printf("224kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_256000)
				bt_shell_printf("256kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_320000)
				bt_shell_printf("320kbps ");
			if (bitrate & MPEG_MP2_BIT_RATE_384000)
				bt_shell_printf("384kbps ");
		}

		if (mpeg->layer & MPEG_LAYER_MP3) {
			bt_shell_printf("\n\tLayer 3 Bit Rate: ");
			if (bitrate & MPEG_MP3_BIT_RATE_32000)
				bt_shell_printf("32kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_40000)
				bt_shell_printf("40kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_48000)
				bt_shell_printf("48kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_56000)
				bt_shell_printf("56kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_64000)
				bt_shell_printf("64kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_80000)
				bt_shell_printf("80kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_96000)
				bt_shell_printf("96kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_112000)
				bt_shell_printf("112kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_128000)
				bt_shell_printf("128kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_160000)
				bt_shell_printf("160kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_192000)
				bt_shell_printf("192kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_224000)
				bt_shell_printf("224kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_256000)
				bt_shell_printf("256kbps ");
			if (bitrate & MPEG_MP3_BIT_RATE_320000)
				bt_shell_printf("320kbps ");
		}
	}

	bt_shell_printf("\n\tVBR: %s", mpeg->vbr ? "Yes" : "No");

	bt_shell_printf("\n\tPayload Format: ");
	if (mpeg->mpf)
		bt_shell_printf("RFC-2250 RFC-3119\n");
	else
		bt_shell_printf("RFC-2250\n");
}

static void print_sbc(a2dp_sbc_t *sbc, uint8_t size)
{
	if (size < sizeof(*sbc)) {
		bt_shell_printf("\tMedia Codec: SBC (broken)\n");
		return;
	}

	bt_shell_printf("\tMedia Codec: SBC\n\tChannel Modes: ");

	if (sbc->channel_mode & SBC_CHANNEL_MODE_MONO)
		bt_shell_printf("Mono ");
	if (sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
		bt_shell_printf("DualChannel ");
	if (sbc->channel_mode & SBC_CHANNEL_MODE_STEREO)
		bt_shell_printf("Stereo ");
	if (sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
		bt_shell_printf("JointStereo");

	bt_shell_printf("\n\tFrequencies: ");
	if (sbc->frequency & SBC_SAMPLING_FREQ_16000)
		bt_shell_printf("16Khz ");
	if (sbc->frequency & SBC_SAMPLING_FREQ_32000)
		bt_shell_printf("32Khz ");
	if (sbc->frequency & SBC_SAMPLING_FREQ_44100)
		bt_shell_printf("44.1Khz ");
	if (sbc->frequency & SBC_SAMPLING_FREQ_48000)
		bt_shell_printf("48Khz ");

	bt_shell_printf("\n\tSubbands: ");
	if (sbc->allocation_method & SBC_SUBBANDS_4)
		bt_shell_printf("4 ");
	if (sbc->allocation_method & SBC_SUBBANDS_8)
		bt_shell_printf("8");

	bt_shell_printf("\n\tBlocks: ");
	if (sbc->block_length & SBC_BLOCK_LENGTH_4)
		bt_shell_printf("4 ");
	if (sbc->block_length & SBC_BLOCK_LENGTH_8)
		bt_shell_printf("8 ");
	if (sbc->block_length & SBC_BLOCK_LENGTH_12)
		bt_shell_printf("12 ");
	if (sbc->block_length & SBC_BLOCK_LENGTH_16)
		bt_shell_printf("16 ");

	bt_shell_printf("\n\tBitpool Range: %d-%d\n",
				sbc->min_bitpool, sbc->max_bitpool);
}

static int print_a2dp_codec(uint8_t codec, void *data, uint8_t size)
{
	int i;

	switch (codec) {
	case A2DP_CODEC_SBC:
		print_sbc(data, size);
		break;
	case A2DP_CODEC_MPEG12:
		print_mpeg12(data, size);
		break;
	case A2DP_CODEC_MPEG24:
		print_mpeg24(data, size);
		break;
	case A2DP_CODEC_VENDOR:
		print_vendor(data, size);
		break;
	default:
		bt_shell_printf("\tMedia Codec: Unknown\n");
		bt_shell_printf("\t\tCodec Data:");
		for (i = 0; i < size - 2; ++i)
			bt_shell_printf(" 0x%.02x", ((unsigned char *)data)[i]);
		bt_shell_printf("\n");
	}

	return 0;
}

static void print_hexdump(const char *label, struct iovec *iov)
{
	if (!iov)
		return;

	bt_shell_printf("%s:\n", label);
	bt_shell_hexdump(iov->iov_base, iov->iov_len);
}

static void print_codec(const char *uuid, uint8_t codec, struct iovec *caps,
						struct iovec *meta)
{
	if (!strcasecmp(uuid, A2DP_SINK_UUID) ||
			!strcasecmp(uuid, A2DP_SOURCE_UUID)) {
		print_a2dp_codec(codec, caps->iov_base, caps->iov_len);
		return;
	}

	if (codec != LC3_ID) {
		print_hexdump("Capabilities", caps);
		print_hexdump("Metadata", meta);
		return;
	}

	print_lc3_caps(caps->iov_base, caps->iov_len);

	if (!meta)
		return;

	print_lc3_meta(meta->iov_base, meta->iov_len);
}

static void print_capabilities(GDBusProxy *proxy)
{
	DBusMessageIter iter, subiter;
	const char *uuid;
	uint8_t codec;
	struct iovec caps, meta;

	if (!g_dbus_proxy_get_property(proxy, "UUID", &iter))
		return;

	dbus_message_iter_get_basic(&iter, &uuid);

	if (!g_dbus_proxy_get_property(proxy, "Codec", &iter))
		return;

	dbus_message_iter_get_basic(&iter, &codec);

	if (!g_dbus_proxy_get_property(proxy, "Capabilities", &iter))
		return;

	dbus_message_iter_recurse(&iter, &subiter);

	dbus_message_iter_get_fixed_array(&subiter, &caps.iov_base,
						(int *)&caps.iov_len);

	if (g_dbus_proxy_get_property(proxy, "Metadata", &iter)) {
		dbus_message_iter_recurse(&iter, &subiter);
		dbus_message_iter_get_fixed_array(&subiter, &meta.iov_base,
						  (int *)&meta.iov_len);
	} else {
		meta.iov_base = NULL;
		meta.iov_len = 0;
	}

	print_codec(uuid, codec, &caps, &meta);
}

static void print_preset(struct codec_preset *codec, uint8_t codec_id)
{
	bt_shell_printf("\tPreset %s\n", codec->name);

	if (codec_id == LC3_ID)
		print_lc3_cfg(codec->data.iov_base, codec->data.iov_len);
}

static void print_local_endpoint(struct endpoint *ep)
{
	bt_shell_printf("Endpoint %s\n", ep->path);
	bt_shell_printf("\tUUID %s\n", ep->uuid);
	bt_shell_printf("\tCodec 0x%02x (%u)\n", ep->codec, ep->codec);

	if (ep->caps)
		print_codec(ep->uuid, ep->codec, ep->caps, ep->meta);

	if (ep->codec_preset)
		print_preset(ep->codec_preset, ep->codec);

	if (ep->locations)
		bt_shell_printf("\tLocations 0x%08x (%u)\n", ep->locations,
				ep->locations);
	if (ep->supported_context)
		bt_shell_printf("\tSupportedContext 0x%08x (%u)\n",
				ep->supported_context, ep->supported_context);
	if (ep->context)
		bt_shell_printf("\tContext 0x%08x (%u)\n", ep->context,
				ep->context);
}

static void print_endpoint_properties(GDBusProxy *proxy)
{
	bt_shell_printf("Endpoint %s\n", g_dbus_proxy_get_path(proxy));

	print_property(proxy, "UUID");
	print_property(proxy, "Codec");
	print_capabilities(proxy);
	print_property(proxy, "Device");
	print_property(proxy, "DelayReporting");
	print_property(proxy, "Locations");
	print_property(proxy, "SupportedContext");
	print_property(proxy, "Context");
	print_property(proxy, "QoS");
}

static void print_endpoints(void *data, void *user_data)
{
	print_endpoint_properties(data);
}

static void print_local_endpoints(void *data, void *user_data)
{
	print_local_endpoint(data);
}

static void cmd_show_endpoint(int argc, char *argv[])
{
	GDBusProxy *proxy;

	/* Show all endpoints if no argument is given */
	if (argc != 2) {
		g_list_foreach(endpoints, print_endpoints, NULL);
		g_list_foreach(local_endpoints, print_local_endpoints, NULL);
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
	}

	proxy = g_dbus_proxy_lookup(endpoints, NULL, argv[1],
						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
	if (!proxy) {
		struct endpoint *ep;

		ep = endpoint_find(argv[1]);
		if (ep)
			return print_local_endpoint(ep);

		bt_shell_printf("Endpoint %s not found\n", argv[1]);
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
	}

	print_endpoint_properties(proxy);

	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" } ),
					GDBUS_ARGS({ "cfg", "ay" } ),
					endpoint_select_configuration) },
	{ GDBUS_ASYNC_METHOD("SelectProperties",
					GDBUS_ARGS({ "properties", "a{sv}" } ),
					GDBUS_ARGS({ "properties", "a{sv}" } ),
					endpoint_select_properties) },
	{ GDBUS_ASYNC_METHOD("ClearConfiguration",
					GDBUS_ARGS({ "transport", "o" } ),
					NULL, endpoint_clear_configuration) },
	{ },
};

static void endpoint_free(void *data)
{
	struct endpoint *ep = data;

	util_iov_free(ep->caps, 1);
	util_iov_free(ep->meta, 1);

	if (ep->msg)
		dbus_message_unref(ep->msg);

	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);

	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;
}

struct vendor {
	uint16_t cid;
	uint16_t vid;
} __packed;

static gboolean endpoint_get_vendor(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct endpoint *ep = data;
	struct vendor vendor = { ep->cid, ep->vid };

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &vendor);

	return TRUE;
}

static gboolean endpoint_vendor_exists(const GDBusPropertyTable *property,
							void *data)
{
	struct endpoint *ep = data;

	return ep->cid && ep->vid;
}

static gboolean endpoint_get_metadata(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct endpoint *ep = data;
	DBusMessageIter array;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
				DBUS_TYPE_BYTE_AS_STRING, &array);

	dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
				&ep->meta->iov_base,
				ep->meta->iov_len);

	dbus_message_iter_close_container(iter, &array);

	return TRUE;
}

static gboolean endpoint_metadata_exists(const GDBusPropertyTable *property,
							void *data)
{
	struct endpoint *ep = data;

	return ep->meta ? TRUE : FALSE;
}

static gboolean endpoint_get_locations(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct endpoint *ep = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &ep->locations);

	return TRUE;
}

static gboolean endpoint_locations_exists(const GDBusPropertyTable *property,
							void *data)
{
	struct endpoint *ep = data;

	return ep->supported_context ? TRUE : FALSE;
}

static gboolean
endpoint_get_supported_context(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct endpoint *ep = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16,
					&ep->supported_context);

	return TRUE;
}

static gboolean
endpoint_supported_context_exists(const GDBusPropertyTable *property,
							void *data)
{
	struct endpoint *ep = data;

	return ep->supported_context ? TRUE : FALSE;
}

static gboolean endpoint_get_context(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct endpoint *ep = data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ep->context);

	return TRUE;
}

static gboolean endpoint_context_exists(const GDBusPropertyTable *property,
							void *data)
{
	struct endpoint *ep = data;

	return ep->context ? TRUE : FALSE;
}

static const GDBusPropertyTable endpoint_properties[] = {
	{ "UUID", "s", endpoint_get_uuid, NULL, NULL },
	{ "Codec", "y", endpoint_get_codec, NULL, NULL },
	{ "Capabilities", "ay", endpoint_get_capabilities, NULL, NULL },
	{ "Metadata", "ay", endpoint_get_metadata, NULL,
				endpoint_metadata_exists },
	{ "Vendor", "u", endpoint_get_vendor, NULL, endpoint_vendor_exists },
	{ "Locations", "u", endpoint_get_locations, NULL,
				endpoint_locations_exists },
	{ "SupportedContext", "q", endpoint_get_supported_context, NULL,
				endpoint_supported_context_exists },
	{ "Context", "q", endpoint_get_context, NULL, endpoint_context_exists },
	{ }
};

static void register_endpoint_setup(DBusMessageIter *iter, void *user_data)
{
	struct endpoint *ep = user_data;
	DBusMessageIter dict;
	const char *key = "Capabilities";
	const char *meta = "Metadata";

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &ep->path);

	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);

	if (ep->cid && ep->vid) {
		struct vendor vendor = { ep->cid, ep->vid };

		g_dbus_dict_append_entry(&dict, "Vendor", DBUS_TYPE_UINT32,
						 &vendor);
	}

	if (ep->caps) {
		g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key,
					DBUS_TYPE_BYTE, &ep->caps->iov_base,
					ep->caps->iov_len);

		bt_shell_printf("Capabilities:\n");
		bt_shell_hexdump(ep->caps->iov_base, ep->caps->iov_len);
	}

	if (ep->meta) {
		g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &meta,
				DBUS_TYPE_BYTE, &ep->meta->iov_base,
				ep->meta->iov_len);

		bt_shell_printf("Metadata:\n");
		bt_shell_hexdump(ep->meta->iov_base, ep->meta->iov_len);
	}

	if (ep->locations)
		g_dbus_dict_append_entry(&dict, "Locations", DBUS_TYPE_UINT32,
						&ep->locations);

	if (ep->supported_context)
		g_dbus_dict_append_entry(&dict, "SupportedContext",
						DBUS_TYPE_UINT16,
						&ep->supported_context);

	if (ep->context)
		g_dbus_dict_append_entry(&dict, "Context", DBUS_TYPE_UINT16,
						&ep->context);

	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);
		if (g_list_find(local_endpoints, ep)) {
			local_endpoints = g_list_remove(local_endpoints, ep);
			g_dbus_unregister_interface(dbus_conn, ep->path,
						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
		}
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Endpoint %s registered\n", ep->path);

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static bool media_supports_uuid(GDBusProxy *proxy, const char *uuid)
{
	DBusMessageIter iter, array;

	if (!g_dbus_proxy_get_property(proxy, "SupportedUUIDs", &iter))
		return false;

	dbus_message_iter_recurse(&iter, &array);
	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
		const char *support_uuid;

		dbus_message_iter_get_basic(&array, &support_uuid);

		if (!strcasecmp(uuid, support_uuid))
			return true;

		dbus_message_iter_next(&array);
	}

	return false;
}

static void endpoint_register(struct endpoint *ep)
{
	GList *l;
	int registered = 0;

	if (!g_dbus_register_interface(dbus_conn, ep->path,
					BLUEZ_MEDIA_ENDPOINT_INTERFACE,
					endpoint_methods, NULL,
					endpoint_properties, ep,
					endpoint_free)) {
		goto fail;
	}

	for (l = medias; l; l = g_list_next(l)) {
		if (!media_supports_uuid(l->data, ep->uuid))
			continue;

		if (!g_dbus_proxy_method_call(l->data, "RegisterEndpoint",
						register_endpoint_setup,
						register_endpoint_reply,
						ep, NULL)) {
			g_dbus_unregister_interface(dbus_conn, ep->path,
						BLUEZ_MEDIA_ENDPOINT_INTERFACE);
			goto fail;
		}

		registered++;
	}

	if (!registered)
		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_iso_stream(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	char *endptr = NULL;
	int value;

	if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
		ep->iso_stream = BT_ISO_QOS_STREAM_UNSET;
	} else {
		value = strtol(input, &endptr, 0);

		if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		ep->iso_stream = value;
	}

	endpoint_register(ep);
}

static void endpoint_iso_group(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	char *endptr = NULL;
	int value;

	if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
		ep->iso_group = BT_ISO_QOS_GROUP_UNSET;
	} else {
		value = strtol(input, &endptr, 0);

		if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		ep->iso_group = value;
	}

	bt_shell_prompt_input(ep->path, "CIS (auto/value):",
		endpoint_iso_stream, ep);
}

static void endpoint_context(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	char *endptr = NULL;
	int value;

	value = strtol(input, &endptr, 0);

	if (!endptr || *endptr != '\0' || value > UINT16_MAX) {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	ep->context = value;

	bt_shell_prompt_input(ep->path, "CIG (auto/value):",
		endpoint_iso_group, ep);
}

static void endpoint_supported_context(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	char *endptr = NULL;
	int value;

	value = strtol(input, &endptr, 0);

	if (!endptr || *endptr != '\0' || value > UINT16_MAX) {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	ep->supported_context = value;

	if (ep->broadcast) {
		endpoint_register(ep);
		return;
	}

	bt_shell_prompt_input(ep->path, "Context (value):", endpoint_context,
									ep);
}

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);

	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	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);
}

static void endpoint_max_transports(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	char *endptr = NULL;
	int value;

	if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
		ep->max_transports = UINT8_MAX;
	} else {
		value = strtol(input, &endptr, 0);

		if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		ep->max_transports = value;
	}

	bt_shell_prompt_input(ep->path, "Locations:", endpoint_locations, ep);
}

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;
		bt_shell_prompt_input(ep->path, "Max Transports (auto/value):",
						endpoint_max_transports, ep);
		return;
	} else if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) {
		ep->auto_accept = false;
		bt_shell_prompt_input(ep->path, "Max Transports (auto/value):",
						endpoint_max_transports, ep);
		return;
	} else {
		bt_shell_printf("Invalid input for Auto Accept\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}
}

static void endpoint_set_metadata(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	struct iovec iov;

	if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) {
		util_iov_free(ep->meta, 1);
		ep->meta = NULL;
		goto done;
	}

	iov.iov_base = str2bytearray((char *) input, &iov.iov_len);
	if (iov.iov_base) {
		util_iov_free(ep->meta, 1);
		ep->meta = util_iov_dup(&iov, 1);
	}

done:
	bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):",
					endpoint_auto_accept, ep);
}

static void endpoint_set_capabilities(const char *input, void *user_data)
{
	struct endpoint *ep = user_data;
	struct iovec iov;

	if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) {
		util_iov_free(ep->caps, 1);
		ep->caps = NULL;
		goto done;
	}

	iov.iov_base = str2bytearray((char *) input, &iov.iov_len);
	if (iov.iov_base) {
		util_iov_free(ep->caps, 1);
		ep->caps = util_iov_dup(&iov, 1);
	}

done:
	bt_shell_prompt_input(ep->path, "Enter Metadata (value/no):",
					endpoint_set_metadata, 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 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;
	char *endptr = NULL;

	ep = g_new0(struct endpoint, 1);
	ep->uuid = g_strdup(argv[1]);
	ep->codec = strtol(argv[2], &endptr, 0);
	ep->cid = 0x0000;
	ep->vid = 0x0000;
	ep->path = g_strdup_printf("%s/ep%u", BLUEZ_MEDIA_ENDPOINT_PATH,
					g_list_length(local_endpoints));
	local_endpoints = g_list_append(local_endpoints, ep);

	if (!strcmp(ep->uuid, BCAA_SERVICE_UUID) ||
		!strcmp(ep->uuid, BAA_SERVICE_UUID)) {
		ep->broadcast = true;
	} else {
		ep->broadcast = false;
	}

	if (strrchr(argv[2], ':')) {
		ep->codec = 0xff;
		parse_vendor_codec(argv[2], &ep->vid, &ep->cid);
		ep->preset = new0(struct preset, 1);
		ep->preset->default_preset = codec_preset_add(ep->preset,
								"custom");
	} else {
		ep->preset = find_presets_name(ep->uuid, argv[2]);
	}

	if (argc > 3)
		endpoint_set_capabilities(argv[3], ep);
	else {
		const struct capabilities *cap;

		cap = find_capabilities(ep->uuid, ep->codec);
		if (cap) {
			/* Copy capabilities */
			util_iov_free(ep->caps, 1);
			ep->caps = util_iov_dup(&cap->data, 1);

			/* Copy metadata */
			util_iov_free(ep->meta, 1);
			ep->meta = util_iov_dup(&cap->meta, 1);

			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);
}

static void config_endpoint_setup(DBusMessageIter *iter, void *user_data)
{
	struct endpoint_config *cfg = user_data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
					&cfg->ep->path);

	append_properties(iter, cfg);
}

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 endpoint_config(const char *input, void *user_data)
{
	struct endpoint_config *cfg = user_data;
	uint8_t *data;
	size_t len = 0;

	data = str2bytearray((char *) input, &len);

	util_iov_append(cfg->caps, data, len);
	free(data);

	endpoint_set_config(cfg);
}

static struct endpoint *endpoint_new(const struct capabilities *cap);

static void endpoint_set_metadata_cfg(const char *input, void *user_data)
{
	struct endpoint_config *cfg = user_data;

	if (!strcasecmp(input, "n") || !strcasecmp(input, "no"))
		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:
	endpoint_set_config(cfg);
}

static void config_endpoint_channel_location(const char *input, void *user_data)
{
	struct endpoint_config *cfg = user_data;
	char *endptr = NULL;
	uint32_t location;
	uint8_t channels = 1;

	if (!strcasecmp(input, "n") || !strcasecmp(input, "no"))
		goto add_meta;

	location = strtol(input, &endptr, 0);

	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	/* Add Channel Allocation LTV in capabilities */
	location = cpu_to_le32(location);
	util_ltv_push(cfg->caps, LC3_CONFIG_CHAN_ALLOC_LEN - 1,
			LC3_CONFIG_CHAN_ALLOC, &location);

	/* Adjust the SDU size based on the number of
	 * locations/channels that is being requested.
	 */
	channels = __builtin_popcount(location);
	if (channels > 1)
		cfg->qos.bcast.io_qos.sdu *= channels;

add_meta:
	/* Add metadata */
	bt_shell_prompt_input(cfg->ep->path, "Enter Metadata (value/no):",
			endpoint_set_metadata_cfg, cfg);
}

static void config_endpoint_sync_factor(const char *input, void *user_data)
{
	struct endpoint_config *cfg = user_data;
	char *endptr = NULL;
	int value;
	uint8_t type = LC3_CONFIG_CHAN_ALLOC;
	bool found = false;

	if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
		cfg->qos.bcast.sync_factor = BT_ISO_SYNC_FACTOR;
	} else {
		value = strtol(input, &endptr, 0);

		if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		cfg->qos.bcast.sync_factor = value;
	}

	/* Check if Channel Allocation is present in caps */
	util_ltv_foreach(cfg->caps->iov_base,
			cfg->caps->iov_len, &type,
			ltv_find, &found);

	/* Add Channel Allocation if it is not present in caps */
	if (!found) {
		bt_shell_prompt_input(cfg->ep->path,
				"Enter channel location (value/no):",
				config_endpoint_channel_location, cfg);
	} else {
		/* Add metadata */
		bt_shell_prompt_input(cfg->ep->path,
				"Enter Metadata (value/no):",
				endpoint_set_metadata_cfg, cfg);
	}
}

static void config_endpoint_iso_stream(const char *input, void *user_data)
{
	struct endpoint_config *cfg = user_data;
	char *endptr = NULL;
	int value;

	if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
		cfg->ep->iso_stream = BT_ISO_QOS_STREAM_UNSET;
	} else {
		value = strtol(input, &endptr, 0);

		if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		cfg->ep->iso_stream = value;
	}

	bt_shell_prompt_input(cfg->ep->path,
			"Enter sync factor (value/auto):",
			config_endpoint_sync_factor, cfg);
}

static void config_endpoint_iso_group(const char *input, void *user_data)
{
	struct endpoint_config *cfg = user_data;
	char *endptr = NULL;
	int value;

	if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
		cfg->ep->iso_group = BT_ISO_QOS_GROUP_UNSET;
	} else {
		value = strtol(input, &endptr, 0);

		if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		cfg->ep->iso_group = value;
	}

	bt_shell_prompt_input(cfg->ep->path,
		"BIS (auto/value):",
		config_endpoint_iso_stream, cfg);
}

static void endpoint_set_config_bcast(struct endpoint_config *cfg)
{
	cfg->ep->bcode = g_new0(struct iovec, 1);
	util_iov_append(cfg->ep->bcode, bcast_code,
			sizeof(bcast_code));

	if ((strcmp(cfg->ep->uuid, BAA_SERVICE_UUID) == 0)) {
		/* A broadcast sink endpoint config does not need
		 * user input.
		 */
		endpoint_set_config(cfg);
		return;
	}

	bt_shell_prompt_input(cfg->ep->path,
		"BIG (auto/value):",
		config_endpoint_iso_group, 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);

	/* Search for the remote endpoint name on DBUS */
	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;
	}

	/* Search for the local endpoint */
	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 = preset_find_name(cfg->ep->preset, argv[3]);
		if (!preset) {
			bt_shell_printf("Preset %s not found\n", argv[3]);
			goto fail;
		}

		cfg->caps = g_new0(struct iovec, 1);
		/* 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;

		if (cfg->ep->broadcast)
			endpoint_set_config_bcast(cfg);
		else
			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 custom_metadata(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct iovec *meta = (void *)&p->meta;

	if (!strcasecmp(input, "n") || !strcasecmp(input, "no"))
		goto done;

	meta->iov_base = str2bytearray((void *)input, &meta->iov_len);
	if (!meta->iov_base) {
		bt_shell_printf("Invalid metadata %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

done:
	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void custom_delay(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct bt_bap_qos *qos = (void *)&p->qos;
	char *endptr = NULL;

	if (!p->target_latency)
		qos->bcast.delay = strtol(input, &endptr, 0);
	else
		qos->ucast.delay = strtol(input, &endptr, 0);

	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_prompt_input("Metadata", "Enter Metadata (value/no):",
				custom_metadata, user_data);
}

static void custom_latency(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct bt_bap_qos *qos = (void *)&p->qos;
	char *endptr = NULL;

	if (!p->target_latency)
		qos->bcast.io_qos.latency = strtol(input, &endptr, 0);
	else
		qos->ucast.io_qos.latency = strtol(input, &endptr, 0);

	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_prompt_input("QoS", "Enter Presentation Delay (us):",
					custom_delay, user_data);
}

static void custom_rtn(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct bt_bap_qos *qos = (void *)&p->qos;
	char *endptr = NULL;

	if (!p->target_latency)
		qos->bcast.io_qos.rtn = strtol(input, &endptr, 0);
	else
		qos->ucast.io_qos.rtn = strtol(input, &endptr, 0);

	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_prompt_input("QoS", "Enter Max Transport Latency (ms):",
					custom_latency, user_data);
}

static void custom_sdu(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct bt_bap_qos *qos = (void *)&p->qos;
	char *endptr = NULL;

	if (!p->target_latency)
		qos->bcast.io_qos.sdu = strtol(input, &endptr, 0);
	else
		qos->ucast.io_qos.sdu = strtol(input, &endptr, 0);

	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_prompt_input("QoS", "Enter RTN:", custom_rtn, user_data);
}

static void custom_phy(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct bt_bap_io_qos *qos;

	if (!p->target_latency)
		qos = &p->qos.bcast.io_qos;
	else
		qos = &p->qos.ucast.io_qos;

	if (!strcmp(input, "1M"))
		qos->phy = 0x01;
	else if (!strcmp(input, "2M"))
		qos->phy = 0x02;
	else {
		char *endptr = NULL;
		uint8_t phy = strtol(input, &endptr, 0);

		if (!endptr || *endptr != '\0') {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		switch (phy) {
		case 0x01:
		case 0x02:
			qos->phy = phy;
			break;
		default:
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}
	}

	bt_shell_prompt_input("QoS", "Enter Max SDU:", custom_sdu, user_data);
}

static void custom_framing(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	uint8_t *framing;

	if (!p->target_latency)
		framing = &p->qos.bcast.framing;
	else
		framing = &p->qos.ucast.framing;

	if (!strcasecmp(input, "Unframed"))
		*framing = 0x00;
	else if (!strcasecmp(input, "Framed"))
		*framing = 0x01;
	else {
		char *endptr = NULL;

		*framing = strtol(input, &endptr, 0);
		if (!endptr || *endptr != '\0') {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}
	}

	bt_shell_prompt_input("QoS", "Enter PHY (1M, 2M):", custom_phy,
							user_data);
}

static void custom_interval(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	char *endptr = NULL;
	struct bt_bap_io_qos *qos;

	if (!p->target_latency)
		qos = &p->qos.bcast.io_qos;
	else
		qos = &p->qos.ucast.io_qos;

	qos->interval = strtol(input, &endptr, 0);
	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_prompt_input("QoS", "Enter Framing (Unframed, Framed):",
				custom_framing, user_data);
}

static void custom_target_latency(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;

	if (!strcasecmp(input, "Low"))
		p->target_latency = 0x01;
	else if (!strcasecmp(input, "Balance"))
		p->target_latency = 0x02;
	else if (!strcasecmp(input, "High"))
		p->target_latency = 0x03;
	else {
		char *endptr = NULL;

		p->target_latency = strtol(input, &endptr, 0);
		if (!endptr || *endptr != '\0') {
			bt_shell_printf("Invalid argument: %s\n", input);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}
	}

	bt_shell_prompt_input("QoS", "Enter SDU Interval (us):",
					custom_interval, user_data);
}

static void custom_length(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct iovec *iov = (void *)&p->data;
	uint8_t ltv[4] = { 0x03, LC3_CONFIG_FRAME_LEN };
	uint16_t len;
	char *endptr = NULL;

	len = strtol(input, &endptr, 0);
	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	ltv[2] = len;
	ltv[3] = len >> 8;

	util_iov_append(iov, ltv, sizeof(ltv));

	bt_shell_prompt_input("QoS", "Enter Target Latency "
				"(Low, Balance, High):",
				custom_target_latency, user_data);
}

static void custom_location(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct iovec *iov = (void *)&p->data;
	uint32_t location;
	char *endptr = NULL;

	location = strtol(input, &endptr, 0);
	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	/* Only add Channel Allocation if set */
	if (location) {
		uint8_t ltv[6] = { 0x05, LC3_CONFIG_CHAN_ALLOC };

		location = cpu_to_le32(location);
		memcpy(&ltv[2], &location, sizeof(location));
		util_iov_append(iov, ltv, sizeof(ltv));
	}

	bt_shell_prompt_input("Codec", "Enter frame length:",
					custom_length, user_data);
}

static uint8_t val2duration(uint32_t val)
{
	switch (val) {
	case 7:
		return 0x00;
	case 10:
		return 0x01;
	default:
		return 0xff;
	}
}

static void custom_duration(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct iovec *iov = (void *)&p->data;
	uint8_t ltv[3] = { 0x02, LC3_CONFIG_DURATION, 0x00 };
	char *endptr = NULL;
	uint32_t val;

	val = strtol(input, &endptr, 0);
	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (strncmp(input, "0x", 2))
		ltv[2] = val2duration(val);
	else
		ltv[2] = val;

	if (ltv[2] == 0xff) {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	util_iov_append(iov, ltv, sizeof(ltv));

	bt_shell_prompt_input("Codec", "Enter channel allocation:",
					custom_location, user_data);
}

static uint8_t val2freq(uint32_t val)
{
	switch (val) {
	case 8:
		return 0x01;
	case 11:
		return 0x02;
	case 16:
		return 0x03;
	case 22:
		return 0x04;
	case 24:
		return 0x05;
	case 32:
		return 0x06;
	case 44:
		return 0x07;
	case 48:
		return 0x08;
	case 88:
		return 0x09;
	case 96:
		return 0x0a;
	case 174:
		return 0x0b;
	case 192:
		return 0x0c;
	case 384:
		return 0x0d;
	default:
		return 0x00;
	}
}

static void custom_frequency(const char *input, void *user_data)
{
	struct codec_preset *p = user_data;
	struct iovec *iov = (void *)&p->data;
	uint8_t ltv[3] = { 0x02, LC3_CONFIG_FREQ, 0x00 };
	uint32_t val;
	char *endptr = NULL;

	val = strtol(input, &endptr, 0);
	if (!endptr || *endptr != '\0') {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (strncmp(input, "0x", 2))
		ltv[2] = val2freq(val);
	else
		ltv[2] = val;

	if (!ltv[2]) {
		bt_shell_printf("Invalid argument: %s\n", input);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	/* Reset iov to start over the codec configuration */
	free(iov->iov_base);
	iov->iov_base = NULL;
	iov->iov_len = 0;
	util_iov_append(iov, ltv, sizeof(ltv));

	bt_shell_prompt_input("Codec", "Enter frame duration (ms):",
				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;

	for (i = 0; i < preset->num_presets; i++) {
		p = &preset->presets[i];

		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;
	struct codec_preset *default_preset = NULL;
	struct endpoint *ep = NULL;

	preset = find_presets_name(argv[1], argv[2]);
	if (!preset) {
		ep = endpoint_find(argv[1]);
		if (!ep) {
			bt_shell_printf("No preset found\n");
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}
		preset = ep->preset;
		argv++;
		argc--;
	} else {
		argv += 2;
		argc -= 2;
	}

	if (argc > 1) {
		default_preset = codec_preset_add(preset, argv[1]);
		if (!default_preset) {
			bt_shell_printf("Preset %s not found\n", argv[1]);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		if (ep)
			ep->codec_preset = default_preset;
		else
			preset->default_preset = default_preset;

		if (argc > 2) {
			struct codec_preset *alt_preset;
			struct iovec *caps = (void *)&default_preset->data;
			struct iovec *meta = (void *)&default_preset->meta;

			/* Check if and alternative preset was given */
			alt_preset = preset_find_name(preset, argv[2]);
			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;
			}

			/* Check if Codec Configuration was entered */
			if (strlen(argv[2])) {
				caps->iov_base = str2bytearray(argv[2],
							      &caps->iov_len);
				if (!caps->iov_base) {
					bt_shell_printf("Invalid configuration "
								"%s\n",
								argv[2]);
					return bt_shell_noninteractive_quit(
								EXIT_FAILURE);
				}
			}

			/* Check if metadata was entered */
			if (argc > 3) {
				meta->iov_base = str2bytearray(argv[3],
								&meta->iov_len);
				if (!meta->iov_base) {
					bt_shell_printf("Invalid metadata %s\n",
							argv[5]);
					return bt_shell_noninteractive_quit(
								EXIT_FAILURE);
				}
			}

			/* If configuration was left empty then ask the
			 * parameters.
			 */
			if (!caps->iov_base || !caps->iov_len)
				goto enter_cc;

			bt_shell_prompt_input("QoS", "Enter Target Latency "
						"(Low, Balance, High):",
						custom_target_latency,
						default_preset);

			return;
		}
	} else if (ep && (ep->codec_preset))
		print_preset(ep->codec_preset, ep->codec);
	else
		print_presets(preset);

enter_cc:
	if (default_preset && default_preset->custom) {
		bt_shell_prompt_input("Codec", "Enter frequency (Khz):",
					custom_frequency, default_preset);
		return;
	}

	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[:company]> [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",      "<endpoint>/<UUID> [codec[:company]] [preset] "
						"[codec config] [metadata]",
						cmd_presets_endpoint,
						"List or add presets",
						uuid_generator },
	{} },
};

static void endpoint_init_bcast(struct endpoint *ep)
{
	if (!strcmp(ep->uuid, BAA_SERVICE_UUID)) {
		ep->locations = EP_SNK_LOCATIONS;
		ep->supported_context = EP_SUPPORTED_SNK_CTXT;
	} else {
		ep->locations = EP_SRC_LOCATIONS;
		ep->supported_context = EP_SUPPORTED_SRC_CTXT;
	}
}

static void endpoint_init_ucast(struct endpoint *ep)
{
	if (!strcmp(ep->uuid, PAC_SINK_UUID)) {
		ep->locations = EP_SNK_LOCATIONS;
		ep->supported_context = EP_SUPPORTED_SNK_CTXT;
		ep->context = EP_SNK_CTXT;
	} else if (!strcmp(ep->uuid, PAC_SOURCE_UUID)) {
		ep->locations = EP_SRC_LOCATIONS;
		ep->supported_context = EP_SUPPORTED_SRC_CTXT;
		ep->context = EP_SRC_CTXT;
	}
}

static void endpoint_init_defaults(struct endpoint *ep)
{
	ep->preset = find_presets(ep->uuid, ep->codec, ep->vid, ep->cid);
	ep->max_transports = UINT8_MAX;
	ep->auto_accept = true;

	if (!strcmp(ep->uuid, A2DP_SOURCE_UUID) ||
			!strcmp(ep->uuid, A2DP_SOURCE_UUID))
		return;

	ep->iso_group = BT_ISO_QOS_GROUP_UNSET;
	ep->iso_stream = BT_ISO_QOS_STREAM_UNSET;

	ep->broadcast = (strcmp(ep->uuid, BCAA_SERVICE_UUID) &&
			strcmp(ep->uuid, BAA_SERVICE_UUID)) ? false : true;

	if (ep->broadcast)
		endpoint_init_bcast(ep);
	else
		endpoint_init_ucast(ep);
}

static struct endpoint *endpoint_new(const struct capabilities *cap)
{
	struct endpoint *ep;

	ep = new0(struct endpoint, 1);
	ep->uuid = g_strdup(cap->uuid);
	ep->codec = cap->codec_id;
	ep->path = g_strdup_printf("%s/%s", BLUEZ_MEDIA_ENDPOINT_PATH,
				cap->name);
	/* Copy capabilities */
	ep->caps = util_iov_dup(&cap->data, 1);
	/* Copy metadata */
	ep->meta = util_iov_dup(&cap->meta, 1);

	local_endpoints = g_list_append(local_endpoints, ep);

	return ep;
}

static void register_endpoints(GDBusProxy *proxy)
{
	struct endpoint *ep;
	size_t i;

	for (i = 0; i < ARRAY_SIZE(caps); i++) {
		const struct capabilities *cap = &caps[i];

		if (!media_supports_uuid(proxy, cap->uuid))
			continue;

		ep = endpoint_new(cap);
		endpoint_init_defaults(ep);
		endpoint_register(ep);
	}
}

static void media_added(GDBusProxy *proxy)
{
	medias = g_list_append(medias, proxy);

	print_media(proxy, COLORED_NEW);

	if (bt_shell_get_env("AUTO_REGISTER_ENDPOINT"))
		register_endpoints(proxy);
}

static void player_added(GDBusProxy *proxy)
{
	players = g_list_append(players, proxy);

	if (default_player == NULL)
		default_player = proxy;

	print_player(proxy, COLORED_NEW);
}

static void print_folder(GDBusProxy *proxy, const char *description)
{
	const char *path;

	path = g_dbus_proxy_get_path(proxy);

	bt_shell_printf("%s%s%sFolder %s\n", description ? "[" : "",
					description ? : "",
					description ? "] " : "",
					path);
}

static void folder_added(GDBusProxy *proxy)
{
	folders = g_list_append(folders, proxy);

	print_folder(proxy, COLORED_NEW);
}

static void print_item(GDBusProxy *proxy, const char *description)
{
	const char *path, *name;
	DBusMessageIter iter;

	path = g_dbus_proxy_get_path(proxy);

	if (g_dbus_proxy_get_property(proxy, "Name", &iter))
		dbus_message_iter_get_basic(&iter, &name);
	else
		name = "<unknown>";

	bt_shell_printf("%s%s%sItem %s %s\n", description ? "[" : "",
					description ? : "",
					description ? "] " : "",
					path, name);
}

static void item_added(GDBusProxy *proxy)
{
	items = g_list_append(items, 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 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;

	interface = g_dbus_proxy_get_interface(proxy);

	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);
	else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
		transport_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)
{
	print_player(proxy, COLORED_DEL);

	if (default_player == proxy)
		default_player = NULL;

	players = g_list_remove(players, proxy);
}

static void folder_removed(GDBusProxy *proxy)
{
	folders = g_list_remove(folders, proxy);

	print_folder(proxy, COLORED_DEL);
}

static void item_removed(GDBusProxy *proxy)
{
	items = g_list_remove(items, 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 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;

	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);
	if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
		transport_removed(proxy);
}

static void player_property_changed(GDBusProxy *proxy, const char *name,
						DBusMessageIter *iter)
{
	char *str;

	str = proxy_description(proxy, "Player", COLORED_CHG);
	print_iter(str, name, iter);
	g_free(str);
}

static void folder_property_changed(GDBusProxy *proxy, const char *name,
						DBusMessageIter *iter)
{
	char *str;

	str = proxy_description(proxy, "Folder", COLORED_CHG);
	print_iter(str, name, iter);
	g_free(str);
}

static void item_property_changed(GDBusProxy *proxy, const char *name,
						DBusMessageIter *iter)
{
	char *str;

	str = proxy_description(proxy, "Item", COLORED_CHG);
	print_iter(str, name, iter);
	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 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 (queue_find(ep->transports, match_str, path))
			return ep;
	}

	return NULL;
}

static GDBusProxy *find_link_by_proxy(GDBusProxy *proxy)
{
	DBusMessageIter iter, array;

	if (!g_dbus_proxy_get_property(proxy, "Links", &iter))
		return NULL;

	dbus_message_iter_recurse(&iter, &array);

	while (dbus_message_iter_get_arg_type(&array) ==
				DBUS_TYPE_OBJECT_PATH) {
		const char *transport;

		dbus_message_iter_get_basic(&array, &transport);

		proxy = g_dbus_proxy_lookup(transports, NULL, transport,
					BLUEZ_MEDIA_TRANSPORT_INTERFACE);
		if (proxy)
			return proxy;
	}

	return NULL;
}

static void transport_close(struct transport *transport)
{
	if (transport->fd < 0)
		return;

	close(transport->fd);
	transport->fd = -1;

	free(transport->filename);
}

static void transport_free(void *data)
{
	struct transport *transport = data;

	io_destroy(transport->timer_io);
	io_destroy(transport->io);
	free(transport);
}

static bool transport_disconnected(struct io *io, void *user_data)
{
	struct transport *transport = user_data;

	bt_shell_printf("Transport fd disconnected\n");

	if (queue_remove(ios, transport))
		transport_free(transport);

	return false;
}

static bool transport_recv(struct io *io, void *user_data)
{
	struct transport *transport = user_data;
	uint8_t buf[1024];
	int ret, len;

	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);
		return true;
	}

	bt_shell_echo("[seq %d] recv: %u bytes", transport->seq, ret);

	transport->seq++;

	if (transport->filename) {
		len = write(transport->fd, buf, ret);
		if (len < 0)
			bt_shell_printf("Unable to write: %s (%d)\n",
						strerror(errno), -errno);
	}

	return true;
}

static void transport_new(GDBusProxy *proxy, int sk, uint16_t mtu[2])
{
	struct transport *transport;

	transport = new0(struct transport, 1);
	transport->proxy = proxy;
	transport->sk = sk;
	transport->mtu[0] = mtu[0];
	transport->mtu[1] = mtu[1];
	transport->io = io_new(sk);
	transport->fd = -1;

	io_set_disconnect_handler(transport->io, transport_disconnected,
							transport, NULL);
	io_set_read_handler(transport->io, transport_recv, transport, NULL);

	if (!ios)
		ios = queue_new();

	queue_push_tail(ios, transport);
}

static void ep_set_acquiring(struct endpoint *ep, GDBusProxy *proxy, bool value)
{
	bt_shell_printf("Transport %s %s\n", g_dbus_proxy_get_path(proxy),
			value ? "acquiring" : "acquiring complete");

	if (value && !ep->acquiring)
		ep->acquiring = queue_new();

	if (value)
		queue_push_tail(ep->acquiring, proxy);
	else
		queue_remove(ep->acquiring, proxy);
}

static void transport_set_acquiring(GDBusProxy *proxy, bool value)
{
	struct endpoint *ep;
	GDBusProxy *link;

	ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy));
	if (!ep)
		return;

	ep_set_acquiring(ep, proxy, value);

	if (!ep->broadcast) {
		link = find_link_by_proxy(proxy);
		if (link) {
			ep = find_ep_by_transport(g_dbus_proxy_get_path(link));
			if (!ep)
				return;

			ep_set_acquiring(ep, link, value);
		}
	}
}

static void acquire_reply(DBusMessage *message, void *user_data)
{
	GDBusProxy *proxy = user_data;
	DBusError error;
	int sk;
	uint16_t mtu[2];

	transport_set_acquiring(proxy, false);

	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, &sk,
				   DBUS_TYPE_UINT16, &mtu[0],
				   DBUS_TYPE_UINT16, &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 %u:%u\n", sk, mtu[0],
								mtu[1]);

	transport_new(proxy, sk, mtu);

	return bt_shell_noninteractive_quit(EXIT_FAILURE);
}

static void free_transport_select_args(struct transport_select_args *args)
{
	queue_destroy(args->links, NULL);
	queue_destroy(args->selecting, NULL);
	g_free(args);
}

static void select_reply(DBusMessage *message, void *user_data)
{
	DBusError error;
	struct transport_select_args *args = user_data;
	GDBusProxy *link;

	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);
		free_transport_select_args(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Select successful\n");

	/* Select next link */
	link = queue_pop_head(args->selecting);
	if (link) {
		args->proxy = link;
		transport_select(args);
	} else {
		free_transport_select_args(args);
		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("Unselect successful\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}


static void prompt_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, proxy, NULL)) {
			transport_set_acquiring(proxy, true);
			return;
		}
		bt_shell_printf("Failed acquire transport\n");
	}
}

static void transport_acquire(GDBusProxy *proxy, bool prompt)
{
	struct endpoint *ep;
	GDBusProxy *link;

	/* 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) {
		bt_shell_printf("transport endpoint not found\n");
		return;
	}

	if (queue_find(ep->acquiring, NULL, proxy)) {
		bt_shell_printf("acquire already in progress\n");
		return;
	}

	if (!ep->broadcast) {
		link = find_link_by_proxy(proxy);
		if (link) {
			ep = find_ep_by_transport(g_dbus_proxy_get_path(link));
			/* if link already acquiring wait it to be complete */
			if (!ep || queue_find(ep->acquiring, NULL, link))
				return;
		}
	}

	if (ep->auto_accept || !prompt) {
		if (!prompt)
			bt_shell_printf("auto acquiring...\n");
		if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
						acquire_reply, proxy, NULL)) {
			bt_shell_printf("failed acquire transport\n");
			return;
		}

		transport_set_acquiring(proxy, true);
		return;
	}

	bt_shell_prompt_input(g_dbus_proxy_get_path(proxy), "acquire (yes/no):",
					prompt_acquire, proxy);
}

static void transport_property_changed(GDBusProxy *proxy, const char *name,
						DBusMessageIter *iter)
{
	char *str;

	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;

	transport_acquire(proxy, true);
}

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, BLUEZ_MEDIA_PLAYER_INTERFACE))
		player_property_changed(proxy, name, iter);
	else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE))
		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);
	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 print_configuration(GDBusProxy *proxy)
{
	DBusMessageIter iter, subiter;
	const char *uuid;
	uint8_t codec;
	uint8_t *data;
	int len;

	if (!g_dbus_proxy_get_property(proxy, "UUID", &iter))
		return;

	dbus_message_iter_get_basic(&iter, &uuid);

	if (!g_dbus_proxy_get_property(proxy, "Codec", &iter))
		return;

	dbus_message_iter_get_basic(&iter, &codec);

	if (!g_dbus_proxy_get_property(proxy, "Configuration", &iter))
		return;

	dbus_message_iter_recurse(&iter, &subiter);

	dbus_message_iter_get_fixed_array(&subiter, &data, &len);

	if (!strcasecmp(uuid, A2DP_SINK_UUID) ||
			!strcasecmp(uuid, A2DP_SOURCE_UUID)) {
		print_a2dp_codec(codec, (void *)data, len);
		return;
	}

	if (codec != LC3_ID) {
		print_property(proxy, "Configuration");
		return;
	}

	print_lc3_cfg(data, len);

	if (!g_dbus_proxy_get_property(proxy, "Metadata", &iter))
		return;

	dbus_message_iter_recurse(&iter, &subiter);

	dbus_message_iter_get_fixed_array(&subiter, &data, &len);

	print_lc3_meta(data, len);
}

static void print_transport_properties(GDBusProxy *proxy)
{
	bt_shell_printf("Transport %s\n", g_dbus_proxy_get_path(proxy));

	print_property(proxy, "UUID");
	print_property(proxy, "Codec");
	print_configuration(proxy);
	print_property(proxy, "Device");
	print_property(proxy, "State");
	print_property(proxy, "Delay");
	print_property(proxy, "Volume");
	print_property(proxy, "Endpoint");
	print_property(proxy, "QoS");
	print_property(proxy, "Location");
	print_property(proxy, "Links");
}

static void print_transports(void *data, void *user_data)
{
	print_transport_properties(data);
}

static void cmd_show_transport(int argc, char *argv[])
{
	GDBusProxy *proxy;

	/* Show all transports if no argument is given */
	if (argc != 2) {
		g_list_foreach(transports, print_transports, NULL);
		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
	}

	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);
	}

	print_transport_properties(proxy);

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static bool match_proxy(const void *data, const void *user_data)
{
	const struct transport *transport = data;
	const GDBusProxy *proxy = user_data;

	return transport->proxy == proxy;
}

static struct transport *find_transport(GDBusProxy *proxy)
{
	return queue_find(ios, match_proxy, proxy);
}

static void cmd_acquire_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_acquire(proxy, false);
	}

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void set_bcode_cb(const DBusError *error, void *user_data)
{
	struct transport_select_args *args = user_data;

	if (dbus_error_is_set(error)) {
		bt_shell_printf("Failed to set broadcast code: %s\n",
								error->name);
		free_transport_select_args(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Setting broadcast code succeeded\n");

	transport_select(args);
}

static void set_bcode(const char *input, void *user_data)
{
	struct transport_select_args *args = user_data;
	char *bcode;

	if (!strcasecmp(input, "n") || !strcasecmp(input, "no"))
		bcode = g_new0(char, 16);
	else
		bcode = g_strdup(input);

	if (g_dbus_proxy_set_property_dict(args->proxy, "QoS",
				set_bcode_cb, user_data,
				NULL, "BCode", DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
				strlen(bcode), bcode, NULL) == FALSE) {
		bt_shell_printf("Setting broadcast code failed\n");
		g_free(bcode);
		free_transport_select_args(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	g_free(bcode);
}

static void transport_select(struct transport_select_args *args)
{
	if (!g_dbus_proxy_method_call(args->proxy, "Select", NULL,
					select_reply, args, NULL)) {
		bt_shell_printf("Failed select transport\n");
		free_transport_select_args(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}
}

static void transport_set_bcode(struct transport_select_args *args)
{
	DBusMessageIter iter, array, entry, value;
	unsigned char encryption;
	const char *key;

	if (g_dbus_proxy_get_property(args->proxy, "QoS", &iter) == FALSE) {
		free_transport_select_args(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	dbus_message_iter_recurse(&iter, &array);

	while (dbus_message_iter_get_arg_type(&array) !=
						DBUS_TYPE_INVALID) {
		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		if (!strcasecmp(key, "Encryption")) {
			dbus_message_iter_next(&entry);
			dbus_message_iter_recurse(&entry, &value);
			dbus_message_iter_get_basic(&value, &encryption);
			if (encryption == 1) {
				bt_shell_prompt_input("",
					"Enter brocast code[value/no]:",
					set_bcode, args);
				return;
			}
			break;
		}
		dbus_message_iter_next(&array);
	}

	/* Go straight to selecting transport, if Broadcast Code
	 * is not required.
	 */
	transport_select(args);
}

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 set_links_cb(const DBusError *error, void *user_data)
{
	struct transport_select_args *args = user_data;
	GDBusProxy *link;

	link = queue_pop_head(args->links);

	if (queue_isempty(args->links)) {
		queue_destroy(args->links, NULL);
		args->links = NULL;
	}

	if (dbus_error_is_set(error)) {
		bt_shell_printf("Failed to set link %s: %s\n",
						g_dbus_proxy_get_path(link),
						error->name);
		free_transport_select_args(args);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Successfully linked transport %s\n",
						g_dbus_proxy_get_path(link));

	if (!args->selecting)
		args->selecting = queue_new();

	/* Enqueue link to mark that it is ready to be selected */
	queue_push_tail(args->selecting, link);

	/* Continue setting the remaining links */
	transport_set_links(args);
}

static void transport_set_links(struct transport_select_args *args)
{
	GDBusProxy *link;
	const char *path;

	link = queue_peek_head(args->links);
	if (link) {
		path = g_dbus_proxy_get_path(link);

		if (g_dbus_proxy_set_property_array(args->proxy, "Links",
					DBUS_TYPE_OBJECT_PATH,
					&path, 1, set_links_cb,
					args, NULL) == FALSE) {
			bt_shell_printf("Linking transport %s failed\n", path);
			free_transport_select_args(args);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		return;
	}

	/* If all links have been set, check is transport requires the
	 * user to provide a Broadcast Code.
	 */
	transport_set_bcode(args);
}

static void cmd_select_transport(int argc, char *argv[])
{
	GDBusProxy *link = NULL;
	struct transport_select_args *args;
	int i;

	args = g_new0(struct transport_select_args, 1);

	for (i = 1; i < argc; i++) {
		link = g_dbus_proxy_lookup(transports, NULL, argv[i],
					BLUEZ_MEDIA_TRANSPORT_INTERFACE);
		if (!link) {
			bt_shell_printf("Transport %s not found\n", argv[i]);
			goto fail;
		}

		if (find_transport(link)) {
			bt_shell_printf("Transport %s already acquired\n",
					argv[i]);
			goto fail;
		}

		if (!args->proxy) {
			args->proxy = link;
			continue;
		}

		if (!args->links)
			args->links = queue_new();

		/* Enqueue all links */
		queue_push_tail(args->links, link);
	}

	/* Link streams before selecting one by one */
	transport_set_links(args);

	return;

fail:
	free_transport_select_args(args);
	return bt_shell_noninteractive_quit(EXIT_FAILURE);
}

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;
	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);
	}

	if (queue_remove(ios, transport))
		transport_free(transport);

	bt_shell_printf("Release successful\n");

	return bt_shell_noninteractive_quit(EXIT_FAILURE);
}

static void cmd_release_transport(int argc, char *argv[])
{
	GDBusProxy *proxy;
	int i;

	for (i = 1; i < argc; i++) {
		struct transport *transport;

		proxy = g_dbus_proxy_lookup(transports, NULL, argv[i],
					BLUEZ_MEDIA_TRANSPORT_INTERFACE);
		if (!proxy) {
			bt_shell_printf("Transport %s not found\n", argv[1]);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		transport = find_transport(proxy);
		if (!transport) {
			bt_shell_printf("Transport %s not acquired\n", argv[i]);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		if (!g_dbus_proxy_method_call(proxy, "Release", NULL,
					release_reply, transport, 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 flags)
{
	int fd = -1;

	bt_shell_printf("Opening %s ...\n", filename);

	if (flags & O_CREAT)
		fd = open(filename, flags, 0755);
	else
		fd = open(filename, flags);

	if (fd <= 0)
		bt_shell_printf("Can't open file %s: %s\n", filename,
						strerror(errno));

	return fd;
}

static int elapsed_time(bool reset, int *secs, int *nsecs)
{
	static struct timespec start;
	struct timespec curr;

	if (reset) {
		if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) {
			bt_shell_printf("clock_gettime: %s (%d)",
						strerror(errno), errno);
			return -errno;
		}
	}

	if (clock_gettime(CLOCK_MONOTONIC, &curr) < 0) {
		bt_shell_printf("clock_gettime: %s (%d)", strerror(errno),
						errno);
		return -errno;
	}

	*secs = curr.tv_sec - start.tv_sec;
	*nsecs = curr.tv_nsec - start.tv_nsec;
	if (*nsecs < 0) {
		(*secs)--;
		*nsecs += 1000000000;
	}

	return 0;
}

static int transport_send_seq(struct transport *transport, int fd, uint32_t num)
{
	uint8_t *buf;
	uint32_t i;

	if (!num)
		return 0;

	buf = malloc(transport->mtu[1]);
	if (!buf)
		return -ENOMEM;

	for (i = 0; i < num; i++, transport->seq++) {
		ssize_t ret;
		int secs = 0, nsecs = 0;
		off_t offset;

		ret = read(fd, buf, transport->mtu[1]);
		if (ret <= 0) {
			if (ret < 0)
				bt_shell_printf("read failed: %s (%d)",
						strerror(errno), errno);
			free(buf);
			return ret;
		}

		ret = send(transport->sk, buf, ret, 0);
		if (ret <= 0) {
			bt_shell_printf("send failed: %s (%d)",
							strerror(errno), errno);
			free(buf);
			return -errno;
		}

		elapsed_time(!transport->seq, &secs, &nsecs);

		if (!transport->seq && fstat(fd, &transport->stat) < 0) {
			bt_shell_printf("fstat failed: %s (%d)",
							strerror(errno), errno);
			free(buf);
			return -errno;
		}

		offset = lseek(fd, 0, SEEK_CUR);

		bt_shell_echo("[seq %d %d.%03ds] send: %lld/%lld bytes",
				transport->seq, secs,
				(nsecs + 500000) / 1000000,
				(long long)offset,
				(long long)transport->stat.st_size);
	}

	free(buf);

	return i;
}

static bool transport_timer_read(struct io *io, void *user_data)
{
	struct transport *transport = user_data;
	struct bt_iso_qos qos;
	socklen_t len;
	int ret, fd;
	uint64_t exp;

	if (transport->fd < 0)
		return false;

	fd = io_get_fd(io);
	if (fd < 0) {
		bt_shell_printf("io_get_fd() returned %d\n", fd);
		return false;
	}

	ret = read(fd, &exp, sizeof(exp));
	if (ret < 0) {
		bt_shell_printf("Failed to read: %s (%d)\n", strerror(errno),
								-errno);
		return false;
	}

	/* Read QoS if available */
	memset(&qos, 0, sizeof(qos));
	len = sizeof(qos);
	if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos,
							&len) < 0) {
		bt_shell_printf("Failed to getsockopt(BT_ISO_QOS): %s (%d)\n",
					strerror(errno), -errno);
		return false;
	}

	ret = transport_send_seq(transport, transport->fd, transport->num);
	if (ret < 0) {
		bt_shell_printf("Unable to send: %s (%d)\n",
					strerror(-ret), ret);
		return false;
	}

	if (!ret) {
		transport_close(transport);
		return false;
	}

	return true;
}

static int transport_send(struct transport *transport, int fd,
					struct bt_iso_io_qos *qos)
{
	struct itimerspec ts;
	int timer_fd;

	transport->seq = 0;

	if (!qos)
		return transport_send_seq(transport, fd, UINT32_MAX);

	if (transport->fd >= 0)
		return -EALREADY;

	timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);
	if (timer_fd < 0)
		return -errno;

	/* Send data in bursts of
	 * num = ROUND_CLOSEST(Transport_Latency (ms) / SDU_Interval (us))
	 * with average data rate = 1 packet / SDU_Interval
	 */
	transport->num = ROUND_CLOSEST(qos->latency * 1000, qos->interval);
	if (!transport->num)
		transport->num = 1;

	memset(&ts, 0, sizeof(ts));
	ts.it_value.tv_nsec = 1;
	ts.it_interval.tv_nsec = transport->num * qos->interval * 1000;

	if (timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &ts, NULL) < 0) {
		close(timer_fd);
		return -errno;
	}

	transport->fd = fd;
	transport->timer_io = io_new(timer_fd);

	io_set_read_handler(transport->timer_io, transport_timer_read,
						transport, NULL);

	/* One extra packet to buffers immediately */
	return transport_send_seq(transport, fd, 1);
}

static void cmd_send_transport(int argc, char *argv[])
{
	GDBusProxy *proxy;
	struct transport *transport;
	int fd = -1, err;
	struct bt_iso_qos qos;
	socklen_t len;
	int i;

	for (i = 1; i < argc; i++) {
		proxy = g_dbus_proxy_lookup(transports, NULL, argv[i],
					BLUEZ_MEDIA_TRANSPORT_INTERFACE);
		if (!proxy) {
			bt_shell_printf("Transport %s not found\n", argv[i]);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		transport = find_transport(proxy);
		if (!transport) {
			bt_shell_printf("Transport %s not acquired\n", argv[i]);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		if (transport->sk < 0) {
			bt_shell_printf("No Transport Socked found\n");
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}

		if (i + 1 < argc) {
			fd = open_file(argv[++i], O_RDONLY);
			if (fd < 0)
				return bt_shell_noninteractive_quit(
								EXIT_FAILURE);
		}

		bt_shell_printf("Sending ...\n");

		/* Read QoS if available */
		memset(&qos, 0, sizeof(qos));
		len = sizeof(qos);
		if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos,
							&len) < 0) {
			bt_shell_printf("Unable to getsockopt(BT_ISO_QOS): %s",
							strerror(errno));
			err = transport_send(transport, fd, NULL);
		} else {
			struct sockaddr_iso addr;
			socklen_t optlen = sizeof(addr);

			err = getpeername(transport->sk,
					(struct sockaddr *)&addr, &optlen);
			if (!err) {
				if (!(bacmp(&addr.iso_bdaddr, BDADDR_ANY)))
					err = transport_send(transport, fd,
							     &qos.bcast.out);
				else
					err = transport_send(transport, fd,
							     &qos.ucast.out);
			}
		}

		if (err < 0) {
			bt_shell_printf("Unable to send: %s (%d)\n",
						strerror(-err), -err);
			close(fd);
			return bt_shell_noninteractive_quit(EXIT_FAILURE);
		}
	}

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}


static void cmd_receive_transport(int argc, char *argv[])
{
	GDBusProxy *proxy;
	struct transport *transport;

	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);
	}

	transport = find_transport(proxy);
	if (!transport) {
		bt_shell_printf("Transport %s not acquired\n", argv[1]);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (transport->sk < 0) {
		bt_shell_printf("No Transport Socked found\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	transport_close(transport);

	transport->fd = open_file(argv[2], O_RDWR | O_CREAT);
	if (transport->fd < 0)
		return bt_shell_noninteractive_quit(EXIT_FAILURE);

	transport->filename = strdup(argv[2]);

	bt_shell_printf("Filename: %s\n", transport->filename);

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void volume_callback(const DBusError *error, void *user_data)
{
	if (dbus_error_is_set(error)) {
		bt_shell_printf("Failed to set Volume: %s\n", error->name);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Changing Volume succeeded\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_volume_transport(int argc, char *argv[])
{
	GDBusProxy *proxy;
	char *endptr = NULL;
	int volume;

	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 (argc == 2) {
		print_property(proxy, "Volume");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	volume = strtol(argv[2], &endptr, 0);
	if (!endptr || *endptr != '\0' || volume > UINT16_MAX) {
		bt_shell_printf("Invalid argument: %s\n", argv[2]);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (!g_dbus_proxy_set_property_basic(proxy, "Volume", DBUS_TYPE_UINT16,
						&volume, volume_callback,
						NULL, NULL)) {
		bt_shell_printf("Failed release transport\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}
}

static void set_metadata_cb(const DBusError *error, void *user_data)
{
	if (dbus_error_is_set(error)) {
		bt_shell_printf("Failed to set Metadata: %s\n", error->name);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	bt_shell_printf("Changing Metadata succeeded\n");

	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_metadata_transport(int argc, char *argv[])
{
	GDBusProxy *proxy;
	struct iovec iov;

	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 (argc == 2) {
		print_property(proxy, "Metadata");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	iov.iov_base = str2bytearray((char *) argv[2], &iov.iov_len);
	if (!iov.iov_base) {
		bt_shell_printf("Invalid argument: %s\n", argv[2]);
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}

	if (!g_dbus_proxy_set_property_array(proxy, "Metadata", DBUS_TYPE_BYTE,
						iov.iov_base, iov.iov_len,
						set_metadata_cb,
						NULL, NULL)) {
		bt_shell_printf("Failed release transport\n");
		return bt_shell_noninteractive_quit(EXIT_FAILURE);
	}
}

static const struct bt_shell_menu transport_menu = {
	.name = "transport",
	.desc = "Media Transport Submenu",
	.pre_run = player_menu_pre_run,
	.entries = {
	{ "list",         NULL,    cmd_list_transport,
						"List available transports" },
	{ "show",        "[transport]", cmd_show_transport,
						"Transport information",
						transport_generator },
	{ "acquire",     "<transport> [transport1...]", cmd_acquire_transport,
						"Acquire Transport",
						transport_generator },
	{ "release",     "<transport> [transport1...]", cmd_release_transport,
						"Release Transport",
						transport_generator },
	{ "send",        "<transport> <filename> [transport1...]",
						cmd_send_transport,
						"Send contents of a file",
						transport_generator },
	{ "receive",     "<transport> [filename]", cmd_receive_transport,
						"Get/Set file to receive",
						transport_generator },
	{ "volume",      "<transport> [value]",	cmd_volume_transport,
						"Get/Set transport volume",
						transport_generator },
	{ "select",      "<transport> [transport1...]", cmd_select_transport,
						"Select Transport",
						transport_generator },
	{ "unselect",    "<transport> [transport1...]", cmd_unselect_transport,
						"Unselect Transport",
						transport_generator },
	{ "metadata",    "<transport> [value...]", cmd_metadata_transport,
						"Get/Set Transport Metadata",
						transport_generator },
	{} },
};

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);
}

static void player_menu_pre_run(const struct bt_shell_menu *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 player_remove_submenu(void)
{
	g_dbus_client_unref(client);
	queue_destroy(ios, transport_free);
}
