From ddc790358fc6b4cbe8d4a9c54907edb909a4ce2e Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Wed, 8 Apr 2020 21:45:06 +0200 Subject: [PATCH] dbus: Ensure GATT Notification/Indication are correctly disconnected --- dbus/CMakeLists.txt | 3 +- dbus/gattlib.c | 212 +--------------------------- dbus/gattlib_internal.h | 5 + dbus/gattlib_notification.c | 266 ++++++++++++++++++++++++++++++++++++ 4 files changed, 274 insertions(+), 212 deletions(-) create mode 100644 dbus/gattlib_notification.c diff --git a/dbus/CMakeLists.txt b/dbus/CMakeLists.txt index 2fc22b2..1ce8e19 100644 --- a/dbus/CMakeLists.txt +++ b/dbus/CMakeLists.txt @@ -1,7 +1,7 @@ # # GattLib - GATT Library # -# Copyright (C) 2017-2019 Olivier Martin +# Copyright (C) 2017-2020 Olivier Martin # # # This program is free software; you can redistribute it and/or modify @@ -84,6 +84,7 @@ set(gattlib_SRCS gattlib.c gattlib_adapter.c gattlib_advertisement.c gattlib_char.c + gattlib_notification.c gattlib_stream.c bluez5/lib/uuid.c ${CMAKE_SOURCE_DIR}/common/gattlib_common.c diff --git a/dbus/gattlib.c b/dbus/gattlib.c index aef9036..0ec9c5c 100644 --- a/dbus/gattlib.c +++ b/dbus/gattlib.c @@ -251,6 +251,7 @@ int gattlib_disconnect(gatt_connection_t* connection) { free(conn_context->device_object_path); g_object_unref(conn_context->device); g_list_free_full(conn_context->dbus_objects, g_object_unref); + disconnect_all_notifications(conn_context); free(connection->context); free(connection); @@ -788,217 +789,6 @@ int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t** return GATTLIB_NOT_SUPPORTED; } -#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40) -gboolean on_handle_battery_level_property_change( - OrgBluezBattery1 *object, - GVariant *arg_changed_properties, - const gchar *const *arg_invalidated_properties, - gpointer user_data) -{ - static guint8 percentage; - gatt_connection_t* connection = user_data; - - if (gattlib_has_valid_handler(&connection->notification)) { - // Retrieve 'Value' from 'arg_changed_properties' - if (g_variant_n_children (arg_changed_properties) > 0) { - GVariantIter *iter; - const gchar *key; - GVariant *value; - - g_variant_get (arg_changed_properties, "a{sv}", &iter); - while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { - if (strcmp(key, "Percentage") == 0) { - //TODO: by declaring 'percentage' as a 'static' would mean we could have issue in case of multiple - // GATT connection notifiying to Battery level - percentage = g_variant_get_byte(value); - - gattlib_call_notification_handler(&connection->notification, - &m_battery_level_uuid, - (const uint8_t*)&percentage, sizeof(percentage)); - break; - } - } - g_variant_iter_free(iter); - } - } - return TRUE; -} -#endif - -static gboolean on_handle_characteristic_property_change( - OrgBluezGattCharacteristic1 *object, - GVariant *arg_changed_properties, - const gchar *const *arg_invalidated_properties, - gpointer user_data) -{ - gatt_connection_t* connection = user_data; - - if (gattlib_has_valid_handler(&connection->notification)) { - // Retrieve 'Value' from 'arg_changed_properties' - if (g_variant_n_children (arg_changed_properties) > 0) { - GVariantIter *iter; - const gchar *key; - GVariant *value; - - g_variant_get (arg_changed_properties, "a{sv}", &iter); - while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { - if (strcmp(key, "Value") == 0) { - uuid_t uuid; - size_t data_length; - const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar)); - - gattlib_string_to_uuid( - org_bluez_gatt_characteristic1_get_uuid(object), - MAX_LEN_UUID_STR + 1, - &uuid); - - gattlib_call_notification_handler(&connection->notification, - &uuid, data, data_length); - break; - } - } - g_variant_iter_free(iter); - } - } - return TRUE; -} - -static gboolean on_handle_characteristic_indication( - OrgBluezGattCharacteristic1 *object, - GVariant *arg_changed_properties, - const gchar *const *arg_invalidated_properties, - gpointer user_data) -{ - gatt_connection_t* connection = user_data; - - if (gattlib_has_valid_handler(&connection->indication)) { - // Retrieve 'Value' from 'arg_changed_properties' - if (g_variant_n_children (arg_changed_properties) > 0) { - GVariantIter *iter; - const gchar *key; - GVariant *value; - - g_variant_get (arg_changed_properties, "a{sv}", &iter); - while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { - if (strcmp(key, "Value") == 0) { - uuid_t uuid; - size_t data_length; - const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar)); - - gattlib_string_to_uuid( - org_bluez_gatt_characteristic1_get_uuid(object), - MAX_LEN_UUID_STR + 1, - &uuid); - - gattlib_call_notification_handler(&connection->indication, - &uuid, data, data_length); - break; - } - } - g_variant_iter_free(iter); - } - } - return TRUE; -} - -static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection, const uuid_t* uuid, void *callback) { - struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid); - if (dbus_characteristic.type == TYPE_NONE) { - return GATTLIB_NOT_FOUND; - } -#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40) - else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) { - // Register a handle for notification - g_signal_connect(dbus_characteristic.battery, - "g-properties-changed", - G_CALLBACK (on_handle_battery_level_property_change), - connection); - - return GATTLIB_SUCCESS; - } else { - assert(dbus_characteristic.type == TYPE_GATT); - } -#endif - - // Register a handle for notification - g_signal_connect(dbus_characteristic.gatt, - "g-properties-changed", - G_CALLBACK(callback), - connection); - - GError *error = NULL; - org_bluez_gatt_characteristic1_call_start_notify_sync(dbus_characteristic.gatt, NULL, &error); - - if (error) { - fprintf(stderr, "Failed to start DBus GATT notification: %s\n", error->message); - g_error_free(error); - return GATTLIB_ERROR_DBUS; - } else { - return GATTLIB_SUCCESS; - } -} - -static int disconnect_signal_to_characteristic_uuid(gatt_connection_t* connection, const uuid_t* uuid, void *callback) { - size_t count; - - struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid); - if (dbus_characteristic.type == TYPE_NONE) { - return GATTLIB_NOT_FOUND; - } -#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40) - else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) { - count = g_signal_handlers_disconnect_by_func( - dbus_characteristic.battery, - G_CALLBACK (on_handle_battery_level_property_change), - connection); - if (count == 0) { - fprintf(stderr, "Could not find any notification attached to this UUID"); - return GATTLIB_NOT_FOUND; - } - return GATTLIB_SUCCESS; - } else { - assert(dbus_characteristic.type == TYPE_GATT); - } -#endif - - count = g_signal_handlers_disconnect_by_func( - dbus_characteristic.gatt, - G_CALLBACK(callback), - connection); - if (count == 0) { - fprintf(stderr, "Could not find any notification attached to this UUID"); - return GATTLIB_NOT_FOUND; - } - - GError *error = NULL; - org_bluez_gatt_characteristic1_call_stop_notify_sync( - dbus_characteristic.gatt, NULL, &error); - - if (error) { - fprintf(stderr, "Failed to stop DBus GATT notification: %s\n", error->message); - g_error_free(error); - return GATTLIB_NOT_FOUND; - } else { - return GATTLIB_SUCCESS; - } -} - -int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid) { - return connect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_property_change); -} - -int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid) { - return disconnect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_property_change); -} - -int gattlib_indication_start(gatt_connection_t* connection, const uuid_t* uuid) { - return connect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_indication); -} - -int gattlib_indication_stop(gatt_connection_t* connection, const uuid_t* uuid) { - return disconnect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_indication); -} - int get_bluez_device_from_mac(struct gattlib_adapter *adapter, const char *mac_address, OrgBluezDevice1 **bluez_device1) { GError *error = NULL; diff --git a/dbus/gattlib_internal.h b/dbus/gattlib_internal.h index 6f1d704..55c8bd9 100644 --- a/dbus/gattlib_internal.h +++ b/dbus/gattlib_internal.h @@ -60,6 +60,9 @@ typedef struct { // List of DBUS Object managed by 'adapter->device_manager' GList *dbus_objects; + + // List of 'OrgBluezGattCharacteristic1*' which has an attached notification + GList *notified_characteristics; } gattlib_context_t; struct gattlib_adapter { @@ -99,4 +102,6 @@ int get_bluez_device_from_mac(struct gattlib_adapter *adapter, const char *mac_a struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid); +void disconnect_all_notifications(gattlib_context_t* conn_context); + #endif diff --git a/dbus/gattlib_notification.c b/dbus/gattlib_notification.c new file mode 100644 index 0000000..51a0e85 --- /dev/null +++ b/dbus/gattlib_notification.c @@ -0,0 +1,266 @@ +/* + * + * GattLib - GATT Library + * + * Copyright (C) 2016-2020 Olivier Martin + * + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include "gattlib_internal.h" + +struct gattlib_notification_handle { + OrgBluezGattCharacteristic1 *gatt; + gulong signal_id; + uuid_t uuid; +}; + +#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40) +gboolean on_handle_battery_level_property_change( + OrgBluezBattery1 *object, + GVariant *arg_changed_properties, + const gchar *const *arg_invalidated_properties, + gpointer user_data) +{ + static guint8 percentage; + gatt_connection_t* connection = user_data; + + if (gattlib_has_valid_handler(&connection->notification)) { + // Retrieve 'Value' from 'arg_changed_properties' + if (g_variant_n_children (arg_changed_properties) > 0) { + GVariantIter *iter; + const gchar *key; + GVariant *value; + + g_variant_get (arg_changed_properties, "a{sv}", &iter); + while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { + if (strcmp(key, "Percentage") == 0) { + //TODO: by declaring 'percentage' as a 'static' would mean we could have issue in case of multiple + // GATT connection notifiying to Battery level + percentage = g_variant_get_byte(value); + + gattlib_call_notification_handler(&connection->notification, + &m_battery_level_uuid, + (const uint8_t*)&percentage, sizeof(percentage)); + break; + } + } + g_variant_iter_free(iter); + } + } + return TRUE; +} +#endif + +static gboolean on_handle_characteristic_property_change( + OrgBluezGattCharacteristic1 *object, + GVariant *arg_changed_properties, + const gchar *const *arg_invalidated_properties, + gpointer user_data) +{ + gatt_connection_t* connection = user_data; + + if (gattlib_has_valid_handler(&connection->notification)) { + // Retrieve 'Value' from 'arg_changed_properties' + if (g_variant_n_children (arg_changed_properties) > 0) { + GVariantIter *iter; + const gchar *key; + GVariant *value; + + g_variant_get (arg_changed_properties, "a{sv}", &iter); + while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { + if (strcmp(key, "Value") == 0) { + uuid_t uuid; + size_t data_length; + const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar)); + + gattlib_string_to_uuid( + org_bluez_gatt_characteristic1_get_uuid(object), + MAX_LEN_UUID_STR + 1, + &uuid); + + gattlib_call_notification_handler(&connection->notification, + &uuid, data, data_length); + break; + } + } + g_variant_iter_free(iter); + } + } + return TRUE; +} + +static gboolean on_handle_characteristic_indication( + OrgBluezGattCharacteristic1 *object, + GVariant *arg_changed_properties, + const gchar *const *arg_invalidated_properties, + gpointer user_data) +{ + gatt_connection_t* connection = user_data; + + if (gattlib_has_valid_handler(&connection->indication)) { + // Retrieve 'Value' from 'arg_changed_properties' + if (g_variant_n_children (arg_changed_properties) > 0) { + GVariantIter *iter; + const gchar *key; + GVariant *value; + + g_variant_get (arg_changed_properties, "a{sv}", &iter); + while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { + if (strcmp(key, "Value") == 0) { + uuid_t uuid; + size_t data_length; + const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar)); + + gattlib_string_to_uuid( + org_bluez_gatt_characteristic1_get_uuid(object), + MAX_LEN_UUID_STR + 1, + &uuid); + + gattlib_call_notification_handler(&connection->indication, + &uuid, data, data_length); + break; + } + } + g_variant_iter_free(iter); + } + } + return TRUE; +} + +static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection, const uuid_t* uuid, void *callback) { + gattlib_context_t* conn_context = connection->context; + + struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid); + if (dbus_characteristic.type == TYPE_NONE) { + puts("Not found"); + return GATTLIB_NOT_FOUND; + } +#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40) + else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) { + // Register a handle for notification + g_signal_connect(dbus_characteristic.battery, + "g-properties-changed", + G_CALLBACK (on_handle_battery_level_property_change), + connection); + + return GATTLIB_SUCCESS; + } else { + assert(dbus_characteristic.type == TYPE_GATT); + } +#endif + + // Register a handle for notification + gulong signal_id = g_signal_connect(dbus_characteristic.gatt, + "g-properties-changed", + G_CALLBACK(callback), + connection); + if (signal_id == 0) { + fprintf(stderr, "Failed to connect signal to DBus GATT notification\n"); + return GATTLIB_ERROR_DBUS; + } + + // Add signal to the list + struct gattlib_notification_handle *notification_handle = malloc(sizeof(struct gattlib_notification_handle)); + if (notification_handle == NULL) { + return GATTLIB_OUT_OF_MEMORY; + } + notification_handle->gatt = dbus_characteristic.gatt; + notification_handle->signal_id = signal_id; + memcpy(¬ification_handle->uuid, uuid, sizeof(*uuid)); + conn_context->notified_characteristics = g_list_append(conn_context->notified_characteristics, notification_handle); + + GError *error = NULL; + org_bluez_gatt_characteristic1_call_start_notify_sync(dbus_characteristic.gatt, NULL, &error); + + if (error) { + fprintf(stderr, "Failed to start DBus GATT notification: %s\n", error->message); + g_error_free(error); + return GATTLIB_ERROR_DBUS; + } else { + return GATTLIB_SUCCESS; + } +} + +static int disconnect_signal_to_characteristic_uuid(gatt_connection_t* connection, const uuid_t* uuid, void *callback) { + gattlib_context_t* conn_context = connection->context; + struct gattlib_notification_handle *notification_handle = NULL; + + // Find notification handle + for (GList *l = conn_context->notified_characteristics; l != NULL; l = l->next) { + struct gattlib_notification_handle *notification_handle_ptr = l->data; + if (gattlib_uuid_cmp(¬ification_handle_ptr->uuid, uuid) == GATTLIB_SUCCESS) { + notification_handle = notification_handle_ptr; + + conn_context->notified_characteristics = g_list_delete_link(conn_context->notified_characteristics, l); + break; + } + } + + if (notification_handle == NULL) { + return GATTLIB_NOT_FOUND; + } + + g_signal_handler_disconnect(notification_handle->gatt, notification_handle->signal_id); + + GError *error = NULL; + org_bluez_gatt_characteristic1_call_stop_notify_sync( + notification_handle->gatt, NULL, &error); + + free(notification_handle); + + if (error) { + fprintf(stderr, "Failed to stop DBus GATT notification: %s\n", error->message); + g_error_free(error); + return GATTLIB_NOT_FOUND; + } else { + return GATTLIB_SUCCESS; + } +} + +int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid) { + return connect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_property_change); +} + +int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid) { + return disconnect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_property_change); +} + +int gattlib_indication_start(gatt_connection_t* connection, const uuid_t* uuid) { + return connect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_indication); +} + +int gattlib_indication_stop(gatt_connection_t* connection, const uuid_t* uuid) { + return disconnect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_indication); +} + +void disconnect_all_notifications(gattlib_context_t* conn_context) { + // Find notification handle + for (GList *l = conn_context->notified_characteristics; l != NULL; l = l->next) { + struct gattlib_notification_handle *notification_handle = l->data; + + g_signal_handler_disconnect(notification_handle->gatt, notification_handle->signal_id); + free(notification_handle); + } + + g_list_free_full(conn_context->notified_characteristics, g_object_unref); +}