Pl_AES_PDF::initializeVector()
{
    if (use_zero_iv)
    {
	for (unsigned int i = 0; i < this->buf_size; ++i)
	{
	    this->cbc_block[i] = 0;
	}
    }
    else if (use_specified_iv)
    {
        std::memcpy(this->cbc_block, this->specified_iv, this->buf_size);
    }
    else if (use_static_iv)
    {
	for (unsigned int i = 0; i < this->buf_size; ++i)
	{
	    this->cbc_block[i] = static_cast<unsigned char>(14U * (1U + i));
	}
    }
    else
    {
        QUtil::initializeWithRandomBytes(this->cbc_block, this->buf_size);
    }
}


Pl_AES_PDF::write(unsigned char* data, size_t len)
{
    size_t bytes_left = len;
    unsigned char* p = data;

    while (bytes_left > 0)
    {
	if (this->offset == this->buf_size)
	{
	    flush(false);
	}

	size_t available = this->buf_size - this->offset;
	size_t bytes = (bytes_left < available ? bytes_left : available);
	bytes_left -= bytes;
	std::memcpy(this->inbuf + this->offset, p, bytes);
	this->offset += bytes;
	p += bytes;
    }
}


Pl_Count::write(unsigned char* buf, size_t len)
{
    if (len)
    {
	this->m->count += QIntC::to_offset(len);
	this->m->last_char = buf[len - 1];
	getNext()->write(buf, len);
    }
}


static void reset_adv_monitors(uint16_t index)
{
	struct mgmt_cp_remove_adv_monitor cp;

	DBG("sending remove Adv Monitor command with handle 0");

	/* Handle 0 indicates to remove all */
	cp.monitor_handle = 0;
	if (mgmt_send(mgmt_master, MGMT_OP_REMOVE_ADV_MONITOR, index,
			sizeof(cp), &cp, reset_adv_monitors_complete, NULL,
			NULL) > 0) {
		return;
	}

	error("Failed to reset Adv Monitors");
}


static void adapter_free(gpointer user_data)
{
	struct btd_adapter *adapter = user_data;

	DBG("%p", adapter);

	/* Make sure the adapter's discovery list is cleaned up before freeing
	 * the adapter.
	 */
	remove_discovery_list(adapter);

	if (adapter->pairable_timeout_id > 0) {
		timeout_remove(adapter->pairable_timeout_id);
		adapter->pairable_timeout_id = 0;
	}

	if (adapter->passive_scan_timeout > 0) {
		timeout_remove(adapter->passive_scan_timeout);
		adapter->passive_scan_timeout = 0;
	}

	if (adapter->load_ltks_timeout > 0)
		timeout_remove(adapter->load_ltks_timeout);

	if (adapter->confirm_name_timeout > 0)
		timeout_remove(adapter->confirm_name_timeout);

	if (adapter->pair_device_timeout > 0)
		timeout_remove(adapter->pair_device_timeout);

	if (adapter->auth_idle_id)
		g_source_remove(adapter->auth_idle_id);

	g_queue_foreach(adapter->auths, free_service_auth, NULL);
	g_queue_free(adapter->auths);

	/*
	 * Unregister all handlers for this specific index since
	 * the adapter bound to them is no longer valid.
	 *
	 * This also avoids having multiple instances of the same
	 * handler in case indexes got removed and re-added.
	 */
	mgmt_unregister_index(adapter->mgmt, adapter->dev_id);

	/*
	 * Cancel all pending commands for this specific index
	 * since the adapter bound to them is no longer valid.
	 */
	mgmt_cancel_index(adapter->mgmt, adapter->dev_id);

	mgmt_unref(adapter->mgmt);

	sdp_list_free(adapter->services, NULL);

	g_slist_free(adapter->connections);

	g_free(adapter->path);
	g_free(adapter->name);
	g_free(adapter->short_name);
	g_free(adapter->system_name);
	g_free(adapter->stored_alias);
	g_free(adapter->current_alias);
	free(adapter->modalias);
	g_free(adapter);
}


static bool is_blocked_key(uint8_t key_type, uint8_t *key_value)
{
	uint32_t i = 0;

	for (i = 0; i < ARRAY_SIZE(blocked_keys); ++i) {
		if (key_type == blocked_keys[i].type &&
				!memcmp(blocked_keys[i].val, key_value,
						sizeof(blocked_keys[i].val)))
			return true;
	}

	return false;
}


struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter)
{
	__sync_fetch_and_add(&adapter->ref_count, 1);

	return adapter;
}


static void clear_discoverable(struct btd_adapter *adapter)
{
	if (!btd_has_kernel_features(KERNEL_CONN_CONTROL))
		return;

	if (!(adapter->current_settings & MGMT_SETTING_DISCOVERABLE))
		return;

	/* If no timeout is set do nothing as both connectable and discoverable
	 * flags are persistent on power toggle.
	 */
	if (!adapter->discoverable_timeout)
		return;

	/* If timeout was set kernel clears discoverable on its own when
	 * powering off controller. This would leave connectable flag set
	 * after power on.
	 *
	 * With kernel control clearing connectable clear also discoverable
	 * flag so we need to clear connectable.
	 */
	set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x00);
}


static void device_unblocked_callback(uint16_t index, uint16_t length,
					const void *param, void *user_data)
{
	const struct mgmt_ev_device_unblocked *ev = param;
	struct btd_adapter *adapter = user_data;
	struct btd_device *device;
	char addr[18];

	if (length < sizeof(*ev)) {
		btd_error(adapter->dev_id, "Too small device unblocked event");
		return;
	}

	ba2str(&ev->addr.bdaddr, addr);
	DBG("hci%u %s unblocked", index, addr);

	device = btd_adapter_find_device(adapter, &ev->addr.bdaddr,
								ev->addr.type);
	if (device)
		device_unblock(device, FALSE, TRUE);
}


static int merge_discovery_filters(struct btd_adapter *adapter, int *rssi,
					uint8_t *transport, GSList **uuids)
{
	GSList *l;
	bool empty_uuid = false;
	bool has_regular_discovery = false;
	bool has_filtered_discovery = false;

	for (l = adapter->discovery_list; l != NULL; l = g_slist_next(l)) {
		struct discovery_client *client = l->data;
		struct discovery_filter *item = client->discovery_filter;

		if (!item) {
			has_regular_discovery = true;
			continue;
		}

		has_filtered_discovery = true;

		*transport |= item->type;

		/*
		 * Rule for merging rssi and pathloss into rssi field of kernel
		 * filter is as follow:
		 * - if there's any client without proximity filter, then do no
		 *   proximity filtering,
		 * - if all clients specified RSSI, then use lowest value,
		 * - if any client specified pathloss, then kernel filter should
		 *   do no proximity, as kernel can't compute pathloss. We'll do
		 *   filtering on our own.
		 */
		if (item->rssi == DISTANCE_VAL_INVALID)
			*rssi = HCI_RSSI_INVALID;
		else if (*rssi != HCI_RSSI_INVALID && *rssi >= item->rssi)
			*rssi = item->rssi;
		else if (item->pathloss != DISTANCE_VAL_INVALID)
			*rssi = HCI_RSSI_INVALID;

		if (!g_slist_length(item->uuids))
			empty_uuid = true;

		g_slist_foreach(item->uuids, extract_unique_uuids, uuids);
	}

	/* If no proximity filtering is set, disable it */
	if (*rssi == DISTANCE_VAL_INVALID)
		*rssi = HCI_RSSI_INVALID;

	/*
	 * Empty_uuid variable determines wether there was any filter with no
	 * uuids. In this case someone might be looking for all devices in
	 * certain proximity, and we need to have empty uuids in kernel filter.
	 */
	if (empty_uuid) {
		g_slist_free(*uuids);
		*uuids = NULL;
	}

	if (has_regular_discovery) {
		if (!has_filtered_discovery)
			return 1;

		/*
		 * It there is both regular and filtered scan running, then
		 * clear whole fitler to report all devices.
		 */
		*transport = get_scan_type(adapter);
		*rssi = HCI_RSSI_INVALID;
		g_slist_free(*uuids);
		*uuids = NULL;
	}

	return 0;
}


static DBusMessage *set_discovery_filter(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct btd_adapter *adapter = user_data;
	struct discovery_client *client;
	struct discovery_filter *discovery_filter;
	const char *sender = dbus_message_get_sender(msg);
	bool is_discovering;

	DBG("sender %s", sender);

	if (!btd_adapter_get_powered(adapter))
		return btd_error_not_ready(msg);

	if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 8))
		return btd_error_not_supported(msg);

	/* parse parameters */
	if (!parse_discovery_filter_dict(adapter, &discovery_filter, msg))
		return btd_error_invalid_args(msg);

	is_discovering = get_discovery_client(adapter, sender, &client);

	if (client) {
		free_discovery_filter(client->discovery_filter);
		client->discovery_filter = discovery_filter;

		if (is_discovering)
			update_discovery_filter(adapter);

		if (discovery_filter || is_discovering)
			return dbus_message_new_method_return(msg);

		/* Removing pre-set filter */
		adapter->set_filter_list = g_slist_remove(
					      adapter->set_filter_list,
					      client);
		discovery_free(client);
		DBG("successfully cleared pre-set filter");
	} else if (discovery_filter) {
		/* Client pre-setting his filter for first time */
		client = g_new0(struct discovery_client, 1);
		client->adapter = adapter;
		client->owner = g_strdup(sender);
		client->discovery_filter = discovery_filter;
		client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender,
						discovery_disconnect, client,
						NULL);
		adapter->set_filter_list = g_slist_prepend(
					     adapter->set_filter_list, client);

		DBG("successfully pre-set filter");
	}

	return dbus_message_new_method_return(msg);
}


