blob: d2019deb693a8a61d836e54e369b9053181c90f7 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2015 Google Inc.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include "advertising.h"
#include <stdint.h>
#include <stdbool.h>
#include <dbus/dbus.h>
#include <gdbus/gdbus.h>
#include "lib/bluetooth.h"
#include "lib/mgmt.h"
#include "lib/sdp.h"
#include "adapter.h"
#include "dbus-common.h"
#include "error.h"
#include "log.h"
#include "src/shared/ad.h"
#include "src/shared/mgmt.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1"
#define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1"
struct btd_advertising {
struct btd_adapter *adapter;
struct queue *ads;
struct mgmt *mgmt;
uint16_t mgmt_index;
uint8_t max_adv_len;
uint8_t max_ads;
unsigned int instance_bitmap;
};
#define AD_TYPE_BROADCAST 0
#define AD_TYPE_PERIPHERAL 1
struct advertisement {
struct btd_advertising *manager;
char *owner;
char *path;
GDBusClient *client;
GDBusProxy *proxy;
DBusMessage *reg;
uint8_t type; /* Advertising type */
bool include_tx_power;
struct bt_ad *data;
uint8_t instance;
};
struct dbus_obj_match {
const char *owner;
const char *path;
};
static bool match_advertisement(const void *a, const void *b)
{
const struct advertisement *ad = a;
const struct dbus_obj_match *match = b;
if (match->owner && g_strcmp0(ad->owner, match->owner))
return false;
if (match->path && g_strcmp0(ad->path, match->path))
return false;
return true;
}
static void advertisement_free(void *data)
{
struct advertisement *ad = data;
if (ad->client) {
g_dbus_client_set_disconnect_watch(ad->client, NULL, NULL);
g_dbus_client_unref(ad->client);
}
bt_ad_unref(ad->data);
g_dbus_proxy_unref(ad->proxy);
if (ad->owner)
g_free(ad->owner);
if (ad->path)
g_free(ad->path);
free(ad);
}
static gboolean advertisement_free_idle_cb(void *data)
{
advertisement_free(data);
return FALSE;
}
static void advertisement_release(void *data)
{
struct advertisement *ad = data;
DBusMessage *message;
DBG("Releasing advertisement %s, %s", ad->owner, ad->path);
message = dbus_message_new_method_call(ad->owner, ad->path,
LE_ADVERTISEMENT_IFACE,
"Release");
if (!message) {
error("Couldn't allocate D-Bus message");
return;
}
g_dbus_send_message(btd_get_dbus_connection(), message);
}
static void advertisement_destroy(void *data)
{
advertisement_release(data);
advertisement_free(data);
}
static void advertisement_remove(void *data)
{
struct advertisement *ad = data;
struct mgmt_cp_remove_advertising cp;
g_dbus_client_set_disconnect_watch(ad->client, NULL, NULL);
cp.instance = ad->instance;
mgmt_send(ad->manager->mgmt, MGMT_OP_REMOVE_ADVERTISING,
ad->manager->mgmt_index, sizeof(cp), &cp, NULL, NULL,
NULL);
queue_remove(ad->manager->ads, ad);
util_clear_uid(&ad->manager->instance_bitmap, ad->instance);
g_idle_add(advertisement_free_idle_cb, ad);
}
static void client_disconnect_cb(DBusConnection *conn, void *user_data)
{
DBG("Client disconnected");
advertisement_remove(user_data);
}
static bool parse_advertising_type(GDBusProxy *proxy, uint8_t *type)
{
DBusMessageIter iter;
const char *msg_type;
if (!g_dbus_proxy_get_property(proxy, "Type", &iter))
return false;
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")) {
*type = AD_TYPE_BROADCAST;
return true;
}
if (!g_strcmp0(msg_type, "peripheral")) {
*type = AD_TYPE_PERIPHERAL;
return true;
}
return false;
}
static bool parse_advertising_service_uuids(GDBusProxy *proxy,
struct bt_ad *data)
{
DBusMessageIter iter, ariter;
if (!g_dbus_proxy_get_property(proxy, "ServiceUUIDs", &iter))
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(data);
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(data, &uuid))
goto fail;
dbus_message_iter_next(&ariter);
}
return true;
fail:
bt_ad_clear_service_uuid(data);
return false;
}
static bool parse_advertising_solicit_uuids(GDBusProxy *proxy,
struct bt_ad *data)
{
DBusMessageIter iter, ariter;
if (!g_dbus_proxy_get_property(proxy, "SolicitUUIDs", &iter))
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(data);
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(data, &uuid))
goto fail;
dbus_message_iter_next(&ariter);
}
return true;
fail:
bt_ad_clear_solicit_uuid(data);
return false;
}
static bool parse_advertising_manufacturer_data(GDBusProxy *proxy,
struct bt_ad *data)
{
DBusMessageIter iter, entries;
if (!g_dbus_proxy_get_property(proxy, "ManufacturerData", &iter))
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(data);
while (dbus_message_iter_get_arg_type(&entries)
== DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter value, entry;
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_ARRAY)
goto fail;
dbus_message_iter_recurse(&entry, &value);
if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_BYTE)
goto fail;
dbus_message_iter_get_fixed_array(&value, &manuf_data, &len);
DBG("Adding ManufacturerData for %04x", manuf_id);
if (!bt_ad_add_manufacturer_data(data, manuf_id, manuf_data,
len))
goto fail;
dbus_message_iter_next(&entries);
}
return true;
fail:
bt_ad_clear_manufacturer_data(data);
return false;
}
static bool parse_advertising_service_data(GDBusProxy *proxy,
struct bt_ad *data)
{
DBusMessageIter iter, entries;
if (!g_dbus_proxy_get_property(proxy, "ServiceData", &iter))
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(data);
while (dbus_message_iter_get_arg_type(&entries)
== DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter value, entry;
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_ARRAY)
goto fail;
dbus_message_iter_recurse(&entry, &value);
if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_BYTE)
goto fail;
dbus_message_iter_get_fixed_array(&value, &service_data, &len);
DBG("Adding ServiceData for %s", uuid_str);
if (!bt_ad_add_service_data(data, &uuid, service_data, len))
goto fail;
dbus_message_iter_next(&entries);
}
return true;
fail:
bt_ad_clear_service_data(data);
return false;
}
static bool parse_advertising_include_tx_power(GDBusProxy *proxy,
bool *included)
{
DBusMessageIter iter;
dbus_bool_t b;
if (!g_dbus_proxy_get_property(proxy, "IncludeTxPower", &iter))
return true;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)
return false;
dbus_message_iter_get_basic(&iter, &b);
*included = b;
return true;
}
static void add_adverting_complete(struct advertisement *ad, uint8_t status)
{
DBusMessage *reply;
if (status) {
error("Failed to add advertisement: %s (0x%02x)",
mgmt_errstr(status), status);
reply = btd_error_failed(ad->reg,
"Failed to register advertisement");
queue_remove(ad->manager->ads, ad);
g_idle_add(advertisement_free_idle_cb, ad);
} else
reply = dbus_message_new_method_return(ad->reg);
g_dbus_send_message(btd_get_dbus_connection(), reply);
dbus_message_unref(ad->reg);
ad->reg = NULL;
}
static void add_advertising_callback(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
struct advertisement *ad = user_data;
const struct mgmt_rp_add_advertising *rp = param;
if (status)
goto done;
if (!param || length < sizeof(*rp)) {
status = MGMT_STATUS_FAILED;
goto done;
}
ad->instance = rp->instance;
g_dbus_client_set_disconnect_watch(ad->client, client_disconnect_cb,
ad);
DBG("Advertisement registered: %s", ad->path);
done:
add_adverting_complete(ad, status);
}
static size_t calc_max_adv_len(struct advertisement *ad, uint32_t flags)
{
size_t max = ad->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 DBusMessage *refresh_advertisement(struct advertisement *ad)
{
struct mgmt_cp_add_advertising *cp;
uint8_t param_len;
uint8_t *adv_data;
size_t adv_data_len;
uint32_t flags = 0;
DBG("Refreshing advertisement: %s", ad->path);
if (ad->type == AD_TYPE_PERIPHERAL)
flags = MGMT_ADV_FLAG_CONNECTABLE | MGMT_ADV_FLAG_DISCOV;
if (ad->include_tx_power)
flags |= MGMT_ADV_FLAG_TX_POWER;
adv_data = bt_ad_generate(ad->data, &adv_data_len);
if (!adv_data || (adv_data_len > calc_max_adv_len(ad, flags))) {
error("Advertising data too long or couldn't be generated.");
return g_dbus_create_error(ad->reg, ERROR_INTERFACE
".InvalidLength",
"Advertising data too long.");
}
param_len = sizeof(struct mgmt_cp_add_advertising) + adv_data_len;
cp = malloc0(param_len);
if (!cp) {
error("Couldn't allocate for MGMT!");
free(adv_data);
return btd_error_failed(ad->reg, "Failed");
}
cp->flags = htobl(flags);
cp->instance = ad->instance;
cp->adv_data_len = adv_data_len;
memcpy(cp->data, adv_data, adv_data_len);
free(adv_data);
if (!mgmt_send(ad->manager->mgmt, MGMT_OP_ADD_ADVERTISING,
ad->manager->mgmt_index, param_len, cp,
add_advertising_callback, ad, NULL)) {
error("Failed to add Advertising Data");
free(cp);
return btd_error_failed(ad->reg, "Failed");
}
free(cp);
return NULL;
}
static DBusMessage *parse_advertisement(struct advertisement *ad)
{
if (!parse_advertising_type(ad->proxy, &ad->type)) {
error("Failed to read \"Type\" property of advertisement");
goto fail;
}
if (!parse_advertising_service_uuids(ad->proxy, ad->data)) {
error("Property \"ServiceUUIDs\" failed to parse");
goto fail;
}
if (!parse_advertising_solicit_uuids(ad->proxy, ad->data)) {
error("Property \"SolicitUUIDs\" failed to parse");
goto fail;
}
if (!parse_advertising_manufacturer_data(ad->proxy, ad->data)) {
error("Property \"ManufacturerData\" failed to parse");
goto fail;
}
if (!parse_advertising_service_data(ad->proxy, ad->data)) {
error("Property \"ServiceData\" failed to parse");
goto fail;
}
if (!parse_advertising_include_tx_power(ad->proxy,
&ad->include_tx_power)) {
error("Property \"IncludeTxPower\" failed to parse");
goto fail;
}
return refresh_advertisement(ad);
fail:
return btd_error_failed(ad->reg, "Failed to parse advertisement.");
}
static void advertisement_proxy_added(GDBusProxy *proxy, void *data)
{
struct advertisement *ad = data;
DBusMessage *reply;
reply = parse_advertisement(ad);
if (!reply)
return;
/* Failed to publish for some reason, remove. */
queue_remove(ad->manager->ads, ad);
g_idle_add(advertisement_free_idle_cb, ad);
g_dbus_send_message(btd_get_dbus_connection(), reply);
dbus_message_unref(ad->reg);
ad->reg = NULL;
}
static struct advertisement *advertisement_create(DBusConnection *conn,
DBusMessage *msg, const char *path)
{
struct advertisement *ad;
const char *sender = dbus_message_get_sender(msg);
if (!path || !g_str_has_prefix(path, "/"))
return NULL;
ad = new0(struct advertisement, 1);
ad->client = g_dbus_client_new_full(conn, sender, path, path);
if (!ad->client)
goto fail;
ad->owner = g_strdup(sender);
if (!ad->owner)
goto fail;
ad->path = g_strdup(path);
if (!ad->path)
goto fail;
DBG("Adding proxy for %s", path);
ad->proxy = g_dbus_proxy_new(ad->client, path, LE_ADVERTISEMENT_IFACE);
if (!ad->proxy)
goto fail;
g_dbus_client_set_proxy_handlers(ad->client, advertisement_proxy_added,
NULL, NULL, ad);
ad->reg = dbus_message_ref(msg);
ad->data = bt_ad_new();
if (!ad->data)
goto fail;
return ad;
fail:
advertisement_free(ad);
return NULL;
}
static DBusMessage *register_advertisement(DBusConnection *conn,
DBusMessage *msg,
void *user_data)
{
struct btd_advertising *manager = user_data;
DBusMessageIter args;
struct advertisement *ad;
struct dbus_obj_match match;
uint8_t instance;
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->ads, match_advertisement, &match))
return btd_error_already_exists(msg);
instance = util_get_uid(&manager->instance_bitmap, manager->max_ads);
if (!instance)
return btd_error_failed(msg, "Maximum advertisements reached");
dbus_message_iter_next(&args);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
return btd_error_invalid_args(msg);
ad = advertisement_create(conn, msg, match.path);
if (!ad)
return btd_error_failed(msg,
"Failed to register advertisement");
DBG("Registered advertisement at path %s", match.path);
ad->instance = instance;
ad->manager = manager;
queue_push_tail(manager->ads, ad);
return NULL;
}
static DBusMessage *unregister_advertisement(DBusConnection *conn,
DBusMessage *msg,
void *user_data)
{
struct btd_advertising *manager = user_data;
DBusMessageIter args;
struct advertisement *ad;
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);
ad = queue_find(manager->ads, match_advertisement, &match);
if (!ad)
return btd_error_does_not_exist(msg);
advertisement_remove(ad);
return dbus_message_new_method_return(msg);
}
static const GDBusMethodTable methods[] = {
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("RegisterAdvertisement",
GDBUS_ARGS({ "advertisement", "o" },
{ "options", "a{sv}" }),
NULL, register_advertisement) },
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("UnregisterAdvertisement",
GDBUS_ARGS({ "service", "o" }),
NULL,
unregister_advertisement) },
{ }
};
static void advertising_manager_destroy(void *user_data)
{
struct btd_advertising *manager = user_data;
queue_destroy(manager->ads, advertisement_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_advertising *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_ads = feat->max_instances;
if (manager->max_ads == 0)
return;
if (!g_dbus_register_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE,
methods, NULL, NULL, manager, NULL))
error("Failed to register " LE_ADVERTISING_MGR_IFACE);
}
static struct btd_advertising *
advertising_manager_create(struct btd_adapter *adapter)
{
struct btd_advertising *manager;
manager = new0(struct btd_advertising, 1);
manager->adapter = adapter;
manager->mgmt = mgmt_new_default();
if (!manager->mgmt) {
error("Failed to access management interface");
free(manager);
return NULL;
}
manager->mgmt_index = btd_adapter_get_index(adapter);
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");
advertising_manager_destroy(manager);
return NULL;
}
manager->ads = queue_new();
return manager;
}
struct btd_advertising *
btd_advertising_manager_new(struct btd_adapter *adapter)
{
struct btd_advertising *manager;
if (!adapter)
return NULL;
manager = advertising_manager_create(adapter);
if (!manager)
return NULL;
DBG("LE Advertising Manager created for adapter: %s",
adapter_get_path(adapter));
return manager;
}
void btd_advertising_manager_destroy(struct btd_advertising *manager)
{
if (!manager)
return;
g_dbus_unregister_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE);
advertising_manager_destroy(manager);
}