// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright 2023-2025 NXP
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE

#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <glib.h>

#include "gdbus/gdbus.h"

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

#include "src/dbus-common.h"
#include "src/shared/util.h"
#include "src/shared/att.h"
#include "src/shared/queue.h"
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-client.h"
#include "src/shared/gatt-server.h"
#include "src/adapter.h"
#include "src/shared/bass.h"
#include "src/shared/bap.h"
#include "src/shared/ad.h"
#include "src/shared/io.h"

#include "btio/btio.h"
#include "src/plugin.h"
#include "src/gatt-database.h"
#include "src/device.h"
#include "src/profile.h"
#include "src/service.h"
#include "src/log.h"
#include "src/error.h"

#define BASS_UUID_STR "0000184f-0000-1000-8000-00805f9b34fb"
#define BCAAS_UUID_STR "00001852-0000-1000-8000-00805f9b34fb"

#define MEDIA_ASSISTANT_INTERFACE "org.bluez.MediaAssistant1"

enum assistant_state {
	ASSISTANT_STATE_IDLE,		/* Assistant object was created for
					 * the stream
					 */
	ASSISTANT_STATE_PENDING,	/* Assistant object was pushed */
	ASSISTANT_STATE_REQUESTING,	/* Remote device requires
					 * Broadcast_Code
					 */
	ASSISTANT_STATE_ACTIVE,		/* Remote device started receiving
					 * stream
					 */
	ASSISTANT_STATE_LOCAL,		/* Assistant object was created for
					 * local stream
					 */
};

static const char *const str_state[] = {
	"ASSISTANT_STATE_IDLE",
	"ASSISTANT_STATE_PENDING",
	"ASSISTANT_STATE_REQUESTING",
	"ASSISTANT_STATE_ACTIVE",
	"ASSISTANT_STATE_LOCAL",
};

struct bass_data {
	struct btd_adapter *adapter;
	struct btd_device *device;
	struct btd_service *service;
	struct bt_bap *bap;
	struct bt_bass *bass;
	struct bt_bap_stream *stream;
	unsigned int src_id;
	unsigned int cp_id;
	unsigned int bis_id;
	unsigned int state_id;
};

struct bass_assistant {
	struct btd_adapter *adapter;	/* Broadcast source device */
	struct btd_device *device;	/* Broadcast source device */
	struct bass_data *data;		/* BASS session with peer device */
	uint8_t sgrp;
	uint8_t sid;
	uint8_t bis;
	uint32_t bid;
	struct bt_bap_qos qos;
	struct iovec *meta;
	struct iovec *caps;
	enum assistant_state state;
	uint8_t src_id;
	struct queue *srcs;		/* Per-device source tracking */
	char *path;
};

struct bass_src {
	struct bt_bass *bass;
	uint8_t src_id;
};

struct bass_delegator {
	struct btd_device *device;	/* Broadcast source device */
	struct btd_service *service;
	struct bt_bcast_src *src;
	struct bt_bap *bap;
	unsigned int state_id;
	unsigned int bcode_id;
	uint8_t sid;
	uint8_t *bcode;
	unsigned int timeout;
	struct queue *bcode_reqs;
	struct queue *setups;
	unsigned int io_id;
	GIOChannel *io;
};

struct bass_setup {
	struct bass_delegator *dg;
	char *path;
	struct bt_bap_stream *stream;
	uint8_t bis;
	struct bt_bap_qos qos;
	struct iovec *meta;
	struct iovec *config;
	struct bt_bap_pac *lpac;
};

struct bass_bcode_req {
	struct bass_setup *setup;
	bt_bap_bcode_reply_t cb;
	void *user_data;
};

static struct queue *sessions;
static struct queue *assistants;
static struct queue *delegators;

static const char *state2str(enum assistant_state state);

static struct bass_data *bass_data_new(struct btd_adapter *adapter,
					struct btd_device *device);
static void bass_data_add(struct bass_data *data);
static void bass_data_remove(struct bass_data *data);

static void bis_probe(uint8_t sid, uint8_t bis, uint8_t sgrp,
			struct iovec *caps, struct iovec *meta,
			struct bt_bap_qos *qos, void *user_data);

static bool match_src_data(const void *data, const void *match_data)
{
	const struct bass_src *src = data;
	const struct bt_bass *bass = match_data;

	return src->bass == bass;
}

static struct bass_src *assistant_add_src(struct bass_assistant *assistant,
					struct bt_bass *bass, uint8_t id);
static void bis_remove(struct bt_bap *bap, void *user_data);


static void bass_debug(const char *str, void *user_data)
{
	DBG_IDX(0xffff, "%s", str);
}

static gboolean req_timeout(gpointer user_data)
{
	struct bass_delegator *dg = user_data;
	struct bass_bcode_req *req;

	DBG("delegator %p", dg);

	dg->timeout = 0;

	while ((req = queue_pop_head(dg->bcode_reqs))) {
		if (req->cb)
			req->cb(req->user_data, -ETIMEDOUT);

		free(req);
	}

	return FALSE;
}

static bool delegator_match_bap(const void *data, const void *match_data)
{
	const struct bass_delegator *dg = data;
	const struct bt_bap *bap = match_data;

	return dg->bap == bap;
}

static void setup_set_bcode(uint8_t *bcode, struct bass_setup *setup,
				bt_bap_bcode_reply_t cb, void *user_data)
{
	struct bt_bap_qos *qos = bt_bap_stream_get_qos(setup->stream);

	/* Allocate Broadcast Code inside setup QoS */
	util_iov_free(setup->qos.bcast.bcode, 1);
	setup->qos.bcast.bcode = util_iov_new(bcode, BT_BASS_BCAST_CODE_SIZE);

	/* Refresh stream bcode */
	qos->bcast.bcode = setup->qos.bcast.bcode;

	if (cb)
		cb(user_data, 0);
}

static bool match_setup_stream(const void *data, const void *user_data)
{
	const struct bass_setup *setup = data;
	const struct bt_bap_stream *stream = user_data;

	return setup->stream == stream;
}

static void bass_req_bcode(struct bt_bap_stream *stream,
	bt_bap_bcode_reply_t reply, void *reply_data,
	void *user_data)
{
	struct bt_bap *bap = bt_bap_stream_get_session(stream);
	struct bass_delegator *dg;
	struct bass_bcode_req *req;
	struct bass_setup *setup;

	dg = queue_find(delegators, delegator_match_bap, bap);
	if (!dg) {
		reply(reply_data, -EINVAL);
		return;
	}

	setup = queue_find(dg->setups, match_setup_stream, stream);
	if (!setup) {
		reply(reply_data, -EINVAL);
		return;
	}

	if (dg->bcode) {
		uint8_t empty_bcode[BT_BASS_BCAST_CODE_SIZE] = {0};

		if (memcmp(dg->bcode, empty_bcode, BT_BASS_BCAST_CODE_SIZE)) {
			/* Broadcast Code has already been received before. */
			setup_set_bcode(dg->bcode, setup, reply, reply_data);
			return;
		}

		error("dg %p has invalid bcode", dg);
		free(dg->bcode);
		dg->bcode = NULL;
	}

	/* Create a request for the Broadcast Code. The request
	 * will be considered handled when the Broadcast Code is
	 * received from a Broadcast Assistant.
	 */
	req = new0(struct bass_bcode_req, 1);
	if (!req)
		return;

	req->setup = setup;
	req->cb = reply;
	req->user_data = reply_data;

	queue_push_tail(dg->bcode_reqs, req);

	/* Mark the encryption status as "Broadcast Code Required"
	 * in the Broadcast Receive State characteristic and notify
	 * Broadcast Assistants.
	 */
	bt_bass_set_enc(dg->src, BT_BASS_BIG_ENC_STATE_BCODE_REQ);

	/* Add timeout for Broadcast Assistants to provide the Code. */
	if (!dg->timeout)
		dg->timeout = g_timeout_add_seconds(10, req_timeout, dg);
}

static int stream_get_bis(struct bt_bap_stream *stream)
{
	char *path = bt_bap_stream_get_user_data(stream);
	const char *strbis;
	int bis;

	strbis = strstr(path, "/bis");
	if (!strbis)
		return 0;

	if (sscanf(strbis, "/bis%d", &bis) < 0)
		return 0;

	return bis;
}

static void append_stream(void *data, void *user_data)
{
	struct bt_bap_stream *stream = data;
	struct sockaddr_iso_bc *addr = user_data;
	uint8_t bis = stream_get_bis(stream);

	DBG("%d", bis);

	addr->bc_bis[addr->bc_num_bis] = bis;
	addr->bc_num_bis++;
}

static bool link_io_unset(const void *data, const void *match_data)
{
	struct bt_bap_stream *link = (struct bt_bap_stream *)data;

	return !bt_bap_stream_get_io(link);
}

