Consolidate device state to prevent concurrent accesses

latest-fixes
Olivier Martin 2024-04-03 15:16:38 +02:00
parent fab0e8fa67
commit aa6a7b79bb
5 changed files with 233 additions and 0 deletions

View File

@ -0,0 +1,198 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
const char* device_state_str[] = {
"NOT_FOUND",
"CONNECTING",
"CONNECTED",
"DISCONNECTING",
"DISCONNECTED"
};
static gint _compare_device_with_device_id(gconstpointer a, gconstpointer b) {
const struct _gattlib_device* device = a;
const char* device_id = b;
return g_ascii_strcasecmp(device->device_id, device_id);
}
static GSList* _find_device_with_device_id(struct gattlib_adapter* adapter, const char* device_id) {
return g_slist_find_custom(adapter->devices, device_id, _compare_device_with_device_id);
}
struct _gattlib_device* gattlib_device_get_device(void* adapter, const char* device_id) {
struct gattlib_adapter* gattlib_adapter = adapter;
struct _gattlib_device* device = NULL;
g_rec_mutex_lock(&gattlib_adapter->mutex);
GSList *item = _find_device_with_device_id(gattlib_adapter, device_id);
if (item == NULL) {
goto EXIT;
}
device = (struct _gattlib_device*)item->data;
EXIT:
g_rec_mutex_unlock(&gattlib_adapter->mutex);
return device;
}
enum _gattlib_device_state gattlib_device_get_state(void* adapter, const char* device_id) {
struct gattlib_adapter* gattlib_adapter = adapter;
enum _gattlib_device_state state = NOT_FOUND;
g_rec_mutex_lock(&gattlib_adapter->mutex);
struct _gattlib_device* device = gattlib_device_get_device(adapter, device_id);
if (device != NULL) {
state = device->state;
}
g_rec_mutex_unlock(&gattlib_adapter->mutex);
return state;
}
int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib_device_state new_state) {
struct gattlib_adapter* gattlib_adapter = adapter;
enum _gattlib_device_state old_state;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&gattlib_adapter->mutex);
old_state = gattlib_device_get_state(adapter, device_id);
if (old_state == NOT_FOUND) {
//
// The device does not exist yet
//
if (new_state != NOT_FOUND) {
struct _gattlib_device* device = calloc(sizeof(struct _gattlib_device), 1);
if (device == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_device_set_state: Cannot allocate device");
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: Set initial state %s", device_id, device_state_str[new_state]);
device->adapter = adapter;
device->device_id = g_strdup(device_id);
device->state = new_state;
gattlib_adapter->devices = g_slist_append(gattlib_adapter->devices, device);
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: No state to set", device_id);
}
} else if (new_state == NOT_FOUND) {
//
// The device needs to be remove and free
//
GSList *item = _find_device_with_device_id(gattlib_adapter, device_id);
if (item == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_device_set_state: The device is not present. It is not expected");
ret = GATTLIB_UNEXPECTED;
goto EXIT;
}
struct _gattlib_device* device = item->data;
switch (device->state) {
case DISCONNECTED:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Free device %p", device);
gattlib_adapter->devices = g_slist_remove(gattlib_adapter->devices, device);
free(device);
break;
case CONNECTING:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Connecting device needs to be removed - ignore it");
ret = GATTLIB_UNEXPECTED;
break;
case CONNECTED:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Connecting device needs to be removed - ignore it");
ret = GATTLIB_UNEXPECTED;
break;
case DISCONNECTING:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Connecting device needs to be removed - ignore it");
ret = GATTLIB_UNEXPECTED;
break;
case NOT_FOUND:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Not found device needs to be removed - ignore it");
ret = GATTLIB_UNEXPECTED;
break;
}
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: Set state %s", device_id, device_state_str[new_state]);
struct _gattlib_device* device = gattlib_device_get_device(adapter, device_id);
device->state = new_state;
}
EXIT:
g_rec_mutex_unlock(&gattlib_adapter->mutex);
return ret;
}
static void _gattlib_device_free(gpointer data) {
struct _gattlib_device* device = data;
switch (device->state) {
case DISCONNECTED:
free(device);
break;
default:
GATTLIB_LOG(GATTLIB_WARNING, "Memory of the BLE device '%s' has not been freed because in state %s",
device->device_id, device_state_str[device->state]);
}
}
int gattlib_devices_free(void* adapter) {
struct gattlib_adapter* gattlib_adapter = adapter;
g_rec_mutex_lock(&gattlib_adapter->mutex);
g_slist_free_full(gattlib_adapter->devices, _gattlib_device_free);
g_rec_mutex_unlock(&gattlib_adapter->mutex);
return 0;
}
static void _gattlib_device_is_disconnected(gpointer data, gpointer user_data) {
struct _gattlib_device* device = data;
bool* devices_are_disconnected_ptr = user_data;
if (device->state != DISCONNECTED) {
*devices_are_disconnected_ptr = false;
}
}
int gattlib_devices_are_disconnected(void* adapter) {
struct gattlib_adapter* gattlib_adapter = adapter;
bool devices_are_disconnected = true;
g_rec_mutex_lock(&gattlib_adapter->mutex);
g_slist_foreach(gattlib_adapter->devices, _gattlib_device_is_disconnected, &devices_are_disconnected);
g_rec_mutex_unlock(&gattlib_adapter->mutex);
return devices_are_disconnected;
}
#ifdef DEBUG
static void _gattlib_device_dump_state(gpointer data, gpointer user_data) {
struct _gattlib_device* device = data;
GATTLIB_LOG(GATTLIB_DEBUG, "\t%s: %s", device->device_id, device_state_str[device->state]);
}
void gattlib_devices_dump_state(void* adapter) {
struct gattlib_adapter* gattlib_adapter = adapter;
g_rec_mutex_lock(&gattlib_adapter->mutex);
GATTLIB_LOG(GATTLIB_DEBUG, "Device list:");
g_slist_foreach(gattlib_adapter->devices, _gattlib_device_dump_state, NULL);
g_rec_mutex_unlock(&gattlib_adapter->mutex);
}
#endif

