Introduces and starts implementation of a LE Advertisement API.
Things that are missing still (and added to the TODO):
* MGMT API for modifying the LE Advertisements
* Semantics for multiple Advertisements (currently any more than one will fail)
* TX Power property parsing / interface
v1 -> v2:
* Cleanup based on comments from Arman
* Add g_dbus_proxy_set_read_watch function to avoid ObjectManager dependency
* Consolidate script updates into single test script
v2 -> v3:
* Change "ManufacturerSpecificData" to "ManufacturerData"
* Add advertisement-api.txt to EXTRA_DIST
* Add Release() callback to org.bluez.LEAdvertisement1
* Remove IncludePower from the LEAdvertisement1 API
v3 -> v4:
* Rename "g_dbus_proxy_set_read_watch" to "g_dbus_proxy_set_ready_watch"
Michael Janssen (15):
doc: Add LE Advertisement D-Bus API documentation
core: advertising: add LEAdvertisingManager stubs
gdbus/client: add g_dbus_proxy_set_ready_watch
unit/test-dbus-client: test for g_dbus_set_ready_watch
advertising-manager: Implement RegisterAdvertisement
tools: Python script to test Advertisement API
build: add advertising-api.txt to docs distributed
advertising-manager: implement UnregisterAdvertisement
shared: add advertising-data
advertising-manager: use advertising_data
advertising-manager: Parse ServiceUUIDs
advertising-manager: Parse SolicitUUIDs
advertising-manager: Parse ManufacturerSpecificData
advertising-manager: Parse ServiceData
Update TODO for LE Advertising
Makefile.am | 5 +-
TODO | 21 +-
doc/advertising-api.txt | 101 +++++++
gdbus/client.c | 17 ++
gdbus/gdbus.h | 3 +
src/adapter.c | 19 ++
src/advertising-manager.c | 614 ++++++++++++++++++++++++++++++++++++++++++
src/advertising-manager.h | 25 ++
src/shared/advertising-data.c | 349 ++++++++++++++++++++++++
src/shared/advertising-data.h | 69 +++++
tools/advertisement-example | 170 ++++++++++++
unit/test-gdbus-client.c | 66 +++++
12 files changed, 1448 insertions(+), 11 deletions(-)
create mode 100644 doc/advertising-api.txt
create mode 100644 src/advertising-manager.c
create mode 100644 src/advertising-manager.h
create mode 100644 src/shared/advertising-data.c
create mode 100644 src/shared/advertising-data.h
create mode 100644 tools/advertisement-example
--
2.2.0.rc0.207.ga3a616c
Hi Michael,
On Mon, Mar 23, 2015 at 5:10 PM, Michael Janssen <[email protected]> wrote:
> Introduces and starts implementation of a LE Advertisement API.
>
> Things that are missing still (and added to the TODO):
> * MGMT API for modifying the LE Advertisements
> * Semantics for multiple Advertisements (currently any more than one will fail)
> * TX Power property parsing / interface
>
> v1 -> v2:
> * Cleanup based on comments from Arman
> * Add g_dbus_proxy_set_read_watch function to avoid ObjectManager dependency
> * Consolidate script updates into single test script
>
> v2 -> v3:
> * Change "ManufacturerSpecificData" to "ManufacturerData"
> * Add advertisement-api.txt to EXTRA_DIST
> * Add Release() callback to org.bluez.LEAdvertisement1
> * Remove IncludePower from the LEAdvertisement1 API
>
> v3 -> v4:
> * Rename "g_dbus_proxy_set_read_watch" to "g_dbus_proxy_set_ready_watch"
>
> Michael Janssen (15):
> doc: Add LE Advertisement D-Bus API documentation
> core: advertising: add LEAdvertisingManager stubs
> gdbus/client: add g_dbus_proxy_set_ready_watch
> unit/test-dbus-client: test for g_dbus_set_ready_watch
> advertising-manager: Implement RegisterAdvertisement
> tools: Python script to test Advertisement API
> build: add advertising-api.txt to docs distributed
> advertising-manager: implement UnregisterAdvertisement
> shared: add advertising-data
> advertising-manager: use advertising_data
> advertising-manager: Parse ServiceUUIDs
> advertising-manager: Parse SolicitUUIDs
> advertising-manager: Parse ManufacturerSpecificData
> advertising-manager: Parse ServiceData
> Update TODO for LE Advertising
>
> Makefile.am | 5 +-
> TODO | 21 +-
> doc/advertising-api.txt | 101 +++++++
> gdbus/client.c | 17 ++
> gdbus/gdbus.h | 3 +
> src/adapter.c | 19 ++
> src/advertising-manager.c | 614 ++++++++++++++++++++++++++++++++++++++++++
> src/advertising-manager.h | 25 ++
> src/shared/advertising-data.c | 349 ++++++++++++++++++++++++
> src/shared/advertising-data.h | 69 +++++
> tools/advertisement-example | 170 ++++++++++++
> unit/test-gdbus-client.c | 66 +++++
> 12 files changed, 1448 insertions(+), 11 deletions(-)
> create mode 100644 doc/advertising-api.txt
> create mode 100644 src/advertising-manager.c
> create mode 100644 src/advertising-manager.h
> create mode 100644 src/shared/advertising-data.c
> create mode 100644 src/shared/advertising-data.h
> create mode 100644 tools/advertisement-example
>
> --
> 2.2.0.rc0.207.ga3a616c
Patch 01/15 was already applied, since I applied my patch for of gdbus
you will probably need to rebase and once you do please change the
name of the file to just advertising.c and make the prefix
core/advertising: for the patches touching it.
--
Luiz Augusto von Dentz
Add doc/advertising-api.txt to the docs.
---
Makefile.am | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile.am b/Makefile.am
index 1ddf3f2..db2978e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -250,7 +250,7 @@ EXTRA_DIST += doc/mgmt-api.txt \
EXTRA_DIST += doc/alert-api.txt \
doc/proximity-api.txt doc/heartrate-api.txt \
doc/thermometer-api.txt doc/cyclingspeed-api.txt \
- doc/gatt-api.txt
+ doc/gatt-api.txt doc/advertising-api.txt
EXTRA_DIST += doc/obex-api.txt doc/obex-agent-api.txt
--
2.2.0.rc0.207.ga3a616c
---
TODO | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/TODO b/TODO
index 65e19ee..f9c1843 100644
--- a/TODO
+++ b/TODO
@@ -64,16 +64,17 @@ General
Low Energy
==========
-- Advertising management. Adapter interface needs to be changed to manage
- connection modes, adapter type and advertising policy. See Volume 3,
- Part C, section 9.3. If Attribute Server is enabled the LE capable
- adapter shall to start advertising. Further investigation is necessary
- to define which connectable mode needs to be supported: Non-connectable,
- directed connectable and undirected connectable. Basically, two connectable
- scenarios shall be addressed:
- 1. GATT client is disconnected, but intends to become a Peripheral to
- receive indications/notifications.
- 2. GATT server intends to accept connections.
+- Connection modes. Adapter interface needs to be changed to manage
+ connection modes and adapter type. See Volume 3, Part C, section 9.3.
+ 1. Mode management: Peripheral / Central
+
+ Priority: Medium
+ Complexity: C2
+
+- Advertising data. A MGMT interface is needed for setting LE Advertisement Data
+ and Scan Response data. An adapter should be advertising whenever it is
+ appropriate to advertise, and it should be possible to determine what advertising
+ is occurring.
Priority: Medium
Complexity: C2
--
2.2.0.rc0.207.ga3a616c
Implement the UnregisterAdvertisement method of the
LEAdvertisingManager1 interface.
---
src/advertising-manager.c | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
index 958177c..3737995 100644
--- a/src/advertising-manager.c
+++ b/src/advertising-manager.c
@@ -125,6 +125,8 @@ static void advertisement_remove(void *data)
g_dbus_client_set_disconnect_watch(ad->client, NULL, NULL);
+ /* TODO: mgmt API call to remove advert */
+
queue_remove(ad->manager->adverts, ad);
g_idle_add(advertisement_free_idle_cb, ad);
@@ -303,10 +305,28 @@ static DBusMessage *unregister_advertisement(DBusConnection *conn,
DBusMessage *msg,
void *user_data)
{
+ struct btd_advertising_manager *manager = user_data;
+ DBusMessageIter args;
+ const char *path;
+ struct advertisement *ad;
+
DBG("UnregisterAdvertisement");
- /* TODO */
- return NULL;
+ 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, &path);
+
+ ad = queue_find(manager->adverts, match_advertisement_path, path);
+ if (!ad)
+ return btd_error_does_not_exist(msg);
+
+ advertisement_remove(ad);
+
+ return dbus_message_new_method_return(msg);
}
static const GDBusMethodTable methods[] = {
--
2.2.0.rc0.207.ga3a616c
Parse the ServiceData property of LEAdvertisement1
---
src/advertising-manager.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
index 7621631..f4ae5ae 100644
--- a/src/advertising-manager.c
+++ b/src/advertising-manager.c
@@ -295,6 +295,61 @@ fail:
return false;
}
+static bool parse_advertising_service_data(GDBusProxy *proxy,
+ struct advertising_data *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);
+
+ advertising_data_clear_manufacturer_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);
+
+ advertising_data_add_service_data(data, &uuid,
+ service_data, len);
+
+ dbus_message_iter_next(&entries);
+ }
+
+ return true;
+
+fail:
+ advertising_data_clear_manufacturer_data(data);
+ return false;
+}
+
static void refresh_advertisement(struct advertisement *ad)
{
DBG("Refreshing advertisement: %s", ad->path);
@@ -325,6 +380,11 @@ static bool parse_advertisement(struct advertisement *ad)
return false;
}
+ if (!parse_advertising_service_data(ad->proxy, ad->data)) {
+ error("Property \"ServiceData\" failed to parse correctly");
+ return false;
+ }
+
/* TODO: parse the rest of the properties */
refresh_advertisement(ad);
--
2.2.0.rc0.207.ga3a616c
Adds a test for the functionality of g_dbus_set_ready_watch.
---
unit/test-gdbus-client.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/unit/test-gdbus-client.c b/unit/test-gdbus-client.c
index 11ad1f7..e2ab9cf 100644
--- a/unit/test-gdbus-client.c
+++ b/unit/test-gdbus-client.c
@@ -908,6 +908,70 @@ static void client_proxy_removed(void)
destroy_context(context);
}
+static void proxy_ready(GDBusProxy *proxy, void *user_data)
+{
+ struct context *context = user_data;
+ DBusMessageIter iter;
+ const char *string;
+
+ if (g_test_verbose())
+ g_print("proxy for %s ready\n",
+ g_dbus_proxy_get_interface(proxy));
+
+ g_assert(context->proxy == proxy);
+
+ g_assert(g_dbus_proxy_get_property(proxy, "String", &iter));
+
+ dbus_message_iter_get_basic(&iter, &string);
+ g_assert(g_strcmp0(string, "value1") == 0);
+
+ g_main_loop_quit(context->main_loop);
+}
+
+static void client_proxy_ready(void)
+{
+ struct context *context = create_context();
+ DBusConnection *conn;
+ DBusMessageIter iter;
+ static const GDBusPropertyTable string_properties[] = {
+ { "String", "s", get_string, set_string, string_exists },
+ { },
+ };
+
+ if (context == NULL)
+ return;
+
+ conn = g_dbus_setup_private(DBUS_BUS_SESSION, SERVICE_NAME1, NULL);
+ g_assert(conn != NULL);
+
+ context->data = g_strdup("value1");
+
+ g_dbus_register_interface(conn,
+ SERVICE_PATH, SERVICE_NAME1,
+ methods, signals, string_properties,
+ context, NULL);
+
+ context->dbus_client = g_dbus_client_new(context->dbus_conn,
+ SERVICE_NAME1, SERVICE_PATH);
+
+ context->proxy = g_dbus_proxy_new(context->dbus_client, SERVICE_PATH,
+ SERVICE_NAME1);
+
+ g_dbus_proxy_set_ready_watch(context->proxy, proxy_ready, context);
+
+ g_assert(!g_dbus_proxy_get_property(context->proxy, "String", &iter));
+
+ g_main_loop_run(context->main_loop);
+
+ g_dbus_unregister_interface(conn, SERVICE_PATH, SERVICE_NAME1);
+
+ dbus_connection_flush(conn);
+ dbus_connection_close(conn);
+ dbus_connection_unref(conn);
+
+ destroy_context(context);
+}
+
static void proxy_force_disconnect(GDBusProxy *proxy, void *user_data)
{
struct context *context = user_data;
@@ -1051,6 +1115,8 @@ int main(int argc, char *argv[])
g_test_add_func("/gdbus/client_proxy_removed", client_proxy_removed);
+ g_test_add_func("/gdbus/client_proxy_ready", client_proxy_ready);
+
g_test_add_func("/gdbus/client_force_disconnect",
client_force_disconnect);
--
2.2.0.rc0.207.ga3a616c
Parse the ServiceUUIDs property of the LEAdvertisement1 object.
---
src/advertising-manager.c | 45 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 44 insertions(+), 1 deletion(-)
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
index ad89c51..bf447cd 100644
--- a/src/advertising-manager.c
+++ b/src/advertising-manager.c
@@ -168,6 +168,44 @@ static bool parse_advertising_type(GDBusProxy *proxy, uint8_t *type)
return false;
}
+static bool parse_advertising_service_uuids(GDBusProxy *proxy,
+ struct advertising_data *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);
+
+ advertising_data_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;
+
+ advertising_data_add_service_uuid(data, &uuid);
+
+ dbus_message_iter_next(&ariter);
+ }
+
+ return true;
+
+fail:
+ advertising_data_clear_service_uuid(data);
+ return false;
+}
+
static void refresh_advertisement(struct advertisement *ad)
{
DBG("Refreshing advertisement: %s", ad->path);
@@ -183,7 +221,12 @@ static bool parse_advertisement(struct advertisement *ad)
return false;
}
- /* TODO: parse the remaining properties into a shared structure */
+ if (!parse_advertising_service_uuids(ad->proxy, ad->data)) {
+ error("Property \"ServiceUUIDs\" failed to parse correctly");
+ return false;
+ }
+
+ /* TODO: parse the rest of the properties */
refresh_advertisement(ad);
--
2.2.0.rc0.207.ga3a616c
Parse the ManufacturerSpecificData property of the LEAdvertisement1
---
src/advertising-manager.c | 60 +++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 58 insertions(+), 2 deletions(-)
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
index 08cc4e4..7621631 100644
--- a/src/advertising-manager.c
+++ b/src/advertising-manager.c
@@ -244,6 +244,57 @@ fail:
return false;
}
+static bool parse_advertising_manufacturer_data(GDBusProxy *proxy,
+ struct advertising_data *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);
+
+ advertising_data_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);
+
+ advertising_data_add_manufacturer_data(data, manuf_id,
+ manuf_data, len);
+
+ dbus_message_iter_next(&entries);
+ }
+
+ return true;
+
+fail:
+ advertising_data_clear_manufacturer_data(data);
+ return false;
+}
+
static void refresh_advertisement(struct advertisement *ad)
{
DBG("Refreshing advertisement: %s", ad->path);
@@ -260,12 +311,17 @@ static bool parse_advertisement(struct advertisement *ad)
}
if (!parse_advertising_service_uuids(ad->proxy, ad->data)) {
- error("Property \"ServiceUUIDs\" failed to parse correctly");
+ error("Property \"ServiceUUIDs\" failed to parse");
return false;
}
if (!parse_advertising_solicit_uuids(ad->proxy, ad->data)) {
- error("Property \"SolicitUUIDs\" failed to parse correctly");
+ error("Property \"SolicitUUIDs\" failed to parse");
+ return false;
+ }
+
+ if (!parse_advertising_manufacturer_data(ad->proxy, ad->data)) {
+ error("Property \"ManufacturerData\" failed to parse");
return false;
}
--
2.2.0.rc0.207.ga3a616c
The advetising_data structure provides an abstraction for easy
translation of defined Advertisement Data fields into the resulting
raw bytes needed by the Bluetooth HCI LE Set Advertising Data command.
---
Makefile.am | 2 +
src/shared/advertising-data.c | 349 ++++++++++++++++++++++++++++++++++++++++++
src/shared/advertising-data.h | 69 +++++++++
3 files changed, 420 insertions(+)
create mode 100644 src/shared/advertising-data.c
create mode 100644 src/shared/advertising-data.h
diff --git a/Makefile.am b/Makefile.am
index db2978e..7334b1a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -111,6 +111,8 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
src/shared/uhid.h src/shared/uhid.c \
src/shared/pcap.h src/shared/pcap.c \
src/shared/btsnoop.h src/shared/btsnoop.c \
+ src/shared/advertising-data.h \
+ src/shared/advertising-data.c \
src/shared/att-types.h \
src/shared/att.h src/shared/att.c \
src/shared/gatt-helpers.h src/shared/gatt-helpers.c \
diff --git a/src/shared/advertising-data.c b/src/shared/advertising-data.c
new file mode 100644
index 0000000..800856c
--- /dev/null
+++ b/src/shared/advertising-data.c
@@ -0,0 +1,349 @@
+/*
+ *
+ * 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 "src/shared/advertising-data.h"
+
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+
+struct advertising_data {
+ int ref_count;
+ struct queue *service_uuids;
+ struct queue *manufacturer_data;
+ struct queue *solicit_uuids;
+ struct queue *service_data;
+};
+
+struct uuid_tagged_data {
+ bt_uuid_t uuid;
+ uint8_t *data;
+ size_t len;
+};
+
+struct manufacturer_tagged_data {
+ uint16_t manufacturer_id;
+ uint8_t *data;
+ size_t len;
+};
+
+struct advertising_data *advertising_data_new(void)
+{
+ struct advertising_data *ad;
+
+ ad = new0(struct advertising_data, 1);
+ if (!ad)
+ return NULL;
+
+ ad->service_uuids = queue_new();
+ if (!ad->service_uuids)
+ goto fail;
+
+ ad->manufacturer_data = queue_new();
+ if (!ad->manufacturer_data)
+ goto fail;
+
+ ad->solicit_uuids = queue_new();
+ if (!ad->solicit_uuids)
+ goto fail;
+
+ ad->service_data = queue_new();
+ if (!ad->service_data)
+ goto fail;
+
+ return advertising_data_ref(ad);
+
+fail:
+ queue_destroy(ad->service_uuids, NULL);
+ queue_destroy(ad->manufacturer_data, NULL);
+ queue_destroy(ad->solicit_uuids, NULL);
+ queue_destroy(ad->service_data, NULL);
+
+ free(ad);
+
+ return NULL;
+}
+
+struct advertising_data *advertising_data_ref(struct advertising_data *ad)
+{
+ ad->ref_count++;
+ return ad;
+}
+
+static void uuid_tagged_destroy(void *data)
+{
+ struct uuid_tagged_data *tagged = data;
+
+ free(tagged->data);
+ free(tagged);
+}
+
+static bool uuid_tagged_match(const void *data, const void *elem)
+{
+ const struct uuid_tagged_data *tagged = elem;
+ const bt_uuid_t *uuid = data;
+
+ return !bt_uuid_cmp(&tagged->uuid, uuid);
+}
+
+static void manuf_tagged_destroy(void *data)
+{
+ struct manufacturer_tagged_data *tagged = data;
+
+ free(tagged->data);
+ free(tagged);
+}
+
+static bool manuf_tagged_match(const void *data, const void *elem)
+{
+ const struct manufacturer_tagged_data *tagged = elem;
+ uint16_t manuf_id = PTR_TO_UINT(elem);
+
+ return tagged->manufacturer_id == manuf_id;
+}
+
+void advertising_data_unref(struct advertising_data *ad)
+{
+ if (!ad)
+ return;
+
+ if (__sync_sub_and_fetch(&ad->ref_count, 1))
+ return;
+
+ queue_destroy(ad->service_uuids, free);
+
+ queue_destroy(ad->manufacturer_data, manuf_tagged_destroy);
+
+ queue_destroy(ad->solicit_uuids, free);
+
+ queue_destroy(ad->service_data, uuid_tagged_destroy);
+
+ free(ad);
+}
+
+uint8_t *advertising_data_generate(struct advertising_data *ad, uint8_t *length)
+{
+ /* TODO: implement */
+ return NULL;
+}
+
+static bool queue_add_uuid(struct queue *queue, const bt_uuid_t *uuid)
+{
+ bt_uuid_t *new_uuid;
+
+ if (!queue)
+ return false;
+
+ new_uuid = new0(bt_uuid_t, 1);
+ if (!new_uuid)
+ return false;
+
+ bt_uuid_to_uuid128(uuid, new_uuid);
+
+ queue_push_tail(queue, new_uuid);
+
+ return true;
+}
+
+static bool uuid_match(const void *data, const void *elem)
+{
+ const bt_uuid_t *match_uuid = data;
+ const bt_uuid_t *uuid = elem;
+
+ return bt_uuid_cmp(match_uuid, uuid);
+}
+
+static bool queue_remove_uuid(struct queue *queue, bt_uuid_t *uuid)
+{
+ bt_uuid_t *removed;
+
+ if (!queue || !uuid)
+ return false;
+
+ removed = queue_remove_if(queue, uuid_match, uuid);
+
+ if (removed) {
+ free(removed);
+ return true;
+ }
+
+ return false;
+}
+
+bool advertising_data_add_service_uuid(struct advertising_data *ad,
+ const bt_uuid_t *uuid)
+{
+ if (!ad)
+ return false;
+
+ return queue_add_uuid(ad->service_uuids, uuid);
+}
+
+bool advertising_data_remove_service_uuid(struct advertising_data *ad,
+ bt_uuid_t *uuid)
+{
+ if (!ad)
+ return false;
+
+ return queue_remove_uuid(ad->service_uuids, uuid);
+}
+
+void advertising_data_clear_service_uuid(struct advertising_data *ad)
+{
+ queue_destroy(ad->service_uuids, free);
+
+ ad->service_uuids = queue_new();
+}
+
+bool advertising_data_add_manufacturer_data(struct advertising_data *ad,
+ uint16_t manufacturer_id,
+ void *data, size_t len)
+{
+ struct manufacturer_tagged_data *new_data;
+
+ if (!ad)
+ return false;
+
+ new_data = new0(struct manufacturer_tagged_data, 1);
+ if (!new_data)
+ return false;
+
+ new_data->manufacturer_id = manufacturer_id;
+
+ new_data->data = malloc(len);
+ if (!new_data->data) {
+ free(new_data);
+ return false;
+ }
+
+ memcpy(new_data->data, data, len);
+
+ if (queue_push_tail(ad->manufacturer_data, new_data))
+ return true;
+
+ manuf_tagged_destroy(new_data);
+
+ return false;
+}
+
+bool advertising_data_remove_manufacturer_data(struct advertising_data *ad,
+ uint16_t manufacturer_id)
+{
+ struct manufacturer_tagged_data *data;
+
+ if (!ad)
+ return false;
+
+ data = queue_remove_if(ad->manufacturer_data, manuf_tagged_match,
+ UINT_TO_PTR(manufacturer_id));
+
+ if (!data)
+ return false;
+
+ manuf_tagged_destroy(data);
+
+ return true;
+}
+
+void advertising_data_clear_manufacturer_data(struct advertising_data *ad)
+{
+ queue_destroy(ad->manufacturer_data, manuf_tagged_destroy);
+
+ ad->manufacturer_data = queue_new();
+}
+
+bool advertising_data_add_solicit_uuid(struct advertising_data *ad,
+ const bt_uuid_t *uuid)
+{
+ if (!ad)
+ return false;
+
+ return queue_add_uuid(ad->solicit_uuids, uuid);
+}
+
+bool advertising_data_remove_solicit_uuid(struct advertising_data *ad,
+ bt_uuid_t *uuid)
+{
+ if (!ad)
+ return false;
+
+ return queue_remove_uuid(ad->solicit_uuids, uuid);
+}
+
+void advertising_data_clear_solicit_uuid(struct advertising_data *ad)
+{
+ queue_destroy(ad->solicit_uuids, free);
+
+ ad->solicit_uuids = queue_new();
+}
+
+bool advertising_data_add_service_data(struct advertising_data *ad,
+ const bt_uuid_t *uuid,
+ void *data, size_t len)
+{
+ struct uuid_tagged_data *new_data;
+
+ if (!ad)
+ return false;
+
+ new_data = new0(struct uuid_tagged_data, 1);
+ if (!new_data)
+ return false;
+
+ bt_uuid_to_uuid128(uuid, &new_data->uuid);
+
+ new_data->data = malloc(len);
+ if (!new_data) {
+ free(new_data);
+ return false;
+ }
+
+ memcpy(new_data->data, data, len);
+
+ if (queue_push_tail(ad->service_data, new_data))
+ return true;
+
+ uuid_tagged_destroy(new_data);
+
+ return false;
+}
+
+bool advertising_data_remove_service_data(struct advertising_data *ad,
+ bt_uuid_t *uuid)
+{
+ struct uuid_tagged_data *data;
+
+ if (!ad)
+ return false;
+
+ data = queue_remove_if(ad->service_data, uuid_tagged_match, uuid);
+
+ if (!data)
+ return false;
+
+ uuid_tagged_destroy(data);
+
+ return true;
+}
+
+void advertising_data_clear_service_data(struct advertising_data *ad)
+{
+ queue_destroy(ad->service_data, uuid_tagged_destroy);
+
+ ad->service_data = queue_new();
+}
diff --git a/src/shared/advertising-data.h b/src/shared/advertising-data.h
new file mode 100644
index 0000000..05d2b51
--- /dev/null
+++ b/src/shared/advertising-data.h
@@ -0,0 +1,69 @@
+/*
+ *
+ * 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 <inttypes.h>
+#include <stdbool.h>
+
+#include "lib/bluetooth.h"
+#include "lib/uuid.h"
+
+struct advertising_data;
+
+struct advertising_data *advertising_data_new(void);
+
+struct advertising_data *advertising_data_ref(struct advertising_data *ad);
+
+void advertising_data_unref(struct advertising_data *ad);
+
+uint8_t *advertising_data_generate(struct advertising_data *ad,
+ uint8_t *length);
+
+bool advertising_data_add_service_uuid(struct advertising_data *ad,
+ const bt_uuid_t *uuid);
+
+bool advertising_data_remove_service_uuid(struct advertising_data *ad,
+ bt_uuid_t *uuid);
+
+void advertising_data_clear_service_uuid(struct advertising_data *ad);
+
+bool advertising_data_add_manufacturer_data(struct advertising_data *ad,
+ uint16_t manufacturer_data,
+ void *data, size_t len);
+
+bool advertising_data_remove_manufacturer_data(struct advertising_data *ad,
+ uint16_t manufacturer_id);
+
+void advertising_data_clear_manufacturer_data(struct advertising_data *ad);
+
+bool advertising_data_add_solicit_uuid(struct advertising_data *ad,
+ const bt_uuid_t *uuid);
+
+bool advertising_data_remove_solicit_uuid(struct advertising_data *ad,
+ bt_uuid_t *uuid);
+
+void advertising_data_clear_solicit_uuid(struct advertising_data *ad);
+
+bool advertising_data_add_service_data(struct advertising_data *ad,
+ const bt_uuid_t *uuid,
+ void *data, size_t len);
+
+bool advertising_data_remove_service_data(struct advertising_data *ad,
+ bt_uuid_t *uuid);
+
+void advertising_data_clear_service_data(struct advertising_data *ad);
--
2.2.0.rc0.207.ga3a616c
This script provides an example of using the advertisement-data
D-Bus API from python.
---
tools/advertisement-example | 170 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 170 insertions(+)
create mode 100644 tools/advertisement-example
diff --git a/tools/advertisement-example b/tools/advertisement-example
new file mode 100644
index 0000000..98aeafa
--- /dev/null
+++ b/tools/advertisement-example
@@ -0,0 +1,170 @@
+#!/usr/bin/python
+
+import dbus
+import dbus.exceptions
+import dbus.mainloop.glib
+import dbus.service
+
+import array
+import gobject
+
+from random import randint
+
+mainloop = None
+
+BLUEZ_SERVICE_NAME = 'org.bluez'
+LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
+DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
+DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
+
+LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1'
+
+
+class InvalidArgsException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'
+
+
+class NotSupportedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.NotSupported'
+
+
+class NotPermittedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.NotPermitted'
+
+
+class InvalidValueLengthException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.InvalidValueLength'
+
+
+class FailedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.Failed'
+
+
+class Advertisement(dbus.service.Object):
+ PATH_BASE = '/org/bluez/example/advertisement'
+
+ def __init__(self, bus, index, advertising_type):
+ self.path = self.PATH_BASE + str(index)
+ self.bus = bus
+ self.ad_type = advertising_type
+ self.service_uuids = None
+ self.manufacturer_data = None
+ self.solicit_uuids = None
+ self.service_data = None
+ dbus.service.Object.__init__(self, bus, self.path)
+
+ def get_properties(self):
+ properties = dict()
+ properties['Type'] = self.ad_type
+ if self.service_uuids is not None:
+ properties['ServiceUUIDs'] = dbus.Array(self.service_uuids,
+ signature='s')
+ if self.solicit_uuids is not None:
+ properties['SolicitUUIDs'] = dbus.Array(self.solicit_uuids,
+ signature='s')
+ if self.manufacturer_data is not None:
+ properties['ManufacturerData'] = dbus.Dictionary(
+ self.manufacturer_data, signature='qay')
+ if self.service_data is not None:
+ properties['ServiceData'] = dbus.Dictionary(self.service_data,
+ signature='say')
+ return {LE_ADVERTISEMENT_IFACE: properties}
+
+ def get_path(self):
+ return dbus.ObjectPath(self.path)
+
+ def add_service_uuid(self, uuid):
+ if not self.service_uuids:
+ self.service_uuids = []
+ self.service_uuids.append(uuid)
+
+ def add_solicit_uuid(self, uuid):
+ if not self.solicit_uuids:
+ self.solicit_uuids = []
+ self.solicit_uuids.append(uuid)
+
+ def add_manufacturer_data(self, manuf_code, data):
+ if not self.manufacturer_data:
+ self.manufacturer_data = dict()
+ self.manufacturer_data[manuf_code] = data
+
+ def add_service_data(self, uuid, data):
+ if not self.service_data:
+ self.service_data = dict()
+ self.service_data[uuid] = data
+
+ @dbus.service.method(DBUS_PROP_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, interface):
+ print 'GetAll'
+ if interface != LE_ADVERTISEMENT_IFACE:
+ raise InvalidArgsException()
+ print 'returning props'
+ return self.get_properties()[LE_ADVERTISEMENT_IFACE]
+
+ @dbus.service.method(LE_ADVERTISEMENT_IFACE,
+ in_signature='',
+ out_signature='')
+ def Release(self):
+ print '%s: Released!' % self.path
+
+class TestAdvertisement(Advertisement):
+
+ def __init__(self, bus, index):
+ Advertisement.__init__(self, bus, index, 'broadcast')
+ self.add_service_uuid('0000180D-0000-1000-8000-00805F9B34FB')
+ self.add_service_uuid('0000180F-0000-1000-8000-00805F9B34FB')
+ self.add_manufacturer_data(0xffff, [0x00, 0x01, 0x02, 0x03, 0x04])
+ self.add_service_data('00009999-0000-1000-8000-00805F9B34FB',
+ [0x00, 0x01, 0x02, 0x03, 0x04])
+
+
+def register_ad_cb():
+ print 'Advertisement registered'
+
+
+def register_ad_error_cb(error):
+ print 'Failed to register advertisement: ' + str(error)
+ mainloop.quit()
+
+
+def find_adapter(bus):
+ remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
+ DBUS_OM_IFACE)
+ objects = remote_om.GetManagedObjects()
+
+ for o, props in objects.iteritems():
+ if LE_ADVERTISING_MANAGER_IFACE in props:
+ return o
+
+ return None
+
+
+def main():
+ global mainloop
+
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SystemBus()
+
+ adapter = find_adapter(bus)
+ if not adapter:
+ print 'LEAdvertisingManager1 interface not found'
+ return
+
+ ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
+ LE_ADVERTISING_MANAGER_IFACE)
+
+ test_advertisement = TestAdvertisement(bus, 0)
+
+ mainloop = gobject.MainLoop()
+
+ ad_manager.RegisterAdvertisement(test_advertisement.get_path(), {},
+ reply_handler=register_ad_cb,
+ error_handler=register_ad_error_cb)
+
+ mainloop.run()
+
+if __name__ == '__main__':
+ main()
--
2.2.0.rc0.207.ga3a616c
Parse the SolicitUUIDs property of the LEAdvertisement1 object.
---
src/advertising-manager.c | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
index bf447cd..08cc4e4 100644
--- a/src/advertising-manager.c
+++ b/src/advertising-manager.c
@@ -206,6 +206,44 @@ fail:
return false;
}
+static bool parse_advertising_solicit_uuids(GDBusProxy *proxy,
+ struct advertising_data *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);
+
+ advertising_data_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;
+
+ advertising_data_add_solicit_uuid(data, &uuid);
+
+ dbus_message_iter_next(&ariter);
+ }
+
+ return true;
+
+fail:
+ advertising_data_clear_solicit_uuid(data);
+ return false;
+}
+
static void refresh_advertisement(struct advertisement *ad)
{
DBG("Refreshing advertisement: %s", ad->path);
@@ -226,6 +264,11 @@ static bool parse_advertisement(struct advertisement *ad)
return false;
}
+ if (!parse_advertising_solicit_uuids(ad->proxy, ad->data)) {
+ error("Property \"SolicitUUIDs\" failed to parse correctly");
+ return false;
+ }
+
/* TODO: parse the rest of the properties */
refresh_advertisement(ad);
--
2.2.0.rc0.207.ga3a616c
Introducing src/advertising-manager which will implement the
org.bluez.LEAdvertisingManager1 D-Bus interface defined in
doc/advertising-api.txt. Each LE-capable controller gets
an instance of the interface.
---
Makefile.am | 1 +
src/adapter.c | 19 +++++++
src/advertising-manager.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++
src/advertising-manager.h | 25 +++++++++
4 files changed, 177 insertions(+)
create mode 100644 src/advertising-manager.c
create mode 100644 src/advertising-manager.h
diff --git a/Makefile.am b/Makefile.am
index 90bb86b..1ddf3f2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -175,6 +175,7 @@ src_bluetoothd_SOURCES = $(builtin_sources) \
src/uinput.h \
src/plugin.h src/plugin.c \
src/storage.h src/storage.c \
+ src/advertising-manager.h src/advertising-manager.c \
src/agent.h src/agent.c \
src/error.h src/error.c \
src/adapter.h src/adapter.c \
diff --git a/src/adapter.c b/src/adapter.c
index 6eeb2f9..dbce2c9 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -74,6 +74,7 @@
#include "attrib/gatt.h"
#include "attrib-server.h"
#include "gatt-database.h"
+#include "advertising-manager.h"
#include "eir.h"
#define ADAPTER_INTERFACE "org.bluez.Adapter1"
@@ -210,6 +211,7 @@ struct btd_adapter {
sdp_list_t *services; /* Services associated to adapter */
struct btd_gatt_database *database;
+ struct btd_advertising_manager *adv_manager;
gboolean initialized;
@@ -4607,6 +4609,9 @@ static void adapter_remove(struct btd_adapter *adapter)
btd_gatt_database_destroy(adapter->database);
adapter->database = NULL;
+ btd_advertising_manager_destroy(adapter->adv_manager);
+ adapter->adv_manager = NULL;
+
g_slist_free(adapter->pin_callbacks);
adapter->pin_callbacks = NULL;
@@ -6671,6 +6676,20 @@ static int adapter_register(struct btd_adapter *adapter)
return -EINVAL;
}
+ /* Don't start advertising managers on non-LE controllers. */
+ if (adapter->supported_settings & MGMT_SETTING_LE) {
+ adapter->adv_manager = btd_advertising_manager_new(adapter);
+ if (!adapter->adv_manager) {
+ error("Failed to register LEAdvertisingManager1 "
+ "interface for adapter");
+ btd_gatt_database_destroy(adapter->database);
+ adapter->database = NULL;
+ return -EINVAL;
+ }
+ } else {
+ info("Not starting LEAdvertisingManager, LE not supported");
+ }
+
db = btd_gatt_database_get_db(adapter->database);
adapter->db_id = gatt_db_register(db, services_modified,
services_modified,
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
new file mode 100644
index 0000000..c3f85c2
--- /dev/null
+++ b/src/advertising-manager.c
@@ -0,0 +1,132 @@
+/*
+ *
+ * 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-manager.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <dbus/dbus.h>
+#include <gdbus/gdbus.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+
+#include "adapter.h"
+#include "dbus-common.h"
+#include "log.h"
+#include "src/shared/util.h"
+
+#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1"
+#define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1"
+
+struct btd_advertising_manager {
+ struct btd_adapter *adapter;
+};
+
+static DBusMessage *register_advertisement(DBusConnection *conn,
+ DBusMessage *msg,
+ void *user_data)
+{
+ DBG("RegisterAdvertisement");
+
+ /* TODO */
+ return NULL;
+}
+
+static DBusMessage *unregister_advertisement(DBusConnection *conn,
+ DBusMessage *msg,
+ void *user_data)
+{
+ DBG("UnregisterAdvertisement");
+
+ /* TODO */
+ return NULL;
+}
+
+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 *manager = user_data;
+
+ free(manager);
+}
+
+static struct btd_advertising_manager *
+advertising_manager_create(struct btd_adapter *adapter)
+{
+ struct btd_advertising_manager *manager;
+
+ manager = new0(struct btd_advertising_manager, 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);
+ free(manager);
+ return NULL;
+ }
+
+ return manager;
+}
+
+struct btd_advertising_manager *
+btd_advertising_manager_new(struct btd_adapter *adapter)
+{
+ struct btd_advertising_manager *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 *manager)
+{
+ if (!manager)
+ return;
+
+ g_dbus_unregister_interface(btd_get_dbus_connection(),
+ adapter_get_path(manager->adapter),
+ LE_ADVERTISING_MGR_IFACE);
+}
diff --git a/src/advertising-manager.h b/src/advertising-manager.h
new file mode 100644
index 0000000..4046013
--- /dev/null
+++ b/src/advertising-manager.h
@@ -0,0 +1,25 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+struct btd_adapter;
+struct btd_advertising_manager;
+
+struct btd_advertising_manager *btd_advertising_manager_new(
+ struct btd_adapter *adapter);
+void btd_advertising_manager_destroy(struct btd_advertising_manager *manager);
--
2.2.0.rc0.207.ga3a616c
Start using the newly introduced struct advertising_data API.
---
src/advertising-manager.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
index 3737995..ad89c51 100644
--- a/src/advertising-manager.c
+++ b/src/advertising-manager.c
@@ -32,6 +32,7 @@
#include "dbus-common.h"
#include "error.h"
#include "log.h"
+#include "src/shared/advertising-data.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
@@ -55,6 +56,7 @@ struct advertisement {
DBusMessage *reg;
uint8_t type; /* Advertising type */
bool published;
+ struct advertising_data *data;
};
static bool match_advertisement_path(const void *a, const void *b)
@@ -74,8 +76,9 @@ static void advertisement_free(void *data)
g_dbus_client_unref(ad->client);
}
- if (ad->proxy)
- g_dbus_proxy_unref(ad->proxy);
+ advertising_data_unref(ad->data);
+
+ g_dbus_proxy_unref(ad->proxy);
if (ad->owner)
g_free(ad->owner);
@@ -250,6 +253,10 @@ static struct advertisement *advertisement_create(DBusConnection *conn,
ad->reg = dbus_message_ref(msg);
+ ad->data = advertising_data_new();
+ if (!ad->data)
+ goto fail;
+
return ad;
fail:
--
2.2.0.rc0.207.ga3a616c
The LE Advertisement API allows external appications to define
persistent LE Advertisement Data packets.
---
doc/advertising-api.txt | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100644 doc/advertising-api.txt
diff --git a/doc/advertising-api.txt b/doc/advertising-api.txt
new file mode 100644
index 0000000..7fb34ee
--- /dev/null
+++ b/doc/advertising-api.txt
@@ -0,0 +1,101 @@
+BlueZ D-Bus LE Advertising API Description
+******************************************
+
+Advertising packets are structured data which is broadcast on the LE Advertising
+channels and available for all devices in range. Because of the limited space
+available in LE Advertising packets (32 bytes), each packet's contents must be
+carefully controlled.
+
+BlueZ acts as a store for the Advertisement Data which is meant to be sent.
+It constructs the correct Advertisement Data from the structured
+data and configured the kernel to send the correct advertisement.
+
+Advertisement Data objects are registered freely and then referenced by BlueZ
+when constructing the data sent to the kernel.
+
+LE Advertisement Data hierarchy
+===============================
+
+Specifies the Advertisement Data to be broadcast and some advertising
+parameters. Properties which are not present will not be included in the
+data. Required advertisement data types will always be included.
+All UUIDs are 128-bit versions in the API, and 16 or 32-bit
+versions of the same UUID will be used in the advertising data as appropriate.
+
+Service org.bluez
+Interface org.bluez.LEAdvertisement1
+Object path freely definable
+
+Methods void Release() [noreply]
+
+ This method gets called when the service daemon
+ removes the Advertisement. A client can use it to do
+ cleanup tasks. There is no need to call
+ UnregisterAdvertisement because when this method gets
+ called it has already been unregistered.
+
+Properties string Type
+
+ Determines the type of advertising packet requested.
+
+ Possible values: "broadcast" or "peripheral"
+
+ array{string} ServiceUUIDs
+
+ List of UUIDs to include in the "Service UUID" field of
+ the Advertising Data.
+
+ dict ManufacturerData
+
+ Manufactuer Data fields to include in
+ the Advertising Data. Keys are the Manufacturer ID
+ to associate with the data.
+
+ array{string} SolicitUUIDs
+
+ Array of UUIDs to include in "Service Solicitation"
+ Advertisement Data.
+
+ dict ServiceData
+
+ Service Data elements to include. The keys are the
+ UUID to associate with the data.
+
+
+LE Advertising Manager hierarchy
+================================
+
+The Advertising Manager allows external applications to register Advertisement
+Data which should be broadcast to devices. Advertisement Data elements must
+follow the API for LE Advertisement Data described above.
+
+Service org.bluez
+Interface org.bluez.LEAdvertisingManager1 [Experimental]
+Object path /org/bluez/{hci0,hci1,...}
+
+Methods RegisterAdvertisement(object advertisement, dict options)
+
+ Registers an advertisement object to be sent over the LE
+ Advertising channel. The service must be exported
+ under interface LEAdvertisement1. InvalidArguments
+ indicates that the object has invalid or conflicting
+ properties. InvalidLength indicates that the data
+ provided generates a data packet which is too long.
+ The properties of this object are parser when it is
+ registered, and any changes are ignored.
+ Currently only one advertisement at a time is supported,
+ attempting to register two advertisements will result in
+ an AlreadyExists error.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.AlreadyExists
+ org.bluez.Error.InvalidLength
+
+ UnregisterAdvertisement(object advertisement)
+
+ This unregisters an advertisement that has been
+ prevously registered. The object path parameter must
+ match the same value that has been used on registration.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.DoesNotExist
--
2.2.0.rc0.207.ga3a616c
Initial implementation of the RegisterAdvertisement function of the
LEAdvertisingManager1 interface.
---
src/advertising-manager.c | 255 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 254 insertions(+), 1 deletion(-)
diff --git a/src/advertising-manager.c b/src/advertising-manager.c
index c3f85c2..958177c 100644
--- a/src/advertising-manager.c
+++ b/src/advertising-manager.c
@@ -30,7 +30,9 @@
#include "adapter.h"
#include "dbus-common.h"
+#include "error.h"
#include "log.h"
+#include "src/shared/queue.h"
#include "src/shared/util.h"
#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1"
@@ -38,15 +40,262 @@
struct btd_advertising_manager {
struct btd_adapter *adapter;
+ struct queue *adverts;
};
+#define AD_TYPE_BROADCAST 0
+#define AD_TYPE_PERIPHERAL 1
+
+struct advertisement {
+ struct btd_advertising_manager *manager;
+ char *owner;
+ char *path;
+ GDBusClient *client;
+ GDBusProxy *proxy;
+ DBusMessage *reg;
+ uint8_t type; /* Advertising type */
+ bool published;
+};
+
+static bool match_advertisement_path(const void *a, const void *b)
+{
+ const struct advertisement *ad = a;
+ const char *path = b;
+
+ return g_strcmp0(ad->path, path);
+}
+
+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);
+ }
+
+ if (ad->proxy)
+ 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 == NULL) {
+ 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;
+
+ g_dbus_client_set_disconnect_watch(ad->client, NULL, NULL);
+
+ queue_remove(ad->manager->adverts, ad);
+
+ 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 void refresh_advertisement(struct advertisement *ad)
+{
+ DBG("Refreshing advertisement: %s", ad->path);
+ if (!ad->published) {
+ /* TODO: MGMT API call to update the advertisement */
+ }
+}
+
+static bool parse_advertisement(struct advertisement *ad)
+{
+ if (!parse_advertising_type(ad->proxy, &ad->type)) {
+ error("Failed to read \"Type\" property of advertisement");
+ return false;
+ }
+
+ /* TODO: parse the remaining properties into a shared structure */
+
+ refresh_advertisement(ad);
+
+ return true;
+}
+
+static void advertisement_proxy_ready(GDBusProxy *proxy, void *data)
+{
+ struct advertisement *ad = data;
+ DBusMessage *reply;
+
+ if (!parse_advertisement(ad)) {
+ error("Failed to parse advertisement");
+
+ reply = btd_error_failed(ad->reg,
+ "Failed to register advertisement");
+ goto done;
+ }
+
+ 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:
+ 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);
+ if (!ad)
+ return NULL;
+
+ ad->published = false;
+
+ 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_proxy_set_ready_watch(ad->proxy, advertisement_proxy_ready, ad);
+
+ ad->reg = dbus_message_ref(msg);
+
+ return ad;
+
+fail:
+ advertisement_free(ad);
+ return NULL;
+}
+
static DBusMessage *register_advertisement(DBusConnection *conn,
DBusMessage *msg,
void *user_data)
{
+ struct btd_advertising_manager *manager = user_data;
+ DBusMessageIter args;
+ const char *path;
+ struct advertisement *ad;
+
DBG("RegisterAdvertisement");
- /* TODO */
+ 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, &path);
+
+ if (queue_find(manager->adverts, match_advertisement_path, path))
+ return btd_error_already_exists(msg);
+
+ /* TODO: support more than one advertisement */
+ if (!queue_isempty(manager->adverts))
+ return btd_error_failed(msg, "Already advertising");
+
+ 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);
+ if (!ad)
+ return btd_error_failed(msg,
+ "Failed to register advertisement");
+
+ DBG("Registered advertisement at path %s", path);
+
+ ad->manager = manager;
+ queue_push_tail(manager->adverts, ad);
+
return NULL;
}
@@ -76,6 +325,8 @@ static void advertising_manager_destroy(void *user_data)
{
struct btd_advertising_manager *manager = user_data;
+ queue_destroy(manager->adverts, advertisement_destroy);
+
free(manager);
}
@@ -100,6 +351,8 @@ advertising_manager_create(struct btd_adapter *adapter)
return NULL;
}
+ manager->adverts = queue_new();
+
return manager;
}
--
2.2.0.rc0.207.ga3a616c
Adds a new gdbus utility function which will set a function to be called
after the properties of a proxy are updated.
This enables using GDBusClient for objects without the ObjectManager
interface as long as you know in advance the object path using
g_dbus_proxy_new
---
gdbus/client.c | 17 +++++++++++++++++
gdbus/gdbus.h | 3 +++
2 files changed, 20 insertions(+)
diff --git a/gdbus/client.c b/gdbus/client.c
index fe0c0db..dcfa0f6 100644
--- a/gdbus/client.c
+++ b/gdbus/client.c
@@ -76,6 +76,8 @@ struct GDBusProxy {
void *prop_data;
GDBusProxyFunction removed_func;
void *removed_data;
+ GDBusProxyFunction read_func;
+ void *read_data;
};
struct prop_entry {
@@ -297,6 +299,9 @@ static void get_all_properties_reply(DBusPendingCall *call, void *user_data)
update_properties(proxy, &iter, FALSE);
+ if (proxy->read_func)
+ proxy->read_func(proxy, proxy->read_data);
+
done:
if (g_list_find(client->proxy_list, proxy) == NULL) {
if (client->proxy_added)
@@ -914,6 +919,18 @@ gboolean g_dbus_proxy_set_removed_watch(GDBusProxy *proxy,
return TRUE;
}
+gboolean g_dbus_proxy_set_ready_watch(GDBusProxy *proxy,
+ GDBusProxyFunction function, void *user_data)
+{
+ if (proxy == NULL)
+ return FALSE;
+
+ proxy->read_func = function;
+ proxy->read_data = user_data;
+
+ return TRUE;
+}
+
static void refresh_properties(GDBusClient *client)
{
GList *list;
diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h
index 9814838..277f592 100644
--- a/gdbus/gdbus.h
+++ b/gdbus/gdbus.h
@@ -353,6 +353,9 @@ gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy,
gboolean g_dbus_proxy_set_removed_watch(GDBusProxy *proxy,
GDBusProxyFunction destroy, void *user_data);
+gboolean g_dbus_proxy_set_ready_watch(GDBusProxy *proxy,
+ GDBusProxyFunction ready, void *user_data);
+
GDBusClient *g_dbus_client_new(DBusConnection *connection,
const char *service, const char *path);
GDBusClient *g_dbus_client_new_full(DBusConnection *connection,
--
2.2.0.rc0.207.ga3a616c