static void connect_cb(GIOChannel *io, GError *err, void *user_data)
{
	struct bass_setup *setup = user_data;
	struct bt_bap_stream *stream;
	struct queue *links;
	int fd;

	DBG("");

	if (!setup || !setup->stream)
		return;

	stream = setup->stream;
	links = bt_bap_stream_io_get_links(stream);

	/* Set fds for the stream and all its links. */
	if (bt_bap_stream_get_io(stream))
		stream = queue_find(links, link_io_unset, NULL);

	fd = g_io_channel_unix_get_fd(io);

	if (bt_bap_stream_set_io(stream, fd)) {
		g_io_channel_set_close_on_unref(io, FALSE);
	}
}

static bool link_enabled(const void *data, const void *match_data)
{
	struct bt_bap_stream *stream = (struct bt_bap_stream *)data;
	uint8_t state = bt_bap_stream_get_state(stream);

	return ((state == BT_BAP_STREAM_STATE_ENABLING) ||
			bt_bap_stream_get_io(stream));
}

static void setup_free(void *data)
{
	struct bass_setup *setup = data;

	DBG("setup %p", setup);

	util_iov_free(setup->qos.bcast.bcode, 1);
	util_iov_free(setup->meta, 1);
	util_iov_free(setup->config, 1);
	free(setup->path);

	/* Clear bis index from the bis sync bitmask, if it
	 * has been previously set.
	 */
	bt_bass_clear_bis_sync(setup->dg->src, setup->bis);

	free(setup);
}

static void delegator_free(struct bass_delegator *dg);

static void delegator_disconnect(struct bass_delegator *dg)
{
	struct btd_device *device = dg->device;
	struct btd_service *service = dg->service;
	struct btd_profile *profile = btd_service_get_profile(service);

	DBG("%p", dg);

	/* Remove delegator from the queue and free it before removing the
	 * service profile. This prevents delegator_attach from finding a
	 * stale entry when a new delegator is created for the same device,
	 * and also avoids use-after-free since device_remove_profile may
	 * trigger bap_detached -> delegator_detach which would otherwise
	 * try to free the delegator again.
	 */
	queue_remove(delegators, dg);
	btd_service_set_user_data(service, NULL);
	delegator_free(dg);

	/* Disconnect service so BAP driver is cleanup properly and bt_bap is
	 * detached from the device.
	 */
	btd_service_disconnect(service);

	/* Remove service since delegator has been freed at this point.
	 * delegator_detach (triggered by bap_detached) will be a no-op
	 * since the delegator has already been removed from the queue.
	 */
	device_remove_profile(device, profile);

	/* If the device is no longer consider connected  it means no other
	 * service was connected so it has no longer any use and can be safely
	 * removed.
	 */
	if (!btd_device_is_connected(device)) {
		struct btd_adapter *adapter;

		adapter = device_get_adapter(device);
		btd_adapter_remove_device(adapter, device);
	}
}

static bool match_bcode_setup(const void *data, const void *user_data)
{
	const struct bass_bcode_req *req = data;
	const struct bass_setup *setup = user_data;

	return req->setup == setup;
}

static void setup_clear(struct bass_setup *setup, int bis)
{
	struct bass_delegator *dg = setup->dg;
	struct bass_bcode_req *req;

	DBG("%p", setup);

	bt_bass_clear_bis_sync(dg->src, bis);
	setup->stream = NULL;

	if (!queue_remove(setup->dg->setups, setup))
		/* Setup has already been removed from the queue (e.g. during
		 * handle_mod_src_req PA_SYNC_NO_SYNC processing). Skip
		 * disconnect check and free since the caller handles cleanup.
		 */
		return;

	/* Remove any pending bcode request associated with setup */
	req = queue_remove_if(dg->bcode_reqs, match_bcode_setup, setup);
	if (req) {
		free(req);
		if (dg->timeout) {
			g_source_remove(dg->timeout);
			dg->timeout = 0;
		}
	}

	setup_free(setup);

	if (queue_isempty(dg->setups))
		delegator_disconnect(dg);
}

static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state,
				uint8_t new_state, void *user_data)
{
	struct bass_delegator *dg = user_data;
	int bis;
	struct bt_bap *bap = bt_bap_stream_get_session(stream);
	struct sockaddr_iso_bc iso_bc_addr = {0};
	struct queue *links;
	GError *gerr = NULL;
	struct bt_bap_qos *bap_qos = bt_bap_stream_get_qos(stream);
	struct bt_iso_qos qos;
	struct bass_setup *setup = queue_find(dg->setups,
				match_setup_stream, stream);

	if (setup == NULL)
		return;

	if (dg->bap != bap)
		return;

	bis = stream_get_bis(stream);

	DBG("stream %p: %s(%u) -> %s(%u)", stream,
			bt_bap_stream_statestr(old_state), old_state,
			bt_bap_stream_statestr(new_state), new_state);

	switch (new_state) {
	case BT_BAP_STREAM_STATE_ENABLING:
		links = bt_bap_stream_io_get_links(stream);

		if (bt_bap_stream_get_io(stream) ||
			queue_find(links, link_enabled, NULL))
			/* The first enabled link will create and set fds
			 * for all links.
			 *
			 * If the stream io has already been set, the stream
			 * will automatically be started once all state_changed
			 * callbacks are notified.
			 *
			 * If there is any other linked stream that has already
			 * been enabled, the stream fd will be set once it is
			 * notified from kernel and the stream will be started.
			 */
			break;

		iso_bc_addr.bc_bdaddr_type =
				btd_device_get_bdaddr_type(dg->device);
		memcpy(&iso_bc_addr.bc_bdaddr, device_get_address(dg->device),
				sizeof(bdaddr_t));

		append_stream(stream, &iso_bc_addr);

		queue_foreach(links, append_stream, &iso_bc_addr);

		bt_bap_qos_to_iso_qos(bap_qos, &qos);

		if (!bt_io_set(dg->io, &gerr,
				BT_IO_OPT_QOS, &qos,
				BT_IO_OPT_INVALID)) {
			error("bt_io_set: %s", gerr->message);
			g_error_free(gerr);
			break;
		}

		if (!bt_io_bcast_accept(dg->io,
				connect_cb, setup, NULL, &gerr,
				BT_IO_OPT_ISO_BC_NUM_BIS,
				iso_bc_addr.bc_num_bis, BT_IO_OPT_ISO_BC_BIS,
				iso_bc_addr.bc_bis, BT_IO_OPT_INVALID)) {
			error("bt_io_bcast_accept: %s", gerr->message);
			g_error_free(gerr);
		}
		break;
	case BT_BAP_STREAM_STATE_STREAMING:
		/* BAP stream was started. Mark BIS index as synced inside the
		 * Broadcast Receive State characteristic and notify peers about
		 * the update.
		 */
		bt_bass_set_bis_sync(dg->src, bis);
		break;
	case BT_BAP_STREAM_STATE_CONFIG:
		if (old_state == BT_BAP_STREAM_STATE_STREAMING)
			/* BAP stream was disabled. Clear BIS index from the
			 * bitmask inside the Broadcast Receive State
			 * characteristic and notify peers about the update.
			 */
			bt_bass_clear_bis_sync(dg->src, bis);
		break;
	case BT_BAP_STREAM_STATE_IDLE:
		setup_clear(setup, bis);
		break;
	}
}

static void bass_req_bcode_cb(void *user_data, int err)
{
	struct bass_setup *setup = user_data;

	if (!err) {
		if (asprintf(&setup->path, "%s/bis%d",
			device_get_path(setup->dg->device),
			setup->bis) < 0)
			return;

		bt_bap_stream_set_user_data(setup->stream, setup->path);

		bt_bap_stream_config(setup->stream, &setup->qos,
				setup->config, NULL, NULL);
		bt_bap_stream_metadata(setup->stream, setup->meta,
				NULL, NULL);
	}
}

static void setup_configure_stream(struct bass_setup *setup)
{
	uint8_t empty_bcode[BT_BASS_BCAST_CODE_SIZE] = {0};

	setup->stream = bt_bap_stream_new(setup->dg->bap, setup->lpac, NULL,
					&setup->qos, setup->config);
	if (!setup->stream)
		return;

	/* Before configuring the stream, check whether it is encrypted.
	 * If so, request the broadcast code from the client.
	 */
	if ((setup->qos.bcast.encryption) &&
	    (!memcmp(setup->qos.bcast.bcode->iov_base,
		     empty_bcode,
		     sizeof(empty_bcode)))) {
		bass_req_bcode(setup->stream, bass_req_bcode_cb, setup, NULL);
		return;
	}

	if (asprintf(&setup->path, "%s/bis%d",
			device_get_path(setup->dg->device),
			setup->bis) < 0)
		return;

	bt_bap_stream_set_user_data(setup->stream, setup->path);

	bt_bap_stream_config(setup->stream, &setup->qos,
			setup->config, NULL, NULL);
	bt_bap_stream_metadata(setup->stream, setup->meta,
			NULL, NULL);
}