static int update_discovery_filter(struct btd_adapter *adapter)
{
	struct mgmt_cp_start_service_discovery *sd_cp;

	DBG("");

	if (discovery_filter_to_mgmt_cp(adapter, &sd_cp)) {
		btd_error(adapter->dev_id,
				"discovery_filter_to_mgmt_cp returned error");
		return -ENOMEM;
	}

	/* Only attempt to overwrite current discoverable setting when not
	 * discoverable.
	 */
	if (!(adapter->current_settings & MGMT_OP_SET_DISCOVERABLE)) {
		GSList *l;

		for (l = adapter->discovery_list; l; l = g_slist_next(l)) {
			struct discovery_client *client = l->data;

			if (!client->discovery_filter)
				continue;

			if (client->discovery_filter->discoverable) {
				set_discovery_discoverable(adapter, true);
				break;
			}
		}
	}

	/*
	 * If filters are equal, then don't update scan, except for when
	 * starting discovery.
	 */
	if (filters_equal(adapter->current_discovery_filter, sd_cp) &&
	    adapter->discovering != 0) {
		DBG("filters were equal, deciding to not restart the scan.");
		g_free(sd_cp);
		return 0;
	}

	g_free(adapter->current_discovery_filter);
	adapter->current_discovery_filter = sd_cp;

	trigger_start_discovery(adapter, 0);

	return -EINPROGRESS;
}


const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter)
{
	return &adapter->bdaddr;
}


int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
					uint8_t addr_type, uint8_t io_cap)
{
	if (adapter->pair_device_id > 0) {
		btd_error(adapter->dev_id,
			"Unable pair since another pairing is in progress");
		return -EBUSY;
	}

	suspend_discovery(adapter);

	return adapter_bonding_attempt(adapter, bdaddr, addr_type, io_cap);
}


static void load_link_keys(struct btd_adapter *adapter, GSList *keys,
							bool debug_keys)
{
	struct mgmt_cp_load_link_keys *cp;
	struct mgmt_link_key_info *key;
	size_t key_count, cp_size;
	unsigned int id;
	GSList *l;

	/*
	 * If the controller does not support BR/EDR operation,
	 * there is no point in trying to load the link keys into
	 * the kernel.
	 *
	 * This is an optimization for Low Energy only controllers.
	 */
	if (!(adapter->supported_settings & MGMT_SETTING_BREDR))
		return;

	key_count = g_slist_length(keys);

	DBG("hci%u keys %zu debug_keys %d", adapter->dev_id, key_count,
								debug_keys);

	cp_size = sizeof(*cp) + (key_count * sizeof(*key));

	cp = g_try_malloc0(cp_size);
	if (cp == NULL) {
		btd_error(adapter->dev_id, "No memory for link keys for hci%u",
							adapter->dev_id);
		return;
	}

	/*
	 * Even if the list of stored keys is empty, it is important to
	 * load an empty list into the kernel. That way it is ensured
	 * that no old keys from a previous daemon are present.
	 *
	 * In addition it is also the only way to toggle the different
	 * behavior for debug keys.
	 */
	cp->debug_keys = debug_keys;
	cp->key_count = htobs(key_count);

	for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) {
		struct link_key_info *info = l->data;

		bacpy(&key->addr.bdaddr, &info->bdaddr);
		key->addr.type = BDADDR_BREDR;
		key->type = info->type;
		memcpy(key->val, info->key, 16);
		key->pin_len = info->pin_len;
	}

	id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_LINK_KEYS,
				adapter->dev_id, cp_size, cp,
				load_link_keys_complete, adapter, NULL);

	g_free(cp);

	if (id == 0)
		btd_error(adapter->dev_id, "Failed to load link keys for hci%u",
							adapter->dev_id);
}


void adapter_shutdown(void)
{
	GList *list;

	DBG("");

	powering_down = true;

	for (list = g_list_first(adapter_list); list;
						list = g_list_next(list)) {
		struct btd_adapter *adapter = list->data;

		if (!(adapter->current_settings & MGMT_SETTING_POWERED))
			continue;

		clear_discoverable(adapter);
		remove_temporary_devices(adapter);
		set_mode(adapter, MGMT_OP_SET_POWERED, 0x00);

		adapter_remaining++;
	}

	if (!adapter_remaining)
		btd_exit();
}


static void iter_append_uuid(gpointer key, gpointer value, gpointer user_data)
{
	DBusMessageIter *iter = user_data;
	const char *uuid = key;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid);
}


static void property_set_pairable(const GDBusPropertyTable *property,
				DBusMessageIter *iter,
				GDBusPendingPropertySet id, void *user_data)
{
	struct btd_adapter *adapter = user_data;

	property_set_mode(adapter, MGMT_SETTING_BONDABLE, iter, id);
}


static void convert_classes_entry(GKeyFile *key_file, void *value)
{
	g_key_file_set_string(key_file, "General", "Class", value);
}


static void service_auth_cancel(struct service_auth *auth)
{
	DBusError derr;

	if (auth->svc_id > 0)
		device_remove_svc_complete_callback(auth->device,
								auth->svc_id);

	dbus_error_init(&derr);
	dbus_set_error_const(&derr, ERROR_INTERFACE ".Canceled", NULL);

	auth->cb(&derr, auth->user_data);

	dbus_error_free(&derr);

	if (auth->agent != NULL) {
		agent_cancel(auth->agent);
		agent_unref(auth->agent);
	}

	g_free(auth);
}


static void store_longtermkey(struct btd_adapter *adapter, const bdaddr_t *peer,
				uint8_t bdaddr_type, const unsigned char *key,
				uint8_t master, uint8_t authenticated,
				uint8_t enc_size, uint16_t ediv,
				uint64_t rand)
{
	const char *group = master ? "LongTermKey" : "SlaveLongTermKey";
	char device_addr[18];
	char filename[PATH_MAX];
	GKeyFile *key_file;
	char key_str[33];
	gsize length = 0;
	char *str;
	int i;

	if (master != 0x00 && master != 0x01) {
		error("Unsupported LTK type %u", master);
		return;
	}

	ba2str(peer, device_addr);

	snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info",
			btd_adapter_get_storage_dir(adapter), device_addr);
	key_file = g_key_file_new();
	g_key_file_load_from_file(key_file, filename, 0, NULL);

	/* Old files may contain this so remove it in case it exists */
	g_key_file_remove_key(key_file, "LongTermKey", "Master", NULL);

	for (i = 0; i < 16; i++)
		sprintf(key_str + (i * 2), "%2.2X", key[i]);

	g_key_file_set_string(key_file, group, "Key", key_str);

	g_key_file_set_integer(key_file, group, "Authenticated",
							authenticated);
	g_key_file_set_integer(key_file, group, "EncSize", enc_size);

	g_key_file_set_integer(key_file, group, "EDiv", ediv);
	g_key_file_set_uint64(key_file, group, "Rand", rand);

	create_file(filename, 0600);

	str = g_key_file_to_data(key_file, &length, NULL);
	g_file_set_contents(filename, str, length, NULL);
	g_free(str);

	g_key_file_free(key_file);
}


static bool set_discovery_discoverable(struct btd_adapter *adapter, bool enable)
{
	if (adapter->discovery_discoverable == enable)
		return true;

	/* Reset discoverable filter if already set */
	if (enable && (adapter->current_settings & MGMT_OP_SET_DISCOVERABLE))
		return true;

	adapter->discovery_discoverable = enable;

	return set_discoverable(adapter, enable, 0);
}


void btd_add_conn_fail_cb(btd_conn_fail_cb func)
{
	conn_fail_list = g_slist_append(conn_fail_list, func);
}


static bool parse_duplicate_data(DBusMessageIter *value,
					struct discovery_filter *filter)
{
	if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
		return false;

	dbus_message_iter_get_basic(value, &filter->duplicate);

	return true;
}


static void load_link_keys_complete(uint8_t status, uint16_t length,
					const void *param, void *user_data)
{
	struct btd_adapter *adapter = user_data;

	if (status != MGMT_STATUS_SUCCESS) {
		btd_error(adapter->dev_id,
			"Failed to load link keys for hci%u: %s (0x%02x)",
				adapter->dev_id, mgmt_errstr(status), status);
		return;
	}

	DBG("link keys loaded for hci%u", adapter->dev_id);
}


