Upgrade bluez5_37 :Merge the code from private
[platform/upstream/bluez.git] / src / advertising.c
index 04492f7..59c8c3d 100644 (file)
 #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
@@ -54,14 +62,28 @@ struct advertisement {
        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)
@@ -73,8 +95,9 @@ 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);
@@ -120,13 +143,20 @@ static void advertisement_destroy(void *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);
 
-       /* 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);
 }
 
@@ -163,46 +193,388 @@ static bool parse_advertising_type(GDBusProxy *proxy, uint8_t *type)
        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);
@@ -219,9 +591,6 @@ static struct advertisement *advertisement_create(DBusConnection *conn,
                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;
@@ -244,6 +613,10 @@ static struct advertisement *advertisement_create(DBusConnection *conn,
 
        ad->reg = dbus_message_ref(msg);
 
+       ad->data = bt_ad_new();
+       if (!ad->data)
+               goto fail;
+
        return ad;
 
 fail:
@@ -257,8 +630,9 @@ static DBusMessage *register_advertisement(DBusConnection *conn,
 {
        struct btd_advertising *manager = user_data;
        DBusMessageIter args;
-       const char *path;
        struct advertisement *ad;
+       struct dbus_obj_match match;
+       uint8_t instance;
 
        DBG("RegisterAdvertisement");
 
@@ -268,28 +642,32 @@ static DBusMessage *register_advertisement(DBusConnection *conn,
        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;
@@ -301,8 +679,8 @@ static DBusMessage *unregister_advertisement(DBusConnection *conn,
 {
        struct btd_advertising *manager = user_data;
        DBusMessageIter args;
-       const char *path;
        struct advertisement *ad;
+       struct dbus_obj_match match;
 
        DBG("UnregisterAdvertisement");
 
@@ -312,9 +690,11 @@ static DBusMessage *unregister_advertisement(DBusConnection *conn,
        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);
 
@@ -341,30 +721,67 @@ static void advertising_manager_destroy(void *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);
-       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;
@@ -396,4 +813,6 @@ void btd_advertising_manager_destroy(struct btd_advertising *manager)
        g_dbus_unregister_interface(btd_get_dbus_connection(),
                                        adapter_get_path(manager->adapter),
                                        LE_ADVERTISING_MGR_IFACE);
+
+       advertising_manager_destroy(manager);
 }