blob: 8970e65f7d13914a3cd1698d0b4416508a2d9a40 [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2015 Google Inc.
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <dbus/dbus.h>
#include <gdbus/gdbus.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/mgmt.h"
#include "bluetooth/sdp.h"
#include "adapter.h"
#include "dbus-common.h"
#include "error.h"
#include "log.h"
#include "eir.h"
#include "btd.h"
#include "src/shared/ad.h"
#include "src/shared/mgmt.h"
#include "src/shared/queue.h"
#include "src/shared/timeout.h"
#include "src/shared/util.h"
#include "src/shared/crypto.h"
#include "advertising.h"
#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1"
#define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1"
struct btd_adv_manager {
struct btd_adapter *adapter;
struct queue *clients;
struct mgmt *mgmt;
uint16_t mgmt_index;
uint8_t max_adv_len;
uint8_t max_scan_rsp_len;
uint8_t max_ads;
uint32_t supported_flags;
uint64_t instance_bitmap;
bool extended_add_cmds;
int8_t min_tx_power;
int8_t max_tx_power;
};
#define AD_TYPE_BROADCAST 0
#define AD_TYPE_PERIPHERAL 1
/* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2585
* defines tx power value indicating no preference
*/
#define ADV_TX_POWER_NO_PREFERENCE 0x7F
struct btd_adv_client {
struct btd_adv_manager *manager;
char *owner;
char *path;
char *name;
uint16_t appearance;
uint16_t duration;
uint16_t timeout;
uint16_t discoverable_to;
unsigned int to_id;
unsigned int disc_to_id;
unsigned int add_adv_id;
GDBusClient *client;
GDBusProxy *proxy;
DBusMessage *reg;
uint8_t type; /* Advertising type */
uint32_t flags;
struct bt_ad *data;
struct bt_ad *scan;
uint8_t instance;
uint32_t min_interval;
uint32_t max_interval;
int8_t tx_power;
mgmt_request_func_t refresh_done_func;
};
struct dbus_obj_match {
const char *owner;
const char *path;
};
static bool match_client(const void *a, const void *b)
{
const struct btd_adv_client *client = a;
const struct dbus_obj_match *match = b;
if (match->owner && g_strcmp0(client->owner, match->owner))
return false;
if (match->path && g_strcmp0(client->path, match->path))
return false;
return true;
}
static void client_free(void *data)
{
struct btd_adv_client *client = data;
if (client->to_id > 0)
timeout_remove(client->to_id);
if (client->disc_to_id > 0)
timeout_remove(client->disc_to_id);
if (client->client) {
g_dbus_client_set_disconnect_watch(client->client, NULL, NULL);
g_dbus_client_unref(client->client);
}
if (client->reg) {
DBusMessage *reply;
reply = btd_error_failed(client->reg,
"Failed to complete registration");
g_dbus_send_message(btd_get_dbus_connection(), reply);
dbus_message_unref(client->reg);
client->reg = NULL;
}
if (client->add_adv_id)
mgmt_cancel(client->manager->mgmt, client->add_adv_id);
if (client->instance)
util_clear_uid(&client->manager->instance_bitmap,
client->instance);
bt_ad_unref(client->data);
bt_ad_unref(client->scan);
g_dbus_proxy_unref(client->proxy);
if (client->owner)
g_free(client->owner);
if (client->path)
g_free(client->path);
free(client->name);
free(client);
}
static gboolean client_free_idle_cb(void *data)
{
client_free(data);
return FALSE;
}
static void client_release(void *data)
{
struct btd_adv_client *client = data;
DBG("Releasing advertisement %s, %s", client->owner, client->path);
g_dbus_proxy_method_call(client->proxy, "Release", NULL, NULL, NULL,
NULL);
}
static void client_destroy(void *data)
{
client_release(data);
client_free(data);
}
static void remove_advertising(struct btd_adv_manager *manager,
uint8_t instance)
{
struct mgmt_cp_remove_advertising cp;
if (instance)
DBG("instance %u", instance);
else
DBG("all instances");
cp.instance = instance;
mgmt_send(manager->mgmt, MGMT_OP_REMOVE_ADVERTISING,
manager->mgmt_index, sizeof(cp), &cp, NULL, NULL, NULL);
}
static void client_remove(struct btd_adv_client *client)
{
struct mgmt_cp_remove_advertising cp;
g_dbus_client_set_proxy_handlers(client->client, NULL, NULL, NULL,
client);
g_dbus_client_set_disconnect_watch(client->client, NULL, NULL);
cp.instance = client->instance;
mgmt_send(client->manager->mgmt, MGMT_OP_REMOVE_ADVERTISING,
client->manager->mgmt_index, sizeof(cp), &cp,
NULL, NULL, NULL);
queue_remove(client->manager->clients, client);
g_idle_add(client_free_idle_cb, client);
g_dbus_emit_property_changed(btd_get_dbus_connection(),
adapter_get_path(client->manager->adapter),
LE_ADVERTISING_MGR_IFACE, "SupportedInstances");
g_dbus_emit_property_changed(btd_get_dbus_connection(),
adapter_get_path(client->manager->adapter),
LE_ADVERTISING_MGR_IFACE, "ActiveInstances");
}
static void client_disconnect_cb(DBusConnection *conn, void *user_data)
{
struct btd_adv_client *client = user_data;
DBG("Client disconnected");
client_remove(client);
}
static bool parse_type(DBusMessageIter *iter, struct btd_adv_client *client)
{
const char *msg_type;
if (!iter)
return true;
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
return false;
dbus_message_iter_get_basic(iter, &msg_type);
if (!g_strcmp0(msg_type, "broadcast")) {
client->type = AD_TYPE_BROADCAST;
return true;
}
if (!g_strcmp0(msg_type, "peripheral")) {
client->type = AD_TYPE_PERIPHERAL;
return true;
}
return false;
}
static bool parse_service_uuids(DBusMessageIter *iter, struct bt_ad *ad)
{
DBusMessageIter ariter;
if (!iter) {
bt_ad_clear_service_uuid(ad);
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(iter, &ariter);
bt_ad_clear_service_uuid(ad);
while (dbus_message_iter_get_arg_type(&ariter) == DBUS_TYPE_STRING) {
const char *uuid_str;
bt_uuid_t uuid;
dbus_message_iter_get_basic(&ariter, &uuid_str);
DBG("Adding ServiceUUID: %s", uuid_str);
if (bt_string_to_uuid(&uuid, uuid_str) < 0)
goto fail;
if (!bt_ad_add_service_uuid(ad, &uuid))
goto fail;
dbus_message_iter_next(&ariter);
}
return true;
fail:
bt_ad_clear_service_uuid(ad);
return false;
}
static bool parse_service_uuids_ad(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_service_uuids(iter, client->data);
}
static bool parse_service_uuids_sr(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_service_uuids(iter, client->scan);
}
static bool parse_solicit_uuids(DBusMessageIter *iter, struct bt_ad *ad)
{
DBusMessageIter ariter;
if (!iter) {
bt_ad_clear_solicit_uuid(ad);
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(iter, &ariter);
bt_ad_clear_solicit_uuid(ad);
while (dbus_message_iter_get_arg_type(&ariter) == DBUS_TYPE_STRING) {
const char *uuid_str;
bt_uuid_t uuid;
dbus_message_iter_get_basic(&ariter, &uuid_str);
DBG("Adding SolicitUUID: %s", uuid_str);
if (bt_string_to_uuid(&uuid, uuid_str) < 0)
goto fail;
if (!bt_ad_add_solicit_uuid(ad, &uuid))
goto fail;
dbus_message_iter_next(&ariter);
}
return true;
fail:
bt_ad_clear_solicit_uuid(ad);
return false;
}
static bool parse_solicit_uuids_ad(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_solicit_uuids(iter, client->data);
}
static bool parse_solicit_uuids_sr(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_solicit_uuids(iter, client->scan);
}
static bool parse_manufacturer_data(DBusMessageIter *iter, struct bt_ad *ad)
{
DBusMessageIter entries;
if (!iter) {
bt_ad_clear_manufacturer_data(ad);
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(iter, &entries);
bt_ad_clear_manufacturer_data(ad);
while (dbus_message_iter_get_arg_type(&entries)
== DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter value, entry, array;
uint16_t manuf_id;
uint8_t *manuf_data;
int len;
dbus_message_iter_recurse(&entries, &entry);
dbus_message_iter_get_basic(&entry, &manuf_id);
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
goto fail;
dbus_message_iter_recurse(&entry, &value);
if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY)
goto fail;
dbus_message_iter_recurse(&value, &array);
if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE)
goto fail;
dbus_message_iter_get_fixed_array(&array, &manuf_data, &len);
DBG("Adding ManufacturerData for %04x", manuf_id);
if (!bt_ad_add_manufacturer_data(ad, manuf_id,
manuf_data, len))
goto fail;
dbus_message_iter_next(&entries);
}
return true;
fail:
bt_ad_clear_manufacturer_data(ad);
return false;
}
static bool parse_manufacturer_data_ad(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_manufacturer_data(iter, client->data);
}
static bool parse_manufacturer_data_sr(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_manufacturer_data(iter, client->scan);
}
static bool parse_service_data(DBusMessageIter *iter, struct bt_ad *ad)
{
DBusMessageIter entries;
if (!iter) {
bt_ad_clear_service_data(ad);
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(iter, &entries);
bt_ad_clear_service_data(ad);
while (dbus_message_iter_get_arg_type(&entries)
== DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter value, entry, array;
const char *uuid_str;
bt_uuid_t uuid;
uint8_t *service_data;
int len;
dbus_message_iter_recurse(&entries, &entry);
dbus_message_iter_get_basic(&entry, &uuid_str);
if (bt_string_to_uuid(&uuid, uuid_str) < 0)
goto fail;
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
goto fail;
dbus_message_iter_recurse(&entry, &value);
if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY)
goto fail;
dbus_message_iter_recurse(&value, &array);
if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE)
goto fail;
dbus_message_iter_get_fixed_array(&array, &service_data, &len);
DBG("Adding ServiceData for %s", uuid_str);
if (!bt_ad_add_service_data(ad, &uuid, service_data,
len))
goto fail;
dbus_message_iter_next(&entries);
}
return true;
fail:
bt_ad_clear_service_data(ad);
return false;
}
static bool parse_service_data_ad(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_service_data(iter, client->data);
}
static bool parse_service_data_sr(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_service_data(iter, client->scan);
}
static bool validate_rsi(const uint8_t *data, uint8_t len)
{
struct bt_crypto *crypto;
uint8_t zero[16] = {};
uint8_t hash[3];
bool ret;
if (!data || len != 6)
return false;
/* Check if a valid SIRK has been set */
if (!memcmp(btd_opts.csis.sirk, zero, sizeof(zero)))
return false;
crypto = bt_crypto_new();
if (!crypto)
return false;
/* Generate a hash using SIRK and prand as input */
ret = bt_crypto_sih(crypto, btd_opts.csis.sirk, data + 3, hash);
if (!ret)
goto done;
/* Check if hash matches */
ret = !(memcmp(hash, data, 3));
if (!ret) {
error("RSI set invalid: hash mismatch");
goto done;
}
DBG("RSI validated");
done:
bt_crypto_unref(crypto);
return ret;
}
static bool set_rsi(struct btd_adv_client *client)
{
struct bt_crypto *crypto;
uint8_t zero[16] = {};
struct bt_ad_data rsi = { .type = BT_AD_CSIP_RSI };
struct bt_ad_data *ad;
uint8_t data[6];
bool ret;
/* Check if a valid SIRK has been set */
if (!memcmp(btd_opts.csis.sirk, zero, sizeof(zero)))
return false;
if (!client)
return true;
/* Check if RSI needs to be set or data already contains RSI data */
ad = bt_ad_has_data(client->data, &rsi);
if (ad) {
ret = validate_rsi(ad->data, ad->len);
return ret;
}
crypto = bt_crypto_new();
if (!crypto)
return false;
ret = bt_crypto_rsi(crypto, btd_opts.csis.sirk, data);
bt_crypto_unref(crypto);
if (!ret)
return ret;
return bt_ad_add_data(client->data, BT_AD_CSIP_RSI, data, sizeof(data));
}
static struct adv_include {
uint8_t flag;
const char *name;
bool (*set)(struct btd_adv_client *client);
} includes[] = {
{ MGMT_ADV_FLAG_TX_POWER, "tx-power" },
{ MGMT_ADV_FLAG_APPEARANCE, "appearance" },
{ MGMT_ADV_FLAG_LOCAL_NAME, "local-name" },
{ 0 , "rsi", set_rsi },
{ },
};
static bool parse_includes(DBusMessageIter *iter,
struct btd_adv_client *client)
{
DBusMessageIter entries;
if (!iter) {
client->flags = 0;
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(iter, &entries);
/* Reset flags before parsing */
client->flags = 0;
while (dbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_STRING) {
const char *str;
struct adv_include *inc;
dbus_message_iter_get_basic(&entries, &str);
for (inc = includes; inc && inc->name; inc++) {
if (strcmp(str, inc->name))
continue;
if (inc->set && inc->set(client)) {
DBG("Including Feature: %s", str);
continue;
}
if (!(client->manager->supported_flags & inc->flag))
continue;
DBG("Including Feature: %s", str);
client->flags |= inc->flag;
}
dbus_message_iter_next(&entries);
}
return true;
}
static bool parse_local_name(DBusMessageIter *iter,
struct btd_adv_client *client)
{
const char *name;
if (!iter) {
free(client->name);
client->name = NULL;
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
return false;
if (client->flags & MGMT_ADV_FLAG_LOCAL_NAME) {
error("Local name already included");
return false;
}
dbus_message_iter_get_basic(iter, &name);
free(client->name);
/* Treat empty string the same as omitting since there is no point on
* adding a empty name as AD data as it just take space that could be
* used for something else.
*/
if (name[0] != '\0')
client->name = strdup(name);
else
client->name = NULL;
return true;
}
static bool parse_appearance(DBusMessageIter *iter,
struct btd_adv_client *client)
{
if (!iter) {
client->appearance = 0;
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16)
return false;
if (client->flags & MGMT_ADV_FLAG_APPEARANCE) {
error("Appearance already included");
return false;
}
dbus_message_iter_get_basic(iter, &client->appearance);
return true;
}
static bool parse_duration(DBusMessageIter *iter,
struct btd_adv_client *client)
{
if (!iter) {
client->duration = 0;
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16)
return false;
dbus_message_iter_get_basic(iter, &client->duration);
return true;
}
static bool client_timeout(void *user_data)
{
struct btd_adv_client *client = user_data;
DBG("");
client->to_id = 0;
client_release(client);
client_remove(client);
return FALSE;
}
static bool parse_timeout(DBusMessageIter *iter,
struct btd_adv_client *client)
{
if (!iter) {
client->timeout = 0;
timeout_remove(client->to_id);
client->to_id = 0;
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16)
return false;
dbus_message_iter_get_basic(iter, &client->timeout);
if (client->to_id)
timeout_remove(client->to_id);
if (client->timeout > 0)
client->to_id = timeout_add_seconds(client->timeout,
client_timeout, client,
NULL);
return true;
}
static bool parse_data(DBusMessageIter *iter, struct bt_ad *ad)
{
DBusMessageIter entries;
if (!iter) {
bt_ad_clear_data(ad);
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(iter, &entries);
bt_ad_clear_data(ad);
while (dbus_message_iter_get_arg_type(&entries)
== DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter value, entry, array;
uint8_t type;
uint8_t *data;
int len;
dbus_message_iter_recurse(&entries, &entry);
dbus_message_iter_get_basic(&entry, &type);
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
goto fail;
dbus_message_iter_recurse(&entry, &value);
if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY)
goto fail;
dbus_message_iter_recurse(&value, &array);
if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE)
goto fail;
dbus_message_iter_get_fixed_array(&array, &data, &len);
DBG("Adding Data for type 0x%02x len %u", type, len);
switch (type) {
case BT_AD_CSIP_RSI:
if (!validate_rsi(data, len))
goto fail;
break;
}
if (!bt_ad_add_data(ad, type, data, len))
goto fail;
dbus_message_iter_next(&entries);
}
return true;
fail:
bt_ad_clear_data(ad);
return false;
}
static bool parse_data_ad(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_data(iter, client->data);
}
static bool parse_data_sr(DBusMessageIter *iter,
struct btd_adv_client *client)
{
return parse_data(iter, client->scan);
}
static bool set_flags(struct btd_adv_client *client, uint8_t flags)
{
/* Set BR/EDR Not Supported for LE only */
if (!btd_adapter_get_bredr(client->manager->adapter))
flags |= BT_AD_FLAG_NO_BREDR;
/* Set BR/EDR Not Supported if adapter is not discoverable but the
* instance is.
*/
if (!btd_adapter_get_discoverable(client->manager->adapter))
flags |= BT_AD_FLAG_NO_BREDR;
if (!bt_ad_add_flags(client->data, &flags, 1))
return false;
return true;
}
static bool parse_discoverable(DBusMessageIter *iter,
struct btd_adv_client *client)
{
uint8_t flags;
dbus_bool_t discoverable;
if (!iter) {
bt_ad_clear_flags(client->data);
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN)
return false;
dbus_message_iter_get_basic(iter, &discoverable);
/* For broadcast mode, need not add any flags
* just return true without adding flags.
*/
if (discoverable)
flags = BT_AD_FLAG_GENERAL;
else if (client->type != AD_TYPE_BROADCAST)
flags = 0x00;
else
return true;
if (!set_flags(client , flags))
goto fail;
DBG("Adding Flags 0x%02x", flags);
return true;
fail:
bt_ad_clear_flags(client->data);
return false;
}
static size_t calc_max_adv_len(struct btd_adv_client *client, uint32_t flags)
{
size_t max = client->manager->max_adv_len;
/*
* Flags which reduce the amount of space available for advertising.
* See doc/mgmt-api.txt
*/
if (flags & MGMT_ADV_FLAG_TX_POWER)
max -= 3;
if (flags & (MGMT_ADV_FLAG_DISCOV | MGMT_ADV_FLAG_LIMITED_DISCOV |
MGMT_ADV_FLAG_MANAGED_FLAGS))
max -= 3;
if (flags & MGMT_ADV_FLAG_APPEARANCE)
max -= 4;
return max;
}
static uint8_t *generate_adv_data(struct btd_adv_client *client,
uint32_t *flags, size_t *len)
{
if ((*flags & MGMT_ADV_FLAG_APPEARANCE) ||
client->appearance != UINT16_MAX) {
uint16_t appearance;
appearance = client->appearance;
if (appearance == UINT16_MAX)
/* TODO: Get the appearance from the adaptor once
* supported.
*/
appearance = 0x000;
bt_ad_add_appearance(client->data, appearance);
}
/* Scan response shall not be used when connectable and setting a
* secondary PHY since that would end up using EA types instead of
* legacy which doesn't support being connectable and scannable
* simultaneously.
*/
if ((*flags & MGMT_ADV_FLAG_CONNECTABLE) &&
(*flags & MGMT_ADV_FLAG_SEC_MASK) &&
client->name) {
*flags &= ~MGMT_ADV_FLAG_LOCAL_NAME;
bt_ad_add_name(client->data, client->name);
}
return bt_ad_generate(client->data, len);
}
static uint8_t *generate_scan_rsp(struct btd_adv_client *client,
uint32_t *flags, size_t *len)
{
if (client->name) {
*flags &= ~MGMT_ADV_FLAG_LOCAL_NAME;
bt_ad_add_name(client->scan, client->name);
} else if (bt_ad_is_empty(client->scan)) {
*len = 0;
return NULL;
}
return bt_ad_generate(client->scan, len);
}
static bool adv_client_has_scan_response(struct btd_adv_client *client,
uint32_t flags)
{
/* The local name isn't added into the bt_ad structure until
* generate_scan_rsp is called, so we must check these conditions as
* well.
*/
if (!(flags & MGMT_ADV_FLAG_LOCAL_NAME) &&
!client->name &&
bt_ad_is_empty(client->scan)) {
return false;
}
/* Scan response shall not be used when connectable and setting a
* secondary PHY since that would end up using EA types instead of
* legacy which doesn't support being connectable and scannable
* simultaneously.
*/
if (flags & MGMT_ADV_FLAG_CONNECTABLE &&
flags & MGMT_ADV_FLAG_SEC_MASK)
return false;
return true;
}
static int get_adv_flags(struct btd_adv_client *client)
{
uint32_t flags = 0;
if (client->type == AD_TYPE_PERIPHERAL) {
flags = MGMT_ADV_FLAG_CONNECTABLE;
if (btd_adapter_get_discoverable(client->manager->adapter) &&
!(bt_ad_has_flags(client->data)))
flags |= MGMT_ADV_FLAG_DISCOV;
}
flags |= client->flags;
/* Detect if the length is bigger that legacy and secondary is not set
* then force it to be set to ensure the kernel uses EA.
*/
if (bt_ad_length(client->data) > BT_AD_MAX_DATA_LEN &&
!(flags & MGMT_ADV_FLAG_SEC_MASK))
flags |= MGMT_ADV_FLAG_SEC_1M;
return flags;
}
static int refresh_legacy_adv(struct btd_adv_client *client,
mgmt_request_func_t func)
{
struct mgmt_cp_add_advertising *cp;
uint8_t param_len;
uint8_t *adv_data;
size_t adv_data_len;
uint8_t *scan_rsp;
size_t scan_rsp_len = -1;
uint32_t flags = 0;
unsigned int mgmt_ret;
DBG("Refreshing advertisement: %s", client->path);
flags = get_adv_flags(client);
adv_data = generate_adv_data(client, &flags, &adv_data_len);
if (!adv_data || (adv_data_len > calc_max_adv_len(client, flags))) {
error("Advertising data too long or couldn't be generated.");
return -EINVAL;
}
scan_rsp = generate_scan_rsp(client, &flags, &scan_rsp_len);
if (!scan_rsp && scan_rsp_len) {
error("Scan data couldn't be generated.");
free(adv_data);
return -EINVAL;
}
param_len = sizeof(struct mgmt_cp_add_advertising) + adv_data_len +
scan_rsp_len;
cp = malloc0(param_len);
if (!cp) {
error("Couldn't allocate for MGMT!");
free(adv_data);
free(scan_rsp);
return -ENOMEM;
}
cp->flags = htobl(flags);
cp->instance = client->instance;
cp->duration = client->duration;
cp->adv_data_len = adv_data_len;
cp->scan_rsp_len = scan_rsp_len;
memcpy(cp->data, adv_data, adv_data_len);
if (scan_rsp)
memcpy(cp->data + adv_data_len, scan_rsp, scan_rsp_len);
free(adv_data);
free(scan_rsp);
mgmt_ret = mgmt_send(client->manager->mgmt, MGMT_OP_ADD_ADVERTISING,
client->manager->mgmt_index, param_len, cp,
func, client, NULL);
if (!mgmt_ret) {
error("Failed to add Advertising Data");
free(cp);
return -EINVAL;
}
if (func)
client->add_adv_id = mgmt_ret;
free(cp);
return 0;
}
static void add_adv_params_callback(uint8_t status, uint16_t length,
const void *param, void *user_data);
static int refresh_extended_adv(struct btd_adv_client *client,
mgmt_request_func_t func)
{
struct mgmt_cp_add_ext_adv_params cp;
uint32_t flags = 0;
unsigned int mgmt_ret = 0;
DBG("Refreshing advertisement parameters: %s", client->path);
flags = get_adv_flags(client);
memset(&cp, 0, sizeof(cp));
cp.instance = client->instance;
/* Not all advertising instances will use all possible parameters. The
* included_params bit field tells the kernel which parameters are
* relevant, and sensible defaults will be used for the rest
*/
if (client->duration) {
cp.duration = client->duration;
flags |= MGMT_ADV_PARAM_DURATION;
}
if (client->min_interval && client->max_interval) {
cp.min_interval = client->min_interval;
cp.max_interval = client->max_interval;
flags |= MGMT_ADV_PARAM_INTERVALS;
}
if (client->tx_power != ADV_TX_POWER_NO_PREFERENCE) {
cp.tx_power = client->tx_power;
flags |= MGMT_ADV_PARAM_TX_POWER;
}
/* Indicate that this instance will be configured as scannable */
if (adv_client_has_scan_response(client, flags) &&
client->manager->supported_flags & MGMT_ADV_PARAM_SCAN_RSP) {
flags |= MGMT_ADV_PARAM_SCAN_RSP;
}
cp.flags = cpu_to_le32(flags);
mgmt_ret = mgmt_send(client->manager->mgmt, MGMT_OP_ADD_EXT_ADV_PARAMS,
client->manager->mgmt_index, sizeof(cp), &cp,
add_adv_params_callback, client, NULL);
if (!mgmt_ret) {
error("Failed to request extended advertising parameters");
return -EINVAL;
}
/* Store callback, called after we set advertising data */
client->refresh_done_func = func;
client->add_adv_id = mgmt_ret;
return 0;
}
static int refresh_advertisement(struct btd_adv_client *client,
mgmt_request_func_t func)
{
if (client->manager->extended_add_cmds)
return refresh_extended_adv(client, func);
return refresh_legacy_adv(client, func);
}
static bool client_discoverable_timeout(void *user_data)
{
struct btd_adv_client *client = user_data;
DBG("");
client->disc_to_id = 0;
bt_ad_clear_flags(client->data);
refresh_advertisement(client, NULL);
return FALSE;
}
static bool parse_discoverable_timeout(DBusMessageIter *iter,
struct btd_adv_client *client)
{
if (!iter) {
client->discoverable_to = 0;
timeout_remove(client->disc_to_id);
client->disc_to_id = 0;
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16)
return false;
dbus_message_iter_get_basic(iter, &client->discoverable_to);
if (client->disc_to_id)
timeout_remove(client->disc_to_id);
if (client->discoverable_to > 0)
client->disc_to_id = timeout_add_seconds(
client->discoverable_to,
client_discoverable_timeout,
client, NULL);
return true;
}
static struct adv_secondary {
int flag;
const char *name;
} secondary[] = {
{ MGMT_ADV_FLAG_SEC_1M, "1M" },
{ MGMT_ADV_FLAG_SEC_2M, "2M" },
{ MGMT_ADV_FLAG_SEC_CODED, "Coded" },
{ },
};
static bool parse_secondary(DBusMessageIter *iter,
struct btd_adv_client *client)
{
const char *str;
struct adv_secondary *sec;
if (!iter) {
/* Reset secondary channels */
client->flags &= ~MGMT_ADV_FLAG_SEC_MASK;
return true;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
return false;
/* Reset secondary channels before parsing */
client->flags &= ~MGMT_ADV_FLAG_SEC_MASK;
dbus_message_iter_get_basic(iter, &str);
for (sec = secondary; sec->name; sec++) {
if (strcmp(str, sec->name))
continue;
if (!(client->manager->supported_flags & sec->flag))
return false;
DBG("Secondary Channel: %s", str);
client->flags |= sec->flag;
return true;
}
return false;
}
static bool parse_min_interval(DBusMessageIter *iter,
struct btd_adv_client *client)
{
uint32_t min_interval_ms;
if (!iter) {
client->min_interval = 0;
return false;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
return false;
dbus_message_iter_get_basic(iter, &min_interval_ms);
/* Convert ms to jiffies to be used in adv request */
client->min_interval = min_interval_ms / 0.625;
/* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2584
* defines acceptable interval range
*/
if (client->min_interval < 0x20 || client->min_interval > 0xFFFFFF) {
client->min_interval = 0;
return false;
}
return true;
}
static bool parse_max_interval(DBusMessageIter *iter,
struct btd_adv_client *client)
{
uint32_t max_interval_ms;
if (!iter) {
client->max_interval = 0;
return false;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
return false;
dbus_message_iter_get_basic(iter, &max_interval_ms);
/* Convert ms to jiffies to be used in adv request */
client->max_interval = max_interval_ms / 0.625;
/* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2584
* defines acceptable interval range
*/
if (client->max_interval < 0x20 || client->max_interval > 0xFFFFFF) {
client->max_interval = 0;
return false;
}
return true;
}
static bool parse_tx_power(DBusMessageIter *iter,
struct btd_adv_client *client)
{
int16_t val;
if (!iter) {
client->tx_power = ADV_TX_POWER_NO_PREFERENCE;
return false;
}
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT16)
return false;
dbus_message_iter_get_basic(iter, &val);
/* BLUETOOTH SPECIFICATION Version 5.2 | Vol 4, Part E, page 2585
* defines acceptable tx power range
*/
if (val < -127 || val > 20)
return false;
client->tx_power = val;
return true;
}
static struct adv_parser {
const char *name;
bool (*func)(DBusMessageIter *iter, struct btd_adv_client *client);
bool experimental;
} parsers[] = {
{ "Type", parse_type },
{ "ServiceUUIDs", parse_service_uuids_ad },
{ "ScanResponseServiceUUIDs", parse_service_uuids_sr, true },
{ "SolicitUUIDs", parse_solicit_uuids_ad },
{ "ScanResponseSolicitUUIDs", parse_solicit_uuids_sr, true },
{ "ManufacturerData", parse_manufacturer_data_ad },
{ "ScanResponseManufacturerData", parse_manufacturer_data_sr, true },
{ "ServiceData", parse_service_data_ad },
{ "ScanResponseServiceData", parse_service_data_sr, true },
{ "Includes", parse_includes },
{ "LocalName", parse_local_name },
{ "Appearance", parse_appearance },
{ "Duration", parse_duration },
{ "Timeout", parse_timeout },
{ "Data", parse_data_ad },
{ "ScanResponseData", parse_data_sr },
{ "Discoverable", parse_discoverable },
{ "DiscoverableTimeout", parse_discoverable_timeout },
{ "SecondaryChannel", parse_secondary },
{ "MinInterval", parse_min_interval },
{ "MaxInterval", parse_max_interval },
{ "TxPower", parse_tx_power },
{ },
};
static void properties_changed(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter, void *user_data)
{
struct btd_adv_client *client = user_data;
struct adv_parser *parser;
for (parser = parsers; parser && parser->name; parser++) {
if (strcmp(parser->name, name))
continue;
/* Ignore experimental parsers if the experimental flag is not
* set.
*/
if (parser->experimental && !(g_dbus_get_flags() &
G_DBUS_FLAG_ENABLE_EXPERIMENTAL))
continue;
if (parser->func(iter, client)) {
refresh_advertisement(client, NULL);
break;
}
}
}
static void add_client_complete(struct btd_adv_client *client, uint8_t status)
{
DBusMessage *reply;
if (status)
error("Failed to add advertisement: %s (0x%02x)",
mgmt_errstr(status), status);
/* If the advertising request was not started by a direct call from
* the client, but rather by a refresh due to properties update or
* our internal timer, there is nothing to reply to.
*/
if (!client->reg)
return;
if (status) {
reply = btd_error_failed(client->reg,
"Failed to register advertisement");
queue_remove(client->manager->clients, client);
g_idle_add(client_free_idle_cb, client);
} else
reply = dbus_message_new_method_return(client->reg);
g_dbus_send_message(btd_get_dbus_connection(), reply);
dbus_message_unref(client->reg);
client->reg = NULL;
}
static void add_adv_callback(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
struct btd_adv_client *client = user_data;
const struct mgmt_rp_add_advertising *rp = param;
client->add_adv_id = 0;
if (status)
goto done;
if (!param || length < sizeof(*rp)) {
status = MGMT_STATUS_FAILED;
goto done;
}
client->instance = rp->instance;
g_dbus_client_set_disconnect_watch(client->client, client_disconnect_cb,
client);
DBG("Advertisement registered: %s", client->path);
g_dbus_emit_property_changed(btd_get_dbus_connection(),
adapter_get_path(client->manager->adapter),
LE_ADVERTISING_MGR_IFACE, "SupportedInstances");
g_dbus_emit_property_changed(btd_get_dbus_connection(),
adapter_get_path(client->manager->adapter),
LE_ADVERTISING_MGR_IFACE, "ActiveInstances");
g_dbus_proxy_set_property_watch(client->proxy, properties_changed,
client);
done:
add_client_complete(client, status);
}
static void add_adv_params_callback(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
struct btd_adv_client *client = user_data;
const struct mgmt_rp_add_ext_adv_params *rp = param;
struct mgmt_cp_add_ext_adv_data *cp = NULL;
uint8_t param_len;
uint8_t *adv_data = NULL;
size_t adv_data_len;
uint8_t *scan_rsp = NULL;
size_t scan_rsp_len = 0;
uint32_t flags = 0;
unsigned int mgmt_ret;
dbus_int16_t tx_power;
client->add_adv_id = 0;
if (status)
goto fail;
if (!param || length < sizeof(*rp)) {
status = MGMT_STATUS_FAILED;
goto fail;
}
DBG("Refreshing advertisement data: %s", client->path);
/* Update tx power held by client */
tx_power = rp->tx_power;
if (tx_power != ADV_TX_POWER_NO_PREFERENCE)
g_dbus_proxy_set_property_basic(client->proxy, "TxPower",
DBUS_TYPE_INT16, &tx_power, NULL, NULL, NULL);
client->instance = rp->instance;
flags = get_adv_flags(client);
adv_data = generate_adv_data(client, &flags, &adv_data_len);
if (!adv_data || (adv_data_len > rp->max_adv_data_len)) {
error("Advertising data too long or couldn't be generated.");
goto fail;
}
if (adv_client_has_scan_response(client, flags)) {
scan_rsp = generate_scan_rsp(client, &flags, &scan_rsp_len);
if ((!scan_rsp && scan_rsp_len) ||
scan_rsp_len > rp->max_scan_rsp_len) {
error("Scan data couldn't be generated.");
goto fail;
}
}
param_len = sizeof(struct mgmt_cp_add_advertising) + adv_data_len +
scan_rsp_len;
cp = malloc0(param_len);
if (!cp) {
error("Couldn't allocate for MGMT!");
goto fail;
}
cp->instance = client->instance;
cp->adv_data_len = adv_data_len;
cp->scan_rsp_len = scan_rsp_len;
memcpy(cp->data, adv_data, adv_data_len);
if (scan_rsp)
memcpy(cp->data + adv_data_len, scan_rsp, scan_rsp_len);
free(adv_data);
free(scan_rsp);
adv_data = NULL;
scan_rsp = NULL;
/* Submit request to update instance data */
mgmt_ret = mgmt_send(client->manager->mgmt, MGMT_OP_ADD_EXT_ADV_DATA,
client->manager->mgmt_index, param_len, cp,
client->refresh_done_func, client, NULL);
if (mgmt_ret && client->refresh_done_func)
client->add_adv_id = mgmt_ret;
/* Clear the callback */
client->refresh_done_func = NULL;
if (!mgmt_ret) {
error("Failed to add Advertising Data");
goto fail;
}
free(cp);
cp = NULL;
return;
fail:
if (adv_data)
free(adv_data);
if (scan_rsp)
free(scan_rsp);
if (cp)
free(cp);
if (!status)
status = -EINVAL;
/* Failure for any reason ends this advertising request */
add_client_complete(client, status);
}
static DBusMessage *parse_advertisement(struct btd_adv_client *client)
{
struct adv_parser *parser;
int err;
for (parser = parsers; parser && parser->name; parser++) {
DBusMessageIter iter;
if (!g_dbus_proxy_get_property(client->proxy, parser->name,
&iter))
continue;
if (!parser->func(&iter, client)) {
error("Error parsing %s property", parser->name);
goto fail;
}
}
if (bt_ad_get_flags(client->data) &
(BT_AD_FLAG_GENERAL | BT_AD_FLAG_LIMITED)) {
/* BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part C
* page 2042:
* A device in the broadcast mode shall not set the
* ‘LE General Discoverable Mode’ flag or the
* ‘LE Limited Discoverable Mode’ flag in the Flags AD Type
* as defined in [Core Specification Supplement], Part A,
* Section 1.3.
*/
if (client->type == AD_TYPE_BROADCAST) {
error("Broadcast cannot set flags");
goto fail;
}
/* Set Limited Discoverable if DiscoverableTimeout is set */
if (client->disc_to_id &&
!set_flags(client, BT_AD_FLAG_LIMITED)) {
error("Failed to set Limited Discoverable Flag");
goto fail;
}
} else if (client->disc_to_id) {
/* Ignore DiscoverableTimeout if not discoverable */
timeout_remove(client->disc_to_id);
client->disc_to_id = 0;
client->discoverable_to = 0;
}
if (client->timeout && client->timeout < client->discoverable_to) {
/* DiscoverableTimeout must not be bigger than Timeout */
error("DiscoverableTimeout > Timeout");
goto fail;
}
if (client->min_interval > client->max_interval) {
/* Min interval must not be bigger than max interval */
error("MinInterval must be less than MaxInterval (%u > %u)",
client->min_interval, client->max_interval);
goto fail;
}
err = refresh_advertisement(client, add_adv_callback);
if (!err)
return NULL;
fail:
return btd_error_failed(client->reg, "Failed to parse advertisement.");
}
static void client_proxy_added(GDBusProxy *proxy, void *data)
{
struct btd_adv_client *client = data;
DBusMessage *reply;
const char *interface;
interface = g_dbus_proxy_get_interface(proxy);
if (g_str_equal(interface, LE_ADVERTISEMENT_IFACE) == FALSE)
return;
reply = parse_advertisement(client);
if (!reply)
return;
/* Failed to publish for some reason, remove. */
queue_remove(client->manager->clients, client);
g_idle_add(client_free_idle_cb, client);
g_dbus_send_message(btd_get_dbus_connection(), reply);
dbus_message_unref(client->reg);
client->reg = NULL;
}
static struct btd_adv_client *client_create(struct btd_adv_manager *manager,
DBusConnection *conn,
DBusMessage *msg, const char *path)
{
struct btd_adv_client *client;
const char *sender = dbus_message_get_sender(msg);
if (!path || !g_str_has_prefix(path, "/"))
return NULL;
client = new0(struct btd_adv_client, 1);
client->client = g_dbus_client_new_full(conn, sender, path, path);
if (!client->client)
goto fail;
client->owner = g_strdup(sender);
if (!client->owner)
goto fail;
client->path = g_strdup(path);
if (!client->path)
goto fail;
DBG("Adding proxy for %s", path);
client->proxy = g_dbus_proxy_new(client->client, path,
LE_ADVERTISEMENT_IFACE);
if (!client->proxy)
goto fail;
g_dbus_client_set_proxy_handlers(client->client, client_proxy_added,
NULL, NULL, client);
client->data = bt_ad_new();
if (!client->data)
goto fail;
bt_ad_set_max_len(client->data, manager->max_adv_len);
client->scan = bt_ad_new();
if (!client->scan)
goto fail;
bt_ad_set_max_len(client->scan, manager->max_scan_rsp_len);
client->manager = manager;
client->appearance = UINT16_MAX;
client->tx_power = ADV_TX_POWER_NO_PREFERENCE;
client->min_interval = 0;
client->max_interval = 0;
client->refresh_done_func = NULL;
return client;
fail:
client_free(client);
return NULL;
}
static DBusMessage *register_advertisement(DBusConnection *conn,
DBusMessage *msg,
void *user_data)
{
struct btd_adv_manager *manager = user_data;
DBusMessageIter args;
struct btd_adv_client *client;
struct dbus_obj_match match;
DBG("RegisterAdvertisement");
if (!dbus_message_iter_init(msg, &args))
return btd_error_invalid_args(msg);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&args, &match.path);
match.owner = dbus_message_get_sender(msg);
if (queue_find(manager->clients, match_client, &match))
return btd_error_already_exists(msg);
dbus_message_iter_next(&args);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
return btd_error_invalid_args(msg);
client = client_create(manager, conn, msg, match.path);
if (!client)
return btd_error_failed(msg,
"Failed to register advertisement");
client->instance = util_get_uid(&manager->instance_bitmap,
manager->max_ads);
if (!client->instance) {
client_free(client);
return btd_error_not_permitted(msg,
"Maximum advertisements reached");
}
DBG("Registered advertisement at path %s", match.path);
client->reg = dbus_message_ref(msg);
queue_push_tail(manager->clients, client);
return NULL;
}
static DBusMessage *unregister_advertisement(DBusConnection *conn,
DBusMessage *msg,
void *user_data)
{
struct btd_adv_manager *manager = user_data;
DBusMessageIter args;
struct btd_adv_client *client;
struct dbus_obj_match match;
DBG("UnregisterAdvertisement");
if (!dbus_message_iter_init(msg, &args))
return btd_error_invalid_args(msg);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&args, &match.path);
match.owner = dbus_message_get_sender(msg);
client = queue_find(manager->clients, match_client, &match);
if (!client)
return btd_error_does_not_exist(msg);
client_remove(client);
return dbus_message_new_method_return(msg);
}
static gboolean get_instances(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_adv_manager *manager = data;
uint8_t instances;
instances = manager->max_ads - queue_length(manager->clients);
dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &instances);
return TRUE;
}
static gboolean get_active_instances(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_adv_manager *manager = data;
uint8_t instances;
instances = queue_length(manager->clients);
dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &instances);
return TRUE;
}
static void append_include(struct btd_adv_manager *manager,
DBusMessageIter *iter)
{
struct adv_include *inc;
for (inc = includes; inc && inc->name; inc++) {
if ((inc->set && inc->set(NULL)) ||
(manager->supported_flags & inc->flag))
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
&inc->name);
}
}
static gboolean get_supported_includes(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_adv_manager *manager = data;
DBusMessageIter entry;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING_AS_STRING, &entry);
append_include(manager, &entry);
dbus_message_iter_close_container(iter, &entry);
return TRUE;
}
static void append_secondary(struct btd_adv_manager *manager,
DBusMessageIter *iter)
{
struct adv_secondary *sec;
for (sec = secondary; sec && sec->name; sec++) {
if (manager->supported_flags & sec->flag)
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
&sec->name);
}
}
static gboolean secondary_exists(const GDBusPropertyTable *property,
void *data)
{
struct btd_adv_manager *manager = data;
/* 1M PHY shall always be supported if ext_adv is supported */
return manager->supported_flags & MGMT_ADV_FLAG_SEC_1M;
}
static gboolean get_supported_secondary(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_adv_manager *manager = data;
DBusMessageIter entry;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING_AS_STRING, &entry);
append_secondary(manager, &entry);
dbus_message_iter_close_container(iter, &entry);
return TRUE;
}
static struct adv_feature {
int flag;
const char *name;
} features[] = {
{ MGMT_ADV_FLAG_CAN_SET_TX_POWER, "CanSetTxPower" },
{ MGMT_ADV_FLAG_HW_OFFLOAD, "HardwareOffload" },
{ },
};
static void append_features(struct btd_adv_manager *manager,
DBusMessageIter *iter)
{
struct adv_feature *feat;
for (feat = features; feat->name; feat++) {
if (manager->supported_flags & feat->flag)
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
&feat->name);
}
}
static gboolean get_supported_features(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_adv_manager *manager = data;
DBusMessageIter entry;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING_AS_STRING, &entry);
append_features(manager, &entry);
dbus_message_iter_close_container(iter, &entry);
return TRUE;
}
static gboolean get_supported_cap(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_adv_manager *manager = data;
DBusMessageIter dict;
int16_t min_tx_power = manager->min_tx_power;
int16_t max_tx_power = manager->max_tx_power;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict);
if (min_tx_power != ADV_TX_POWER_NO_PREFERENCE)
dict_append_entry(&dict, "MinTxPower", DBUS_TYPE_INT16,
&min_tx_power);
if (max_tx_power != ADV_TX_POWER_NO_PREFERENCE)
dict_append_entry(&dict, "MaxTxPower", DBUS_TYPE_INT16,
&max_tx_power);
dict_append_entry(&dict, "MaxAdvLen", DBUS_TYPE_BYTE,
&manager->max_adv_len);
dict_append_entry(&dict, "MaxScnRspLen", DBUS_TYPE_BYTE,
&manager->max_scan_rsp_len);
dbus_message_iter_close_container(iter, &dict);
return TRUE;
}
static const GDBusPropertyTable properties[] = {
{ "ActiveInstances", "y", get_active_instances, NULL, NULL },
{ "SupportedInstances", "y", get_instances, NULL, NULL },
{ "SupportedIncludes", "as", get_supported_includes, NULL, NULL },
{ "SupportedSecondaryChannels", "as", get_supported_secondary, NULL,
secondary_exists },
{ "SupportedFeatures", "as", get_supported_features, NULL, NULL },
{ "SupportedCapabilities", "a{sv}", get_supported_cap, NULL, NULL },
{ }
};
static const GDBusMethodTable methods[] = {
{ GDBUS_ASYNC_METHOD("RegisterAdvertisement",
GDBUS_ARGS({ "advertisement", "o" },
{ "options", "a{sv}" }),
NULL, register_advertisement) },
{ GDBUS_ASYNC_METHOD("UnregisterAdvertisement",
GDBUS_ARGS({ "service", "o" }),
NULL,
unregister_advertisement) },
{ }
};
static void manager_destroy(void *user_data)
{
struct btd_adv_manager *manager = user_data;
queue_destroy(manager->clients, client_destroy);
mgmt_unref(manager->mgmt);
free(manager);
}
static void read_adv_features_callback(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
struct btd_adv_manager *manager = user_data;
const struct mgmt_rp_read_adv_features *feat = param;
if (status || !param) {
error("Failed to read advertising features: %s (0x%02x)",
mgmt_errstr(status), status);
return;
}
if (length < sizeof(*feat)) {
error("Wrong size of read adv features response");
return;
}
manager->max_adv_len = feat->max_adv_data_len;
manager->max_scan_rsp_len = feat->max_scan_rsp_len;
manager->max_ads = feat->max_instances;
manager->supported_flags |= feat->supported_flags;
/* Registering interface after querying properties */
if (!g_dbus_register_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE, methods,
NULL, properties, manager, NULL))
error("Failed to register " LE_ADVERTISING_MGR_IFACE);
if (manager->max_ads == 0)
return;
/* Reset existing instances */
if (feat->num_instances)
remove_advertising(manager, 0);
/* Emit property update */
g_dbus_emit_property_changed(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE, "SupportedFeatures");
g_dbus_emit_property_changed(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE, "SupportedIncludes");
g_dbus_emit_property_changed(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE, "SupportedSecondaryChannels");
}
static void read_controller_cap_complete(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
struct btd_adv_manager *manager = user_data;
const struct mgmt_rp_read_controller_cap *rp = param;
const uint8_t *ptr = rp->cap;
size_t offset = 0;
uint8_t tag_len;
uint8_t tag_type;
if (status || !param) {
error("Failed to read advertising features: %s (0x%02x)",
mgmt_errstr(status), status);
return;
}
if (sizeof(rp->cap_len) + rp->cap_len != length) {
error("Controller capabilities malformed, size %zu != %u",
sizeof(rp->cap_len) + rp->cap_len, length);
return;
}
while (offset < rp->cap_len) {
tag_len = ptr[offset++];
tag_type = ptr[offset++];
switch (tag_type) {
case MGMT_CAP_LE_TX_PWR:
if ((tag_len - sizeof(tag_type)) !=
2*sizeof(manager->min_tx_power)) {
error("TX power had unexpected length %d",
tag_len);
break;
}
memcpy(&manager->min_tx_power, &ptr[offset], tag_len);
memcpy(&manager->max_tx_power, &ptr[offset+1], tag_len);
}
/* Step to the next entry */
offset += (tag_len - sizeof(tag_type));
}
}
static struct btd_adv_manager *manager_create(struct btd_adapter *adapter,
struct mgmt *mgmt)
{
struct btd_adv_manager *manager;
manager = new0(struct btd_adv_manager, 1);
manager->adapter = adapter;
manager->mgmt = mgmt_ref(mgmt);
if (!manager->mgmt) {
error("Failed to access management interface");
free(manager);
return NULL;
}
manager->mgmt_index = btd_adapter_get_index(adapter);
manager->clients = queue_new();
manager->supported_flags = MGMT_ADV_FLAG_LOCAL_NAME;
manager->extended_add_cmds =
btd_has_kernel_features(KERNEL_HAS_EXT_ADV_ADD_CMDS);
manager->min_tx_power = ADV_TX_POWER_NO_PREFERENCE;
manager->max_tx_power = ADV_TX_POWER_NO_PREFERENCE;
if (!mgmt_send(manager->mgmt, MGMT_OP_READ_ADV_FEATURES,
manager->mgmt_index, 0, NULL,
read_adv_features_callback, manager, NULL)) {
error("Failed to read advertising features");
goto fail;
}
/* Query controller capabilities. This will be used to display valid
* advertising tx power range to the client.
*/
if (btd_has_kernel_features(KERNEL_HAS_CONTROLLER_CAP_CMD))
mgmt_send(manager->mgmt, MGMT_OP_READ_CONTROLLER_CAP,
manager->mgmt_index, 0, NULL,
read_controller_cap_complete, manager, NULL);
return manager;
fail:
manager_destroy(manager);
return NULL;
}
struct btd_adv_manager *btd_adv_manager_new(struct btd_adapter *adapter,
struct mgmt *mgmt)
{
struct btd_adv_manager *manager;
if (!adapter || !mgmt)
return NULL;
manager = manager_create(adapter, mgmt);
if (!manager)
return NULL;
DBG("LE Advertising Manager created for adapter: %s",
adapter_get_path(adapter));
return manager;
}
void btd_adv_manager_destroy(struct btd_adv_manager *manager)
{
if (!manager)
return;
g_dbus_unregister_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE);
manager_destroy(manager);
}
static void manager_refresh(void *data, void *user_data)
{
struct btd_adv_client *client = data;
refresh_advertisement(client, NULL);
}
void btd_adv_manager_refresh(struct btd_adv_manager *manager)
{
if (!manager)
return;
queue_foreach(manager->clients, manager_refresh, NULL);
}