static void store_adapter_info(struct btd_adapter *adapter)
{
	GKeyFile *key_file;
	char filename[PATH_MAX];
	char *str;
	gsize length = 0;
	gboolean discoverable;

	key_file = g_key_file_new();

	if (adapter->pairable_timeout != btd_opts.pairto)
		g_key_file_set_integer(key_file, "General", "PairableTimeout",
					adapter->pairable_timeout);

	if ((adapter->current_settings & MGMT_SETTING_DISCOVERABLE) &&
						!adapter->discoverable_timeout)
		discoverable = TRUE;
	else
		discoverable = FALSE;

	g_key_file_set_boolean(key_file, "General", "Discoverable",
							discoverable);

	if (adapter->discoverable_timeout != btd_opts.discovto)
		g_key_file_set_integer(key_file, "General",
					"DiscoverableTimeout",
					adapter->discoverable_timeout);

	if (adapter->stored_alias)
		g_key_file_set_string(key_file, "General", "Alias",
							adapter->stored_alias);

	snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings",
					btd_adapter_get_storage_dir(adapter));

	create_file(filename, 0600);

	str = g_key_file_to_data(key_file, &length, NULL);
	g_file_set_contents(filename, str, length, NULL);
	g_free(str);

	g_key_file_free(key_file);
}


static void remove_device_complete(uint8_t status, uint16_t length,
					const void *param, void *user_data)
{
	const struct mgmt_rp_remove_device *rp = param;
	char addr[18];

	if (length < sizeof(*rp)) {
		error("Too small Remove Device complete event");
		return;
	}

	ba2str(&rp->addr.bdaddr, addr);

	if (status != MGMT_STATUS_SUCCESS) {
		error("Failed to remove device %s (%u): %s (0x%02x)",
			addr, rp->addr.type, mgmt_errstr(status), status);
		return;
	}

	DBG("%s (%u) removed from kernel connect list", addr, rp->addr.type);
}


static struct smp_ltk_info *get_ltk(GKeyFile *key_file, const char *peer,
					uint8_t peer_type, const char *group)
{
	struct smp_ltk_info *ltk = NULL;
	GError *gerr = NULL;
	bool master;
	char *key;
	char *rand = NULL;

	key = g_key_file_get_string(key_file, group, "Key", NULL);
	if (!key || strlen(key) < 32)
		goto failed;

	rand = g_key_file_get_string(key_file, group, "Rand", NULL);
	if (!rand)
		goto failed;

	ltk = g_new0(struct smp_ltk_info, 1);

	/* Default to assuming a master key */
	ltk->master = true;

	str2ba(peer, &ltk->bdaddr);
	ltk->bdaddr_type = peer_type;

	/*
	 * Long term keys should respond to an identity address which can
	 * either be a public address or a random static address. Keys
	 * stored for resolvable random and unresolvable random addresses
	 * are ignored.
	 *
	 * This is an extra sanity check for older kernel versions or older
	 * daemons that might have been instructed to store long term keys
	 * for these temporary addresses.
	 */
	if (ltk->bdaddr_type == BDADDR_LE_RANDOM &&
					(ltk->bdaddr.b[5] & 0xc0) != 0xc0) {
		g_free(ltk);
		ltk = NULL;
		goto failed;
	}

	if (!strncmp(key, "0x", 2))
		str2buf(&key[2], ltk->val, sizeof(ltk->val));
	else
		str2buf(&key[0], ltk->val, sizeof(ltk->val));

	if (!strncmp(rand, "0x", 2)) {
		uint64_t rand_le;
		str2buf(&rand[2], (uint8_t *) &rand_le, sizeof(rand_le));
		ltk->rand = le64_to_cpu(rand_le);
	} else {
		sscanf(rand, "%" PRIu64, &ltk->rand);
	}

	ltk->authenticated = g_key_file_get_integer(key_file, group,
							"Authenticated", NULL);
	ltk->enc_size = g_key_file_get_integer(key_file, group, "EncSize",
									NULL);
	ltk->ediv = g_key_file_get_integer(key_file, group, "EDiv", NULL);

	master = g_key_file_get_boolean(key_file, group, "Master", &gerr);
	if (gerr)
		g_error_free(gerr);
	else
		ltk->master = master;

	ltk->is_blocked = is_blocked_key(HCI_BLOCKED_KEY_TYPE_LTK,
								ltk->val);

failed:
	g_free(key);
	g_free(rand);

	return ltk;
}


static void remove_driver(gpointer data, gpointer user_data)
{
	struct btd_adapter_driver *driver = data;
	struct btd_adapter *adapter = user_data;

	if (driver->remove)
		driver->remove(adapter);
}


static void bonding_complete(struct btd_adapter *adapter,
					const bdaddr_t *bdaddr,
					uint8_t addr_type, uint8_t status)
{
	struct btd_device *device;

	if (status == 0)
		device = btd_adapter_get_device(adapter, bdaddr, addr_type);
	else
		device = btd_adapter_find_device(adapter, bdaddr, addr_type);

	if (device != NULL)
		device_bonding_complete(device, addr_type, status);

	resume_discovery(adapter);

	check_oob_bonding_complete(adapter, bdaddr, status);
}


static int device_path_cmp(gconstpointer a, gconstpointer b)
{
	const struct btd_device *device = a;
	const char *path = b;
	const char *dev_path = device_get_path(device);

	return strcasecmp(dev_path, path);
}


void btd_unregister_adapter_driver(struct btd_adapter_driver *driver)
{
	adapter_drivers = g_slist_remove(adapter_drivers, driver);

	adapter_foreach(unload_driver, driver);
}


static void set_dev_class(struct btd_adapter *adapter)
{
	struct mgmt_cp_set_dev_class cp;

	/*
	 * If the controller does not support BR/EDR operation,
	 * there is no point in trying to set a major and minor
	 * class value.
	 *
	 * This is an optimization for Low Energy only controllers.
	 */
	if (!(adapter->supported_settings & MGMT_SETTING_BREDR))
		return;

	memset(&cp, 0, sizeof(cp));

	/*
	 * Silly workaround for a really stupid kernel bug :(
	 *
	 * All current kernel versions assign the major and minor numbers
	 * straight to dev_class[0] and dev_class[1] without considering
	 * the proper bit shifting.
	 *
	 * To make this work, shift the value in userspace for now until
	 * we get a fixed kernel version.
	 */
	cp.major = adapter->major_class & 0x1f;
	cp.minor = adapter->minor_class << 2;

	DBG("sending set device class command for index %u", adapter->dev_id);

	if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEV_CLASS,
				adapter->dev_id, sizeof(cp), &cp,
				set_dev_class_complete, adapter, NULL) > 0)
		return;

	btd_error(adapter->dev_id,
		"Failed to set class of device for index %u", adapter->dev_id);
}


static void remove_keys(struct btd_adapter *adapter,
					struct btd_device *device, uint8_t type)
{
	char device_addr[18];
	char filename[PATH_MAX];
	GKeyFile *key_file;
	gsize length = 0;
	char *str;

	ba2str(device_get_address(device), device_addr);

	snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info",
			btd_adapter_get_storage_dir(adapter), device_addr);
	key_file = g_key_file_new();
	g_key_file_load_from_file(key_file, filename, 0, NULL);

	if (type == BDADDR_BREDR) {
		g_key_file_remove_group(key_file, "LinkKey", NULL);
	} else {
		g_key_file_remove_group(key_file, "LongTermKey", NULL);
		g_key_file_remove_group(key_file, "LocalSignatureKey", NULL);
		g_key_file_remove_group(key_file, "RemoteSignatureKey", NULL);
		g_key_file_remove_group(key_file, "IdentityResolvingKey", NULL);
	}

	str = g_key_file_to_data(key_file, &length, NULL);
	g_file_set_contents(filename, str, length, NULL);
	g_free(str);

	g_key_file_free(key_file);
}


static void agent_auth_cb(struct agent *agent, DBusError *derr,
							void *user_data)
{
	struct btd_adapter *adapter = user_data;
	struct service_auth *auth = g_queue_pop_head(adapter->auths);

	if (!auth) {
		DBG("No pending authorization");
		return;
	}

	auth->cb(derr, auth->user_data);

	if (auth->agent)
		agent_unref(auth->agent);

	g_free(auth);

	/* Stop processing if queue is empty */
	if (g_queue_is_empty(adapter->auths)) {
		if (adapter->auth_idle_id > 0)
			g_source_remove(adapter->auth_idle_id);
		return;
	}

	if (adapter->auth_idle_id > 0)
		return;

	adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter);
}


static int adapter_unregister(struct btd_adapter *adapter)
{
	DBG("Unregister path: %s", adapter->path);

	adapters = g_slist_remove(adapters, adapter);

	if (adapter->is_default && adapters != NULL) {
		struct btd_adapter *new_default;

		new_default = adapter_find_by_id(hci_get_route(NULL));
		if (new_default == NULL)
			new_default = adapters->data;

		new_default->is_default = true;
	}

	adapter_list = g_list_remove(adapter_list, adapter);

	adapter_remove(adapter);
	btd_adapter_unref(adapter);

	return 0;
}


