Single lock for all gattlib library

pull/277/head 0.6.0
Olivier Martin 2024-04-05 13:25:51 +02:00
parent 014c2802ee
commit 0e34df58e5
16 changed files with 826 additions and 233 deletions

View File

@ -50,22 +50,31 @@ static gpointer _gattlib_connected_device_thread(gpointer data) {
gattlib_connection_t* connection = data;
const gchar *device_mac_address = org_bluez_device1_get_address(connection->backend.device);
// Mutex to ensure the device is valid and not freed during its use
g_mutex_lock(&connection->device->device_mutex);
// Mutex to ensure the handler is valid
g_rec_mutex_lock(&connection->on_connection.mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return NULL;
}
if (!gattlib_has_valid_handler(&connection->on_connection)) {
goto EXIT;
g_rec_mutex_unlock(&m_gattlib_mutex);
return NULL;
}
// Ensure we increment device reference counter to prevent the device/connection is freed during the execution
gattlib_device_ref(connection->device);
// We need to release the lock here to ensure the connection callback that is actually
// doing the application sepcific work is not locking the BLE state.
g_rec_mutex_unlock(&m_gattlib_mutex);
connection->on_connection.callback.connection_handler(
connection->device->adapter, device_mac_address, connection, 0 /* no error */,
connection->on_connection.user_data);
EXIT:
g_rec_mutex_unlock(&connection->on_connection.mutex);
g_mutex_unlock(&connection->device->device_mutex);
gattlib_device_unref(connection->device);
return NULL;
}
@ -75,6 +84,10 @@ static void* _connected_device_thread_args_allocator(va_list args) {
}
void gattlib_on_connected_device(gattlib_connection_t* connection) {
if (!gattlib_device_is_valid(connection->device)) {
return;
}
gattlib_handler_dispatch_to_thread(
&connection->on_connection,
#if defined(WITH_PYTHON)

View File

@ -31,9 +31,14 @@ void gattlib_disconnected_device_python_callback(gattlib_connection_t* connectio
#endif
void gattlib_on_disconnected_device(gattlib_connection_t* connection) {
if (gattlib_has_valid_handler(&connection->on_disconnection)) {
g_rec_mutex_lock(&connection->on_disconnection.mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return;
}
if (gattlib_has_valid_handler(&connection->on_disconnection)) {
#if defined(WITH_PYTHON)
// Check if we are using the Python callback, in case of Python argument we keep track of the argument to free them
// once we are done with the handler.
@ -44,16 +49,16 @@ void gattlib_on_disconnected_device(gattlib_connection_t* connection) {
// For GATT disconnection we do not use thread to ensure the callback is synchronous.
connection->on_disconnection.callback.disconnection_handler(connection, connection->on_disconnection.user_data);
g_rec_mutex_unlock(&connection->on_disconnection.mutex);
}
// Signal the device is now disconnected
g_mutex_lock(&connection->device->device_mutex);
connection->disconnection_wait.value = true;
g_cond_broadcast(&connection->disconnection_wait.condition);
g_mutex_unlock(&connection->device->device_mutex);
// Clean GATTLIB connection on disconnection
gattlib_connection_free(connection);
g_rec_mutex_unlock(&m_gattlib_mutex);
// Signal the device is now disconnected
g_mutex_lock(&m_gattlib_signal.mutex);
m_gattlib_signal.signals |= GATTLIB_SIGNAL_DEVICE_DISCONNECTION;
g_cond_broadcast(&m_gattlib_signal.condition);
g_mutex_unlock(&m_gattlib_signal.mutex);
}

View File

@ -55,21 +55,33 @@ struct gattlib_discovered_device_thread_args {
static gpointer _gattlib_discovered_device_thread(gpointer data) {
struct gattlib_discovered_device_thread_args* args = data;
g_rec_mutex_lock(&args->gattlib_adapter->discovered_device_callback.mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_has_valid_handler(&args->gattlib_adapter->discovered_device_callback)) {
if (!gattlib_adapter_is_valid(args->gattlib_adapter)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
goto EXIT;
}
if (!gattlib_has_valid_handler(&args->gattlib_adapter->discovered_device_callback)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
goto EXIT;
}
// Increase adapter reference counter to ensure the adapter is not freed while
// the callback is in use.
gattlib_adapter_ref(args->gattlib_adapter);
g_rec_mutex_unlock(&m_gattlib_mutex);
args->gattlib_adapter->discovered_device_callback.callback.discovered_device(
args->gattlib_adapter,
args->mac_address, args->name,
args->gattlib_adapter->discovered_device_callback.user_data
);
EXIT:
g_rec_mutex_unlock(&args->gattlib_adapter->discovered_device_callback.mutex);
gattlib_adapter_unref(args->gattlib_adapter);
EXIT:
free(args->mac_address);
if (args->name != NULL) {
free(args->name);
@ -96,6 +108,10 @@ static void* _discovered_device_thread_args_allocator(va_list args) {
}
void gattlib_on_discovered_device(gattlib_adapter_t* gattlib_adapter, OrgBluezDevice1* device1) {
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
return;
}
gattlib_handler_dispatch_to_thread(
&gattlib_adapter->discovered_device_callback,
#if defined(WITH_PYTHON)

View File

@ -56,14 +56,19 @@ void gattlib_notification_device_thread(gpointer data, gpointer user_data) {
struct gattlib_notification_device_thread_args* args = data;
struct gattlib_handler* handler = user_data;
g_rec_mutex_lock(&handler->mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(args->connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return;
}
handler->callback.notification_handler(
args->uuid, args->data, args->data_length,
handler->user_data
);
g_rec_mutex_unlock(&handler->mutex);
g_rec_mutex_unlock(&m_gattlib_mutex);
if (args->uuid != NULL) {
free(args->uuid);

View File

@ -8,11 +8,21 @@
#include "gattlib_internal.h"
int gattlib_register_notification(gattlib_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data) {
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_device_is_valid(connection->device)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
connection->notification.callback.notification_handler = notification_handler;
@ -25,19 +35,33 @@ int gattlib_register_notification(gattlib_connection_t* connection, gattlib_even
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_notification: Failed to create thread pool: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_INTERNAL;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
} else {
assert(connection->notification.thread_pool != NULL);
return GATTLIB_SUCCESS;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_register_indication(gattlib_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data) {
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_device_is_valid(connection->device)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
connection->indication.callback.notification_handler = indication_handler;
connection->indication.user_data = user_data;
@ -48,19 +72,36 @@ int gattlib_register_indication(gattlib_connection_t* connection, gattlib_event_
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_indication: Failed to create thread pool: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_INTERNAL;
} else {
return GATTLIB_SUCCESS;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_register_on_disconnect(gattlib_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) {
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_device_is_valid(connection->device)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
connection->on_disconnection.callback.disconnection_handler = handler;
connection->on_disconnection.user_data = user_data;
return GATTLIB_SUCCESS;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
void bt_uuid_to_uuid(bt_uuid_t* bt_uuid, uuid_t* uuid) {
@ -144,10 +185,8 @@ int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2) {
}
void gattlib_handler_free(struct gattlib_handler* handler) {
g_rec_mutex_lock(&handler->mutex);
if (!gattlib_has_valid_handler(handler)) {
goto EXIT;
return;
}
// Reset callback to stop calling it after we stopped
@ -172,9 +211,6 @@ void gattlib_handler_free(struct gattlib_handler* handler) {
g_thread_pool_free(handler->thread_pool, FALSE /* immediate */, TRUE /* wait */);
handler->thread_pool = NULL;
}
EXIT:
g_rec_mutex_unlock(&handler->mutex);
}
bool gattlib_has_valid_handler(struct gattlib_handler* handler) {
@ -185,8 +221,11 @@ void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*
GThreadFunc thread_func, const char* thread_name, void* (*thread_args_allocator)(va_list args), ...) {
GError *error = NULL;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_has_valid_handler(handler)) {
// We do not have (anymore) a callback, nothing to do
g_rec_mutex_unlock(&m_gattlib_mutex);
return;
}
@ -198,6 +237,8 @@ void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*
}
#endif
g_rec_mutex_unlock(&m_gattlib_mutex);
// We create a thread to ensure the callback is not blocking the mainloop
va_list args;
va_start(args, thread_args_allocator);
@ -218,3 +259,10 @@ void gattlib_free_mem(void *ptr) {
free(ptr);
}
}
int gattlib_device_ref(gattlib_device_t* device) {
g_rec_mutex_lock(&m_gattlib_mutex);
device->reference_counter++;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}

View File

@ -0,0 +1,108 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021-2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
// Keep track of the allocated adapters to avoid an adapter to be freed twice.
// It could happen when using Python wrapper.
GSList *m_adapter_list;
bool gattlib_adapter_is_valid(gattlib_adapter_t* adapter) {
bool is_valid;
g_rec_mutex_lock(&m_gattlib_mutex);
GSList *adapter_entry = g_slist_find(m_adapter_list, adapter);
if (adapter_entry == NULL) {
is_valid = false;
} else {
is_valid = true;
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return is_valid;
}
bool gattlib_adapter_is_scanning(gattlib_adapter_t* adapter) {
bool is_scanning = false;
g_rec_mutex_lock(&m_gattlib_mutex);
GSList *adapter_entry = g_slist_find(m_adapter_list, adapter);
if (adapter_entry == NULL) {
goto EXIT;
}
is_scanning = adapter->backend.ble_scan.is_scanning;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return is_scanning;
}
struct _device_is_valid {
gattlib_device_t* device;
bool found;
};
static void _gattlib_device_is_valid(gpointer data, gpointer user_data) {
gattlib_adapter_t* adapter = data;
struct _device_is_valid* device_is_valid = user_data;
GSList *device_entry = g_slist_find(adapter->devices, device_is_valid->device);
if (device_entry != NULL) {
device_is_valid->found = true;
}
}
bool gattlib_device_is_valid(gattlib_device_t* device) {
struct _device_is_valid device_is_valid = {
.device = device,
.found = false
};
g_rec_mutex_lock(&m_gattlib_mutex);
g_slist_foreach(m_adapter_list, _gattlib_device_is_valid, &device_is_valid);
g_rec_mutex_unlock(&m_gattlib_mutex);
return device_is_valid.found;
}
struct _connection_is_connected {
gattlib_connection_t* connection;
bool is_connected;
};
static gint _is_device_connection(gconstpointer a, gconstpointer b) {
const gattlib_device_t* device = a;
return (&device->connection == b);
}
static void _gattlib_connection_is_connected(gpointer data, gpointer user_data) {
gattlib_adapter_t* adapter = data;
struct _connection_is_connected* connection_is_connected = user_data;
GSList *device_entry = g_slist_find_custom(adapter->devices, user_data, _is_device_connection);
if (device_entry == NULL) {
return;
}
gattlib_device_t* device = device_entry->data;
connection_is_connected->is_connected = (device->state == CONNECTED);
}
bool gattlib_connection_is_connected(gattlib_connection_t* connection) {
struct _connection_is_connected connection_is_connected = {
.connection = connection,
.is_connected = false
};
g_rec_mutex_lock(&m_gattlib_mutex);
g_slist_foreach(m_adapter_list, _gattlib_connection_is_connected, &connection_is_connected);
g_rec_mutex_unlock(&m_gattlib_mutex);
return connection_is_connected.is_connected;
}

View File

@ -8,10 +8,10 @@
const char* device_state_str[] = {
"NOT_FOUND",
"CONNECTING",
"CONNECTED",
"DISCONNECTING",
"DISCONNECTED"
"CONNECTING",
"CONNECTED",
"DISCONNECTING",
"DISCONNECTED"
};
static gint _compare_device_with_device_id(gconstpointer a, gconstpointer b) {
@ -26,41 +26,33 @@ static GSList* _find_device_with_device_id(gattlib_adapter_t* adapter, const cha
}
gattlib_device_t* gattlib_device_get_device(gattlib_adapter_t* adapter, const char* device_id) {
gattlib_device_t* device = NULL;
g_rec_mutex_lock(&adapter->mutex);
GSList *item = _find_device_with_device_id(adapter, device_id);
if (item == NULL) {
goto EXIT;
return NULL;
}
device = (gattlib_device_t*)item->data;
EXIT:
g_rec_mutex_unlock(&adapter->mutex);
return device;
return (gattlib_device_t*)item->data;
}
enum _gattlib_device_state gattlib_device_get_state(gattlib_adapter_t* adapter, const char* device_id) {
enum _gattlib_device_state state = NOT_FOUND;
g_rec_mutex_lock(&adapter->mutex);
gattlib_device_t* device = gattlib_device_get_device(adapter, device_id);
if (device != NULL) {
state = device->state;
if (device == NULL) {
return NOT_FOUND;
}
g_rec_mutex_unlock(&adapter->mutex);
return state;
return device->state;
}
int gattlib_device_set_state(gattlib_adapter_t* adapter, const char* device_id, enum _gattlib_device_state new_state) {
enum _gattlib_device_state old_state;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&adapter->mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
old_state = gattlib_device_get_state(adapter, device_id);
if (old_state == NOT_FOUND) {
@ -77,6 +69,7 @@ int gattlib_device_set_state(gattlib_adapter_t* adapter, const char* device_id,
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: Set initial state %s", device_id, device_state_str[new_state]);
device->reference_counter = 1;
device->adapter = adapter;
device->device_id = g_strdup(device_id);
device->state = new_state;
@ -103,7 +96,7 @@ int gattlib_device_set_state(gattlib_adapter_t* adapter, const char* device_id,
case DISCONNECTED:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Free device %p", device);
adapter->devices = g_slist_remove(adapter->devices, device);
free(device);
gattlib_device_unref(device);
break;
case CONNECTING:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Connecting device needs to be removed - ignore it");
@ -130,7 +123,7 @@ int gattlib_device_set_state(gattlib_adapter_t* adapter, const char* device_id,
}
EXIT:
g_rec_mutex_unlock(&adapter->mutex);
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
@ -139,7 +132,7 @@ static void _gattlib_device_free(gpointer data) {
switch (device->state) {
case DISCONNECTED:
free(device);
gattlib_device_unref(device);
break;
default:
GATTLIB_LOG(GATTLIB_WARNING, "Memory of the BLE device '%s' has not been freed because in state %s",
@ -148,13 +141,24 @@ static void _gattlib_device_free(gpointer data) {
}
int gattlib_devices_free(gattlib_adapter_t* adapter) {
g_rec_mutex_lock(&adapter->mutex);
g_slist_free_full(adapter->devices, _gattlib_device_free);
g_rec_mutex_unlock(&adapter->mutex);
return 0;
}
int gattlib_device_unref(gattlib_device_t* device) {
g_rec_mutex_lock(&m_gattlib_mutex);
device->reference_counter--;
if (device->reference_counter > 0) {
goto EXIT;
}
free(device);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
static void _gattlib_device_is_disconnected(gpointer data, gpointer user_data) {
gattlib_device_t* device = data;
bool* devices_are_disconnected_ptr = user_data;
@ -167,9 +171,7 @@ static void _gattlib_device_is_disconnected(gpointer data, gpointer user_data) {
int gattlib_devices_are_disconnected(gattlib_adapter_t* adapter) {
bool devices_are_disconnected = true;
g_rec_mutex_lock(&adapter->mutex);
g_slist_foreach(adapter->devices, _gattlib_device_is_disconnected, &devices_are_disconnected);
g_rec_mutex_unlock(&adapter->mutex);
return devices_are_disconnected;
}
@ -182,10 +184,17 @@ static void _gattlib_device_dump_state(gpointer data, gpointer user_data) {
}
void gattlib_devices_dump_state(gattlib_adapter_t* adapter) {
g_rec_mutex_lock(&adapter->mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
goto EXIT;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Device list:");
g_slist_foreach(adapter->devices, _gattlib_device_dump_state, NULL);
g_rec_mutex_unlock(&adapter->mutex);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
#endif

View File

@ -24,6 +24,18 @@ struct gattlib_python_args {
};
#endif
#define GATTLIB_SIGNAL_DEVICE_DISCONNECTION (1 << 0)
#define GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING (1 << 1)
struct gattlib_signal {
// Used by gattlib_disconnection when we want to wait for the disconnection to be effective
GCond condition;
// Mutex for condition
GMutex mutex;
// Identify the gattlib signals
uint32_t signals;
};
struct gattlib_handler {
union {
gattlib_discovered_device_t discovered_device;
@ -36,11 +48,6 @@ struct gattlib_handler {
void* user_data;
// We create a thread to ensure the callback is not blocking the mainloop
GThread *thread;
// The mutex ensures the callbacks is not being freed while being called
// We use a recursive mutex to be able to disable BLE scan from 'on_discovered_device'
// when we want to connect to the discovered device.
// Note: The risk is that we are actually realising the handle from the one we are executing
GRecMutex mutex;
// Thread pool
GThreadPool *thread_pool;
#if defined(WITH_PYTHON)
@ -64,8 +71,9 @@ struct _gattlib_adapter {
// BLE adapter name
char* name;
// The recursive mutex allows to ensure sensible operations are always covered by a mutex in a same thread
GRecMutex mutex;
// reference counter is used to know whether the adapter is still use by callback
// When the reference counter is 0 then the adapter is freed
uintptr_t reference_counter;
// List of `gattlib_device_t`. This list allows to know weither a device is
// discovered/disconnected/connecting/connected/disconnecting.
@ -81,13 +89,6 @@ struct _gattlib_connection {
// Context specific to the backend implementation (eg: dbus backend)
struct _gattlib_connection_backend backend;
struct {
// Used by gattlib_disconnection when we want to wait for the disconnection to be effective
GCond condition;
// Used to avoid spurious or stolen wakeup
bool value;
} disconnection_wait;
struct gattlib_handler on_connection;
struct gattlib_handler notification;
struct gattlib_handler indication;
@ -98,7 +99,10 @@ typedef struct _gattlib_device {
struct _gattlib_adapter* adapter;
// On some platform, the name could be a UUID, on others its the DBUS device path
char* device_id;
GMutex device_mutex;
// reference counter is used to know whether the device is still use by callback
// When the reference counter is 0 then the device is freed
uintptr_t reference_counter;
// We keep the state to prevent concurrent connecting/connected/disconnecting operation
enum _gattlib_device_state state;
@ -106,6 +110,27 @@ typedef struct _gattlib_device {
struct _gattlib_connection connection;
} gattlib_device_t;
// This recursive mutex ensures all gattlib objects can be accessed in a multi-threaded environment
// The recursive mutex allows a same thread to lock twice the mutex without being blocked by itself.
extern GRecMutex m_gattlib_mutex;
// Keep track of the allocated adapters to avoid an adapter to be freed twice.
// It could happen when using Python wrapper.
extern GSList *m_adapter_list;
// This structure is used for inter-thread communication
extern struct gattlib_signal m_gattlib_signal;
bool gattlib_adapter_is_valid(gattlib_adapter_t* adapter);
bool gattlib_adapter_is_scanning(gattlib_adapter_t* adapter);
int gattlib_adapter_ref(gattlib_adapter_t* adapter);
int gattlib_adapter_unref(gattlib_adapter_t* adapter);
bool gattlib_device_is_valid(gattlib_device_t* device);
int gattlib_device_ref(gattlib_device_t* device);
int gattlib_device_unref(gattlib_device_t* device);
bool gattlib_connection_is_connected(gattlib_connection_t* connection);
void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*python_callback)(),
GThreadFunc thread_func, const char* thread_name, void* (*thread_args_allocator)(va_list args), ...);
void gattlib_handler_free(struct gattlib_handler* handler);

View File

@ -73,6 +73,7 @@ set(gattlib_SRCS gattlib.c
gattlib_stream.c
bluez5/lib/uuid.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_common.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_common_adapter.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_device_state_management.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_eddystone.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_connected_device.c

View File

@ -19,6 +19,12 @@ static void _on_device_connect(gattlib_connection_t* connection) {
GDBusObjectManager *device_manager;
GError *error = NULL;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_device_is_valid(connection->device)) {
goto EXIT;
}
// Stop the timeout for connection
if (connection->backend.connection_timeout_id) {
g_source_remove(connection->backend.connection_timeout_id);
@ -35,17 +41,20 @@ static void _on_device_connect(gattlib_connection_t* connection) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Failed to get device manager from adapter");
}
//TODO: Free device
return;
goto EXIT;
}
connection->backend.dbus_objects = g_dbus_object_manager_get_objects(device_manager);
gattlib_device_set_state(connection->device->adapter, connection->device->device_id, CONNECTED);
gattlib_on_connected_device(connection);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
gboolean on_handle_device_property_change(
OrgBluezGattCharacteristic1 *object,
GDBusProxy *proxy,
GVariant *arg_changed_properties,
const gchar *const *arg_invalidated_properties,
gpointer user_data)
@ -54,6 +63,7 @@ gboolean on_handle_device_property_change(
// Retrieve 'Value' from 'arg_changed_properties'
if (g_variant_n_children (arg_changed_properties) > 0) {
const gchar* device_object_path = g_dbus_proxy_get_object_path(proxy);
GVariantIter *iter;
const gchar *key;
GVariant *value;
@ -62,17 +72,14 @@ gboolean on_handle_device_property_change(
while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
if (strcmp(key, "Connected") == 0) {
if (!g_variant_get_boolean(value)) {
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Disconnection",
connection->backend.device_object_path);
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Disconnection", device_object_path);
gattlib_on_disconnected_device(connection);
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Connection",
connection->backend.device_object_path);
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Connection", device_object_path);
}
} else if (strcmp(key, "ServicesResolved") == 0) {
if (g_variant_get_boolean(value)) {
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Service Resolved",
connection->backend.device_object_path);
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Service Resolved", device_object_path);
_on_device_connect(connection);
}
}
@ -132,9 +139,18 @@ void get_device_path_from_mac(const char *adapter_name, const char *mac_address,
static gboolean _stop_connect_func(gpointer data) {
gattlib_connection_t *connection = data;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_device_is_valid(connection->device)) {
goto EXIT;
}
// Reset the connection timeout
connection->backend.connection_timeout_id = 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
// We return FALSE when it is a one-off event
return FALSE;
}
@ -159,7 +175,7 @@ int gattlib_connect(gattlib_adapter_t* adapter, const char *dst,
{
const char* adapter_name = NULL;
GError *error = NULL;
char object_path[100];
char object_path[GATTLIB_DBUS_OBJECT_PATH_SIZE_MAX];
int ret = GATTLIB_SUCCESS;
// In case NULL is passed, we initialized default adapter
@ -180,14 +196,23 @@ int gattlib_connect(gattlib_adapter_t* adapter, const char *dst,
get_device_path_from_mac(adapter_name, dst, object_path, sizeof(object_path));
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
gattlib_device_t* device = gattlib_device_get_device(adapter, object_path);
if (device == NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Cannot find connection %s", dst);
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
} else if (device->state != DISCONNECTED) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Cannot connect to '%s'. Device is in state %s",
dst, device_state_str[device->state]);
return GATTLIB_BUSY;
ret = GATTLIB_BUSY;
goto EXIT;
}
device->connection.on_connection.callback.connection_handler = connect_cb;
@ -214,6 +239,7 @@ int gattlib_connect(gattlib_adapter_t* adapter, const char *dst,
} else {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Failed to connect to DBus Bluez Device");
}
goto EXIT;
} else {
device->connection.backend.device = bluez_device;
device->connection.backend.device_object_path = strdup(object_path);
@ -255,6 +281,7 @@ int gattlib_connect(gattlib_adapter_t* adapter, const char *dst,
// and 'org.bluez.GattCharacteristic1' to be advertised at that moment.
device->connection.backend.connection_timeout_id = g_timeout_add_seconds(CONNECT_TIMEOUT_SEC, _stop_connect_func, &device->connection);
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
FREE_DEVICE:
@ -265,10 +292,12 @@ FREE_DEVICE:
gattlib_adapter_close(adapter);
}
EXIT:
if (ret != GATTLIB_SUCCESS) {
connect_cb(adapter, dst, NULL, ret /* error */, user_data);
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
@ -281,7 +310,6 @@ FREE_DEVICE:
void gattlib_connection_free(gattlib_connection_t* connection) {
char* device_id;
g_mutex_lock(&connection->device->device_mutex);
device_id = connection->device->device_id;
// Remove signal
@ -312,26 +340,24 @@ void gattlib_connection_free(gattlib_connection_t* connection) {
// Mark the device has disconnected
gattlib_device_set_state(connection->device->adapter, device_id, DISCONNECTED);
g_mutex_unlock(&connection->device->device_mutex);
}
int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection) {
int ret = GATTLIB_SUCCESS;
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Cannot disconnect - connection parameter is not valid.");
return GATTLIB_INVALID_PARAMETER;
}
g_mutex_lock(&connection->device->device_mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection->device->state != CONNECTED) {
if (!gattlib_connection_is_connected(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "Cannot disconnect - connection is not in connected state (state=%s).",
device_state_str[connection->device->state]);
ret = GATTLIB_BUSY;
goto EXIT;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_BUSY;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Disconnecting bluetooth device %s", connection->backend.device_object_path);
@ -340,6 +366,8 @@ int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection
if (error) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to disconnect DBus Bluez Device: %s", error->message);
g_error_free(error);
// We continue, we still want to set the correct state
}
// Mark the device has disconnected
@ -348,35 +376,53 @@ int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection
//Note: Signals and memory will be removed/clean on disconnction callback
// See _gattlib_clean_on_disconnection()
// We must release the mutex before the loop to leave other threads to signal the disconnection
g_rec_mutex_unlock(&m_gattlib_mutex);
if (wait_disconnection) {
gint64 end_time;
g_mutex_lock(&m_gattlib_signal.mutex);
end_time = g_get_monotonic_time() + GATTLIB_DISCONNECTION_WAIT_TIMEOUT_SEC * G_TIME_SPAN_SECOND;
while (!connection->disconnection_wait.value) {
if (!g_cond_wait_until(&connection->disconnection_wait.condition, &connection->device->device_mutex, end_time)) {
while (gattlib_connection_is_connected(connection)) {
if (!g_cond_wait_until(&m_gattlib_signal.condition, &m_gattlib_signal.mutex, end_time)) {
ret = GATTLIB_TIMEOUT;
break;
}
}
g_mutex_unlock(&m_gattlib_signal.mutex);
}
EXIT:
g_mutex_unlock(&connection->device->device_mutex);
return ret;
}
// Bluez was using org.bluez.Device1.GattServices until 5.37 to expose the list of available GATT Services
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 38)
int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
const gchar* const* service_str;
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib connection not initialized.");
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
if (!gattlib_device_is_valid(connection->device)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
// Increase 'bluez_device' reference counter to avoid to keep the lock longer
OrgBluezDevice1* bluez_device = connection->backend.device;
const gchar* const* service_str;
GError *error = NULL;
g_object_ref(bluez_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
const gchar* const* service_strs = org_bluez_device1_get_gatt_services(bluez_device);
@ -398,7 +444,8 @@ int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_s
gattlib_primary_service_t* primary_services = calloc(count_max * sizeof(gattlib_primary_service_t), 1);
if (primary_services == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
for (service_str = service_strs; *service_str != NULL; service_str++) {
@ -440,21 +487,33 @@ int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_s
if (services_count != NULL) {
*services_count = count;
}
return GATTLIB_SUCCESS;
EXIT:
g_object_unref(bluez_device);
return ret;
}
#else
int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib connection not initialized.");
return GATTLIB_INVALID_PARAMETER;
}
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
OrgBluezDevice1* device = connection->backend.device;
const gchar* const* service_str;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib connection not initialized.");
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_device_is_valid(connection->device)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
GDBusObjectManager *device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
OrgBluezDevice1* device = connection->backend.device;
const gchar* const* service_strs = org_bluez_device1_get_uuids(device);
if (device_manager == NULL) {
@ -466,7 +525,7 @@ int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_s
ret = GATTLIB_ERROR_DBUS;
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return ret;
goto EXIT;
}
if (service_strs == NULL) {
@ -476,7 +535,7 @@ int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_s
if (services_count != NULL) {
*services_count = 0;
}
return GATTLIB_SUCCESS;
goto EXIT;
}
// Maximum number of primary services
@ -487,7 +546,8 @@ int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_s
gattlib_primary_service_t* primary_services = calloc(count_max * sizeof(gattlib_primary_service_t), 1);
if (primary_services == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
GList *l;
@ -590,6 +650,9 @@ int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_s
if (ret != GATTLIB_SUCCESS) {
free(primary_services);
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
#endif
@ -597,9 +660,21 @@ int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_s
// Bluez was using org.bluez.Device1.GattServices until 5.37 to expose the list of available GATT Services
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 38)
int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
OrgBluezDevice1* bluez_device = connection->backend.bluez_device;
GError *error = NULL;
int handle;
int ret = GATTLIB_SUCCESS;
// Increase bluez_device object reference counter to avoid to keep locking the mutex
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_device_is_valid(connection->device)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
OrgBluezDevice1* bluez_device = connection->backend.bluez_device;
g_object_ref(bluez_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
const gchar* const* service_strs = org_bluez_device1_get_gatt_services(bluez_device);
const gchar* const* service_str;
@ -607,7 +682,8 @@ int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start
const gchar* characteristic_str;
if (service_strs == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
// Maximum number of primary services
@ -656,7 +732,8 @@ int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start
gattlib_characteristic_t* characteristic_list = calloc(count_max * sizeof(gattlib_characteristic_t), 1);
if (characteristic_list == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
for (service_str = service_strs; *service_str != NULL; service_str++) {
@ -744,7 +821,10 @@ int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start
*characteristics = characteristic_list;
*characteristics_count = count;
return GATTLIB_SUCCESS;
EXIT:
g_object_unref(bluez_device);
return ret;
}
#else
static void add_characteristics_from_service(struct _gattlib_connection_backend* backend, GDBusObjectManager *device_manager,
@ -840,10 +920,18 @@ static void add_characteristics_from_service(struct _gattlib_connection_backend*
int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
GDBusObjectManager *device_manager;
GList *l;
int ret;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_device_is_valid(connection->device)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
@ -853,7 +941,7 @@ int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start
ret = GATTLIB_ERROR_DBUS;
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return ret;
goto EXIT;
}
// Count the maximum number of characteristic to allocate the array (we count all the characterstic for all devices)
@ -877,7 +965,8 @@ int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start
gattlib_characteristic_t* characteristic_list = calloc(count_max * sizeof(gattlib_characteristic_t), 1);
if (characteristic_list == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
// List all services for this device
@ -941,7 +1030,9 @@ int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start
*characteristics = characteristic_list;
*characteristics_count = count;
return GATTLIB_SUCCESS;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
#endif
@ -961,10 +1052,17 @@ int gattlib_discover_desc(gattlib_connection_t* connection, gattlib_descriptor_t
int get_bluez_device_from_mac(struct _gattlib_adapter *adapter, const char *mac_address, OrgBluezDevice1 **bluez_device1)
{
GError *error = NULL;
char object_path[100];
int ret;
char object_path[GATTLIB_DBUS_OBJECT_PATH_SIZE_MAX];
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
if (adapter->backend.adapter_proxy == NULL) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_NO_ADAPTER;
}
@ -974,6 +1072,9 @@ int get_bluez_device_from_mac(struct _gattlib_adapter *adapter, const char *mac_
get_device_path_from_mac(NULL, mac_address, object_path, sizeof(object_path));
}
// No need to keep the mutex longer. After it is DBUS specific operations not depending on gattlib structure
g_rec_mutex_unlock(&m_gattlib_mutex);
*bluez_device1 = org_bluez_device1_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
@ -982,10 +1083,9 @@ int get_bluez_device_from_mac(struct _gattlib_adapter *adapter, const char *mac_
NULL,
&error);
if (error) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s", error->message);
g_error_free(error);
return ret;
return GATTLIB_ERROR_DBUS_WITH_ERROR(error);
}
return GATTLIB_SUCCESS;
@ -993,15 +1093,31 @@ int get_bluez_device_from_mac(struct _gattlib_adapter *adapter, const char *mac_
int gattlib_get_rssi(gattlib_connection_t *connection, int16_t *rssi)
{
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
}
if (rssi == NULL) {
return GATTLIB_INVALID_PARAMETER;
}
*rssi = org_bluez_device1_get_rssi(connection->backend.device);
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
if (!gattlib_device_is_valid(connection->device)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
// device is actually a GObject. Increasing its reference counter prevents to
// be freed if the connection is released.
OrgBluezDevice1* dbus_device = connection->backend.device;
g_object_ref(dbus_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
*rssi = org_bluez_device1_get_rssi(dbus_device);
g_object_unref(dbus_device);
return GATTLIB_SUCCESS;
}
@ -1015,9 +1131,12 @@ int gattlib_get_rssi_from_mac(gattlib_adapter_t* adapter, const char *mac_addres
return GATTLIB_INVALID_PARAMETER;
}
//
// No need of locking the mutex in this function. get_bluez_device_from_mac() ensures the adapter is valid.
//
ret = get_bluez_device_from_mac(adapter, mac_address, &bluez_device1);
if (ret != GATTLIB_SUCCESS) {
g_object_unref(bluez_device1);
return ret;
}

View File

@ -6,10 +6,12 @@
#include "gattlib_internal.h"
// Keep track of the allocated adapters to avoid an adapter to be freed twice.
// It could happen when using Python wrapper.
static GSList *m_adapter_list;
static GMutex m_adapter_list_mutex;
// This recursive mutex ensures all gattlib objects can be accessed in a multi-threaded environment
// The recursive mutex allows a same thread to lock twice the mutex without being blocked by itself.
GRecMutex m_gattlib_mutex;
// This structure is used for inter-thread communication
struct gattlib_signal m_gattlib_signal;
int gattlib_adapter_open(const char* adapter_name, gattlib_adapter_t** adapter) {
@ -57,17 +59,23 @@ int gattlib_adapter_open(const char* adapter_name, gattlib_adapter_t** adapter)
// Initialize stucture
gattlib_adapter->name = strdup(adapter_name);
gattlib_adapter->reference_counter = 1;
gattlib_adapter->backend.adapter_proxy = adapter_proxy;
g_mutex_lock(&m_adapter_list_mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
m_adapter_list = g_slist_append(m_adapter_list, gattlib_adapter);
g_mutex_unlock(&m_adapter_list_mutex);
*adapter = gattlib_adapter;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
const char *gattlib_adapter_get_name(gattlib_adapter_t* adapter) {
//
// Note: There is a risk we access the memory when it has been freed
// What we should do is to take 'm_gattlib_mutex', then to check the adapter is valid
// then to duplicate the string
//
return adapter->name;
}
@ -84,8 +92,6 @@ gattlib_adapter_t* init_default_adapter(void) {
}
GDBusObjectManager *get_device_manager_from_adapter(gattlib_adapter_t* gattlib_adapter, GError **error) {
g_mutex_lock(&m_adapter_list_mutex);
if (gattlib_adapter->backend.device_manager) {
goto EXIT;
}
@ -103,12 +109,10 @@ GDBusObjectManager *get_device_manager_from_adapter(gattlib_adapter_t* gattlib_a
NULL, NULL, NULL, NULL,
error);
if (gattlib_adapter->backend.device_manager == NULL) {
g_mutex_unlock(&m_adapter_list_mutex);
return NULL;
}
EXIT:
g_mutex_unlock(&m_adapter_list_mutex);
return gattlib_adapter->backend.device_manager;
}
@ -138,7 +142,13 @@ static void device_manager_on_added_device1_signal(const char* device1_path, gat
return;
}
g_rec_mutex_lock(&gattlib_adapter->mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(device1);
return;
}
//TODO: Add support for connected device with 'gboolean org_bluez_device1_get_connected (OrgBluezDevice1 *object);'
// When the device is connected, we potentially need to initialize some attributes
@ -147,7 +157,7 @@ static void device_manager_on_added_device1_signal(const char* device1_path, gat
gattlib_on_discovered_device(gattlib_adapter, device1);
}
g_rec_mutex_unlock(&gattlib_adapter->mutex);
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(device1);
}
}
@ -196,10 +206,6 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
const char* proxy_object_path = g_dbus_proxy_get_object_path(interface_proxy);
gattlib_adapter_t* gattlib_adapter = user_data;
if (gattlib_adapter->backend.device_manager == NULL) {
return;
}
// Count number of invalidated properties
size_t invalidated_properties_count = 0;
if (invalidated_properties != NULL) {
@ -216,6 +222,16 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
g_variant_print(changed_properties, TRUE),
invalidated_properties_count);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
goto EXIT;
}
if (gattlib_adapter->backend.device_manager == NULL) {
goto EXIT;
}
// Check if the object is a 'org.bluez.Device1'
if (strcmp(g_dbus_proxy_get_interface_name(interface_proxy), "org.bluez.Device1") == 0) {
// It is a 'org.bluez.Device1'
@ -229,10 +245,10 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
if (error) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s", error->message);
g_error_free(error);
return;
goto EXIT;
} else if (device1 == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Unexpected NULL device");
return;
goto EXIT;
}
// Check if the device has been disconnected
@ -241,8 +257,6 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
GVariant* has_rssi = g_variant_dict_lookup_value(&dict, "RSSI", NULL);
GVariant* has_manufacturer_data = g_variant_dict_lookup_value(&dict, "ManufacturerData", NULL);
g_rec_mutex_lock(&gattlib_adapter->mutex);
enum _gattlib_device_state old_device_state = gattlib_device_get_state(gattlib_adapter, proxy_object_path);
if (old_device_state == NOT_FOUND) {
@ -254,11 +268,12 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
}
}
g_rec_mutex_unlock(&gattlib_adapter->mutex);
g_variant_dict_end(&dict);
g_object_unref(device1);
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
/**
@ -267,11 +282,11 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
* It either called when we wait for BLE scan to complete or when we close the BLE adapter
*/
static void _wait_scan_loop_stop_scanning(gattlib_adapter_t* gattlib_adapter) {
g_mutex_lock(&gattlib_adapter->backend.ble_scan.scan_loop.mutex);
while (gattlib_adapter->backend.ble_scan.is_scanning) {
g_cond_wait(&gattlib_adapter->backend.ble_scan.scan_loop.cond, &gattlib_adapter->backend.ble_scan.scan_loop.mutex);
g_mutex_lock(&m_gattlib_signal.mutex);
while (gattlib_adapter_is_scanning(gattlib_adapter)) {
g_cond_wait(&m_gattlib_signal.condition, &m_gattlib_signal.mutex);
}
g_mutex_unlock(&gattlib_adapter->backend.ble_scan.scan_loop.mutex);
g_mutex_unlock(&m_gattlib_signal.mutex);
}
/**
@ -280,16 +295,26 @@ static void _wait_scan_loop_stop_scanning(gattlib_adapter_t* gattlib_adapter) {
static gboolean _stop_scan_on_timeout(gpointer data) {
gattlib_adapter_t* gattlib_adapter = data;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return FALSE;
}
if (gattlib_adapter->backend.ble_scan.is_scanning) {
g_mutex_lock(&gattlib_adapter->backend.ble_scan.scan_loop.mutex);
g_mutex_lock(&m_gattlib_signal.mutex);
gattlib_adapter->backend.ble_scan.is_scanning = false;
g_cond_broadcast(&gattlib_adapter->backend.ble_scan.scan_loop.cond);
g_mutex_unlock(&gattlib_adapter->backend.ble_scan.scan_loop.mutex);
m_gattlib_signal.signals |= GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING;
g_cond_broadcast(&m_gattlib_signal.condition);
g_mutex_unlock(&m_gattlib_signal.mutex);
}
// Unset timeout ID to not try removing it
gattlib_adapter->backend.ble_scan.ble_scan_timeout_id = 0;
g_rec_mutex_unlock(&m_gattlib_mutex);
GATTLIB_LOG(GATTLIB_DEBUG, "BLE scan is stopped after scanning time has expired.");
return FALSE;
}
@ -301,6 +326,12 @@ static gboolean _stop_scan_on_timeout(gpointer data) {
static void* _ble_scan_loop_thread(void* args) {
gattlib_adapter_t* gattlib_adapter = args;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
goto EXIT;
}
if (gattlib_adapter->backend.ble_scan.ble_scan_timeout_id > 0) {
GATTLIB_LOG(GATTLIB_WARNING, "A BLE scan seems to already be in progress.");
}
@ -314,11 +345,20 @@ static void* _ble_scan_loop_thread(void* args) {
_stop_scan_on_timeout, gattlib_adapter);
}
g_rec_mutex_unlock(&m_gattlib_mutex);
// Wait for the BLE scan to be explicitely stopped by 'gattlib_adapter_scan_disable()' or timeout.
_wait_scan_loop_stop_scanning(gattlib_adapter);
// Note: The function only resumes when loop timeout as expired or g_main_loop_quit has been called.
g_rec_mutex_lock(&m_gattlib_mutex);
// Confirm gattlib_adapter is still valid
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
goto EXIT;
}
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.added_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.removed_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.changed_signal_id);
@ -326,7 +366,9 @@ static void* _ble_scan_loop_thread(void* args) {
// Ensure BLE device discovery is stopped
gattlib_adapter_scan_disable(gattlib_adapter);
return 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return NULL;
}
static int _gattlib_adapter_scan_enable_with_filter(gattlib_adapter_t* adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
@ -443,12 +485,19 @@ int gattlib_adapter_scan_enable_with_filter(gattlib_adapter_t* adapter, uuid_t *
gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
{
GError *error = NULL;
int ret;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
discovered_device_cb, timeout, user_data);
if (ret != GATTLIB_SUCCESS) {
return ret;
goto EXIT;
}
adapter->backend.ble_scan.is_scanning = true;
@ -457,42 +506,67 @@ int gattlib_adapter_scan_enable_with_filter(gattlib_adapter_t* adapter, uuid_t *
if (adapter->backend.ble_scan.scan_loop_thread == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create BLE scan thread: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_INTERNAL;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
g_mutex_lock(&adapter->backend.ble_scan.scan_loop.mutex);
while (adapter->backend.ble_scan.is_scanning) {
g_cond_wait(&adapter->backend.ble_scan.scan_loop.cond, &adapter->backend.ble_scan.scan_loop.mutex);
// We need to release the mutex to ensure we leave the other thread to signal us
g_rec_mutex_unlock(&m_gattlib_mutex);
g_mutex_lock(&m_gattlib_signal.mutex);
while (gattlib_adapter_is_scanning(adapter)) {
g_cond_wait(&m_gattlib_signal.condition, &m_gattlib_signal.mutex);
}
g_mutex_unlock(&m_gattlib_signal.mutex);
// Get the mutex again
g_rec_mutex_lock(&m_gattlib_mutex);
// Ensure the adapter is still valid when we get the mutex again
if (!gattlib_adapter_is_valid(adapter)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
// Free thread
g_thread_unref(adapter->backend.ble_scan.scan_loop_thread);
adapter->backend.ble_scan.scan_loop_thread = NULL;
g_mutex_unlock(&adapter->backend.ble_scan.scan_loop.mutex);
return 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_scan_enable_with_filter_non_blocking(gattlib_adapter_t* adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
{
GError *error = NULL;
int ret;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
discovered_device_cb, timeout, user_data);
if (ret != GATTLIB_SUCCESS) {
return ret;
goto EXIT;
}
adapter->backend.ble_scan.scan_loop_thread = g_thread_try_new("gattlib_ble_scan", _ble_scan_loop_thread, adapter, &error);
if (adapter->backend.ble_scan.scan_loop_thread == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create BLE scan thread: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_INTERNAL;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
return 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_scan_enable(gattlib_adapter_t* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
@ -505,14 +579,21 @@ int gattlib_adapter_scan_enable(gattlib_adapter_t* adapter, gattlib_discovered_d
int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter) {
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (adapter->backend.adapter_proxy == NULL) {
GATTLIB_LOG(GATTLIB_INFO, "Could not disable BLE scan. No BLE adapter setup.");
return GATTLIB_NO_ADAPTER;
ret = GATTLIB_NO_ADAPTER;
goto EXIT;
}
g_mutex_lock(&adapter->backend.ble_scan.scan_loop.mutex);
if (!org_bluez_adapter1_get_discovering(adapter->backend.adapter_proxy)) {
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping (1).");
goto EXIT;
@ -526,6 +607,7 @@ int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter) {
org_bluez_adapter1_call_stop_discovery_sync(adapter->backend.adapter_proxy, NULL, &error);
if (error != NULL) {
if (((error->domain == 238) || (error->domain == 239)) && (error->code == 36)) {
GATTLIB_LOG(GATTLIB_WARNING, "No bluetooth scan has been started.");
// Correspond to error: GDBus.Error:org.bluez.Error.Failed: No discovery started
goto EXIT;
} else {
@ -539,7 +621,10 @@ int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter) {
// Stop BLE scan loop thread
if (adapter->backend.ble_scan.is_scanning) {
adapter->backend.ble_scan.is_scanning = false;
g_cond_broadcast(&adapter->backend.ble_scan.scan_loop.cond);
g_mutex_lock(&m_gattlib_signal.mutex);
m_gattlib_signal.signals |= GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING;
g_cond_broadcast(&m_gattlib_signal.condition);
g_mutex_unlock(&m_gattlib_signal.mutex);
}
// Remove timeout
@ -549,20 +634,33 @@ int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter) {
}
EXIT:
g_mutex_unlock(&adapter->backend.ble_scan.scan_loop.mutex);
return GATTLIB_SUCCESS;
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_close(gattlib_adapter_t* adapter) {
bool are_devices_disconnected;
int ret = GATTLIB_SUCCESS;
//
// TODO: Should we use reference counting to be able to open multiple times an adapter
// without freeing it on the first gattlib_adapter_close()
//
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
are_devices_disconnected = gattlib_devices_are_disconnected(adapter);
if (!are_devices_disconnected) {
GATTLIB_LOG(GATTLIB_ERROR, "Adapter cannot be closed as some devices are not disconnected");
return GATTLIB_BUSY;
ret = GATTLIB_BUSY;
goto EXIT;
}
g_mutex_lock(&m_adapter_list_mutex);
GSList *adapter_entry = g_slist_find(m_adapter_list, adapter);
if (adapter_entry == NULL) {
GATTLIB_LOG(GATTLIB_WARNING, "Adapter has already been closed");
@ -578,6 +676,34 @@ int gattlib_adapter_close(gattlib_adapter_t* adapter) {
g_thread_join(adapter->backend.ble_scan.scan_loop_thread);
}
// Unref/Free the adapter
gattlib_adapter_unref(adapter);
// Remove adapter from the global list
m_adapter_list = g_slist_remove(m_adapter_list, adapter);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_ref(gattlib_adapter_t* adapter) {
g_rec_mutex_lock(&m_gattlib_mutex);
adapter->reference_counter++;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
int gattlib_adapter_unref(gattlib_adapter_t* adapter) {
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
adapter->reference_counter--;
if (adapter->reference_counter > 0) {
goto EXIT;
}
// Ensure the thread is freed on adapter closing
if (adapter->backend.ble_scan.scan_loop_thread) {
g_thread_unref(adapter->backend.ble_scan.scan_loop_thread);
@ -603,12 +729,7 @@ int gattlib_adapter_close(gattlib_adapter_t* adapter) {
free(adapter);
// Remove adapter from the global list
m_adapter_list = g_slist_remove(m_adapter_list, adapter);
adapter = NULL;
EXIT:
g_mutex_unlock(&m_adapter_list_mutex);
return GATTLIB_SUCCESS;
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}

View File

@ -116,13 +116,33 @@ int gattlib_get_advertisement_data(gattlib_connection_t *connection,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
{
int ret;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
return get_advertisement_data_from_device(connection->backend.device,
advertisement_data, advertisement_data_count,
manufacturer_id, manufacturer_data, manufacturer_data_size);
if (!gattlib_device_is_valid(connection->device)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
// device is actually a GObject. Increasing its reference counter prevents to
// be freed if the connection is released.
OrgBluezDevice1* dbus_device = connection->backend.device;
g_object_ref(dbus_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
ret = get_advertisement_data_from_device(dbus_device,
advertisement_data, advertisement_data_count,
manufacturer_id, manufacturer_data, manufacturer_data_size);
g_object_unref(dbus_device);
return ret;
}
int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const char *mac_address,
@ -132,10 +152,14 @@ int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const ch
OrgBluezDevice1 *bluez_device1;
int ret;
//
// No need of locking the mutex in this function. It is already done by get_bluez_device_from_mac()
// and get_advertisement_data_from_device() does not depend on gattlib objects
//
ret = get_bluez_device_from_mac(adapter, mac_address, &bluez_device1);
if (ret != GATTLIB_SUCCESS) {
g_object_unref(bluez_device1);
return ret;
goto EXIT;
}
ret = get_advertisement_data_from_device(bluez_device1,
@ -144,6 +168,7 @@ int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const ch
g_object_unref(bluez_device1);
EXIT:
return ret;
}

View File

@ -36,6 +36,11 @@
#define GATTLIB_DEFAULT_ADAPTER "hci0"
// Arbitrary size used to build DBUS object path from adapter name and mac address.
// Otherwise DBUs Object path are unlimited:
// See: https://dbus.freedesktop.org/doc/api/html/group__DBusProtocol.html#ga80186ac58d031d83127d1ad6644b0011
#define GATTLIB_DBUS_OBJECT_PATH_SIZE_MAX 200
struct _gattlib_connection_backend {
char* device_object_path;
OrgBluezDevice1* device;
@ -69,11 +74,6 @@ struct _gattlib_adapter_backend {
GThread *scan_loop_thread; // Thread used to run the '_scan_loop()' when non-blocking
bool is_scanning;
struct {
GMutex mutex;
GCond cond;
} scan_loop;
uint32_t enabled_filters;
} ble_scan;
};

View File

@ -103,13 +103,20 @@ static bool handle_dbus_battery_from_uuid(struct _gattlib_connection_backend* ba
struct dbus_characteristic get_characteristic_from_uuid(gattlib_connection_t* connection, const uuid_t* uuid) {
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
GDBusObjectManager *device_manager;
bool is_battery_level_uuid = false;
struct dbus_characteristic dbus_characteristic = {
.type = TYPE_NONE
.type = TYPE_NONE
};
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
goto EXIT;
}
device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized (%d, %d).", error->domain, error->code);
@ -117,7 +124,7 @@ struct dbus_characteristic get_characteristic_from_uuid(gattlib_connection_t* co
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return dbus_characteristic; // Return characteristic of type TYPE_NONE
goto EXIT; // Return characteristic of type TYPE_NONE
}
// Some GATT Characteristics are handled by D-BUS
@ -125,7 +132,7 @@ struct dbus_characteristic get_characteristic_from_uuid(gattlib_connection_t* co
is_battery_level_uuid = true;
} else if (gattlib_uuid_cmp(uuid, &m_ccc_uuid) == 0) {
GATTLIB_LOG(GATTLIB_ERROR, "Error: Bluez v5.42+ does not expose Client Characteristic Configuration Descriptor through DBUS interface");
return dbus_characteristic;
goto EXIT;
}
GList *l;
@ -162,18 +169,27 @@ struct dbus_characteristic get_characteristic_from_uuid(gattlib_connection_t* co
}
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return dbus_characteristic;
}
static struct dbus_characteristic get_characteristic_from_handle(gattlib_connection_t* connection, unsigned int handle) {
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
unsigned int char_handle;
GDBusObjectManager *device_manager;
struct dbus_characteristic dbus_characteristic = {
.type = TYPE_NONE
.type = TYPE_NONE
};
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
goto EXIT;
}
device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized (%d, %d).", error->domain, error->code);
@ -181,7 +197,7 @@ static struct dbus_characteristic get_characteristic_from_handle(gattlib_connect
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return dbus_characteristic;
goto EXIT;
}
for (GList *l = connection->backend.dbus_objects; l != NULL; l = l->next) {
@ -209,6 +225,8 @@ static struct dbus_characteristic get_characteristic_from_handle(gattlib_connect
}
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return dbus_characteristic;
}
@ -272,6 +290,11 @@ static int read_battery_level(struct dbus_characteristic *dbus_characteristic, v
#endif
int gattlib_read_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, void **buffer, size_t *buffer_len) {
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -297,6 +320,11 @@ int gattlib_read_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, vo
int gattlib_read_char_by_uuid_async(gattlib_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) {
int ret = GATTLIB_SUCCESS;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -391,6 +419,11 @@ int gattlib_write_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, c
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -410,6 +443,11 @@ int gattlib_write_char_by_handle(gattlib_connection_t* connection, uint16_t hand
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_handle() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -425,6 +463,11 @@ int gattlib_write_without_response_char_by_uuid(gattlib_connection_t* connection
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -444,6 +487,11 @@ int gattlib_write_without_response_char_by_handle(gattlib_connection_t* connecti
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_handle() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;

View File

@ -31,6 +31,13 @@ gboolean on_handle_battery_level_property_change(
g_variant_print(arg_changed_properties, TRUE),
arg_invalidated_properties);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return FALSE;
}
if (gattlib_has_valid_handler(&connection->notification)) {
// Retrieve 'Value' from 'arg_changed_properties'
if (g_variant_n_children (arg_changed_properties) > 0) {
@ -54,6 +61,7 @@ gboolean on_handle_battery_level_property_change(
g_variant_iter_free(iter);
}
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return TRUE;
}
#endif
@ -66,6 +74,13 @@ static gboolean on_handle_characteristic_property_change(
{
gattlib_connection_t* connection = user_data;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return FALSE;
}
if (gattlib_has_valid_handler(&connection->notification)) {
GVariantDict dict;
g_variant_dict_init(&dict, arg_changed_properties);
@ -96,6 +111,8 @@ static gboolean on_handle_characteristic_property_change(
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "on_handle_characteristic_property_change: not a notification handler");
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return TRUE;
}
@ -142,10 +159,17 @@ static gboolean on_handle_characteristic_indication(
}
static int connect_signal_to_characteristic_uuid(gattlib_connection_t* connection, const uuid_t* uuid, void *callback) {
int ret;
int ret = GATTLIB_SUCCESS;
assert(callback != NULL);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
char uuid_str[MAX_LEN_UUID_STR + 1];
@ -153,7 +177,8 @@ static int connect_signal_to_characteristic_uuid(gattlib_connection_t* connectio
gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str));
GATTLIB_LOG(GATTLIB_ERROR, "GATT characteristic '%s' not found", uuid_str);
return GATTLIB_NOT_FOUND;
ret = GATTLIB_NOT_FOUND;
goto EXIT;
}
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
@ -163,7 +188,8 @@ static int connect_signal_to_characteristic_uuid(gattlib_connection_t* connectio
G_CALLBACK (on_handle_battery_level_property_change),
connection);
return GATTLIB_SUCCESS;
ret = GATTLIB_SUCCESS;
goto EXIT;
} else {
assert(dbus_characteristic.type == TYPE_GATT);
}
@ -176,33 +202,47 @@ static int connect_signal_to_characteristic_uuid(gattlib_connection_t* connectio
connection);
if (signal_id == 0) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connect signal to DBus GATT notification");
return GATTLIB_ERROR_DBUS;
ret = GATTLIB_ERROR_DBUS;
goto EXIT;
}
// Add signal to the list
struct gattlib_notification_handle *notification_handle = calloc(sizeof(struct gattlib_notification_handle), 1);
if (notification_handle == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
notification_handle->gatt = dbus_characteristic.gatt;
notification_handle->signal_id = signal_id;
memcpy(&notification_handle->uuid, uuid, sizeof(*uuid));
connection->backend.notified_characteristics = g_list_append(connection->backend.notified_characteristics, notification_handle);
// Note: An optimisation could be to release mutex here after increasing reference counter of 'dbus_characteristic.gatt'
GError *error = NULL;
org_bluez_gatt_characteristic1_call_start_notify_sync(dbus_characteristic.gatt, NULL, &error);
if (error) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to start DBus GATT notification: %s", error->message);
g_error_free(error);
return ret;
} else {
return GATTLIB_SUCCESS;
goto EXIT;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
static int disconnect_signal_to_characteristic_uuid(gattlib_connection_t* connection, const uuid_t* uuid, void *callback) {
struct gattlib_notification_handle *notification_handle = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
// Find notification handle
for (GList *l = connection->backend.notified_characteristics; l != NULL; l = l->next) {
@ -216,7 +256,8 @@ static int disconnect_signal_to_characteristic_uuid(gattlib_connection_t* connec
}
if (notification_handle == NULL) {
return GATTLIB_NOT_FOUND;
ret = GATTLIB_NOT_FOUND;
goto EXIT;
}
g_signal_handler_disconnect(notification_handle->gatt, notification_handle->signal_id);
@ -230,10 +271,13 @@ static int disconnect_signal_to_characteristic_uuid(gattlib_connection_t* connec
if (error) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to stop DBus GATT notification: %s", error->message);
g_error_free(error);
return GATTLIB_NOT_FOUND;
} else {
return GATTLIB_SUCCESS;
ret = GATTLIB_NOT_FOUND;
goto EXIT;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_notification_start(gattlib_connection_t* connection, const uuid_t* uuid) {

View File

@ -31,13 +31,19 @@ int gattlib_write_char_stream_close(gattlib_stream_t *stream)
int gattlib_write_char_by_uuid_stream_open(gattlib_connection_t* connection, uuid_t* uuid, gattlib_stream_t **stream, uint16_t *mtu)
{
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
GError *error = NULL;
GUnixFDList *fd_list;
GVariant *out_fd;
int ret;
int fd;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
GVariantBuilder *variant_options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
org_bluez_gatt_characteristic1_call_acquire_write_sync(