blob: ab797e5258739d5a4858b6894d9e1085563d9624 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
/*
* Copyright (C) 2024 Amlogic, Inc. All rights reserved
*/
#include "application.h"
#include "adapter.h"
#include "logger.h"
#include "characteristic.h"
#include "utility.h"
#include <errno.h>
#define GATT_SERV_INTERFACE "org.bluez.GattService1"
#define GATT_CHAR_INTERFACE "org.bluez.GattCharacteristic1"
#define GATT_DESC_INTERFACE "org.bluez.GattDescriptor1"
static const char *const TAG = "Application";
static const char *const CHARACTERISTIC_METHOD_READ_VALUE = "ReadValue";
static const char *const CHARACTERISTIC_METHOD_WRITE_VALUE = "WriteValue";
static const char *const CHARACTERISTIC_METHOD_STOP_NOTIFY = "StopNotify";
static const char *const CHARACTERISTIC_METHOD_START_NOTIFY = "StartNotify";
static const char *const CHARACTERISTIC_METHOD_CONFIRM = "Confirm";
static const char *const DESCRIPTOR_METHOD_READ_VALUE = "ReadValue";
static const char *const DESCRIPTOR_METHOD_WRITE_VALUE = "WriteValue";
static const gchar object_manager_xml[] =
"<node name='/'>"
" <interface name='org.freedesktop.DBus.ObjectManager'>"
" <method name='GetManagedObjects'>"
" <arg type='a{oa{sa{sv}}}' name='object_paths_interfaces_and_properties' direction='out'/>"
" </method>"
" </interface>"
"</node>";
static const gchar service_xml[] =
"<node name='/'>"
" <interface name='org.freedesktop.DBus.Properties'>"
" <property type='s' name='UUID' access='read' />"
" <property type='b' name='primary' access='read' />"
" <property type='o' name='Device' access='read' />"
" <property type='ao' name='Characteristics' access='read' />"
" <property type='s' name='Includes' access='read' />"
" </interface>"
"</node>";
static const gchar characteristic_xml[] =
"<node name='/'>"
" <interface name='org.bluez.GattCharacteristic1'>"
" <method name='ReadValue'>"
" <arg type='a{sv}' name='options' direction='in' />"
" <arg type='ay' name='value' direction='out'/>"
" </method>"
" <method name='WriteValue'>"
" <arg type='ay' name='value' direction='in'/>"
" <arg type='a{sv}' name='options' direction='in' />"
" </method>"
" <method name='StartNotify'/>"
" <method name='StopNotify' />"
" <method name='Confirm' />"
" </interface>"
" <interface name='org.freedesktop.DBus.Properties'>"
" <property type='s' name='UUID' access='read' />"
" <property type='o' name='Service' access='read' />"
" <property type='ay' name='Value' access='readwrite' />"
" <property type='b' name='Notifying' access='read' />"
" <property type='as' name='Flags' access='read' />"
" <property type='ao' name='Descriptors' access='read' />"
" </interface>"
"</node>";
static const gchar descriptor_xml[] =
"<node name='/'>"
" <interface name='org.bluez.GattDescriptor1'>"
" <method name='ReadValue'>"
" <arg type='a{sv}' name='options' direction='in' />"
" <arg type='ay' name='value' direction='out'/>"
" </method>"
" <method name='WriteValue'>"
" <arg type='ay' name='value' direction='in'/>"
" <arg type='a{sv}' name='options' direction='in' />"
" </method>"
" </interface>"
" <interface name='org.freedesktop.DBus.Properties'>"
" <property type='s' name='UUID' access='read' />"
" <property type='o' name='Characteristic' access='read' />"
" <property type='ay' name='Value' access='readwrite' />"
" <property type='as' name='Flags' access='read' />"
" </interface>"
"</node>";
struct binc_application {
char *path;
guint registration_id;
GDBusConnection *connection;
GHashTable *services;
onLocalCharacteristicWrite on_char_write;
onLocalCharacteristicRead on_char_read;
onLocalCharacteristicUpdated on_char_updated;
onLocalCharacteristicStartNotify on_char_start_notify;
onLocalCharacteristicStopNotify on_char_stop_notify;
onLocalDescriptorWrite on_desc_write;
onLocalDescriptorRead on_desc_read;
};
typedef struct binc_local_service {
char *path;
char *uuid;
guint registration_id;
GHashTable *characteristics;
Application *application;
} LocalService;
typedef struct local_characteristic {
char *service_uuid;
char *service_path;
char *uuid;
char *path;
guint registration_id;
GByteArray *value;
guint permissions;
GList *flags;
gboolean notifying;
GHashTable *descriptors;
Application *application;
} LocalCharacteristic;
typedef struct local_descriptor {
char *path;
char *char_path;
char *uuid;
char *char_uuid;
char *service_uuid;
guint registration_id;
GByteArray *value;
guint permissions;
GList *flags;
Application *application;
} LocalDescriptor;
static void binc_local_desc_free(LocalDescriptor *localDescriptor) {
g_assert(localDescriptor != NULL);
log_debug(TAG, "freeing descriptor %s", localDescriptor->path);
if (localDescriptor->registration_id != 0) {
gboolean result = g_dbus_connection_unregister_object(localDescriptor->application->connection,
localDescriptor->registration_id);
if (!result) {
log_debug(TAG, "error: could not unregister descriptor %s", localDescriptor->path);
}
localDescriptor->registration_id = 0;
}
if (localDescriptor->value != NULL) {
g_byte_array_free(localDescriptor->value, TRUE);
localDescriptor->value = NULL;
}
g_free(localDescriptor->path);
localDescriptor->path = NULL;
g_free(localDescriptor->char_path);
localDescriptor->char_path = NULL;
g_free(localDescriptor->uuid);
localDescriptor->uuid = NULL;
g_free(localDescriptor->char_uuid);
localDescriptor->char_uuid = NULL;
g_free(localDescriptor->service_uuid);
localDescriptor->service_uuid = NULL;
if (localDescriptor->flags != NULL) {
g_list_free_full(localDescriptor->flags, g_free);
localDescriptor->flags = NULL;
}
g_free(localDescriptor);
}
static void binc_local_char_free(LocalCharacteristic *localCharacteristic) {
g_assert(localCharacteristic != NULL);
log_debug(TAG, "freeing characteristic %s", localCharacteristic->path);
if (localCharacteristic->descriptors != NULL) {
g_hash_table_destroy(localCharacteristic->descriptors);
localCharacteristic->descriptors = NULL;
}
if (localCharacteristic->registration_id != 0) {
gboolean result = g_dbus_connection_unregister_object(localCharacteristic->application->connection,
localCharacteristic->registration_id);
if (!result) {
log_debug(TAG, "error: could not unregister service %s", localCharacteristic->path);
}
localCharacteristic->registration_id = 0;
}
if (localCharacteristic->value != NULL) {
g_byte_array_free(localCharacteristic->value, TRUE);
localCharacteristic->value = NULL;
}
g_free(localCharacteristic->path);
localCharacteristic->path = NULL;
g_free(localCharacteristic->uuid);
localCharacteristic->uuid = NULL;
g_free(localCharacteristic->service_uuid);
localCharacteristic->service_uuid = NULL;
g_free(localCharacteristic->service_path);
localCharacteristic->service_path = NULL;
if (localCharacteristic->flags != NULL) {
g_list_free_full(localCharacteristic->flags, g_free);
localCharacteristic->flags = NULL;
}
g_free(localCharacteristic);
}
void binc_local_service_free(LocalService *localService) {
g_assert(localService != NULL);
log_debug(TAG, "freeing service %s", localService->path);
if (localService->characteristics != NULL) {
g_hash_table_destroy(localService->characteristics);
localService->characteristics = NULL;
}
if (localService->registration_id != 0) {
gboolean result = g_dbus_connection_unregister_object(localService->application->connection,
localService->registration_id);
if (!result) {
log_debug(TAG, "error: could not unregister service %s", localService->path);
}
localService->registration_id = 0;
}
g_free(localService->path);
localService->path = NULL;
g_free(localService->uuid);
localService->uuid = NULL;
g_free(localService);
}
typedef struct read_options {
char *device;
guint16 mtu;
guint16 offset;
char *link_type;
} ReadOptions;
void read_options_free(ReadOptions *options) {
if (options->link_type != NULL) g_free(options->link_type);
if (options->device != NULL) g_free(options->device);
g_free(options);
}
static ReadOptions *parse_read_options(GVariant *params) {
g_assert(g_str_equal(g_variant_get_type_string(params), "(a{sv})"));
ReadOptions *options = g_new0(ReadOptions, 1);
GVariantIter *optionsVariant;
g_variant_get(params, "(a{sv})", &optionsVariant);
GVariant *property_value;
gchar *property_name;
while (g_variant_iter_loop(optionsVariant, "{&sv}", &property_name, &property_value)) {
if (g_str_equal(property_name, "offset")) {
options->offset = g_variant_get_uint16(property_value);
} else if (g_str_equal(property_name, "mtu")) {
options->mtu = g_variant_get_uint16(property_value);
} else if (g_str_equal(property_name, "device")) {
options->device = path_to_address(g_variant_get_string(property_value, NULL));
} else if (g_str_equal(property_name, "link")) {
options->link_type = g_strdup(g_variant_get_string(property_value, NULL));
}
}
g_variant_iter_free(optionsVariant);
log_debug(TAG, "read with offset=%u, mtu=%u, link=%s, device=%s", (unsigned int) options->offset,
(unsigned int) options->mtu, options->link_type, options->device);
return options;
}
typedef struct write_options {
char *write_type;
char *device;
guint16 mtu;
guint16 offset;
char *link_type;
} WriteOptions;
void write_options_free(WriteOptions *options) {
if (options->link_type != NULL) g_free(options->link_type);
if (options->device != NULL) g_free(options->device);
if (options->write_type != NULL) g_free(options->write_type);
g_free(options);
}
static WriteOptions *parse_write_options(GVariant *optionsVariant) {
g_assert(g_str_equal(g_variant_get_type_string(optionsVariant), "a{sv}"));
WriteOptions *options = g_new0(WriteOptions, 1);
GVariantIter iter;
g_variant_iter_init(&iter, optionsVariant);
GVariant *property_value;
gchar *property_name;
while (g_variant_iter_loop(&iter, "{&sv}", &property_name, &property_value)) {
if (g_str_equal(property_name, "offset")) {
options->offset = g_variant_get_uint16(property_value);
} else if (g_str_equal(property_name, "type")) {
options->write_type = g_strdup(g_variant_get_string(property_value, NULL));
} else if (g_str_equal(property_name, "mtu")) {
options->mtu = g_variant_get_uint16(property_value);
} else if (g_str_equal(property_name, "device")) {
options->device = path_to_address(g_variant_get_string(property_value, NULL));
} else if (g_str_equal(property_name, "link")) {
options->link_type = g_strdup(g_variant_get_string(property_value, NULL));
}
}
log_debug(TAG, "write with offset=%u, mtu=%u, link=%s, device=%s", (unsigned int) options->offset,
(unsigned int) options->mtu, options->link_type, options->device);
return options;
}
static void add_char_path(gpointer key, gpointer value, gpointer userdata) {
LocalCharacteristic *localCharacteristic = (LocalCharacteristic *) value;
g_variant_builder_add((GVariantBuilder *) userdata, "o", localCharacteristic->path);
}
static GVariant *binc_local_service_get_characteristics(const LocalService *localService) {
g_assert(localService != NULL);
GVariantBuilder *characteristics_builder = g_variant_builder_new(G_VARIANT_TYPE("ao"));
g_hash_table_foreach(localService->characteristics, add_char_path, characteristics_builder);
GVariant *result = g_variant_builder_end(characteristics_builder);
g_variant_builder_unref(characteristics_builder);
return result;
}
static void add_desc_path(gpointer key, gpointer value, gpointer userdata) {
LocalDescriptor *localDescriptor = (LocalDescriptor *) value;
g_variant_builder_add((GVariantBuilder *) userdata, "o", localDescriptor->path);
}
static GVariant *binc_local_characteristic_get_descriptors(const LocalCharacteristic *localCharacteristic) {
g_assert(localCharacteristic != NULL);
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("ao"));
g_hash_table_foreach(localCharacteristic->descriptors, add_desc_path, builder);
GVariant *result = g_variant_builder_end(builder);
g_variant_builder_unref(builder);
return result;
}
static GVariant *binc_local_characteristic_get_flags(const LocalCharacteristic *localCharacteristic) {
g_assert(localCharacteristic != NULL);
GVariantBuilder *flags_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
for (GList *iterator = localCharacteristic->flags; iterator; iterator = iterator->next) {
g_variant_builder_add(flags_builder, "s", (char *) iterator->data);
}
GVariant *result = g_variant_builder_end(flags_builder);
g_variant_builder_unref(flags_builder);
return result;
}
static GVariant *binc_local_descriptor_get_flags(const LocalDescriptor *localDescriptor) {
g_assert(localDescriptor != NULL);
GVariantBuilder *flags_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
for (GList *iterator = localDescriptor->flags; iterator; iterator = iterator->next) {
g_variant_builder_add(flags_builder, "s", (char *) iterator->data);
}
GVariant *result = g_variant_builder_end(flags_builder);
g_variant_builder_unref(flags_builder);
return result;
}
static void add_descriptors(GVariantBuilder *builder,
LocalCharacteristic *localCharacteristic) {
// NOTE that the CCCD is automatically added by Bluez so no need to add it.
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, localCharacteristic->descriptors);
while (g_hash_table_iter_next(&iter, &key, &value)) {
LocalDescriptor *localDescriptor = (LocalDescriptor *) value;
log_debug(TAG, "adding %s", localDescriptor->path);
GVariantBuilder *descriptors_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sa{sv}}"));
GVariantBuilder *desc_properties_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
GByteArray *byteArray = localDescriptor->value;
GVariant *byteArrayVariant = NULL;
if (byteArray != NULL) {
byteArrayVariant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, byteArray->data,
byteArray->len, sizeof(guint8));
g_variant_builder_add(desc_properties_builder, "{sv}", "Value", byteArrayVariant);
}
g_variant_builder_add(desc_properties_builder, "{sv}", "UUID",
g_variant_new_string(localDescriptor->uuid));
g_variant_builder_add(desc_properties_builder, "{sv}", "Characteristic",
g_variant_new("o", localDescriptor->char_path));
g_variant_builder_add(desc_properties_builder, "{sv}", "Flags",
binc_local_descriptor_get_flags(localDescriptor));
// Add the descriptor to result
g_variant_builder_add(descriptors_builder, "{sa{sv}}", GATT_DESC_INTERFACE,
desc_properties_builder);
g_variant_builder_unref(desc_properties_builder);
g_variant_builder_add(builder, "{oa{sa{sv}}}", localDescriptor->path, descriptors_builder);
g_variant_builder_unref(descriptors_builder);
}
}
static void add_characteristics(GVariantBuilder *builder, LocalService *localService) {
// Build service characteristics
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, localService->characteristics);
while (g_hash_table_iter_next(&iter, &key, &value)) {
LocalCharacteristic *localCharacteristic = (LocalCharacteristic *) value;
log_debug(TAG, "adding %s", localCharacteristic->path);
GVariantBuilder *characteristic_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sa{sv}}"));
GVariantBuilder *char_properties_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
// Build characteristic properties
GByteArray *byteArray = localCharacteristic->value;
GVariant *byteArrayVariant = NULL;
if (byteArray != NULL) {
byteArrayVariant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, byteArray->data,
byteArray->len, sizeof(guint8));
g_variant_builder_add(char_properties_builder, "{sv}", "Value", byteArrayVariant);
}
g_variant_builder_add(char_properties_builder, "{sv}", "UUID",
g_variant_new_string(localCharacteristic->uuid));
g_variant_builder_add(char_properties_builder, "{sv}", "Service",
g_variant_new("o", localService->path));
g_variant_builder_add(char_properties_builder, "{sv}", "Flags",
binc_local_characteristic_get_flags(localCharacteristic));
g_variant_builder_add(char_properties_builder, "{sv}", "Notifying",
g_variant_new("b", localCharacteristic->notifying));
g_variant_builder_add(char_properties_builder, "{sv}", "Descriptors",
binc_local_characteristic_get_descriptors(localCharacteristic));
// Add the characteristic to result
g_variant_builder_add(characteristic_builder, "{sa{sv}}", GATT_CHAR_INTERFACE,
char_properties_builder);
g_variant_builder_unref(char_properties_builder);
g_variant_builder_add(builder, "{oa{sa{sv}}}", localCharacteristic->path, characteristic_builder);
g_variant_builder_unref(characteristic_builder);
add_descriptors(builder, localCharacteristic);
}
}
static void add_services(Application *application, GVariantBuilder *builder) {
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, application->services);
while (g_hash_table_iter_next(&iter, (gpointer) &key, &value)) {
LocalService *localService = (LocalService *) value;
log_debug(TAG, "adding %s", localService->path);
GVariantBuilder *service_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sa{sv}}"));
// Build service properties
GVariantBuilder *service_properties_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
g_variant_builder_add(service_properties_builder, "{sv}", "UUID",
g_variant_new_string((char *) key));
g_variant_builder_add(service_properties_builder, "{sv}", "Primary",
g_variant_new_boolean(TRUE));
g_variant_builder_add(service_properties_builder, "{sv}", "Characteristics",
binc_local_service_get_characteristics(localService));
// Add the service to result
g_variant_builder_add(service_builder, "{sa{sv}}", GATT_SERV_INTERFACE, service_properties_builder);
g_variant_builder_unref(service_properties_builder);
g_variant_builder_add(builder, "{oa{sa{sv}}}", localService->path, service_builder);
g_variant_builder_unref(service_builder);
add_characteristics(builder, localService);
}
}
static void binc_internal_application_method_call(GDBusConnection *conn,
const gchar *sender,
const gchar *path,
const gchar *interface,
const gchar *method,
GVariant *params,
GDBusMethodInvocation *invocation,
void *userdata) {
Application *application = (Application *) userdata;
g_assert(application != NULL);
if (g_str_equal(method, "GetManagedObjects")) {
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("a{oa{sa{sv}}}"));
if (application->services != NULL && g_hash_table_size(application->services) > 0) {
add_services(application, builder);
}
GVariant *result = g_variant_builder_end(builder);
g_variant_builder_unref(builder);
g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&result, 1));
}
}
static const GDBusInterfaceVTable application_method_table = {
.method_call = binc_internal_application_method_call,
};
void binc_application_publish(Application *application, const Adapter *adapter) {
g_assert(application != NULL);
g_assert(adapter != NULL);
GError *error = NULL;
GDBusNodeInfo *info = g_dbus_node_info_new_for_xml(object_manager_xml, &error);
if (error) {
log_debug(TAG, "Unable to create manager node: %s\n", error->message);
g_clear_error(&error);
return;
}
application->registration_id = g_dbus_connection_register_object(application->connection,
application->path,
info->interfaces[0],
&application_method_table,
application,
NULL,
&error);
g_dbus_node_info_unref(info);
if (application->registration_id == 0 && error != NULL) {
log_debug(TAG, "failed to publish application");
g_clear_error(&error);
return;
}
log_debug(TAG, "successfully published application");
}
Application *binc_create_application(const Adapter *adapter) {
g_assert(adapter != NULL);
Application *application = g_new0(Application, 1);
application->connection = binc_adapter_get_dbus_connection(adapter);
application->path = g_strdup("/org/bluez/bincapplication");
application->services = g_hash_table_new_full(g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) binc_local_service_free);
binc_application_publish(application, adapter);
return application;
}
void binc_application_free(Application *application) {
g_assert(application != NULL);
log_debug(TAG, "freeing application %s", application->path);
if (application->services != NULL) {
g_hash_table_destroy(application->services);
application->services = NULL;
}
if (application->registration_id != 0) {
gboolean result = g_dbus_connection_unregister_object(application->connection, application->registration_id);
if (!result) {
log_debug(TAG, "error: could not unregister application %s", application->path);
}
application->registration_id = 0;
}
if (application->path != NULL) {
g_free(application->path);
application->path = NULL;
}
g_free(application);
}
static const GDBusInterfaceVTable service_table = {};
int binc_application_add_service(Application *application, const char *service_uuid) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (is_valid_uuid(service_uuid), EINVAL);
GError *error = NULL;
GDBusNodeInfo *info = g_dbus_node_info_new_for_xml(service_xml, &error);
if (error) {
log_debug(TAG, "Unable to create service node: %s\n", error->message);
g_clear_error(&error);
return EINVAL;
}
LocalService *localService = g_new0(LocalService, 1);
localService->uuid = g_strdup(service_uuid);
localService->application = application;
localService->characteristics = g_hash_table_new_full(
g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) binc_local_char_free);
localService->path = g_strdup_printf(
"%s/service%d",
application->path,
g_hash_table_size(application->services));
g_hash_table_insert(application->services, g_strdup(service_uuid), localService);
localService->registration_id = g_dbus_connection_register_object(application->connection,
localService->path,
info->interfaces[0],
&service_table,
localService,
NULL,
&error);
g_dbus_node_info_unref(info);
if (localService->registration_id == 0) {
log_debug(TAG, "failed to publish local service");
log_debug(TAG, "Error %s", error->message);
g_hash_table_remove(application->services, service_uuid);
binc_local_service_free(localService);
g_clear_error(&error);
return EINVAL;
}
log_debug(TAG, "successfully published local service %s", service_uuid);
return 0;
}
static LocalService *binc_application_get_service(const Application *application, const char *service_uuid) {
g_return_val_if_fail (application != NULL, NULL);
g_return_val_if_fail (is_valid_uuid(service_uuid), NULL);
return g_hash_table_lookup(application->services, service_uuid);
}
static GList *permissions2Flags(const guint permissions) {
GList *list = NULL;
if (permissions & GATT_CHR_PROP_READ) {
list = g_list_append(list, g_strdup("read"));
}
if (permissions & GATT_CHR_PROP_WRITE_WITHOUT_RESP) {
list = g_list_append(list, g_strdup("write-without-response"));
}
if (permissions & GATT_CHR_PROP_WRITE) {
list = g_list_append(list, g_strdup("write"));
}
if (permissions & GATT_CHR_PROP_NOTIFY) {
list = g_list_append(list, g_strdup("notify"));
}
if (permissions & GATT_CHR_PROP_INDICATE) {
list = g_list_append(list, g_strdup("indicate"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_READ) {
list = g_list_append(list, g_strdup("encrypt-read"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_WRITE) {
list = g_list_append(list, g_strdup("encrypt-write"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_NOTIFY) {
list = g_list_append(list, g_strdup("encrypt-notify"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_INDICATE) {
list = g_list_append(list, g_strdup("encrypt-indicate"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_AUTH_READ) {
list = g_list_append(list, g_strdup("encrypt-authenticated-read"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_AUTH_WRITE) {
list = g_list_append(list, g_strdup("encrypt-authenticated-write"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_AUTH_NOTIFY) {
list = g_list_append(list, g_strdup("encrypt-authenticated-notify"));
}
if (permissions & GATT_CHR_PROP_ENCRYPT_AUTH_INDICATE) {
list = g_list_append(list, g_strdup("encrypt-authenticated-indicate"));
}
if (permissions & GATT_CHR_PROP_SECURE_READ) {
list = g_list_append(list, g_strdup("secure-read"));
}
if (permissions & GATT_CHR_PROP_SECURE_WRITE) {
list = g_list_append(list, g_strdup("secure-write"));
}
if (permissions & GATT_CHR_PROP_SECURE_NOTIFY) {
list = g_list_append(list, g_strdup("secure-notify"));
}
if (permissions & GATT_CHR_PROP_SECURE_INDICATE) {
list = g_list_append(list, g_strdup("secure-indicate"));
}
return list;
}
static int binc_characteristic_set_value(const Application *application, LocalCharacteristic *characteristic,
GByteArray *byteArray) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (characteristic != NULL, EINVAL);
g_return_val_if_fail (byteArray != NULL, EINVAL);
GString *byteArrayStr = g_byte_array_as_hex(byteArray);
log_debug(TAG, "set value <%s> to <%s>", byteArrayStr->str, characteristic->uuid);
g_string_free(byteArrayStr, TRUE);
if (characteristic->value != NULL) {
g_byte_array_free(characteristic->value, TRUE);
}
characteristic->value = byteArray;
if (application->on_char_updated != NULL) {
application->on_char_updated(characteristic->application, characteristic->service_uuid,
characteristic->uuid, byteArray);
}
return 0;
}
static int binc_descriptor_set_value(const Application *application, LocalDescriptor *descriptor,
GByteArray *byteArray) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (descriptor != NULL, EINVAL);
g_return_val_if_fail (byteArray != NULL, EINVAL);
GString *byteArrayStr = g_byte_array_as_hex(byteArray);
log_debug(TAG, "set value <%s> to <%s>", byteArrayStr->str, descriptor->uuid);
g_string_free(byteArrayStr, TRUE);
if (descriptor->value != NULL) {
g_byte_array_free(descriptor->value, TRUE);
}
descriptor->value = byteArray;
// if (application->on_char_updated != NULL) {
// application->on_char_updated(characteristic->application, characteristic->service_uuid,
// characteristic->uuid, byteArray);
// }
return 0;
}
static LocalCharacteristic *get_local_characteristic(const Application *application, const char *service_uuid,
const char *char_uuid) {
g_return_val_if_fail (application != NULL, NULL);
g_return_val_if_fail (is_valid_uuid(service_uuid), NULL);
g_return_val_if_fail (is_valid_uuid(char_uuid), NULL);
LocalService *service = binc_application_get_service(application, service_uuid);
if (service != NULL) {
return g_hash_table_lookup(service->characteristics, char_uuid);
}
return NULL;
}
static LocalDescriptor *get_local_descriptor(const Application *application, const char *service_uuid,
const char *char_uuid, const char *desc_uuid) {
g_return_val_if_fail (application != NULL, NULL);
g_return_val_if_fail (is_valid_uuid(service_uuid), NULL);
g_return_val_if_fail (is_valid_uuid(char_uuid), NULL);
g_return_val_if_fail (is_valid_uuid(desc_uuid), NULL);
LocalCharacteristic *characteristic = get_local_characteristic(application, service_uuid, char_uuid);
if (characteristic != NULL) {
return g_hash_table_lookup(characteristic->descriptors, desc_uuid);
}
return NULL;
}
static void binc_internal_descriptor_method_call(GDBusConnection *conn,
const gchar *sender,
const gchar *path,
const gchar *interface,
const gchar *method,
GVariant *params,
GDBusMethodInvocation *invocation,
void *userdata) {
LocalDescriptor *localDescriptor = (LocalDescriptor *) userdata;
g_assert(localDescriptor != NULL);
Application *application = localDescriptor->application;
g_assert(application != NULL);
if (g_str_equal(method, DESCRIPTOR_METHOD_READ_VALUE)) {
ReadOptions *options = parse_read_options(params);
log_debug(TAG, "read descriptor <%s> by ", localDescriptor->uuid, options->device);
const char *result = NULL;
if (application->on_desc_read != NULL) {
result = application->on_desc_read(localDescriptor->application, options->device,
localDescriptor->service_uuid,
localDescriptor->char_uuid, localDescriptor->uuid);
}
read_options_free(options);
if (result) {
g_dbus_method_invocation_return_dbus_error(invocation, result, "read descriptor error");
log_debug(TAG, "read descriptor error");
return;
}
if (localDescriptor->value != NULL) {
GVariant *resultVariant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
localDescriptor->value->data,
localDescriptor->value->len,
sizeof(guint8));
g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&resultVariant, 1));
} else {
g_dbus_method_invocation_return_dbus_error(invocation, BLUEZ_ERROR_FAILED, "no value for descriptor");
}
} else if (g_str_equal(method, DESCRIPTOR_METHOD_WRITE_VALUE)) {
g_assert(g_str_equal(g_variant_get_type_string(params), "(aya{sv})"));
GVariant *valueVariant, *optionsVariant;
g_variant_get(params, "(@ay@a{sv})", &valueVariant, &optionsVariant);
WriteOptions *options = parse_write_options(optionsVariant);
g_variant_unref(optionsVariant);
log_debug(TAG, "write descriptor <%s> by %s", localDescriptor->uuid, options->device);
size_t data_length = 0;
guint8 *data = (guint8 *) g_variant_get_fixed_array(valueVariant, &data_length, sizeof(guint8));
GByteArray *byteArray = g_byte_array_sized_new(data_length);
g_byte_array_append(byteArray, data, data_length);
g_variant_unref(valueVariant);
// Allow application to accept/reject the characteristic value before setting it
const char *result = NULL;
if (application->on_desc_write != NULL) {
result = application->on_desc_write(localDescriptor->application,
options->device,
localDescriptor->service_uuid,
localDescriptor->char_uuid,
localDescriptor->uuid,
byteArray);
}
write_options_free(options);
if (result) {
g_dbus_method_invocation_return_dbus_error(invocation, result, "write error");
log_debug(TAG, "write error");
return;
}
binc_descriptor_set_value(application, localDescriptor, byteArray);
g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
}
}
static const GDBusInterfaceVTable descriptor_table = {
.method_call = binc_internal_descriptor_method_call,
};
int binc_application_add_descriptor(Application *application, const char *service_uuid,
const char *char_uuid, const char *desc_uuid, guint permissions) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (is_valid_uuid(service_uuid), EINVAL);
LocalCharacteristic *localCharacteristic = get_local_characteristic(application, service_uuid, char_uuid);
if (localCharacteristic == NULL) {
g_critical("characteristic %s does not exist", char_uuid);
return EINVAL;
}
GError *error = NULL;
GDBusNodeInfo *info = g_dbus_node_info_new_for_xml(descriptor_xml, &error);
if (error) {
log_debug(TAG, "Unable to create descriptor node: %s\n", error->message);
g_clear_error(&error);
return EINVAL;
}
LocalDescriptor *localDescriptor = g_new0(LocalDescriptor, 1);
localDescriptor->uuid = g_strdup(desc_uuid);
localDescriptor->application = application;
localDescriptor->char_path = g_strdup(localCharacteristic->path);
localDescriptor->char_uuid = g_strdup(char_uuid);
localDescriptor->service_uuid = g_strdup(service_uuid);
localDescriptor->flags = permissions2Flags(permissions);
localDescriptor->path = g_strdup_printf("%s/desc%d",
localCharacteristic->path,
g_hash_table_size(localCharacteristic->descriptors));
g_hash_table_insert(localCharacteristic->descriptors, g_strdup(desc_uuid), localDescriptor);
// Register characteristic
localDescriptor->registration_id = g_dbus_connection_register_object(application->connection,
localDescriptor->path,
info->interfaces[0],
&descriptor_table,
localDescriptor,
NULL,
&error);
g_dbus_node_info_unref(info);
if (localDescriptor->registration_id == 0) {
log_debug(TAG, "failed to publish local characteristic");
log_debug(TAG, "Error %s", error->message);
g_clear_error(&error);
g_hash_table_remove(localCharacteristic->descriptors, desc_uuid);
return EINVAL;
}
log_debug(TAG, "successfully published local descriptor %s", desc_uuid);
return 0;
}
int binc_application_set_char_value(const Application *application, const char *service_uuid,
const char *char_uuid, GByteArray *byteArray) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (service_uuid != NULL, EINVAL);
g_return_val_if_fail (char_uuid != NULL, EINVAL);
g_return_val_if_fail (byteArray != NULL, EINVAL);
g_return_val_if_fail (is_valid_uuid(service_uuid), EINVAL);
g_return_val_if_fail (is_valid_uuid(char_uuid), EINVAL);
LocalCharacteristic *characteristic = get_local_characteristic(application, service_uuid, char_uuid);
if (characteristic == NULL) {
g_critical("%s: characteristic with uuid %s does not exist", G_STRFUNC, char_uuid);
return EINVAL;
}
return binc_characteristic_set_value(application, characteristic, byteArray);
}
int binc_application_set_desc_value(const Application *application, const char *service_uuid,
const char *char_uuid, const char *desc_uuid, GByteArray *byteArray) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (service_uuid != NULL, EINVAL);
g_return_val_if_fail (char_uuid != NULL, EINVAL);
g_return_val_if_fail (byteArray != NULL, EINVAL);
g_return_val_if_fail (is_valid_uuid(service_uuid), EINVAL);
g_return_val_if_fail (is_valid_uuid(char_uuid), EINVAL);
LocalDescriptor *descriptor = get_local_descriptor(application, service_uuid, char_uuid, desc_uuid);
if (descriptor == NULL) {
g_critical("%s: characteristic with uuid %s does not exist", G_STRFUNC, char_uuid);
return EINVAL;
}
return binc_descriptor_set_value(application, descriptor, byteArray);
}
GByteArray *binc_application_get_char_value(const Application *application, const char *service_uuid,
const char *char_uuid) {
g_return_val_if_fail (application != NULL, NULL);
g_return_val_if_fail (service_uuid != NULL, NULL);
g_return_val_if_fail (char_uuid != NULL, NULL);
g_return_val_if_fail (g_uuid_string_is_valid(service_uuid), NULL);
g_return_val_if_fail (g_uuid_string_is_valid(char_uuid), NULL);
LocalCharacteristic *characteristic = get_local_characteristic(application, service_uuid, char_uuid);
if (characteristic != NULL) {
return characteristic->value;
}
return NULL;
}
static void binc_internal_characteristic_method_call(GDBusConnection *conn,
const gchar *sender,
const gchar *path,
const gchar *interface,
const gchar *method,
GVariant *params,
GDBusMethodInvocation *invocation,
void *userdata) {
LocalCharacteristic *characteristic = (LocalCharacteristic *) userdata;
g_assert(characteristic != NULL);
Application *application = characteristic->application;
g_assert(application != NULL);
if (g_str_equal(method, CHARACTERISTIC_METHOD_READ_VALUE)) {
log_debug(TAG, "read <%s>", characteristic->uuid);
ReadOptions *options = parse_read_options(params);
// Allow application to accept/reject the characteristic value before setting it
const char *result = NULL;
if (application->on_char_read != NULL) {
result = application->on_char_read(characteristic->application, options->device,
characteristic->service_uuid,
characteristic->uuid);
}
read_options_free(options);
if (result) {
g_dbus_method_invocation_return_dbus_error(invocation, result, "read characteristic error");
log_debug(TAG, "read characteristic error '%s'", result);
return;
}
// TODO deal with the offset & mtu parameter
if (characteristic->value != NULL) {
GVariant *resultVariant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
characteristic->value->data,
characteristic->value->len,
sizeof(guint8));
g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&resultVariant, 1));
} else {
g_dbus_method_invocation_return_dbus_error(invocation, BLUEZ_ERROR_FAILED, "no value");
}
} else if (g_str_equal(method, CHARACTERISTIC_METHOD_WRITE_VALUE)) {
log_debug(TAG, "write <%s>", characteristic->uuid);
g_assert(g_str_equal(g_variant_get_type_string(params), "(aya{sv})"));
GVariant *valueVariant, *optionsVariant;
g_variant_get(params, "(@ay@a{sv})", &valueVariant, &optionsVariant);
WriteOptions *options = parse_write_options(optionsVariant);
g_variant_unref(optionsVariant);
size_t data_length = 0;
guint8 *data = (guint8 *) g_variant_get_fixed_array(valueVariant, &data_length, sizeof(guint8));
GByteArray *byteArray = g_byte_array_sized_new(data_length);
g_byte_array_append(byteArray, data, data_length);
g_variant_unref(valueVariant);
// Allow application to accept/reject the characteristic value before setting it
const char *result = NULL;
if (application->on_char_write != NULL) {
result = application->on_char_write(characteristic->application, options->device,
characteristic->service_uuid,
characteristic->uuid, byteArray);
}
write_options_free(options);
if (result) {
g_dbus_method_invocation_return_dbus_error(invocation, result, "write error");
log_debug(TAG, "write error");
return;
}
// TODO deal with offset and mtu
binc_characteristic_set_value(application, characteristic, byteArray);
// Send properties changed signal with new value
binc_application_notify(application, characteristic->service_uuid, characteristic->uuid, byteArray);
g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
} else if (g_str_equal(method, CHARACTERISTIC_METHOD_START_NOTIFY)) {
log_debug(TAG, "start notify <%s>", characteristic->uuid);
characteristic->notifying = TRUE;
g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
if (application->on_char_start_notify != NULL) {
application->on_char_start_notify(characteristic->application, characteristic->service_uuid,
characteristic->uuid);
}
} else if (g_str_equal(method, CHARACTERISTIC_METHOD_STOP_NOTIFY)) {
log_debug(TAG, "stop notify <%s>", characteristic->uuid);
characteristic->notifying = FALSE;
g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
if (application->on_char_stop_notify != NULL) {
application->on_char_stop_notify(characteristic->application, characteristic->service_uuid,
characteristic->uuid);
}
} else if (g_str_equal(method, CHARACTERISTIC_METHOD_CONFIRM)) {
log_debug(TAG, "indication confirmed <%s>", characteristic->uuid);
g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
}
}
GVariant *characteristic_get_property(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GError **error,
gpointer user_data) {
log_debug(TAG, "local characteristic get property : %s", property_name);
LocalCharacteristic *characteristic = (LocalCharacteristic *) user_data;
g_assert(characteristic != NULL);
GVariant *ret = NULL;
if (g_str_equal(property_name, "UUID")) {
ret = g_variant_new_string(characteristic->uuid);
} else if (g_str_equal(property_name, "Service")) {
ret = g_variant_new_object_path(characteristic->path);
} else if (g_str_equal(property_name, "Flags")) {
ret = binc_local_characteristic_get_flags(characteristic);
} else if (g_str_equal(property_name, "Notifying")) {
ret = g_variant_new_boolean(characteristic->notifying);
} else if (g_str_equal(property_name, "Value")) {
ret = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, characteristic->value->data, characteristic->value->len,
sizeof(guint8));
}
return ret;
}
static const GDBusInterfaceVTable characteristic_table = {
.method_call = binc_internal_characteristic_method_call,
.get_property = characteristic_get_property
};
int binc_application_add_characteristic(Application *application, const char *service_uuid,
const char *char_uuid, guint permissions) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (is_valid_uuid(service_uuid), EINVAL);
g_return_val_if_fail (is_valid_uuid(char_uuid), EINVAL);
LocalService *localService = binc_application_get_service(application, service_uuid);
if (localService == NULL) {
g_critical("service %s does not exist", service_uuid);
return EINVAL;
}
GError *error = NULL;
GDBusNodeInfo *info = NULL;
info = g_dbus_node_info_new_for_xml(characteristic_xml, &error);
if (error) {
log_debug(TAG, "Unable to create node: %s\n", error->message);
g_clear_error(&error);
return EINVAL;
}
LocalCharacteristic *characteristic = g_new0(LocalCharacteristic, 1);
characteristic->service_uuid = g_strdup(service_uuid);
characteristic->service_path = g_strdup(localService->path);
characteristic->uuid = g_strdup(char_uuid);
characteristic->permissions = permissions;
characteristic->flags = permissions2Flags(permissions);
characteristic->value = NULL;
characteristic->application = application;
characteristic->path = g_strdup_printf("%s/char%d",
localService->path,
g_hash_table_size(localService->characteristics));
characteristic->descriptors = g_hash_table_new_full(
g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) binc_local_desc_free);
g_hash_table_insert(localService->characteristics, g_strdup(char_uuid), characteristic);
// Register characteristic
characteristic->registration_id = g_dbus_connection_register_object(application->connection,
characteristic->path,
info->interfaces[0],
&characteristic_table,
characteristic,
NULL,
&error);
g_dbus_node_info_unref(info);
if (characteristic->registration_id == 0) {
log_debug(TAG, "failed to publish local characteristic");
log_debug(TAG, "Error %s", error->message);
g_clear_error(&error);
g_hash_table_remove(localService->characteristics, char_uuid);
return EINVAL;
}
log_debug(TAG, "successfully published local characteristic %s", char_uuid);
return 0;
}
const char *binc_application_get_path(const Application *application) {
g_assert(application != NULL);
return application->path;
}
void binc_application_set_char_read_cb(Application *application, onLocalCharacteristicRead callback) {
g_assert(application != NULL);
g_assert(callback != NULL);
application->on_char_read = callback;
}
void binc_application_set_char_write_cb(Application *application, onLocalCharacteristicWrite callback) {
g_assert(application != NULL);
g_assert(callback != NULL);
application->on_char_write = callback;
}
void binc_application_set_desc_read_cb(Application *application, onLocalDescriptorRead callback) {
g_assert(application != NULL);
g_assert(callback != NULL);
application->on_desc_read = callback;
}
void binc_application_set_desc_write_cb(Application *application, onLocalDescriptorWrite callback) {
g_assert(application != NULL);
g_assert(callback != NULL);
application->on_desc_write = callback;
}
void binc_application_set_char_start_notify_cb(Application *application, onLocalCharacteristicStartNotify callback) {
g_assert(application != NULL);
g_assert(callback != NULL);
application->on_char_start_notify = callback;
}
void binc_application_set_char_stop_notify_cb(Application *application, onLocalCharacteristicStopNotify callback) {
g_assert(application != NULL);
g_assert(callback != NULL);
application->on_char_stop_notify = callback;
}
int binc_application_notify(const Application *application, const char *service_uuid, const char *char_uuid,
const GByteArray *byteArray) {
g_return_val_if_fail (application != NULL, EINVAL);
g_return_val_if_fail (byteArray != NULL, EINVAL);
g_return_val_if_fail (is_valid_uuid(service_uuid), EINVAL);
g_return_val_if_fail (is_valid_uuid(char_uuid), EINVAL);
LocalCharacteristic *characteristic = get_local_characteristic(application, service_uuid, char_uuid);
if (characteristic == NULL) {
g_critical("%s: characteristic %s does not exist", G_STRFUNC, service_uuid);
return EINVAL;
}
GVariant *valueVariant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
byteArray->data,
byteArray->len,
sizeof(guint8));
GVariantBuilder *properties_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
g_variant_builder_add(properties_builder, "{sv}", "Value", valueVariant);
GVariantBuilder *invalidated_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
GError *error = NULL;
gboolean result = g_dbus_connection_emit_signal(application->connection,
NULL,
characteristic->path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
g_variant_new("(sa{sv}as)",
"org.bluez.GattCharacteristic1",
properties_builder, invalidated_builder),
&error);
g_variant_builder_unref(invalidated_builder);
g_variant_builder_unref(properties_builder);
if (result != TRUE) {
if (error != NULL) {
log_debug(TAG, "error emitting signal: %s", error->message);
g_clear_error(&error);
}
return EINVAL;
}
GString *byteArrayStr = g_byte_array_as_hex(byteArray);
log_debug(TAG, "notified <%s> on <%s>", byteArrayStr->str, characteristic->uuid);
g_string_free(byteArrayStr, TRUE);
return 0;
}
gboolean binc_application_char_is_notifying(const Application *application, const char *service_uuid,
const char *char_uuid) {
g_return_val_if_fail (application != NULL, FALSE);
g_return_val_if_fail (is_valid_uuid(service_uuid), FALSE);
g_return_val_if_fail (is_valid_uuid(char_uuid), FALSE);
LocalCharacteristic *characteristic = get_local_characteristic(application, service_uuid, char_uuid);
if (characteristic == NULL) {
g_critical("%s: characteristic %s does not exist", G_STRFUNC, service_uuid);
return FALSE;
}
return characteristic->notifying;
}