int btd_adapter_read_local_oob_data(struct btd_adapter *adapter)
{
	DBG("hci%u", adapter->dev_id);

	if (mgmt_send(adapter->mgmt, MGMT_OP_READ_LOCAL_OOB_DATA,
			adapter->dev_id, 0, NULL, read_local_oob_data_complete,
			adapter, NULL) > 0)
		return 0;

	return -EIO;
}


static gboolean property_get_discoverable_timeout(
					const GDBusPropertyTable *property,
					DBusMessageIter *iter, void *user_data)
{
	struct btd_adapter *adapter = user_data;
	dbus_uint32_t value = adapter->discoverable_timeout;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value);

	return TRUE;
}


static bool is_supported_uuid(const uuid_t *uuid)
{
	uuid_t tmp;

	/* mgmt versions from 1.3 onwards support all types of UUIDs */
	if (MGMT_VERSION(mgmt_version, mgmt_revision) >= MGMT_VERSION(1, 3))
		return true;

	uuid_to_uuid128(&tmp, uuid);

	if (!sdp_uuid128_to_uuid(&tmp))
		return false;

	if (tmp.type != SDP_UUID16)
		return false;

	return true;
}


static void adapter_stop(struct btd_adapter *adapter)
{
	/* check pending requests */
	reply_pending_requests(adapter);

	cancel_passive_scanning(adapter);

	remove_discovery_list(adapter);

	discovery_cleanup(adapter, 0);

	adapter->filtered_discovery = false;
	adapter->no_scan_restart_delay = false;
	g_free(adapter->current_discovery_filter);
	adapter->current_discovery_filter = NULL;

	set_discovery_discoverable(adapter, false);
	adapter->discovering = false;

	while (adapter->connections) {
		struct btd_device *device = adapter->connections->data;
		uint8_t addr_type = btd_device_get_bdaddr_type(device);

		adapter_remove_connection(adapter, device, BDADDR_BREDR);
		if (addr_type != BDADDR_BREDR)
			adapter_remove_connection(adapter, device, addr_type);
	}

	g_dbus_emit_property_changed(dbus_conn, adapter->path,
					ADAPTER_INTERFACE, "Discovering");

	if (adapter->dev_class) {
		/* the kernel should reset the class of device when powering
		 * down, but it does not. So force it here ... */
		adapter->dev_class = 0;
		g_dbus_emit_property_changed(dbus_conn, adapter->path,
						ADAPTER_INTERFACE, "Class");
	}

	g_dbus_emit_property_changed(dbus_conn, adapter->path,
						ADAPTER_INTERFACE, "Powered");

	DBG("adapter %s has been disabled", adapter->path);
}


void btd_adapter_for_each_device(struct btd_adapter *adapter,
			void (*cb)(struct btd_device *device, void *data),
			void *data)
{
	g_slist_foreach(adapter->devices, (GFunc) cb, data);
}


static int adapter_id_cmp(gconstpointer a, gconstpointer b)
{
	struct btd_adapter *adapter = (struct btd_adapter *) a;
	uint16_t id = GPOINTER_TO_UINT(b);

	return adapter->dev_id == id ? 0 : -1;
}


NodeId_encodeJsonInternal(UA_NodeId const *src, CtxJson *ctx) {
    status ret = UA_STATUSCODE_GOOD;
    switch (src->identifierType) {
    case UA_NODEIDTYPE_NUMERIC:
        ret |= writeJsonKey(ctx, UA_JSONKEY_ID);
        ret |= ENCODE_DIRECT_JSON(&src->identifier.numeric, UInt32);
        break;
    case UA_NODEIDTYPE_STRING:
        ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE);
        ret |= writeChar(ctx, '1');
        ret |= writeJsonKey(ctx, UA_JSONKEY_ID);
        ret |= ENCODE_DIRECT_JSON(&src->identifier.string, String);
        break;
    case UA_NODEIDTYPE_GUID:
        ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE);
        ret |= writeChar(ctx, '2');
        ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */
        ret |= ENCODE_DIRECT_JSON(&src->identifier.guid, Guid);
        break;
    case UA_NODEIDTYPE_BYTESTRING:
        ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE);
        ret |= writeChar(ctx, '3');
        ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */
        ret |= ENCODE_DIRECT_JSON(&src->identifier.byteString, ByteString);
        break;
    default:
        return UA_STATUSCODE_BADINTERNALERROR;
    }
    return ret;
}


writeJsonKey(CtxJson *ctx, const char* key) {
    size_t size = strlen(key);
    if(ctx->pos + size + 4 > ctx->end) /* +4 because of " " : and , */
        return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
    status ret = writeJsonCommaIfNeeded(ctx);
    ctx->commaNeeded[ctx->depth] = true;
    if(ctx->calcOnly) {
        ctx->commaNeeded[ctx->depth] = true;
        ctx->pos += 3;
        ctx->pos += size;
        return ret;
    }

    ret |= writeChar(ctx, '\"');
    for(size_t i = 0; i < size; i++) {
        *(ctx->pos++) = (u8)key[i];
    }
    ret |= writeChar(ctx, '\"');
    ret |= writeChar(ctx, ':');
    return ret;
}


DECODE_JSON(StatusCode) {
    status ret = DECODE_DIRECT_JSON(dst, UInt32);
    if(ret != UA_STATUSCODE_GOOD)
        return ret;

    if(moveToken)
        parseCtx->index++;
    return UA_STATUSCODE_GOOD;
}


decodeJsonSignature getDecodeSignature(u8 index) {
    return decodeJsonJumpTable[index];
}


DECODE_JSON(Int16) {
    CHECK_TOKEN_BOUNDS;
    CHECK_PRIMITIVE;
    size_t tokenSize;
    char* tokenData;
    GET_TOKEN(tokenData, tokenSize);

    UA_Int64 out = 0;
    UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out);
    *dst = (UA_Int16)out;

    if(moveToken)
        parseCtx->index++;
    return s;
}


decodeJsonStructure(void *dst, const UA_DataType *type, CtxJson *ctx,
                    ParseCtx *parseCtx, UA_Boolean moveToken) {
    (void) moveToken;
    /* Check the recursion limit */
    if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION)
        return UA_STATUSCODE_BADENCODINGERROR;
    ctx->depth++;

    uintptr_t ptr = (uintptr_t)dst;
    status ret = UA_STATUSCODE_GOOD;
    u8 membersSize = type->membersSize;
    const UA_DataType *typelists[2] = { UA_TYPES, &type[-type->typeIndex] };

    UA_STACKARRAY(DecodeEntry, entries, membersSize);

    for(size_t i = 0; i < membersSize && ret == UA_STATUSCODE_GOOD; ++i) {
        const UA_DataTypeMember *m = &type->members[i];
        const UA_DataType *mt = &typelists[!m->namespaceZero][m->memberTypeIndex];

        entries[i].type = mt;
        if(!m->isArray) {
            ptr += m->padding;
            entries[i].fieldName = m->memberName;
            entries[i].fieldPointer = (void*)ptr;
            entries[i].function = decodeJsonJumpTable[mt->typeKind];
            entries[i].found = false;
            ptr += mt->memSize;
        } else {
            ptr += m->padding;
            ptr += sizeof(size_t);
            entries[i].fieldName = m->memberName;
            entries[i].fieldPointer = (void*)ptr;
            entries[i].function = (decodeJsonSignature)Array_decodeJson;
            entries[i].found = false;
            ptr += sizeof(void*);
        }
    }

    ret = decodeFields(ctx, parseCtx, entries, membersSize, type);

    ctx->depth--;
    return ret;
}


DECODE_JSON(Int64) {
    CHECK_TOKEN_BOUNDS;
    CHECK_STRING;
    size_t tokenSize;
    char* tokenData;
    GET_TOKEN(tokenData, tokenSize);

    UA_Int64 out = 0;
    UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out);
    *dst = (UA_Int64)out;

    if(moveToken)
        parseCtx->index++;
    return s;
}


DECODE_JSON(DataValue) {
    ALLOW_NULL;
    CHECK_OBJECT;

    DecodeEntry entries[6] = {
       {UA_JSONKEY_VALUE, &dst->value, (decodeJsonSignature) Variant_decodeJson, false, NULL},
       {UA_JSONKEY_STATUS, &dst->status, (decodeJsonSignature) StatusCode_decodeJson, false, NULL},
       {UA_JSONKEY_SOURCETIMESTAMP, &dst->sourceTimestamp, (decodeJsonSignature) DateTime_decodeJson, false, NULL},
       {UA_JSONKEY_SOURCEPICOSECONDS, &dst->sourcePicoseconds, (decodeJsonSignature) UInt16_decodeJson, false, NULL},
       {UA_JSONKEY_SERVERTIMESTAMP, &dst->serverTimestamp, (decodeJsonSignature) DateTime_decodeJson, false, NULL},
       {UA_JSONKEY_SERVERPICOSECONDS, &dst->serverPicoseconds, (decodeJsonSignature) UInt16_decodeJson, false, NULL}};

    status ret = decodeFields(ctx, parseCtx, entries, 6, type);
    dst->hasValue = entries[0].found; dst->hasStatus = entries[1].found;
    dst->hasSourceTimestamp = entries[2].found; dst->hasSourcePicoseconds = entries[3].found;
    dst->hasServerTimestamp = entries[4].found; dst->hasServerPicoseconds = entries[5].found;
    return ret;
}