View File

@ -48,12 +48,26 @@ struct gattlib_handler {
#endif
};
enum _gattlib_device_state {
NOT_FOUND = 0,
CONNECTING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
};
struct _gattlib_device {
// Context specific to the backend implementation (eg: dbus backend)
void* context;
void* adapter;
// On some platform, the name could be a UUID, on others its the DBUS device path
char* device_id;
GMutex device_mutex;
// We keep the state to prevent concurrent connecting/connected/disconnecting operation
enum _gattlib_device_state state;
struct {
// Used by gattlib_disconnection when we want to wait for the disconnection to be effective
GCond condition;
@ -82,6 +96,17 @@ void gattlib_notification_device_thread(gpointer data, gpointer user_data);
*/
void gattlib_connection_free(gatt_connection_t* connection);
extern const char* device_state_str[];
struct _gattlib_device* gattlib_device_get_device(void* adapter, const char* device_id);
enum _gattlib_device_state gattlib_device_get_state(void* adapter, const char* device_id);
int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib_device_state new_state);
int gattlib_devices_are_disconnected(void* adapter);
int gattlib_devices_free(void* adapter);
#ifdef DEBUG
void gattlib_devices_dump_state(void* adapter);
#endif
#if defined(WITH_PYTHON)
// Callback used by Python to create arguments used by native callback
void* gattlib_python_callback_args(PyObject* python_callback, PyObject* python_args);

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_device_state_management.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_eddystone.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_connected_device.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_disconnected_device.c

View File

@ -596,6 +596,8 @@ int gattlib_adapter_close(void* adapter)
gattlib_adapter->adapter_name = NULL;
}
gattlib_devices_free(gattlib_adapter);
free(gattlib_adapter);
// Remove adapter from the global list

View File

@ -62,6 +62,9 @@ struct gattlib_adapter {
OrgBluezAdapter1 *adapter_proxy;
char* adapter_name;
// The recursive mutex allows to ensure sensible operations are always covered by a mutex in a same thread
GRecMutex mutex;
// Internal attributes only needed during BLE scanning
struct {
// This list is used to stored discovered devices during BLE scan.
@ -84,6 +87,10 @@ struct gattlib_adapter {
struct gattlib_handler discovered_device_callback;
} ble_scan;
// List of `struct _gattlib_device`. This list allows to know weither a device is
// discovered/disconnected/connecting/connected/disconnecting.
GSList *devices;
};
struct dbus_characteristic {