mirror of https://github.com/labapart/gattlib
446 lines
14 KiB
C
446 lines
14 KiB
C
/*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright (c) 2016-2021, Olivier Martin <olivier@labapart.org>
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "gattlib_internal.h"
|
|
|
|
#define BLUEZ_GATT_WRITE_VALUE_TYPE_MASK (0x7)
|
|
#define BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE (1 << 0)
|
|
#define BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE (1 << 1)
|
|
#define BLUEZ_GATT_WRITE_VALUE_TYPE_RELIABLE_WRITE (1 << 2)
|
|
|
|
|
|
const uuid_t m_battery_level_uuid = CREATE_UUID16(0x2A19);
|
|
static const uuid_t m_ccc_uuid = CREATE_UUID16(0x2902);
|
|
|
|
|
|
static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_context, const uuid_t* uuid,
|
|
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
|
|
{
|
|
OrgBluezGattCharacteristic1 *characteristic = NULL;
|
|
|
|
*error = NULL;
|
|
characteristic = org_bluez_gatt_characteristic1_proxy_new_for_bus_sync (
|
|
G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
|
"org.bluez",
|
|
object_path,
|
|
NULL,
|
|
error);
|
|
if (characteristic) {
|
|
if (uuid != NULL) {
|
|
uuid_t characteristic_uuid;
|
|
const gchar *characteristic_uuid_str = org_bluez_gatt_characteristic1_get_uuid(characteristic);
|
|
if (characteristic_uuid_str == NULL) {
|
|
// It should not be expected to get NULL from GATT characteristic UUID but we still test it
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Error: %s path unexpectly returns a NULL UUID.", object_path);
|
|
g_object_unref(characteristic);
|
|
return false;
|
|
}
|
|
|
|
gattlib_string_to_uuid(characteristic_uuid_str, strlen(characteristic_uuid_str) + 1, &characteristic_uuid);
|
|
|
|
if (gattlib_uuid_cmp(uuid, &characteristic_uuid) != 0) {
|
|
g_object_unref(characteristic);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We found the right characteristic, now we check if it's the right device.
|
|
*error = NULL;
|
|
OrgBluezGattService1* service = org_bluez_gatt_service1_proxy_new_for_bus_sync (
|
|
G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
|
"org.bluez",
|
|
org_bluez_gatt_characteristic1_get_service(characteristic),
|
|
NULL,
|
|
error);
|
|
|
|
if (service) {
|
|
const bool found = !strcmp(conn_context->device_object_path, org_bluez_gatt_service1_get_device(service));
|
|
|
|
g_object_unref(service);
|
|
|
|
if (found) {
|
|
dbus_characteristic->gatt = characteristic;
|
|
dbus_characteristic->type = TYPE_GATT;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
g_object_unref(characteristic);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
|
static bool handle_dbus_battery_from_uuid(gattlib_context_t* conn_context, const uuid_t* uuid,
|
|
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
|
|
{
|
|
OrgBluezBattery1 *battery = NULL;
|
|
|
|
*error = NULL;
|
|
battery = org_bluez_battery1_proxy_new_for_bus_sync (
|
|
G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
|
"org.bluez",
|
|
object_path,
|
|
NULL,
|
|
error);
|
|
if (battery) {
|
|
dbus_characteristic->battery = battery;
|
|
dbus_characteristic->type = TYPE_BATTERY_LEVEL;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid) {
|
|
gattlib_context_t* conn_context = connection->context;
|
|
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter);
|
|
GError *error = NULL;
|
|
bool is_battery_level_uuid = false;
|
|
|
|
struct dbus_characteristic dbus_characteristic = {
|
|
.type = TYPE_NONE
|
|
};
|
|
|
|
if (device_manager == NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
|
|
return dbus_characteristic; // Return characteristic of type TYPE_NONE
|
|
}
|
|
|
|
// Some GATT Characteristics are handled by D-BUS
|
|
if (gattlib_uuid_cmp(uuid, &m_battery_level_uuid) == 0) {
|
|
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;
|
|
}
|
|
|
|
GList *l;
|
|
for (l = conn_context->dbus_objects; l != NULL; l = l->next) {
|
|
GDBusInterface *interface;
|
|
bool found = false;
|
|
GDBusObject *object = l->data;
|
|
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
|
|
|
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1");
|
|
if (interface) {
|
|
g_object_unref(interface);
|
|
|
|
found = handle_dbus_gattcharacteristic_from_path(conn_context, uuid, &dbus_characteristic, object_path, &error);
|
|
if (found) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found && is_battery_level_uuid) {
|
|
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
|
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.Battery1");
|
|
if (interface) {
|
|
g_object_unref(interface);
|
|
|
|
found = handle_dbus_battery_from_uuid(conn_context, uuid, &dbus_characteristic, object_path, &error);
|
|
if (found) {
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
GATTLIB_LOG(GATTLIB_ERROR, "You might use Bluez v5.48 with gattlib built for pre-v5.40");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return dbus_characteristic;
|
|
}
|
|
|
|
static struct dbus_characteristic get_characteristic_from_handle(gatt_connection_t* connection, int handle) {
|
|
gattlib_context_t* conn_context = connection->context;
|
|
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter);
|
|
GError *error = NULL;
|
|
int char_handle;
|
|
|
|
struct dbus_characteristic dbus_characteristic = {
|
|
.type = TYPE_NONE
|
|
};
|
|
|
|
if (device_manager == NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib context not initialized.");
|
|
return dbus_characteristic;
|
|
}
|
|
|
|
for (GList *l = conn_context->dbus_objects; l != NULL; l = l->next) {
|
|
GDBusInterface *interface;
|
|
bool found;
|
|
GDBusObject *object = l->data;
|
|
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
|
|
|
interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.GattCharacteristic1");
|
|
if (interface) {
|
|
g_object_unref(interface);
|
|
|
|
// Object path is in the form '/org/bluez/hci0/dev_DE_79_A2_A1_E9_FA/service0024'.
|
|
// We convert the last 4 hex characters into the handle
|
|
sscanf(object_path + strlen(object_path) - 4, "%x", &char_handle);
|
|
|
|
if (char_handle != handle) {
|
|
continue;
|
|
}
|
|
|
|
found = handle_dbus_gattcharacteristic_from_path(conn_context, NULL, &dbus_characteristic, object_path, &error);
|
|
if (found) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return dbus_characteristic;
|
|
}
|
|
|
|
static int read_gatt_characteristic(struct dbus_characteristic *dbus_characteristic, void **buffer, size_t* buffer_len) {
|
|
GVariant *out_value;
|
|
GError *error = NULL;
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
|
org_bluez_gatt_characteristic1_call_read_value_sync(
|
|
dbus_characteristic->gatt, &out_value, NULL, &error);
|
|
#else
|
|
GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
|
org_bluez_gatt_characteristic1_call_read_value_sync(
|
|
dbus_characteristic->gatt, g_variant_builder_end(options), &out_value, NULL, &error);
|
|
g_variant_builder_unref(options);
|
|
#endif
|
|
if (error != NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to read DBus GATT characteristic: %s", error->message);
|
|
g_error_free(error);
|
|
return GATTLIB_ERROR_DBUS;
|
|
}
|
|
|
|
gsize n_elements = 0;
|
|
gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar));
|
|
if (const_buffer) {
|
|
*buffer = malloc(n_elements);
|
|
if (*buffer == NULL) {
|
|
ret = GATTLIB_OUT_OF_MEMORY;
|
|
goto EXIT;
|
|
}
|
|
|
|
*buffer_len = n_elements;
|
|
memcpy(*buffer, const_buffer, n_elements);
|
|
} else {
|
|
*buffer_len = 0;
|
|
}
|
|
|
|
EXIT:
|
|
g_variant_unref(out_value);
|
|
return ret;
|
|
}
|
|
|
|
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
|
|
static int read_battery_level(struct dbus_characteristic *dbus_characteristic, void** buffer, size_t* buffer_len) {
|
|
guchar percentage = org_bluez_battery1_get_percentage(dbus_characteristic->battery);
|
|
|
|
*buffer = malloc(sizeof(uint8_t));
|
|
if (buffer == NULL) {
|
|
*buffer_len = 0;
|
|
return GATTLIB_OUT_OF_MEMORY;
|
|
}
|
|
|
|
memcpy(*buffer, &percentage, sizeof(uint8_t));
|
|
*buffer_len = sizeof(uint8_t);
|
|
|
|
g_object_unref(dbus_characteristic->battery);
|
|
return GATTLIB_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void **buffer, size_t *buffer_len) {
|
|
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) {
|
|
return read_battery_level(&dbus_characteristic, buffer, buffer_len);
|
|
}
|
|
#endif
|
|
else {
|
|
int ret;
|
|
|
|
assert(dbus_characteristic.type == TYPE_GATT);
|
|
|
|
ret = read_gatt_characteristic(&dbus_characteristic, buffer, buffer_len);
|
|
|
|
g_object_unref(dbus_characteristic.gatt);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) {
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
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) {
|
|
//TODO: Having 'percentage' as a 'static' is a limitation when we would support multiple connections
|
|
static uint8_t percentage;
|
|
|
|
percentage = org_bluez_battery1_get_percentage(dbus_characteristic.battery);
|
|
|
|
gatt_read_cb((const void*)&percentage, sizeof(percentage));
|
|
|
|
return GATTLIB_SUCCESS;
|
|
} else {
|
|
assert(dbus_characteristic.type == TYPE_GATT);
|
|
}
|
|
#endif
|
|
|
|
GVariant *out_value;
|
|
GError *error = NULL;
|
|
|
|
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
|
org_bluez_gatt_characteristic1_call_read_value_sync(
|
|
dbus_characteristic.gatt, &out_value, NULL, &error);
|
|
#else
|
|
GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
|
org_bluez_gatt_characteristic1_call_read_value_sync(
|
|
dbus_characteristic.gatt, g_variant_builder_end(options), &out_value, NULL, &error);
|
|
g_variant_builder_unref(options);
|
|
#endif
|
|
if (error != NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to read DBus GATT characteristic: %s", error->message);
|
|
g_error_free(error);
|
|
ret = GATTLIB_ERROR_DBUS;
|
|
goto EXIT;
|
|
}
|
|
|
|
gsize n_elements;
|
|
gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar));
|
|
if (const_buffer) {
|
|
gatt_read_cb(const_buffer, n_elements);
|
|
}
|
|
|
|
g_object_unref(dbus_characteristic.gatt);
|
|
g_variant_unref(out_value);
|
|
|
|
EXIT:
|
|
return ret;
|
|
}
|
|
|
|
static int write_char(struct dbus_characteristic *dbus_characteristic, const void* buffer, size_t buffer_len, uint32_t options)
|
|
{
|
|
GVariant *value = g_variant_new_from_data(G_VARIANT_TYPE ("ay"), buffer, buffer_len, TRUE, NULL, NULL);
|
|
GError *error = NULL;
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
|
|
org_bluez_gatt_characteristic1_call_write_value_sync(dbus_characteristic->gatt, value, NULL, &error);
|
|
#else
|
|
GVariantBuilder *variant_options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
|
|
|
if ((options & BLUEZ_GATT_WRITE_VALUE_TYPE_MASK) == BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE) {
|
|
g_variant_builder_add(variant_options, "{sv}", "type", g_variant_new("s", "command"));
|
|
}
|
|
|
|
org_bluez_gatt_characteristic1_call_write_value_sync(dbus_characteristic->gatt, value, g_variant_builder_end(variant_options), NULL, &error);
|
|
g_variant_builder_unref(variant_options);
|
|
#endif
|
|
|
|
if (error != NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to write DBus GATT characteristic: %s", error->message);
|
|
g_error_free(error);
|
|
return GATTLIB_ERROR_DBUS;
|
|
}
|
|
|
|
//
|
|
// @note: No need to free `value` has it is freed by org_bluez_gatt_characteristic1_call_write_value_sync()
|
|
// See: https://developer.gnome.org/gio/stable/GDBusProxy.html#g-dbus-proxy-call
|
|
//
|
|
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
|
|
{
|
|
int ret;
|
|
|
|
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
|
if (dbus_characteristic.type == TYPE_NONE) {
|
|
return GATTLIB_NOT_FOUND;
|
|
} else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
|
return GATTLIB_NOT_SUPPORTED; // Battery level does not support write
|
|
} else {
|
|
assert(dbus_characteristic.type == TYPE_GATT);
|
|
}
|
|
|
|
ret = write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE);
|
|
|
|
g_object_unref(dbus_characteristic.gatt);
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
|
|
{
|
|
int ret;
|
|
|
|
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
|
|
if (dbus_characteristic.type == TYPE_NONE) {
|
|
return GATTLIB_NOT_FOUND;
|
|
}
|
|
|
|
ret = write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITH_RESPONSE);
|
|
|
|
g_object_unref(dbus_characteristic.gatt);
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
|
|
{
|
|
int ret;
|
|
|
|
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
|
|
if (dbus_characteristic.type == TYPE_NONE) {
|
|
return GATTLIB_NOT_FOUND;
|
|
} else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
|
|
return GATTLIB_NOT_SUPPORTED; // Battery level does not support write
|
|
} else {
|
|
assert(dbus_characteristic.type == TYPE_GATT);
|
|
}
|
|
|
|
ret = write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE);
|
|
|
|
g_object_unref(dbus_characteristic.gatt);
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
|
|
{
|
|
int ret;
|
|
|
|
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
|
|
if (dbus_characteristic.type == TYPE_NONE) {
|
|
return GATTLIB_NOT_FOUND;
|
|
}
|
|
|
|
ret = write_char(&dbus_characteristic, buffer, buffer_len, BLUEZ_GATT_WRITE_VALUE_TYPE_WRITE_WITHOUT_RESPONSE);
|
|
|
|
g_object_unref(dbus_characteristic.gatt);
|
|
return ret;
|
|
}
|
|
|
|
void gattlib_characteristic_free_value(void *ptr) {
|
|
free(ptr);
|
|
}
|