Array_decodeJson_internal(void **dst, const UA_DataType *type,
        CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) {
    (void) moveToken;
    status ret;

    if(parseCtx->tokenArray[parseCtx->index].type != JSMN_ARRAY)
        return UA_STATUSCODE_BADDECODINGERROR;

    size_t length = (size_t)parseCtx->tokenArray[parseCtx->index].size;

    /* Save the length of the array */
    size_t *p = (size_t*) dst - 1;
    *p = length;

    /* Return early for empty arrays */
    if(length == 0) {
        *dst = UA_EMPTY_ARRAY_SENTINEL;
        return UA_STATUSCODE_GOOD;
    }

    /* Allocate memory */
    *dst = UA_calloc(length, type->memSize);
    if(*dst == NULL)
        return UA_STATUSCODE_BADOUTOFMEMORY;

    parseCtx->index++; /* We go to first Array member!*/

    /* Decode array members */
    uintptr_t ptr = (uintptr_t)*dst;
    for(size_t i = 0; i < length; ++i) {
        ret = decodeJsonJumpTable[type->typeKind]((void*)ptr, type, ctx, parseCtx, true);
        if(ret != UA_STATUSCODE_GOOD) {
            UA_Array_delete(*dst, i+1, type);
            *dst = NULL;
            return ret;
        }
        ptr += type->memSize;
    }
    return UA_STATUSCODE_GOOD;
}


ENCODE_JSON(NodeId) {
    UA_StatusCode ret = writeJsonObjStart(ctx);
    ret |= NodeId_encodeJsonInternal(src, ctx);
    if(ctx->useReversible) {
        if(src->namespaceIndex > 0) {
            ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
            ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16);
        }
    } else {
        /* For the non-reversible encoding, the field is the NamespaceUri
         * associated with the NamespaceIndex, encoded as a JSON string.
         * A NamespaceIndex of 1 is always encoded as a JSON number. */
        if(src->namespaceIndex == 1) {
            ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
            ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16);
        } else {
            ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);

            /* Check if Namespace given and in range */
            if(src->namespaceIndex < ctx->namespacesSize && ctx->namespaces != NULL) {
                UA_String namespaceEntry = ctx->namespaces[src->namespaceIndex];
                ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String);
            } else {
                return UA_STATUSCODE_BADNOTFOUND;
            }
        }
    }

    ret |= writeJsonObjEnd(ctx);
    return ret;
}


ENCODE_JSON(UInt16) {
    char buf[6];
    UA_UInt16 digits = itoaUnsigned(*src, buf, 10);

    if(ctx->pos + digits > ctx->end)
        return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;

    if(!ctx->calcOnly)
        memcpy(ctx->pos, buf, digits);
    ctx->pos += digits;
    return UA_STATUSCODE_GOOD;
}


UA_decodeJson(const UA_ByteString *src, void *dst, const UA_DataType *type) {

#ifndef UA_ENABLE_TYPEDESCRIPTION
    return UA_STATUSCODE_BADNOTSUPPORTED;
#endif

    if(dst == NULL || src == NULL || type == NULL) {
        return UA_STATUSCODE_BADARGUMENTSMISSING;
    }

    /* Set up the context */
    CtxJson ctx;
    ParseCtx parseCtx;
    parseCtx.tokenArray = (jsmntok_t*)UA_malloc(sizeof(jsmntok_t) * UA_JSON_MAXTOKENCOUNT);
    if(!parseCtx.tokenArray)
        return UA_STATUSCODE_BADOUTOFMEMORY;

    status ret = tokenize(&parseCtx, &ctx, src);
    if(ret != UA_STATUSCODE_GOOD)
        goto cleanup;

    /* Assume the top-level element is an object */
    if(parseCtx.tokenCount < 1 || parseCtx.tokenArray[0].type != JSMN_OBJECT) {
        if(parseCtx.tokenCount == 1) {
            if(parseCtx.tokenArray[0].type == JSMN_PRIMITIVE ||
               parseCtx.tokenArray[0].type == JSMN_STRING) {
               /* Only a primitive to parse. Do it directly. */
               memset(dst, 0, type->memSize); /* Initialize the value */
               ret = decodeJsonJumpTable[type->typeKind](dst, type, &ctx, &parseCtx, true);
               goto cleanup;
            }
        }
        ret = UA_STATUSCODE_BADDECODINGERROR;
        goto cleanup;
    }

    /* Decode */
    memset(dst, 0, type->memSize); /* Initialize the value */
    ret = decodeJsonJumpTable[type->typeKind](dst, type, &ctx, &parseCtx, true);

    cleanup:
    UA_free(parseCtx.tokenArray);

    /* sanity check if all Tokens were processed */
    if(!(parseCtx.index == parseCtx.tokenCount ||
         parseCtx.index == parseCtx.tokenCount-1)) {
        ret = UA_STATUSCODE_BADDECODINGERROR;
    }

    if(ret != UA_STATUSCODE_GOOD)
        UA_deleteMembers(dst, type); /* Clean up */
    return ret;
}


std::string heif::BoxHeader::get_type_string() const
{
  if (m_type == fourcc("uuid")) {
    // 8-4-4-4-12

    std::ostringstream sstr;
    sstr << std::hex;
    sstr << std::setfill('0');
    sstr << std::setw(2);

    for (int i=0;i<16;i++) {
      if (i==4 || i==6 || i==8 || i==10) {
        sstr << '-';
      }

      sstr << ((int)m_uuid_type[i]);
    }

    return sstr.str();
  }
  else {
    return to_fourcc(m_type);
  }
}


bool Fraction::is_valid() const
{
  return denominator != 0;
}


Fraction Fraction::operator-(int v) const
{
  return Fraction { numerator - v * denominator, denominator };
}


std::string Box_dinf::dump(Indent& indent) const
{
  std::ostringstream sstr;
  sstr << Box::dump(indent);
  sstr << dump_children(indent);

  return sstr.str();
}


std::string Box_ipco::dump(Indent& indent) const
{
  std::ostringstream sstr;
  sstr << Box::dump(indent);

  sstr << dump_children(indent);

  return sstr.str();
}


Error Box_pitm::write(StreamWriter& writer) const
{
  size_t box_start = reserve_box_header_space(writer);

  if (get_version()==0) {
    assert(m_item_ID <= 0xFFFF);
    writer.write16((uint16_t)m_item_ID);
  }
  else {
    writer.write32(m_item_ID);
  }

  prepend_header(writer, box_start);

  return Error::Ok;
}


Error Box_iloc::write(StreamWriter& writer) const
{
  size_t box_start = reserve_box_header_space(writer);

  m_iloc_box_start = writer.get_position();

  int nSkip = 0;

  nSkip += 2;
  nSkip += (get_version()<2) ? 2 : 4; // item_count

  for (const auto& item : m_items) {
    nSkip += (get_version()<2) ? 2 : 4; // item_ID
    nSkip += (get_version()>=1) ? 2 : 0; // construction method
    nSkip += 4 + m_base_offset_size;

    for (const auto& extent : item.extents) {
      (void)extent;

      if (get_version()>=1) {
        nSkip += m_index_size;
      }

      nSkip += m_offset_size + m_length_size;
    }
  }

  writer.skip(nSkip);
  prepend_header(writer, box_start);

  return Error::Ok;
}


Error Box_iloc::append_data(heif_item_id item_ID,
                            const std::vector<uint8_t>& data,
                            uint8_t construction_method)
{
  // check whether this item ID already exists

  size_t idx;
  for (idx=0;idx<m_items.size();idx++) {
    if (m_items[idx].item_ID == item_ID) {
      break;
    }
  }

  // item does not exist -> add a new one to the end

  if (idx == m_items.size()) {
    Item item;
    item.item_ID = item_ID;
    item.construction_method = construction_method;

    m_items.push_back(item);
  }

  if (m_items[idx].construction_method != construction_method) {
    // TODO: return error: construction methods do not match
  }

  Extent extent;
  extent.data = data;
  m_items[idx].extents.push_back( std::move(extent) );

  return Error::Ok;
}


std::string Box_clap::dump(Indent& indent) const
{
  std::ostringstream sstr;
  sstr << Box::dump(indent);

  sstr << indent << "clean_aperture: " << m_clean_aperture_width.numerator
       << "/" << m_clean_aperture_width.denominator << " x "
       << m_clean_aperture_height.numerator << "/"
       << m_clean_aperture_height.denominator << "\n";
  sstr << indent << "offset: " << m_horizontal_offset.numerator << "/"
       << m_horizontal_offset.denominator << " ; "
       << m_vertical_offset.numerator << "/"
       << m_vertical_offset.denominator << "\n";

  return sstr.str();
}


