#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"
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
GDBusProxy *proxy;
DBusMessage *reg;
uint8_t type; /* Advertising type */
+ bool include_tx_power;
+ struct bt_ad *data;
+ uint8_t instance;
};
-static bool match_advertisement_path(const void *a, const void *b)
+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 char *path = b;
+ 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 g_strcmp0(ad->path, path);
+ return true;
}
static void advertisement_free(void *data)
g_dbus_client_unref(ad->client);
}
- if (ad->proxy)
- g_dbus_proxy_unref(ad->proxy);
+ bt_ad_unref(ad->data);
+
+ g_dbus_proxy_unref(ad->proxy);
if (ad->owner)
g_free(ad->owner);
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);
- /* TODO: mgmt API call to remove advert */
+ 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);
}
return false;
}
-static void refresh_advertisement(struct advertisement *ad)
+static bool parse_advertising_service_uuids(GDBusProxy *proxy,
+ struct bt_ad *data)
{
- DBG("Refreshing advertisement: %s", ad->path);
+ 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_advertisement(struct advertisement *ad)
+static bool parse_advertising_solicit_uuids(GDBusProxy *proxy,
+ struct bt_ad *data)
{
- if (!parse_advertising_type(ad->proxy, &ad->type)) {
- error("Failed to read \"Type\" property of advertisement");
+ 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);
}
- /* TODO: parse the remaining properties into a shared structure */
+ return true;
- refresh_advertisement(ad);
+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 advertisement_proxy_added(GDBusProxy *proxy, void *data)
+static void add_adverting_complete(struct advertisement *ad, uint8_t status)
{
- struct advertisement *ad = data;
DBusMessage *reply;
- if (!parse_advertisement(ad)) {
- error("Failed to parse advertisement");
-
+ 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);
-
- reply = dbus_message_new_method_return(ad->reg);
-
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 = 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);
return NULL;
ad = new0(struct advertisement, 1);
- if (!ad)
- return NULL;
-
ad->client = g_dbus_client_new_full(conn, sender, path, path);
if (!ad->client)
goto fail;
ad->reg = dbus_message_ref(msg);
+ ad->data = bt_ad_new();
+ if (!ad->data)
+ goto fail;
+
return ad;
fail:
{
struct btd_advertising *manager = user_data;
DBusMessageIter args;
- const char *path;
struct advertisement *ad;
+ struct dbus_obj_match match;
+ uint8_t instance;
DBG("RegisterAdvertisement");
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
- dbus_message_iter_get_basic(&args, &path);
+ dbus_message_iter_get_basic(&args, &match.path);
+
+ match.owner = dbus_message_get_sender(msg);
- if (queue_find(manager->ads, match_advertisement_path, path))
+ if (queue_find(manager->ads, match_advertisement, &match))
return btd_error_already_exists(msg);
- /* TODO: support more than one advertisement */
- if (!queue_isempty(manager->ads))
- return btd_error_failed(msg, "Already advertising");
+ 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, path);
+ ad = advertisement_create(conn, msg, match.path);
if (!ad)
return btd_error_failed(msg,
"Failed to register advertisement");
- DBG("Registered advertisement at path %s", path);
+ DBG("Registered advertisement at path %s", match.path);
+ ad->instance = instance;
ad->manager = manager;
+
queue_push_tail(manager->ads, ad);
return NULL;
{
struct btd_advertising *manager = user_data;
DBusMessageIter args;
- const char *path;
struct advertisement *ad;
+ struct dbus_obj_match match;
DBG("UnregisterAdvertisement");
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
return btd_error_invalid_args(msg);
- dbus_message_iter_get_basic(&args, &path);
+ dbus_message_iter_get_basic(&args, &match.path);
+
+ match.owner = dbus_message_get_sender(msg);
- ad = queue_find(manager->ads, match_advertisement_path, path);
+ ad = queue_find(manager->ads, match_advertisement, &match);
if (!ad)
return btd_error_does_not_exist(msg);
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);
- if (!manager)
- return NULL;
-
manager->adapter = adapter;
- if (!g_dbus_register_interface(btd_get_dbus_connection(),
- adapter_get_path(adapter),
- LE_ADVERTISING_MGR_IFACE,
- methods, NULL, NULL, manager,
- advertising_manager_destroy)) {
- error("Failed to register " LE_ADVERTISING_MGR_IFACE);
+ 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;
g_dbus_unregister_interface(btd_get_dbus_connection(),
adapter_get_path(manager->adapter),
LE_ADVERTISING_MGR_IFACE);
+
+ advertising_manager_destroy(manager);
}