static void stream_unlink(void *data, void *user_data)
{
	struct bt_bap_stream *link = data;
	struct bt_bap_stream *stream = user_data;

	bt_bap_stream_io_unlink(link, stream);
}

static void bass_remove_bis(struct bass_setup *setup)
{
	struct queue *links = bt_bap_stream_io_get_links(setup->stream);

	DBG("%p", setup);

	queue_foreach(links, stream_unlink, setup->stream);
	bt_bap_stream_release(setup->stream, NULL, NULL);
}

static void setup_disable_streaming(void *data, void *user_data)
{
	struct bass_setup *setup = data;
	struct queue *links = bt_bap_stream_io_get_links(setup->stream);

	if (!setup->stream)
		return;

	if (bt_bap_stream_get_state(setup->stream) !=
				BT_BAP_STREAM_STATE_STREAMING)
		return;

	queue_foreach(links, stream_unlink, setup->stream);
	bt_bap_stream_disable(setup->stream, false, NULL, NULL);
}

static void bass_add_bis(struct bass_setup *setup)
{
	DBG("%p", setup);

	queue_foreach(setup->dg->setups, setup_disable_streaming, NULL);
	setup_configure_stream(setup);
}

static void bis_handler(uint8_t sid, uint8_t bis, uint8_t sgrp,
			struct iovec *caps, struct iovec *meta,
			struct bt_bap_qos *qos, void *user_data)
{
	struct bass_delegator *dg = user_data;
	struct bt_bap_pac *lpac;
	struct bass_setup *setup;

	/* Check if this stream caps match any local PAC */
	bt_bap_verify_bis(dg->bap, bis, caps, &lpac);
	if (!lpac)
		return;

	setup = new0(struct bass_setup, 1);
	if (!setup)
		return;

	setup->dg = dg;
	setup->bis = bis;
	setup->lpac = lpac;

	setup->qos = *qos;
	setup->qos.bcast.bcode = util_iov_dup(qos->bcast.bcode, 1);

	setup->meta = util_iov_dup(meta, 1);
	setup->config = util_iov_dup(caps, 1);

	queue_push_tail(setup->dg->setups, setup);

	/* Only handle streams required by the Broadcast Assistant. */
	if (!bt_bass_check_bis(dg->src, bis))
		return;

	setup_configure_stream(setup);
}

static gboolean big_info_cb(GIOChannel *io, GIOCondition cond,
							gpointer user_data)
{
	struct bass_delegator *dg = user_data;
	GError *err = NULL;
	struct bt_iso_base base;
	struct bt_iso_qos qos;
	struct iovec iov;
	struct bt_bap_qos bap_qos = {0};
	uint8_t sid;

	dg->io_id = 0;

	bt_io_get(io, &err,
			BT_IO_OPT_BASE, &base,
			BT_IO_OPT_QOS, &qos,
			BT_IO_OPT_ISO_BC_SID, &sid,
			BT_IO_OPT_INVALID);
	if (err) {
		error("%s", err->message);
		g_error_free(err);
		return FALSE;
	}

	iov.iov_base = base.base;
	iov.iov_len = base.base_len;

	/* Create BAP QoS structure */
	bt_bap_iso_qos_to_bap_qos(&qos, &bap_qos);

	bt_bap_parse_base(sid, &iov, &bap_qos, bass_debug, bis_handler, dg);

	util_iov_free(bap_qos.bcast.bcode, 1);

	return FALSE;
}

static void confirm_cb(GIOChannel *io, void *user_data)
{
	struct bass_delegator *dg = user_data;

	DBG("");

	/* Close the listen io */
	g_io_channel_shutdown(dg->io, TRUE, NULL);
	g_io_channel_unref(dg->io);

	g_io_channel_ref(io);
	dg->io = io;

	/* Update Broadcast Receive State characteristic value and notify
	 * peers.
	 */
	if (bt_bass_set_pa_sync(dg->src, BT_BASS_SYNCHRONIZED_TO_PA))
		DBG("Failed to update Broadcast Receive State characteristic");

	/* Register BAP stream state changed callback. */
	dg->state_id = bt_bap_state_register(dg->bap, bap_state_changed,
			NULL, dg, NULL);

	/* Register callback to handle Broadcast Code requests from
	 * upper layers.
	 */
	dg->bcode_id = bt_bap_bcode_cb_register(dg->bap, bass_req_bcode,
							NULL, NULL);

	dg->io_id = g_io_add_watch(io, G_IO_OUT, big_info_cb, dg);
}

static void src_ad_search_bid(void *data, void *user_data)
{
	struct bt_ad_service_data *sd = data;
	struct bass_assistant *assistant = user_data;
	struct iovec iov;

	if (sd->uuid.type != BT_UUID16 || sd->uuid.value.u16 != BCAA_SERVICE)
		return;

	iov.iov_base = sd->data;
	iov.iov_len = sd->len;

	util_iov_pull_le24(&iov, &assistant->bid);
}

static struct bass_assistant *
assistant_new(struct btd_adapter *adapter,
		struct btd_device *device, struct bass_data *data,
		uint8_t sgrp, uint8_t sid, uint8_t bis, struct bt_bap_qos *qos,
		struct iovec *meta, struct iovec *caps)
{
	struct bass_assistant *assistant;
	char src_addr[18];

	assistant = new0(struct bass_assistant, 1);
	if (!assistant)
		return NULL;

	DBG("assistant %p", assistant);

	assistant->adapter = adapter;
	assistant->device = device;
	assistant->data = data;
	assistant->sgrp = sgrp;
	assistant->sid = sid;
	assistant->bis = bis;
	assistant->qos = *qos;

	/* Create an internal copy for bcode */
	assistant->qos.bcast.bcode = util_iov_dup(qos->bcast.bcode, 1);
	assistant->meta = util_iov_dup(meta, 1);
	assistant->caps = util_iov_dup(caps, 1);

	if (device) {
		btd_device_foreach_service_data(device, src_ad_search_bid,
							assistant);
		ba2str(device_get_address(device), src_addr);
		assistant->path = g_strdup_printf("%s/src_%s/sid%d/bis%d",
						device_get_path(data->device),
						src_addr, sid, bis);
	} else {
		assistant->path = g_strdup_printf("%s/sid%d/bis%d",
						adapter_get_path(data->adapter),
						sid, bis);
		assistant->state = ASSISTANT_STATE_LOCAL;
	}

	g_strdelimit(assistant->path, ":", '_');

	if (!assistants)
		assistants = queue_new();

	queue_push_tail(assistants, assistant);

	return assistant;
}

static void assistant_set_state(struct bass_assistant *assistant,
					enum assistant_state state)
{
	enum assistant_state old_state = assistant->state;
	const char *str;

	if (old_state == state || old_state == ASSISTANT_STATE_LOCAL)
		return;

	assistant->state = state;

	DBG("State changed %s: %s -> %s", assistant->path, str_state[old_state],
							str_state[state]);

	str = state2str(state);

	if (g_strcmp0(str, state2str(old_state)) != 0)
		g_dbus_emit_property_changed(btd_get_dbus_connection(),
						assistant->path,
						MEDIA_ASSISTANT_INTERFACE,
						"State");
}

static int assistant_parse_qos(struct bass_assistant *assistant,
						DBusMessageIter *iter)
{
	DBusMessageIter dict;
	const char *key;

	dbus_message_iter_recurse(iter, &dict);

	while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter value, entry;
		int var;

		dbus_message_iter_recurse(&dict, &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, "BCode")) {
			DBusMessageIter array;
			struct iovec iov = {0};

			if (var != DBUS_TYPE_ARRAY)
				return -EINVAL;

			dbus_message_iter_recurse(&value, &array);
			dbus_message_iter_get_fixed_array(&array,
							&iov.iov_base,
							(int *)&iov.iov_len);

			if (iov.iov_len != BT_BASS_BCAST_CODE_SIZE) {
				error("Invalid size for BCode: %zu != 16",
								iov.iov_len);
				return -EINVAL;
			}

			util_iov_free(assistant->qos.bcast.bcode, 1);
			assistant->qos.bcast.bcode = util_iov_dup(&iov, 1);

			return 0;
		}

		dbus_message_iter_next(&dict);
	}

	return 0;
}

static bool match_device(const void *data, const void *match_data)
{
	const struct bass_data *bdata = data;
	const struct btd_device *device = match_data;

	return bdata->device == device;
}