Error Box_imir::parse(BitstreamRange& range)
{
  //parse_full_box_header(range);

  uint16_t axis = range.read8();
  if (axis & 1) {
    m_axis = MirrorAxis::Horizontal;
  }
  else {
    m_axis = MirrorAxis::Vertical;
  }

  return range.get_error();
}


void Box::derive_box_version_recursive()
{
  derive_box_version();

  for (auto& child : m_children) {
    child->derive_box_version_recursive();
  }
}


std::string Box_ipma::dump(Indent& indent) const
{
  std::ostringstream sstr;
  sstr << Box::dump(indent);

  for (const Entry& entry : m_entries) {
    sstr << indent << "associations for item ID: " << entry.item_ID << "\n";
    indent++;
    for (const auto& assoc : entry.associations) {
      sstr << indent << "property index: " << assoc.property_index
           << " (essential: " << std::boolalpha << assoc.essential << ")\n";
    }
    indent--;
  }

  return sstr.str();
}


std::string Box_pixi::dump(Indent& indent) const
{
  std::ostringstream sstr;
  sstr << Box::dump(indent);

  sstr << indent << "bits_per_channel: ";

  for (size_t i=0;i<m_bits_per_channel.size();i++) {
    if (i>0) sstr << ",";
    sstr << ((int)m_bits_per_channel[i]);
  }

  sstr << "\n";

  return sstr.str();
}


Error Box_ipco::parse(BitstreamRange& range)
{
  //parse_full_box_header(range);

  return read_children(range);
}


HeifContext::Image::Image(HeifContext* context, heif_item_id id)
  : m_heif_context(context),
    m_id(id)
{
  memset(&m_depth_representation_info, 0, sizeof(m_depth_representation_info));
}


Error HeifContext::read_from_file(const char* input_filename)
{
  m_heif_file = std::make_shared<HeifFile>();
  Error err = m_heif_file->read_from_file(input_filename);
  if (err) {
    return err;
  }

  return interpret_heif_file();
}


std::string HeifContext::debug_dump_boxes() const
{
  return m_heif_file->debug_dump_boxes();
}


Error HeifContext::decode_overlay_image(heif_item_id ID,
                                        std::shared_ptr<HeifPixelImage>& img,
                                        const std::vector<uint8_t>& overlay_data) const
{
  // find the IDs this image is composed of

  auto iref_box = m_heif_file->get_iref_box();

  if (!iref_box) {
    return Error(heif_error_Invalid_input,
                 heif_suberror_No_iref_box,
                 "No iref box available, but needed for iovl image");
  }

  std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg"));

  /* TODO: probably, it is valid that an iovl image has no references ?

  if (image_references.empty()) {
    return Error(heif_error_Invalid_input,
                 heif_suberror_Missing_grid_images,
                 "'iovl' image with more than one reference image");
  }
  */

  ImageOverlay overlay;
  Error err = overlay.parse(image_references.size(), overlay_data);
  if (err) {
    return err;
  }

  if (image_references.size() != overlay.get_num_offsets()) {
    return Error(heif_error_Invalid_input,
                 heif_suberror_Invalid_overlay_data,
                 "Number of image offsets does not match the number of image references");
  }

  uint32_t w = overlay.get_canvas_width();
  uint32_t h = overlay.get_canvas_height();

  if (w >= MAX_IMAGE_WIDTH || h >= MAX_IMAGE_HEIGHT) {
    std::stringstream sstr;
    sstr << "Image size " << w << "x" << h << " exceeds the maximum image size "
         << MAX_IMAGE_WIDTH << "x" << MAX_IMAGE_HEIGHT << "\n";

    return Error(heif_error_Memory_allocation_error,
                 heif_suberror_Security_limit_exceeded,
                 sstr.str());
  }

  // TODO: seems we always have to compose this in RGB since the background color is an RGB value
  img = std::make_shared<HeifPixelImage>();
  img->create(w,h,
              heif_colorspace_RGB,
              heif_chroma_444);
  img->add_plane(heif_channel_R,w,h,8); // TODO: other bit depths
  img->add_plane(heif_channel_G,w,h,8); // TODO: other bit depths
  img->add_plane(heif_channel_B,w,h,8); // TODO: other bit depths

  uint16_t bkg_color[4];
  overlay.get_background_color(bkg_color);

  err = img->fill_RGB_16bit(bkg_color[0], bkg_color[1], bkg_color[2], bkg_color[3]);
  if (err) {
    return err;
  }

  for (size_t i=0;i<image_references.size();i++) {
    std::shared_ptr<HeifPixelImage> overlay_img;
    err = decode_image(image_references[i], overlay_img);
    if (err != Error::Ok) {
      return err;
    }

    overlay_img = convert_colorspace(overlay_img, heif_colorspace_RGB, heif_chroma_444);
    if (!overlay_img) {
      return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion);
    }

    int32_t dx,dy;
    overlay.get_offset(i, &dx,&dy);

    err = img->overlay(overlay_img, dx,dy);
    if (err) {
      if (err.error_code == heif_error_Invalid_input &&
          err.sub_error_code == heif_suberror_Overlay_image_outside_of_canvas) {
        // NOP, ignore this error

        err = Error::Ok;
      }
      else {
        return err;
      }
    }
  }

  return err;
}


Error HeifContext::encode_thumbnail(std::shared_ptr<HeifPixelImage> image,
                                    struct heif_encoder* encoder,
                                    const struct heif_encoding_options* options,
                                    int bbox_size,
                                    std::shared_ptr<Image>& out_thumbnail_handle)
{
  Error error;

  int orig_width  = image->get_width();
  int orig_height = image->get_height();

  int thumb_width, thumb_height;

  if (orig_width <= bbox_size && orig_height <= bbox_size) {
    // original image is smaller than thumbnail size -> do not encode any thumbnail

    out_thumbnail_handle.reset();
    return Error::Ok;
  }
  else if (orig_width > orig_height) {
    thumb_height = orig_height * bbox_size / orig_width;
    thumb_width  = bbox_size;
  }
  else {
    thumb_width  = orig_width * bbox_size / orig_height;
    thumb_height = bbox_size;
  }

  // round size to even width and height

  thumb_width  &= ~1;
  thumb_height &= ~1;

  std::shared_ptr<HeifPixelImage> thumbnail_image;
  error = image->scale_nearest_neighbor(thumbnail_image, thumb_width, thumb_height);
  if (error) {
    return error;
  }

  error = encode_image(thumbnail_image,
                       encoder, options,
                       heif_image_input_class_thumbnail,
                       out_thumbnail_handle);
  if (error) {
    return error;
  }

  return error;
}


WriteStructField(mat_t *mat,matvar_t *matvar)
{
    mat_uint32_t array_flags;
    mat_int32_t  array_name_type = MAT_T_INT8;
    int array_flags_type = MAT_T_UINT32, dims_array_type = MAT_T_INT32;
    int array_flags_size = 8, pad4 = 0, matrix_type = MAT_T_MATRIX;
    int nBytes, i, nzmax = 0;
    long start = 0, end = 0;

    if ( mat == NULL )
        return 1;

    if ( NULL == matvar ) {
        size_t dims[2] = {0,0};
        Mat_WriteEmptyVariable5(mat, NULL, 2, dims);
        return 0;
    }

    fwrite(&matrix_type,4,1,(FILE*)mat->fp);
    fwrite(&pad4,4,1,(FILE*)mat->fp);
    if ( MAT_C_EMPTY == matvar->class_type ) {
        /* exit early if this is an empty data */
        return 0;
    }
    start = ftell((FILE*)mat->fp);

    /* Array Flags */
    array_flags = matvar->class_type & CLASS_TYPE_MASK;
    if ( matvar->isComplex )
        array_flags |= MAT_F_COMPLEX;
    if ( matvar->isGlobal )
        array_flags |= MAT_F_GLOBAL;
    if ( matvar->isLogical )
        array_flags |= MAT_F_LOGICAL;
    if ( matvar->class_type == MAT_C_SPARSE )
        nzmax = ((mat_sparse_t *)matvar->data)->nzmax;

    if ( mat->byteswap )
        array_flags = Mat_int32Swap((mat_int32_t*)&array_flags);
    fwrite(&array_flags_type,4,1,(FILE*)mat->fp);
    fwrite(&array_flags_size,4,1,(FILE*)mat->fp);
    fwrite(&array_flags,4,1,(FILE*)mat->fp);
    fwrite(&nzmax,4,1,(FILE*)mat->fp);
    /* Rank and Dimension */
    nBytes = matvar->rank * 4;
    fwrite(&dims_array_type,4,1,(FILE*)mat->fp);
    fwrite(&nBytes,4,1,(FILE*)mat->fp);
    for ( i = 0; i < matvar->rank; i++ ) {
        mat_int32_t dim;
        dim = matvar->dims[i];
        fwrite(&dim,4,1,(FILE*)mat->fp);
    }
    if ( matvar->rank % 2 != 0 )
        fwrite(&pad4,4,1,(FILE*)mat->fp);

    /* Name of variable */
    fwrite(&array_name_type,4,1,(FILE*)mat->fp);
    fwrite(&pad4,4,1,(FILE*)mat->fp);

    WriteType(mat,matvar);
    end = ftell((FILE*)mat->fp);
    if ( start != -1L && end != -1L ) {
        nBytes = (int)(end-start);
        (void)fseek((FILE*)mat->fp,(long)-(nBytes+4),SEEK_CUR);
        fwrite(&nBytes,4,1,(FILE*)mat->fp);
        (void)fseek((FILE*)mat->fp,end,SEEK_SET);
    } else {
        Mat_Critical("Couldn't determine file position");
    }

    return 0;
}


