| /* |
| * |
| * 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); |
| } |