static int assistant_parse_props(struct bass_assistant *assistant,
					DBusMessageIter *props)
{
	DBusMessageIter value, entry, array;
	const char *key, *path;
	struct btd_device *device;
	struct bass_data *data;

	while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
		dbus_message_iter_recurse(props, &entry);
		dbus_message_iter_get_basic(&entry, &key);

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

		if (!strcasecmp(key, "Metadata")) {
			struct iovec iov;

			if (dbus_message_iter_get_arg_type(&value) !=
							DBUS_TYPE_ARRAY)
				goto fail;

			dbus_message_iter_recurse(&value, &array);
			dbus_message_iter_get_fixed_array(&array,
							&iov.iov_base,
							(int *)&iov.iov_len);

			util_iov_free(assistant->meta, 1);
			assistant->meta = util_iov_dup(&iov, 1);
			DBG("Parsed Metadata");
		} else if (!strcasecmp(key, "QoS")) {
			if (dbus_message_iter_get_arg_type(&value) !=
							DBUS_TYPE_ARRAY)
				goto fail;

			if (assistant_parse_qos(assistant, &value))
				goto fail;

			DBG("Parsed QoS");
		} else if (!strcasecmp(key, "Device")) {
			if (assistant->state != ASSISTANT_STATE_LOCAL) {
				error("Device property is for local assistant "
				      "only");
				goto fail;
			}

			if (dbus_message_iter_get_arg_type(&value) !=
							DBUS_TYPE_OBJECT_PATH)
				goto fail;

			dbus_message_iter_get_basic(&value, &path);

			device = btd_adapter_find_device_by_path(
							assistant->adapter,
							path);
			if (!device) {
				error("Unable to find device %s", path);
				goto fail;
			}

			data = queue_find(sessions, match_device, device);
			if (!data) {
				error("Unable to find data for device %s",
					path);
				goto fail;
			}

			if (!data->bass) {
				error("Unable to find bass for device %s",
					path);
				goto fail;
			}

			if (assistant->data->bass)
				bt_bass_unref(assistant->data->bass);

			assistant->data->bass = bt_bass_ref(data->bass);
		}

		dbus_message_iter_next(props);
	}

	return 0;

fail:
	DBG("Failed parsing %s", key);

	return -EINVAL;
}

static bool match_bass(const void *data, const void *match_data)
{
	const struct bass_data *bdata = data;
	const struct bt_bass *bass = match_data;

	/* Ignore data from own broadcast source */
	if (!bdata->device)
		return false;

	return bdata->bass == bass;
}

static void assistant_past(struct bass_assistant *assistant)
{
	struct io *io = bt_bap_stream_get_io(assistant->data->stream);
	struct btd_device *device = assistant->device;
	int sk;
	struct sockaddr_iso *addr;
	int err;

	DBG("");

	if (!io) {
		error("stream io not set");
		return;
	}

	sk = io_get_fd(io);
	if (sk < 0)
		return;

	if (!device) {
		struct bt_bass *bass = assistant->data->bass;
		struct bass_data *data;

		data = queue_find(sessions, match_bass, bass);
		if (!data) {
			error("Unable to find data for bass %p", bass);
			return;
		}

		device = data->device;
		if (!device) {
			error("Unable to find device for bass %p", bass);
			return;
		}
	}

	addr = malloc(sizeof(*addr) + sizeof(*addr->iso_bc));
	memset(addr, 0, sizeof(*addr) + sizeof(*addr->iso_bc));
	addr->iso_family = AF_BLUETOOTH;

	bacpy(&addr->iso_bc->bc_bdaddr, (void *) device_get_address(device));
	addr->iso_bc->bc_bdaddr_type = device_get_le_address_type(device);

	err = bind(sk, (struct sockaddr *) addr, sizeof(*addr) +
						sizeof(*addr->iso_bc));
	if (err)
		error("bind: %s", strerror(errno));

	free(addr);
}

static DBusMessage *push_mod_src(struct bass_assistant *assistant,
					DBusMessage *msg)
{
	struct bt_bass_bcast_audio_scan_cp_hdr hdr;
	struct bt_bass_mod_src_params params = {0};
	struct iovec iov = {0};
	uint32_t bis_sync = 0;
	uint8_t meta_len = 0;
	int err;

	hdr.op = BT_BASS_MOD_SRC;

	params.id = assistant->src_id;
	params.pa_sync = PA_SYNC_NO_SYNC;
	params.pa_interval = PA_INTERVAL_UNKNOWN;
	params.num_subgroups = assistant->sgrp + 1;

	util_iov_append(&iov, &params, sizeof(params));

	for (uint8_t sgrp = 0; sgrp < params.num_subgroups; sgrp++) {
		util_iov_append(&iov, &bis_sync, sizeof(bis_sync));
		util_iov_append(&iov, &meta_len, sizeof(meta_len));
	}

	err = bt_bass_send(assistant->data->bass, &hdr, &iov);
	if (err) {
		DBG("Unable to send BASS Write Command");
		return btd_error_failed(msg, strerror(-err));
	}

	free(iov.iov_base);

	if (assistant->device) {
		assistant_set_state(assistant, ASSISTANT_STATE_IDLE);
	} else {
		struct bass_src *src;

		src = queue_remove_if(assistant->srcs, match_src_data,
						assistant->data->bass);
		free(src);
	}

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *push_add_src(struct bass_assistant *assistant,
					DBusMessage *msg)
{
	struct bt_bass_bcast_audio_scan_cp_hdr hdr;
	struct bt_bass_add_src_params params = {0};
	struct iovec iov = {0};
	uint32_t bis_sync = 0;
	uint8_t meta_len = 0;
	int err;
	struct io *io;

	hdr.op = BT_BASS_ADD_SRC;

	if (assistant->device) {
		if (device_get_le_address_type(assistant->device) ==
						BDADDR_LE_PUBLIC)
			params.addr_type = BT_BASS_ADDR_PUBLIC;
		else
			params.addr_type = BT_BASS_ADDR_RANDOM;

		bacpy(&params.addr, device_get_address(assistant->device));
		params.sid = assistant->sid;
		put_le24(assistant->bid, params.bid);
		params.pa_sync = PA_SYNC_NO_PAST;
		params.pa_interval = PA_INTERVAL_UNKNOWN;
		params.num_subgroups = assistant->sgrp + 1;
	} else {
		io = bt_bap_stream_get_io(assistant->data->stream);
		if (io) {
			int fd = io_get_fd(io);
			struct sockaddr_iso addr;
			socklen_t olen = sizeof(addr);

			memset(&addr, 0, sizeof(addr));
			if (getsockname(fd, (struct sockaddr *) &addr,
					&olen) < 0) {
				error("getsockname: %s", strerror(errno));
				return btd_error_invalid_args(msg);
			}

			if (addr.iso_bdaddr_type == BDADDR_LE_PUBLIC)
				params.addr_type = BT_BASS_ADDR_PUBLIC;
			else
				params.addr_type = BT_BASS_ADDR_RANDOM;

			bacpy(&params.addr, &addr.iso_bdaddr);
		} else {
			if (btd_adapter_get_address_type(assistant->adapter) ==
							BDADDR_LE_PUBLIC)
				params.addr_type = BT_BASS_ADDR_PUBLIC;
			else
				params.addr_type = BT_BASS_ADDR_RANDOM;

			bacpy(&params.addr,
				btd_adapter_get_address(assistant->adapter));
		}

		params.sid = assistant->sid;
		/* TODO: Add a way to recover BID */
		put_le24(assistant->bid, params.bid);

		if (assistant->data->stream &&
				btd_adapter_has_settings(assistant->adapter,
						MGMT_SETTING_PAST_SENDER))
			params.pa_sync = PA_SYNC_PAST;
		else
			params.pa_sync = PA_SYNC_NO_PAST;

		params.pa_interval = PA_INTERVAL_UNKNOWN;
		params.num_subgroups = assistant->sgrp + 1;
	}

	util_iov_append(&iov, &params, sizeof(params));

	/* Metadata and the BIS index associated with the MediaAssistant
	 * object will be set in the subgroup they belong to. For the other
	 * subgroups, no metadata and no BIS index will be provided.
	 */
	for (uint8_t sgrp = 0; sgrp < assistant->sgrp; sgrp++) {
		util_iov_append(&iov, &bis_sync, sizeof(bis_sync));
		util_iov_append(&iov, &meta_len, sizeof(meta_len));
	}

	/* Use 0xFFFFFFFF to indicate no preference (any BIS index) to allow
	 * delegators to sync with BIG with multiple BIS
	 */
	bis_sync = 0xFFFFFFFF;
	meta_len = assistant->meta->iov_len;

	util_iov_append(&iov, &bis_sync, sizeof(bis_sync));
	util_iov_append(&iov, &meta_len, sizeof(meta_len));
	util_iov_append(&iov, assistant->meta->iov_base,
				assistant->meta->iov_len);

	err = bt_bass_send(assistant->data->bass, &hdr, &iov);
	if (err) {
		DBG("Unable to send BASS Write Command");
		return btd_error_failed(msg, strerror(-err));
	}

	free(iov.iov_base);

	if (assistant->state == ASSISTANT_STATE_LOCAL)
		assistant_add_src(assistant, assistant->data->bass, 0);
	else
		assistant_set_state(assistant, ASSISTANT_STATE_PENDING);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *push(DBusConnection *conn, DBusMessage *msg,
							  void *user_data)
{
	struct bass_assistant *assistant = user_data;
	DBusMessageIter props, dict;

	DBG("");

	dbus_message_iter_init(msg, &props);

	if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_ARRAY) {
		DBG("Unable to parse properties");
		return btd_error_invalid_args(msg);
	}

	dbus_message_iter_recurse(&props, &dict);

	if (assistant_parse_props(assistant, &dict)) {
		DBG("Unable to parse properties");
		return btd_error_invalid_args(msg);
	}

	if (assistant->state == ASSISTANT_STATE_ACTIVE) {
		return push_mod_src(assistant, msg);
	} else if (assistant->state == ASSISTANT_STATE_LOCAL) {
		struct bass_src *src;

		src = queue_find(assistant->srcs, match_src_data,
						assistant->data->bass);
		if (src) {
			assistant->src_id = src->src_id;
			return push_mod_src(assistant, msg);
		}
	}

	return push_add_src(assistant, msg);
}

static const GDBusMethodTable assistant_methods[] = {
	{GDBUS_EXPERIMENTAL_ASYNC_METHOD("Push",
					GDBUS_ARGS({ "Props", "a{sv}" }),
					NULL, push)},
	{},
};

static const char *state2str(enum assistant_state state)
{
	switch (state) {
	case ASSISTANT_STATE_IDLE:
		return "idle";
	case ASSISTANT_STATE_PENDING:
		return "pending";
	case ASSISTANT_STATE_REQUESTING:
		return "requesting";
	case ASSISTANT_STATE_ACTIVE:
		return "active";
	case ASSISTANT_STATE_LOCAL:
		return "local";
	}

	return NULL;
}

static gboolean get_state(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct bass_assistant *assistant = data;
	const char *state = state2str(assistant->state);

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &state);

	return TRUE;
}