ReadNextStructField( mat_t *mat, matvar_t *matvar )
{
    mat_uint32_t fieldname_size;
    int err;
    size_t bytesread = 0, nfields, i;
    matvar_t **fields = NULL;
    size_t nelems = 1, nelems_x_nfields;

    err = SafeMulDims(matvar, &nelems);
    if ( err ) {
        Mat_Critical("Integer multiplication overflow");
        return bytesread;
    }
    if ( matvar->compression == MAT_COMPRESSION_ZLIB ) {
#if defined(HAVE_ZLIB)
        mat_uint32_t uncomp_buf[16] = {0,};
        int nbytes;
        mat_uint32_t array_flags, len;

        /* Field name length */
        bytesread += InflateVarTag(mat,matvar,uncomp_buf);
        if ( mat->byteswap ) {
            (void)Mat_uint32Swap(uncomp_buf);
            (void)Mat_uint32Swap(uncomp_buf+1);
        }
        if ( (uncomp_buf[0] & 0x0000ffff) == MAT_T_INT32 && uncomp_buf[1] > 0 ) {
            fieldname_size = uncomp_buf[1];
        } else {
            Mat_Critical("Error getting fieldname size");
            return bytesread;
        }

        /* Field name tag */
        bytesread += InflateVarTag(mat,matvar,uncomp_buf);
        if ( mat->byteswap )
            (void)Mat_uint32Swap(uncomp_buf);
        /* Name of field */
        if ( uncomp_buf[0] == MAT_T_INT8 ) {    /* Name not in tag */
            if ( mat->byteswap )
                len = Mat_uint32Swap(uncomp_buf+1);
            else
                len = uncomp_buf[1];
            nfields = len / fieldname_size;
            if ( nfields*fieldname_size % 8 != 0 )
                i = 8-(nfields*fieldname_size % 8);
            else
                i = 0;
            if ( nfields ) {
                char *ptr = (char*)malloc(nfields*fieldname_size+i);
                if ( NULL != ptr ) {
                    bytesread += InflateVarName(mat,matvar,ptr,nfields*fieldname_size+i);
                    SetFieldNames(matvar, ptr, nfields, fieldname_size);
                    free(ptr);
                }
            } else {
                matvar->internal->num_fields = 0;
                matvar->internal->fieldnames = NULL;
            }
        } else {
            len = (uncomp_buf[0] & 0xffff0000) >> 16;
            if ( ((uncomp_buf[0] & 0x0000ffff) == MAT_T_INT8) && len > 0 && len <= 4 ) {
                /* Name packed in tag */
                nfields = len / fieldname_size;
                if ( nfields ) {
                    SetFieldNames(matvar, (char*)(uncomp_buf + 1), nfields, fieldname_size);
                } else {
                    matvar->internal->num_fields = 0;
                    matvar->internal->fieldnames = NULL;
                }
            } else {
                nfields = 0;
            }
        }

        matvar->data_size = sizeof(matvar_t *);
        err = SafeMul(&nelems_x_nfields, nelems, nfields);
        if ( err ) {
            Mat_Critical("Integer multiplication overflow");
            return bytesread;
        }
        err = SafeMul(&matvar->nbytes, nelems_x_nfields, matvar->data_size);
        if ( err ) {
            Mat_Critical("Integer multiplication overflow");
            return bytesread;
        }
        if ( !matvar->nbytes )
            return bytesread;

        matvar->data = calloc(nelems_x_nfields, matvar->data_size);
        if ( NULL == matvar->data ) {
            Mat_Critical("Couldn't allocate memory for the data");
            return bytesread;
        }

        fields = (matvar_t**)matvar->data;
        for ( i = 0; i < nelems; i++ ) {
            size_t k;
            for ( k = 0; k < nfields; k++ ) {
                fields[i*nfields+k] = Mat_VarCalloc();
            }
        }
        if ( NULL != matvar->internal->fieldnames ) {
            for ( i = 0; i < nelems; i++ ) {
                size_t k;
                for ( k = 0; k < nfields; k++ ) {
                    if ( NULL != matvar->internal->fieldnames[k] ) {
                        fields[i*nfields+k]->name = strdup(matvar->internal->fieldnames[k]);
                    }
                }
            }
        }

        for ( i = 0; i < nelems_x_nfields; i++ ) {
            /* Read variable tag for struct field */
            bytesread += InflateVarTag(mat,matvar,uncomp_buf);
            if ( mat->byteswap ) {
                (void)Mat_uint32Swap(uncomp_buf);
                (void)Mat_uint32Swap(uncomp_buf+1);
            }
            nbytes = uncomp_buf[1];
            if ( uncomp_buf[0] != MAT_T_MATRIX ) {
                Mat_VarFree(fields[i]);
                fields[i] = NULL;
                Mat_Critical("fields[%" SIZE_T_FMTSTR "], Uncompressed type not MAT_T_MATRIX", i);
                continue;
            } else if ( 0 == nbytes ) {
                /* Empty field: Memory optimization */
                free(fields[i]->internal);
                fields[i]->internal = NULL;
                continue;
            }
            fields[i]->compression = MAT_COMPRESSION_ZLIB;
            bytesread += InflateArrayFlags(mat,matvar,uncomp_buf);
            nbytes -= 16;
            if ( mat->byteswap ) {
                (void)Mat_uint32Swap(uncomp_buf);
                (void)Mat_uint32Swap(uncomp_buf+1);
                (void)Mat_uint32Swap(uncomp_buf+2);
                (void)Mat_uint32Swap(uncomp_buf+3);
            }
            /* Array flags */
            if ( uncomp_buf[0] == MAT_T_UINT32 ) {
               array_flags = uncomp_buf[2];
               fields[i]->class_type = CLASS_FROM_ARRAY_FLAGS(array_flags);
               fields[i]->isComplex  = (array_flags & MAT_F_COMPLEX);
               fields[i]->isGlobal   = (array_flags & MAT_F_GLOBAL);
               fields[i]->isLogical  = (array_flags & MAT_F_LOGICAL);
               if ( fields[i]->class_type == MAT_C_SPARSE ) {
                   /* Need to find a more appropriate place to store nzmax */
                   fields[i]->nbytes = uncomp_buf[3];
               }
            } else {
                Mat_Critical("Expected MAT_T_UINT32 for array tags, got %d",
                    uncomp_buf[0]);
                bytesread+=InflateSkip(mat,matvar->internal->z,nbytes);
            }
            if ( fields[i]->class_type != MAT_C_OPAQUE ) {
                mat_uint32_t* dims = NULL;
                int do_clean = 0;
                bytesread += InflateRankDims(mat,matvar,uncomp_buf,sizeof(uncomp_buf),&dims);
                if ( NULL == dims )
                    dims = uncomp_buf + 2;
                else
                    do_clean = 1;
                nbytes -= 8;
                if ( mat->byteswap ) {
                    (void)Mat_uint32Swap(uncomp_buf);
                    (void)Mat_uint32Swap(uncomp_buf+1);
                }
                /* Rank and dimension */
                if ( uncomp_buf[0] == MAT_T_INT32 ) {
                    int j;
                    fields[i]->rank = uncomp_buf[1];
                    nbytes -= fields[i]->rank;
                    fields[i]->rank /= 4;
                    fields[i]->dims = (size_t*)malloc(fields[i]->rank*
                                             sizeof(*fields[i]->dims));
                    if ( mat->byteswap ) {
                        for ( j = 0; j < fields[i]->rank; j++ )
                            fields[i]->dims[j] = Mat_uint32Swap(dims+j);
                    } else {
                        for ( j = 0; j < fields[i]->rank; j++ )
                            fields[i]->dims[j] = dims[j];
                    }
                    if ( fields[i]->rank % 2 != 0 )
                        nbytes -= 4;
                }
                if ( do_clean )
                    free(dims);
                /* Variable name tag */
                bytesread += InflateVarTag(mat,matvar,uncomp_buf);
                nbytes -= 8;
                fields[i]->internal->z = (z_streamp)calloc(1,sizeof(z_stream));
                if ( fields[i]->internal->z != NULL ) {
                    err = inflateCopy(fields[i]->internal->z,matvar->internal->z);
                    if ( err == Z_OK ) {
                        fields[i]->internal->datapos = ftell((FILE*)mat->fp);
                        if ( fields[i]->internal->datapos != -1L ) {
                            fields[i]->internal->datapos -= matvar->internal->z->avail_in;
                            if ( fields[i]->class_type == MAT_C_STRUCT )
                                bytesread+=ReadNextStructField(mat,fields[i]);
                            else if ( fields[i]->class_type == MAT_C_CELL )
                                bytesread+=ReadNextCell(mat,fields[i]);
                            else if ( nbytes <= (1 << MAX_WBITS) ) {
                                /* Memory optimization: Read data if less in size
                                   than the zlib inflate state (approximately) */
                                Mat_VarRead5(mat,fields[i]);
                                fields[i]->internal->data = fields[i]->data;
                                fields[i]->data = NULL;
                            }
                            (void)fseek((FILE*)mat->fp,fields[i]->internal->datapos,SEEK_SET);
                        } else {
                            Mat_Critical("Couldn't determine file position");
                        }
                        if ( fields[i]->internal->data != NULL ||
                             fields[i]->class_type == MAT_C_STRUCT ||
                             fields[i]->class_type == MAT_C_CELL ) {
                            /* Memory optimization: Free inflate state */
                            inflateEnd(fields[i]->internal->z);
                            free(fields[i]->internal->z);
                            fields[i]->internal->z = NULL;
                        }
                    } else {
                        Mat_Critical("inflateCopy returned error %s",zError(err));
                    }
                } else {
                    Mat_Critical("Couldn't allocate memory");
                }
            }
            bytesread+=InflateSkip(mat,matvar->internal->z,nbytes);
        }
#else
        Mat_Critical("Not compiled with zlib support");
#endif
    } else {
        mat_uint32_t buf[6];
        int nBytes;
        mat_uint32_t array_flags, len;

        bytesread+=fread(buf,4,2,(FILE*)mat->fp);
        if ( mat->byteswap ) {
            (void)Mat_uint32Swap(buf);
            (void)Mat_uint32Swap(buf+1);
        }
        if ( (buf[0] & 0x0000ffff) == MAT_T_INT32 && buf[1] > 0 ) {
            fieldname_size = buf[1];
        } else {
            Mat_Critical("Error getting fieldname size");
            return bytesread;
        }

        /* Field name tag */
        bytesread+=fread(buf,4,2,(FILE*)mat->fp);
        if ( mat->byteswap )
            (void)Mat_uint32Swap(buf);
        /* Name of field */
        if ( buf[0] == MAT_T_INT8 ) {    /* Name not in tag */
            if ( mat->byteswap )
                len = Mat_uint32Swap(buf+1);
            else
                len = buf[1];
            nfields = len / fieldname_size;
            if ( nfields ) {
                char *ptr = (char*)malloc(nfields*fieldname_size);
                if ( NULL != ptr ) {
                    size_t readresult = fread(ptr, 1, nfields*fieldname_size, (FILE*)mat->fp);
                    bytesread += readresult;
                    if ( nfields*fieldname_size == readresult )
                        SetFieldNames(matvar, ptr, nfields, fieldname_size);
                    else
                        matvar->internal->fieldnames = NULL;
                    free(ptr);
                }
                if ( (nfields*fieldname_size) % 8 ) {
                    (void)fseek((FILE*)mat->fp,8-((nfields*fieldname_size) % 8),SEEK_CUR);
                    bytesread+=8-((nfields*fieldname_size) % 8);
                }
            } else {
                matvar->internal->num_fields = 0;
                matvar->internal->fieldnames = NULL;
            }
       } else {
            len = (buf[0] & 0xffff0000) >> 16;
            if ( ((buf[0] & 0x0000ffff) == MAT_T_INT8) && len > 0 && len <= 4 ) {
                /* Name packed in tag */
                nfields = len / fieldname_size;
                if ( nfields ) {
                    SetFieldNames(matvar, (char*)(buf + 1), nfields, fieldname_size);
                } else {
                    matvar->internal->num_fields = 0;
                    matvar->internal->fieldnames = NULL;
                }
            } else {
                nfields = 0;
            }
        }

        matvar->data_size = sizeof(matvar_t *);
        err = SafeMul(&nelems_x_nfields, nelems, nfields);
        if ( err ) {
            Mat_Critical("Integer multiplication overflow");
            return bytesread;
        }
        err = SafeMul(&matvar->nbytes, nelems_x_nfields, matvar->data_size);
        if ( err ) {
            Mat_Critical("Integer multiplication overflow");
            return bytesread;
        }
        if ( !matvar->nbytes )
            return bytesread;

        matvar->data = malloc(matvar->nbytes);
        if ( NULL == matvar->data )
            return bytesread;

        fields = (matvar_t**)matvar->data;
        for ( i = 0; i < nelems; i++ ) {
            size_t k;
            for ( k = 0; k < nfields; k++ ) {
                fields[i*nfields+k] = Mat_VarCalloc();
            }
        }
        if ( NULL != matvar->internal->fieldnames ) {
            for ( i = 0; i < nelems; i++ ) {
                size_t k;
                for ( k = 0; k < nfields; k++ ) {
                    if ( NULL != matvar->internal->fieldnames[k] ) {
                        fields[i*nfields+k]->name = strdup(matvar->internal->fieldnames[k]);
                    }
                }
            }
        }

        for ( i = 0; i < nelems_x_nfields; i++ ) {
            /* Read variable tag for struct field */
            bytesread += fread(buf,4,2,(FILE*)mat->fp);
            if ( mat->byteswap ) {
                (void)Mat_uint32Swap(buf);
                (void)Mat_uint32Swap(buf+1);
            }
            nBytes = buf[1];
            if ( buf[0] != MAT_T_MATRIX ) {
                Mat_VarFree(fields[i]);
                fields[i] = NULL;
                Mat_Critical("fields[%" SIZE_T_FMTSTR "] not MAT_T_MATRIX, fpos = %ld", i,
                    ftell((FILE*)mat->fp));
                return bytesread;
            } else if ( 0 == nBytes ) {
                /* Empty field: Memory optimization */
                free(fields[i]->internal);
                fields[i]->internal = NULL;
                continue;
            }

            /* Read array flags and the dimensions tag */
            bytesread += fread(buf,4,6,(FILE*)mat->fp);
            if ( mat->byteswap ) {
                (void)Mat_uint32Swap(buf);
                (void)Mat_uint32Swap(buf+1);
                (void)Mat_uint32Swap(buf+2);
                (void)Mat_uint32Swap(buf+3);
                (void)Mat_uint32Swap(buf+4);
                (void)Mat_uint32Swap(buf+5);
            }
            nBytes-=24;
            /* Array flags */
            if ( buf[0] == MAT_T_UINT32 ) {
                array_flags = buf[2];
                fields[i]->class_type = CLASS_FROM_ARRAY_FLAGS(array_flags);
                fields[i]->isComplex  = (array_flags & MAT_F_COMPLEX);
                fields[i]->isGlobal   = (array_flags & MAT_F_GLOBAL);
                fields[i]->isLogical  = (array_flags & MAT_F_LOGICAL);
                if ( fields[i]->class_type == MAT_C_SPARSE ) {
                    /* Need to find a more appropriate place to store nzmax */
                    fields[i]->nbytes = buf[3];
                }
            }
            /* Rank and dimension */
            {
                size_t nbytes = ReadRankDims(mat, fields[i], (enum matio_types)buf[4], buf[5]);
                bytesread += nbytes;
                nBytes -= nbytes;
            }
            /* Variable name tag */
            bytesread+=fread(buf,1,8,(FILE*)mat->fp);
            nBytes-=8;
            fields[i]->internal->datapos = ftell((FILE*)mat->fp);
            if ( fields[i]->internal->datapos != -1L ) {
                if ( fields[i]->class_type == MAT_C_STRUCT )
                    bytesread+=ReadNextStructField(mat,fields[i]);
                else if ( fields[i]->class_type == MAT_C_CELL )
                    bytesread+=ReadNextCell(mat,fields[i]);
                (void)fseek((FILE*)mat->fp,fields[i]->internal->datapos+nBytes,SEEK_SET);
            } else {
                Mat_Critical("Couldn't determine file position");
            }
        }
    }

    return bytesread;
}


SetFieldNames(matvar_t *matvar, char *buf, size_t nfields, mat_uint32_t fieldname_length)
{
    size_t i;
    matvar->internal->num_fields = nfields;
    matvar->internal->fieldnames =
        (char**)calloc(nfields,sizeof(*matvar->internal->fieldnames));
    if ( NULL != matvar->internal->fieldnames ) {
        for ( i = 0; i < nfields; i++ ) {
            matvar->internal->fieldnames[i] = (char*)malloc(fieldname_length);
            if ( NULL != matvar->internal->fieldnames[i] ) {
                memcpy(matvar->internal->fieldnames[i], buf+i*fieldname_length, fieldname_length);
                matvar->internal->fieldnames[i][fieldname_length-1] = '\0';
            }
        }
    }
}