static gboolean get_metadata(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct bass_assistant *assistant = data;
	DBusMessageIter array;

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

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

	dbus_message_iter_close_container(iter, &array);

	return TRUE;
}

static gboolean get_qos(const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *data)
{
	struct bass_assistant *assistant = data;
	DBusMessageIter dict;
	uint8_t arr[BT_BASS_BCAST_CODE_SIZE] = {0};
	uint8_t *bcode = arr;

	if (assistant->qos.bcast.bcode)
		memcpy(arr, assistant->qos.bcast.bcode->iov_base,
						BT_BASS_BCAST_CODE_SIZE);

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
					DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
					DBUS_TYPE_STRING_AS_STRING
					DBUS_TYPE_VARIANT_AS_STRING
					DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
					&dict);

	dict_append_entry(&dict, "Encryption", DBUS_TYPE_BYTE,
				&assistant->qos.bcast.encryption);
	dict_append_array(&dict, "BCode", DBUS_TYPE_BYTE,
				&bcode, BT_BASS_BCAST_CODE_SIZE);

	dbus_message_iter_close_container(iter, &dict);

	return TRUE;
}

static const GDBusPropertyTable assistant_properties[] = {
	{ "State", "s", get_state },
	{ "Metadata", "ay", get_metadata, NULL, NULL,
					G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
	{ "QoS", "a{sv}", get_qos, NULL, NULL,
					G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
	{ }
};

static void assistant_free(void *data)
{
	struct bass_assistant *assistant = data;

	g_free(assistant->path);
	util_iov_free(assistant->meta, 1);
	util_iov_free(assistant->caps, 1);
	queue_destroy(assistant->srcs, free);

	free(assistant);
}

static void bis_src_handler(uint8_t sid, uint8_t bis, uint8_t sgrp,
				struct iovec *caps, struct iovec *meta,
				struct bt_bap_qos *qos, void *user_data)
{
	struct bass_data *data = user_data;
	struct bass_assistant *assistant;
	char addr[18];

	ba2str(btd_adapter_get_address(data->adapter), addr);

	DBG("%s data %p BIS %d", addr, data, bis);

	assistant = assistant_new(data->adapter, NULL, data, sgrp, sid, bis,
					qos, meta, caps);
	if (!g_dbus_register_interface(btd_get_dbus_connection(),
					assistant->path,
					MEDIA_ASSISTANT_INTERFACE,
					assistant_methods, NULL,
					assistant_properties,
					assistant,
					assistant_free))
		DBG("Could not register path %s", assistant->path);
}

static bool assistant_match_data(const void *data, const void *match_data)
{
	const struct bass_assistant *assistant = data;
	const struct bass_data *bdata = match_data;

	return (assistant->data == bdata);
}

static void unregister_assistant(void *data)
{
	struct bass_assistant *assistant = data;

	DBG("%p", assistant);

	g_dbus_unregister_interface(btd_get_dbus_connection(),
				assistant->path, MEDIA_ASSISTANT_INTERFACE);
}

static void bap_state_src_changed(struct bt_bap_stream *stream,
				uint8_t old_state, uint8_t new_state,
				void *user_data)
{
	struct bass_data *data = user_data;
	struct assistant *assistant;
	struct bt_bap_qos *qos = NULL;
	struct iovec *base;

	DBG("stream %p: %s(%u) -> %s(%u)", stream,
			bt_bap_stream_statestr(old_state), old_state,
			bt_bap_stream_statestr(new_state), new_state);

	switch (new_state) {
	case BT_BAP_STREAM_STATE_IDLE:
		/* Unregister assistant object if one exists */
		assistant = queue_remove_if(assistants, assistant_match_data,
						data);
		if (assistant)
			unregister_assistant(assistant);
		data->stream = NULL;
		break;
	case BT_BAP_STREAM_STATE_STREAMING:
		/* If already streaming don't attempt to parse it again */
		if (old_state == new_state)
			break;

		base = bt_bap_stream_get_base(stream);
		if (!base) {
			error("Unable to read BASE of stream %p", stream);
			break;
		}

		if (!bt_bap_stream_io_get_qos(stream, NULL, &qos)) {
			error("Unable to read QoS of stream %p", stream);
			break;
		}

		bt_bap_parse_base(0x00, base, qos, bass_debug, bis_src_handler,
					data);
		data->stream = stream;
		break;
	}
}

static void bap_bc_attached(struct bt_bap *bap, void *user_data)
{
	struct btd_gatt_database *db;
	struct btd_adapter *adapter;
	struct bass_data *data;

	/* Only handle broadcast session */
	if (bt_bap_get_att(bap))
		return;

	DBG("%p", bap);

	db = btd_gatt_database_get(bt_bap_get_db(bap, false));
	if (!db)
		return;

	adapter = btd_gatt_database_get_adapter(db);
	if (!adapter)
		return;

	/* Create BASS session with the local Broadcast Source */
	data = bass_data_new(adapter, NULL);
	data->bap = bap;
	data->state_id = bt_bap_state_register(bap, bap_state_src_changed,
						NULL, data, NULL);

	bass_data_add(data);
}

static bool delegator_match_device(const void *data, const void *match_data)
{
	const struct bass_delegator *dg = data;
	const struct btd_device *device = match_data;

	return dg->device == device;
}

static void delegator_attach(struct bt_bap *bap, struct btd_device *device,
				struct btd_service *service)
{
	struct bass_delegator *dg;
	GError *err = NULL;

	dg = queue_find(delegators, delegator_match_device, device);
	if (!dg)
		/* Only probe devices added via Broadcast Assistants */
		return;

	DBG("delegator %p", dg);

	if (dg->service)
		/* Service has already been probed */
		return;

	dg->service = service;
	dg->bap = bap;

	dg->io = bt_io_listen(NULL, confirm_cb, dg,
		NULL, &err,
		BT_IO_OPT_SOURCE_BDADDR,
		btd_adapter_get_address(device_get_adapter(device)),
		BT_IO_OPT_SOURCE_TYPE,
		btd_adapter_get_address_type(device_get_adapter(device)),
		BT_IO_OPT_DEST_BDADDR,
		device_get_address(device),
		BT_IO_OPT_DEST_TYPE,
		btd_device_get_bdaddr_type(device),
		BT_IO_OPT_MODE, BT_IO_MODE_ISO,
		BT_IO_OPT_QOS, &bap_sink_pa_qos,
		BT_IO_OPT_ISO_BC_SID, dg->sid,
		BT_IO_OPT_INVALID);
	if (!dg->io) {
		error("%s", err->message);
		g_error_free(err);
		return;
	}

	/* Take ownership for the service by setting the user data. */
	btd_service_set_user_data(service, dg);
}

static void bap_attached(struct bt_bap *bap, void *user_data)
{
	struct btd_service *service;
	struct btd_profile *p;
	struct btd_device *device;
	struct btd_adapter *adapter;
	struct bass_data *data;

	service = bt_bap_get_user_data(bap);
	if (!service)
		return bap_bc_attached(bap, user_data);

	DBG("%p", bap);

	p = btd_service_get_profile(service);
	if (!p)
		return;

	/* Only handle sessions with Broadcast Sources */
	if (!g_str_equal(p->remote_uuid, BCAAS_UUID_STR))
		return;

	device = btd_service_get_device(service);
	adapter = device_get_adapter(device);

	/* Create BASS session with the Broadcast Source */
	data = bass_data_new(adapter, device);

	/*
	 * Store BAP reference so match_bap() can find this session
	 * when bap_detached() is called during device removal.
	 */
	data->bap = bap;
	data->bis_id = bt_bap_bis_cb_register(bap, bis_probe,
					bis_remove, device, NULL);

	bass_data_add(data);

	delegator_attach(bap, device, service);
}

static bool match_bap(const void *data, const void *match_data)
{
	const struct bass_data *d = data;
	const struct bt_bap *bap = match_data;

	return (d->bap == bap);
}

static void bap_bc_detached(struct bt_bap *bap, struct bass_data *data)
{
	DBG("%p", bap);

	/*
	 * Unregister BIS callback before removing session to ensure
	 * no stale device pointers remain in the BAP callback queue.
	 */
	if (data->bis_id) {
		bt_bap_bis_cb_unregister(bap, data->bis_id);
		data->bis_id = 0;
	}

	bt_bap_state_unregister(bap, data->state_id);
	bass_data_remove(data);
}

static void delegator_free(struct bass_delegator *dg)
{
	DBG("%p", dg);

	if (dg->io_id)
		g_source_remove(dg->io_id);

	if (dg->io) {
		g_io_channel_shutdown(dg->io, TRUE, NULL);
		g_io_channel_unref(dg->io);
	}

	queue_destroy(dg->setups, setup_free);

	/* Update Broadcast Receive State characteristic value and notify
	 * peers.
	 */
	if (bt_bass_set_pa_sync(dg->src, BT_BASS_NOT_SYNCHRONIZED_TO_PA))
		DBG("Failed to update Broadcast Receive State characteristic");

	/* Unregister BAP stream state changed callback. */
	bt_bap_state_unregister(dg->bap, dg->state_id);

	bt_bap_bcode_cb_unregister(dg->bap, dg->bcode_id);

	if (dg->timeout)
		g_source_remove(dg->timeout);

	queue_destroy(dg->bcode_reqs, free);

	free(dg->bcode);

	free(dg);
}

static bool match_service(const void *data, const void *match_data)
{
	const struct bass_data *bdata = data;
	const struct btd_service *service = match_data;

	return bdata->service == service;
}

static void delegator_detach(struct btd_service *service)
{
	struct bass_delegator *dg;

	dg = btd_service_get_user_data(service);
	if (!dg)
		return;

	if (!queue_remove(delegators, dg))
		return;

	DBG("%p", dg);

	delegator_free(dg);

	btd_service_set_user_data(service, NULL);
}

static void bap_detached(struct bt_bap *bap, void *user_data)
{
	struct btd_service *service;
	struct bass_data *data;

	data = queue_find(sessions, match_bap, bap);
	if (data)
		return bap_bc_detached(bap, data);

	DBG("%p", bap);

	service = bt_bap_get_user_data(bap);
	if (!service)
		return;

	/* Remove BASS session with the Broadcast Source device */
	data = queue_find(sessions, match_service, service);
	if (data) {
		bt_bap_bis_cb_unregister(bap, data->bis_id);
		bt_bap_state_unregister(bap, data->state_id);
		bass_data_remove(data);
	}

	delegator_detach(service);
}

static void bis_probe(uint8_t sid, uint8_t bis, uint8_t sgrp,
			struct iovec *caps, struct iovec *meta,
			struct bt_bap_qos *qos, void *user_data)
{
	struct btd_device *device = user_data;
	const struct queue_entry *entry;
	struct bt_bap *bap;
	struct bt_bap_pac *pac;
	struct bass_assistant *assistant;
	char addr[18];

	for (entry = queue_get_entries(sessions); entry; entry = entry->next) {
		struct bass_data *data = entry->data;
		struct btd_adapter *adapter = device_get_adapter(data->device);

		if (!bt_bass_get_client(data->bass))
			/* Only client sessions must be handled */
			continue;

		bap = bt_bap_get_session(bt_bass_get_att(data->bass), NULL);
		if (!bap)
			continue;

		/* Check stream capabilities against peer caps. */
		bt_bap_verify_bis(bap, bis, caps, &pac);

		if (!pac)
			/* Capabilities did not match. */
			continue;

		ba2str(device_get_address(device), addr);

		DBG("%s data %p BIS %d", addr, data, bis);

		assistant = assistant_new(adapter, device, data, sgrp,
						sid, bis, qos, meta, caps);

		if (g_dbus_register_interface(btd_get_dbus_connection(),
						assistant->path,
						MEDIA_ASSISTANT_INTERFACE,
						assistant_methods, NULL,
						assistant_properties,
						assistant,
						assistant_free) == FALSE)
			DBG("Could not register path %s", assistant->path);
	}
}

static bool assistant_match_device(const void *data, const void *match_data)
{
	const struct bass_assistant *assistant = data;
	const struct btd_device *device = match_data;

	return (assistant->device == device);
}

static void bis_remove(struct bt_bap *bap, void *user_data)
{
	struct btd_device *device = user_data;

	queue_remove_all(assistants, assistant_match_device,
		device, unregister_assistant);
}

static struct bass_data *bass_data_new(struct btd_adapter *adapter,
					struct btd_device *device)
{
	struct bass_data *data;

	data = new0(struct bass_data, 1);
	data->adapter = adapter;
	data->device = device;

	return data;
}

static void bass_data_add(struct bass_data *data)
{
	bool initiator = false;

	DBG("data %p", data);

	if (queue_find(sessions, NULL, data)) {
		error("data %p already added", data);
		return;
	}

	bt_bass_set_debug(data->bass, bass_debug, NULL, NULL);

	if (!sessions)
		sessions = queue_new();

	queue_push_tail(sessions, data);

	if (data->service) {
		btd_service_set_user_data(data->service, data);
		initiator = btd_service_is_initiator(data->service);
	}

	if ((!initiator && btd_adapter_has_settings(data->adapter,
				MGMT_SETTING_PAST_RECEIVER)) || (initiator &&
				btd_adapter_has_settings(data->adapter,
				MGMT_SETTING_PAST_SENDER)))
		device_set_past_support(data->device, true);
}

static void bass_data_free(struct bass_data *data)
{
	if (data->service) {
		btd_service_set_user_data(data->service, NULL);
		bt_bass_set_user_data(data->bass, NULL);
	}

	bt_bass_src_unregister(data->bass, data->src_id);
	bt_bass_cp_handler_unregister(data->bass, data->cp_id);

	bt_bass_unref(data->bass);

	queue_remove_all(assistants, assistant_match_data,
		data, unregister_assistant);

	free(data);
}

static void bass_data_remove(struct bass_data *data)
{
	DBG("data %p", data);

	if (!queue_remove(sessions, data))
		return;

	bass_data_free(data);

	if (queue_isempty(sessions)) {
		queue_destroy(sessions, NULL);
		sessions = NULL;
	}
}

static void bass_detached(struct bt_bass *bass, void *user_data)
{
	struct bass_data *data;

	DBG("%p", bass);

	data = queue_find(sessions, match_bass, bass);
	if (!data) {
		error("Unable to find bass session");
		return;
	}

	/* If there is a service it means there is BASS thus we can keep
	 * instance allocated.
	 */
	if (data->service)
		return;

	bass_data_remove(data);
}

static struct bass_delegator *
bass_delegator_new(struct btd_device *device, struct bt_bcast_src *src,
			uint8_t sid)
{
	struct bass_delegator *dg;

	dg = new0(struct bass_delegator, 1);
	if (!dg)
		return NULL;

	dg->device = device;
	dg->src = src;
	dg->sid = sid;
	dg->bcode_reqs = queue_new();
	dg->setups = queue_new();

	if (!delegators)
		delegators = queue_new();

	queue_push_tail(delegators, dg);

	DBG("delegator %p", dg);

	/* Add Broadcast Audio Announcement Service UUID
	 * to device and probe service.
	 */
	btd_device_add_uuid(device, BCAAS_UUID_STR);

	if (!dg->service)
		error("Unable to probe service for %s", BCAAS_UUID_STR);

	return dg;
}

static int handle_add_src_req(struct bt_bcast_src *bcast_src,
			struct bt_bass_add_src_params *params,
			struct bass_data *data)
{
	struct btd_adapter *adapter = device_get_adapter(data->device);
	struct btd_device *device;
	struct bass_delegator *dg;

	/* Detect if PAST can be used then it can be used as destination since
	 * PAST Receiver uses the ACL connection itself.
	 */
	if (params->pa_sync == PA_SYNC_PAST) {
		/* Check if MGMT_SETTING_PAST_RECEIVER is supported then set
		 * DEVICE_FLAG_PAST since the device is requesting PAST to be
		 * used.
		 */
		if (btd_adapter_has_settings(data->adapter,
						MGMT_SETTING_PAST_RECEIVER)) {
			device_set_past_support(data->device, true);
			device = data->device;
			goto done;
		}
	}

	/* Create device for Broadcast Source using the parameters
	 * provided by Broadcast Assistant.
	 */
	device = btd_adapter_get_device(adapter, &params->addr,
						params->addr_type);

	if (!device) {
		DBG("Unable to get device");
		return -EINVAL;
	}

done:
	DBG("device %p", device);

	/* Probe Broadcast Source, if it has not already been
	 * autonomously probed inside BAP.
	 */
	if (!btd_device_get_service(device, BCAAS_UUID_STR)) {
		dg = bass_delegator_new(device, bcast_src, params->sid);
		if (!dg)
			return -ENOMEM;
	}

	/* Set PA sync state, this has to be done after probing otherwise there
	 * is a race where the remote may receive BT_BASS_SYNC_INFO_RE and start
	 * PAST before the kernel has sent PAST Parameter command that enables
	 * receiving it.
	 */
	if (params->pa_sync == PA_SYNC_PAST) {
		if (btd_adapter_has_settings(data->adapter,
						MGMT_SETTING_PAST_RECEIVER))
			bt_bass_set_pa_sync(bcast_src, BT_BASS_SYNC_INFO_RE);
		else
			bt_bass_set_pa_sync(bcast_src, BT_BASS_NO_PAST);
	}

	return 0;
}

static bool delegator_match_src(const void *data, const void *match_data)
{
	const struct bass_delegator *dg = data;
	const struct bt_bcast_src *src = match_data;

	return dg->src == src;
}

static int handle_set_bcode_req(struct bt_bcast_src *bcast_src,
			struct bt_bass_set_bcast_code_params *params,
			struct bass_data *data)
{
	struct bass_delegator *dg;
	struct bass_bcode_req *req;

	dg = queue_find(delegators, delegator_match_src, bcast_src);
	if (!dg)
		return -EINVAL;

	dg->bcode = new0(uint8_t, BT_BASS_BCAST_CODE_SIZE);
	memcpy(dg->bcode, params->bcast_code, BT_BASS_BCAST_CODE_SIZE);

	if (dg->timeout) {
		g_source_remove(dg->timeout);
		dg->timeout = 0;
	}

	/* Set the Broadcast Code for each stream that required it. */
	while ((req = queue_pop_head(dg->bcode_reqs))) {
		setup_set_bcode(dg->bcode, req->setup, req->cb,
							req->user_data);
		free(req);
	}

	return 0;
}

static bool check_bis_sync(struct bt_bass_mod_src_params *params, uint8_t bis)
{
	uint32_t bitmask = 1 << (bis - 1);
	struct iovec iov = {
		.iov_base = params->subgroup_data,
		.iov_len = 0,
	};

	/* Calculate subgroup data length based on each subgroup's
	 * bis_sync (4 bytes) + meta_len (1 byte) + meta fields.
	 */
	for (uint8_t i = 0; i < params->num_subgroups; i++) {
		uint32_t bis_sync;
		uint8_t meta_len;

		iov.iov_len += sizeof(bis_sync) + sizeof(meta_len);

		memcpy(&bis_sync, params->subgroup_data + iov.iov_len -
				sizeof(bis_sync) - sizeof(meta_len),
				sizeof(bis_sync));
		memcpy(&meta_len, params->subgroup_data + iov.iov_len -
				sizeof(meta_len), sizeof(meta_len));

		if (le32_to_cpu(bis_sync) & bitmask)
			return true;

		iov.iov_len += meta_len;
	}

	return false;
}

static void bass_update_bis_sync(struct bass_delegator *dg,
				struct bt_bass_mod_src_params *params)
{
	const struct queue_entry *entry;

	/* Check if existing setups if BIS needs to be added/removed */
	for (entry = queue_get_entries(dg->setups); entry;) {
		struct bass_setup *setup = entry->data;
		uint8_t state;

		/* Prefetch next entry since the likes of bass_remove_bis can
		 * end up removing the next entry.
		 */
		entry = entry->next;

		state = bt_bap_stream_get_state(setup->stream);

		DBG("stream %p: BIS %d state %s(%u)", setup->stream, setup->bis,
				bt_bap_stream_statestr(state), state);

		if (!setup->stream && check_bis_sync(params, setup->bis))
			bass_add_bis(setup);
		else if (setup->stream &&
				state == BT_BAP_STREAM_STATE_STREAMING &&
				!check_bis_sync(params, setup->bis))
			bass_remove_bis(setup);
	}
}

static int handle_mod_src_req(struct bt_bcast_src *bcast_src,
			struct bt_bass_mod_src_params *params,
			struct bass_data *data)
{
	struct bass_delegator *dg;
	uint8_t sync_state;
	int err = 0;

	DBG("");

	dg = queue_find(delegators, delegator_match_src, bcast_src);
	if (!dg)
		return -EINVAL;

	err = bt_bass_get_pa_sync(bcast_src, &sync_state);
	if (err)
		return err;

	DBG("PA sync state %d", sync_state);

	switch (sync_state) {
	case BT_BASS_SYNCHRONIZED_TO_PA:
		if (params->pa_sync == PA_SYNC_NO_SYNC) {
			/* Release all setups. Note: bass_remove_bis may
			 * trigger synchronous state transitions that call
			 * setup_clear which will return early since the
			 * setup has already been removed from the queue.
			 */
			struct bass_setup *setup;

			while ((setup = queue_pop_head(dg->setups))) {
				bass_remove_bis(setup);
				setup_free(setup);
			}

			delegator_disconnect(dg);
		} else
			bass_update_bis_sync(dg, params);
		break;
	case BT_BASS_NOT_SYNCHRONIZED_TO_PA:
		if (params->pa_sync == PA_SYNC_NO_PAST) {
			struct btd_adapter *adapter =
					device_get_adapter(dg->device);
			GError *err = NULL;

			dg->io = bt_io_listen(NULL, confirm_cb, dg,
				NULL, &err,
				BT_IO_OPT_SOURCE_BDADDR,
				btd_adapter_get_address(adapter),
				BT_IO_OPT_SOURCE_TYPE,
				btd_adapter_get_address_type(adapter),
				BT_IO_OPT_DEST_BDADDR,
				device_get_address(dg->device),
				BT_IO_OPT_DEST_TYPE,
				btd_device_get_bdaddr_type(dg->device),
				BT_IO_OPT_MODE, BT_IO_MODE_ISO,
				BT_IO_OPT_QOS, &bap_sink_pa_qos,
				BT_IO_OPT_ISO_BC_SID, dg->sid,
				BT_IO_OPT_INVALID);
			if (!dg->io) {
				error("%s", err->message);
				g_error_free(err);
			}
		}

		break;
	}

	return 0;
}

static int cp_handler(struct bt_bcast_src *bcast_src, uint8_t op, void *params,
		void *user_data)
{
	struct bass_data *data = user_data;
	int err = 0;

	switch (op) {
	case BT_BASS_ADD_SRC:
		err = handle_add_src_req(bcast_src, params, data);
		break;
	case BT_BASS_SET_BCAST_CODE:
		err = handle_set_bcode_req(bcast_src, params, data);
		break;
	case BT_BASS_MOD_SRC:
		err = handle_mod_src_req(bcast_src, params, data);
		break;
	}

	return err;
}

static void bass_attached(struct bt_bass *bass, void *user_data)
{
	struct bass_data *data;
	struct bt_att *att;
	struct btd_device *device;

	DBG("%p", bass);

	data = queue_find(sessions, match_bass, bass);
	if (data)
		return;

	att = bt_bass_get_att(bass);
	if (!att)
		return;

	device = btd_adapter_find_device_by_fd(bt_att_get_fd(att));
	if (!device) {
		error("Unable to find device");
		return;
	}

	data = bass_data_new(device_get_adapter(device), device);
	data->bass = bass;

	data->cp_id = bt_bass_cp_handler_register(data->bass,
			cp_handler, NULL, data);

	bass_data_add(data);
}

static void bass_handle_bcode_req(struct bass_assistant *assistant, int id)
{
	struct bt_bass_bcast_audio_scan_cp_hdr hdr;
	struct bt_bass_set_bcast_code_params params = {0};
	struct iovec iov = {0};
	int err;

	assistant_set_state(assistant, ASSISTANT_STATE_REQUESTING);

	hdr.op = BT_BASS_SET_BCAST_CODE;

	params.id = id;

	if (assistant->qos.bcast.bcode)
		memcpy(params.bcast_code,
			assistant->qos.bcast.bcode->iov_base,
			BT_BASS_BCAST_CODE_SIZE);

	iov.iov_base = malloc0(sizeof(params));
	if (!iov.iov_base)
		return;

	util_iov_push_mem(&iov, sizeof(params), &params);

	err = bt_bass_send(assistant->data->bass, &hdr, &iov);
	if (err) {
		DBG("Unable to send BASS Write Command");
		return;
	}

	free(iov.iov_base);
}

static struct bass_src *assistant_add_src(struct bass_assistant *assistant,
					struct bt_bass *bass, uint8_t id)
{
	struct bass_src *src;

	if (!assistant->srcs)
		assistant->srcs = queue_new();

	src = queue_find(assistant->srcs, match_src_data, bass);
	if (src) {
		src->src_id = id;
		return src;
	}

	src = new0(struct bass_src, 1);
	src->bass = bass;
	src->src_id = id;

	queue_push_tail(assistant->srcs, src);

	return src;
}

static void bass_src_changed(uint8_t id, uint32_t bid, uint8_t state,
				uint8_t enc, uint32_t bis_sync, void *user_data)
{
	struct bass_data *data = user_data;
	const struct queue_entry *entry;

	for (entry = queue_get_entries(assistants); entry;
						entry = entry->next) {
		struct bass_assistant *assistant = entry->data;
		uint32_t bis = 1 << (assistant->bis - 1);

		if (bid && assistant->bid != bid)
			/* Only handle assistant objects
			 * that match the source
			 */
			continue;

		/* If BID is not set it may happen to be local stream so ignore
		 * non-local assistants unless their BID is also not set.
		 */
		if (!bid && assistant->bid)
			continue;

		if (state == BT_BASS_SYNC_INFO_RE) {
			assistant_past(assistant);
			return;
		}

		switch (enc) {
		case BT_BASS_BIG_ENC_STATE_BCODE_REQ:
			if (assistant->state != ASSISTANT_STATE_PENDING &&
				assistant->state != ASSISTANT_STATE_LOCAL)
				/* Only handle assistant objects that
				 * have been pushed by the user
				 */
				break;

			/* Provide Broadcast Code to peer */
			bass_handle_bcode_req(assistant, id);

			if (assistant->state == ASSISTANT_STATE_LOCAL)
				return;

			break;
		case BT_BASS_BIG_ENC_STATE_NO_ENC:
			if (assistant->state != ASSISTANT_STATE_PENDING)
				/* Only handle assistant objects that
				 * have been pushed by the user
				 */
				break;

			/* Match BIS index */
			if (bis & bis_sync) {
				assistant->src_id = id;
				assistant_set_state(assistant,
						ASSISTANT_STATE_ACTIVE);
			}
			break;
		case BT_BASS_BIG_ENC_STATE_DEC:
			/* Only handle assistant objects that
			 * have requested a Broadcast Code
			 */
			if (assistant->state != ASSISTANT_STATE_REQUESTING &&
				assistant->state != ASSISTANT_STATE_LOCAL)
				break;

			/* Match BIS index */
			if (bis & bis_sync) {
				assistant->src_id = id;

				if (assistant->state ==
						ASSISTANT_STATE_LOCAL) {
					assistant_add_src(assistant, data->bass,
								id);
					break;
				}

				assistant_set_state(assistant,
						ASSISTANT_STATE_ACTIVE);
			}
			break;
		default:
			continue;
		}
	}
}

static int bass_probe(struct btd_service *service)
{
	struct btd_device *device = btd_service_get_device(service);
	struct btd_adapter *adapter = device_get_adapter(device);
	struct btd_gatt_database *database = btd_adapter_get_database(adapter);
	struct bass_data *data = btd_service_get_user_data(service);
	char addr[18];

	ba2str(device_get_address(device), addr);
	DBG("%s", addr);

	/* Ignore, if we were probed for this device already */
	if (data) {
		error("Profile probed twice for the same device!");
		return -EINVAL;
	}

	data = bass_data_new(adapter, device);
	data->service = service;

	data->bass = bt_bass_new(btd_gatt_database_get_db(database),
					btd_device_get_gatt_db(device),
					btd_adapter_get_address(adapter));
	if (!data->bass) {
		error("Unable to create BASS instance");
		free(data);
		return -EINVAL;
	}

	bass_data_add(data);
	bt_bass_set_user_data(data->bass, service);

	/* Register callback to be called when notifications for
	 * Broadcast Receive State characteristics are received.
	 */
	data->src_id = bt_bass_src_register(data->bass, bass_src_changed,
						data, NULL);

	data->cp_id = bt_bass_cp_handler_register(data->bass,
			cp_handler, NULL, data);

	return 0;
}

static void bass_remove(struct btd_service *service)
{
	struct btd_device *device = btd_service_get_device(service);
	struct bass_data *data;
	char addr[18];

	ba2str(device_get_address(device), addr);
	DBG("%s", addr);

	data = btd_service_get_user_data(service);
	if (!data) {
		error("BASS service not handled by profile");
		return;
	}

	bass_data_remove(data);
}

static int bass_accept(struct btd_service *service)
{
	struct btd_device *device = btd_service_get_device(service);
	struct bt_gatt_client *client = btd_device_get_gatt_client(device);
	struct bass_data *data = btd_service_get_user_data(service);
	char addr[18];

	ba2str(device_get_address(device), addr);
	DBG("%s", addr);

	if (!data) {
		error("BASS service not handled by profile");
		return -EINVAL;
	}

	/* Only attach client if initiator of the connection otherwise act as
	 * delegator.
	 */
	if (btd_service_is_initiator(service) &&
			!bt_bass_attach(data->bass, client)) {
		error("BASS unable to attach");
		return -EINVAL;
	}

	btd_service_connecting_complete(service, 0);

	return 0;
}

static int bass_disconnect(struct btd_service *service)
{
	struct bass_data *data = btd_service_get_user_data(service);
	struct btd_device *device = btd_service_get_device(service);
	char addr[18];

	ba2str(device_get_address(device), addr);
	DBG("%s", addr);

	bt_bass_detach(data->bass);

	btd_service_disconnecting_complete(service, 0);

	return 0;
}

static int bass_server_probe(struct btd_profile *p,
				struct btd_adapter *adapter)
{
	struct btd_gatt_database *database = btd_adapter_get_database(adapter);

	DBG("BASS path %s", adapter_get_path(adapter));

	bt_bass_add_db(btd_gatt_database_get_db(database),
				btd_adapter_get_address(adapter));

	return 0;
}

static void bass_server_remove(struct btd_profile *p,
					struct btd_adapter *adapter)
{
	DBG("BASS remove Adapter");
}

static struct btd_profile bass_service = {
	.name		= "bass",
	.priority	= BTD_PROFILE_PRIORITY_MEDIUM,
	.bearer		= BTD_PROFILE_BEARER_LE,
	.remote_uuid	= BASS_UUID_STR,
	.device_probe	= bass_probe,
	.device_remove	= bass_remove,
	.accept		= bass_accept,
	.disconnect	= bass_disconnect,
	.adapter_probe	= bass_server_probe,
	.adapter_remove	= bass_server_remove,
	.experimental	= true,
};

static unsigned int bass_id;
static unsigned int bap_id;

static int bass_init(void)
{
	int err;

	err = btd_profile_register(&bass_service);
	if (err)
		return err;

	bass_id = bt_bass_register(bass_attached, bass_detached, NULL);
	bap_id = bt_bap_register(bap_attached, bap_detached, NULL);

	return 0;
}

static void bass_exit(void)
{
	btd_profile_unregister(&bass_service);
	bt_bass_unregister(bass_id);
	bt_bap_unregister(bap_id);
}

BLUETOOTH_PLUGIN_DEFINE(bass, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
							bass_init, bass_exit)
