| // SPDX-License-Identifier: (GPL-2.0-only OR MIT) |
| /* |
| * Copyright (C) 2024 Amlogic, Inc. All rights reserved |
| */ |
| |
| /* |
| * Open issues: |
| * |
| * - We never get 'flags' for a descriptor. So not exposing flags-related methods yet |
| */ |
| |
| #include "descriptor.h" |
| #include "device_internal.h" |
| #include "utility.h" |
| #include "logger.h" |
| |
| static const char *const TAG = "Descriptor"; |
| |
| static const char *const BLUEZ_DBUS = "org.bluez"; |
| static const char *const INTERFACE_DESCRIPTOR = "org.bluez.GattDescriptor1"; |
| static const char *const DESCRIPTOR_METHOD_READ_VALUE = "ReadValue"; |
| static const char *const DESCRIPTOR_METHOD_WRITE_VALUE = "WriteValue"; |
| |
| struct binc_descriptor { |
| Device *device; // Borrowed |
| Characteristic *characteristic; // Borrowed |
| GDBusConnection *connection; // Borrowed |
| const char *path; // Owned |
| const char *char_path; // Owned |
| const char *uuid; // Owned |
| GList *flags; // Owned |
| |
| OnDescReadCallback on_read_cb; |
| OnDescWriteCallback on_write_cb; |
| }; |
| |
| Descriptor *binc_descriptor_create(Device *device, const char *path) { |
| g_assert(device != NULL); |
| g_assert(path != NULL); |
| g_assert(strlen(path) > 0); |
| |
| Descriptor *descriptor = g_new0(Descriptor, 1); |
| descriptor->device = device; |
| descriptor->connection = binc_device_get_dbus_connection(device); |
| descriptor->path = g_strdup(path); |
| return descriptor; |
| } |
| |
| void binc_descriptor_free(Descriptor *descriptor) { |
| g_assert(descriptor != NULL); |
| |
| if (descriptor->flags != NULL) { |
| g_list_free_full(descriptor->flags, g_free); |
| descriptor->flags = NULL; |
| } |
| |
| g_free((char *) descriptor->uuid); |
| descriptor->uuid = NULL; |
| g_free((char *) descriptor->path); |
| descriptor->path = NULL; |
| g_free((char *) descriptor->char_path); |
| descriptor->char_path = NULL; |
| |
| descriptor->characteristic = NULL; |
| descriptor->device = NULL; |
| descriptor->connection = NULL; |
| g_free(descriptor); |
| } |
| |
| const char *binc_descriptor_to_string(const Descriptor *descriptor) { |
| g_assert(descriptor != NULL); |
| |
| GString *flags = g_string_new("["); |
| if (g_list_length(descriptor->flags) > 0) { |
| for (GList *iterator = descriptor->flags; iterator; iterator = iterator->next) { |
| g_string_append_printf(flags, "%s, ", (char *) iterator->data); |
| } |
| g_string_truncate(flags, flags->len - 2); |
| } |
| g_string_append(flags, "]"); |
| |
| char *result = g_strdup_printf( |
| "Descriptor{uuid='%s', flags='%s', properties=%d, char_uuid='%s'}", |
| descriptor->uuid, |
| flags->str, |
| 0, |
| binc_characteristic_get_uuid(descriptor->characteristic)); |
| |
| g_string_free(flags, TRUE); |
| return result; |
| } |
| |
| void binc_descriptor_set_uuid(Descriptor *descriptor, const char *uuid) { |
| g_assert(descriptor != NULL); |
| g_assert(is_valid_uuid(uuid)); |
| |
| g_free((char *) descriptor->uuid); |
| descriptor->uuid = g_strdup(uuid); |
| } |
| |
| void binc_descriptor_set_char_path(Descriptor *descriptor, const char *path) { |
| g_assert(descriptor != NULL); |
| g_assert(path != NULL); |
| |
| g_free((char *) descriptor->char_path); |
| descriptor->char_path = g_strdup(path); |
| } |
| |
| const char *binc_descriptor_get_char_path(const Descriptor *descriptor) { |
| g_assert(descriptor != NULL); |
| return descriptor->char_path; |
| } |
| |
| const char *binc_descriptor_get_uuid(const Descriptor *descriptor) { |
| g_assert(descriptor != NULL); |
| return descriptor->uuid; |
| } |
| |
| void binc_descriptor_set_char(Descriptor *descriptor, Characteristic *characteristic) { |
| g_assert(descriptor != NULL); |
| g_assert(characteristic != NULL); |
| |
| descriptor->characteristic = characteristic; |
| } |
| |
| void binc_descriptor_set_flags(Descriptor *descriptor, GList *flags) { |
| g_assert(descriptor != NULL); |
| g_assert(flags != NULL); |
| |
| if (descriptor->flags != NULL) { |
| g_list_free_full(descriptor->flags, g_free); |
| } |
| descriptor->flags = flags; |
| } |
| |
| static void binc_internal_descriptor_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { |
| GError *error = NULL; |
| GByteArray *byteArray = NULL; |
| GVariant *innerArray = NULL; |
| Descriptor *descriptor = (Descriptor *) user_data; |
| g_assert(descriptor != NULL); |
| |
| GVariant *value = g_dbus_connection_call_finish(descriptor->connection, res, &error); |
| if (value != NULL) { |
| g_assert(g_str_equal(g_variant_get_type_string(value), "(ay)")); |
| innerArray = g_variant_get_child_value(value, 0); |
| byteArray = g_variant_get_byte_array(innerArray); |
| } |
| |
| if (descriptor->on_read_cb != NULL) { |
| descriptor->on_read_cb(descriptor->device, descriptor, byteArray, error); |
| } |
| |
| if (byteArray != NULL) { |
| g_byte_array_free(byteArray, FALSE); |
| } |
| |
| if (innerArray != NULL) { |
| g_variant_unref(innerArray); |
| } |
| |
| if (value != NULL) { |
| g_variant_unref(value); |
| } |
| |
| if (error != NULL) { |
| log_debug(TAG, "failed to call '%s' (error %d: %s)", DESCRIPTOR_METHOD_READ_VALUE, error->code, |
| error->message); |
| g_clear_error(&error); |
| } |
| } |
| |
| void binc_descriptor_read(Descriptor *descriptor) { |
| g_assert(descriptor != NULL); |
| |
| log_debug(TAG, "reading <%s>", descriptor->uuid); |
| |
| guint16 offset = 0; |
| GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); |
| g_variant_builder_add(builder, "{sv}", "offset", g_variant_new_uint16(offset)); |
| GVariant *options = g_variant_builder_end(builder); |
| g_variant_builder_unref(builder); |
| |
| g_dbus_connection_call(descriptor->connection, |
| BLUEZ_DBUS, |
| descriptor->path, |
| INTERFACE_DESCRIPTOR, |
| DESCRIPTOR_METHOD_READ_VALUE, |
| g_variant_new("(@a{sv})", options), |
| G_VARIANT_TYPE("(ay)"), |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| (GAsyncReadyCallback) binc_internal_descriptor_read_cb, |
| descriptor); |
| } |
| |
| typedef struct binc_desc_write_data { |
| GVariant *value; |
| Descriptor *descriptor; |
| } WriteDescData; |
| |
| static void binc_internal_descriptor_write_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { |
| WriteDescData *writeData = (WriteDescData *) user_data; |
| Descriptor *descriptor = writeData->descriptor; |
| g_assert(descriptor != NULL); |
| |
| GByteArray *byteArray = NULL; |
| GError *error = NULL; |
| GVariant *value = g_dbus_connection_call_finish(descriptor->connection, res, &error); |
| |
| if (writeData->value != NULL) { |
| byteArray = g_variant_get_byte_array(writeData->value); |
| } |
| |
| if (descriptor->on_write_cb != NULL) { |
| descriptor->on_write_cb(descriptor->device, descriptor, byteArray, error); |
| } |
| |
| if (byteArray != NULL) { |
| g_byte_array_free(byteArray, FALSE); |
| } |
| g_variant_unref(writeData->value); |
| g_free(writeData); |
| |
| if (value != NULL) { |
| g_variant_unref(value); |
| } |
| |
| if (error != NULL) { |
| log_debug(TAG, "failed to call '%s' (error %d: %s)", DESCRIPTOR_METHOD_WRITE_VALUE, |
| error->code, error->message); |
| g_clear_error(&error); |
| } |
| } |
| |
| void binc_descriptor_write(Descriptor *descriptor, const GByteArray *byteArray) { |
| g_assert(descriptor != NULL); |
| g_assert(byteArray != NULL); |
| g_assert(byteArray->len > 0); |
| |
| GString *byteArrayStr = g_byte_array_as_hex(byteArray); |
| log_debug(TAG, "writing <%s> to <%s>", byteArrayStr->str, descriptor->uuid); |
| g_string_free(byteArrayStr, TRUE); |
| |
| GVariant *value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, byteArray->data, byteArray->len, sizeof(guint8)); |
| |
| WriteDescData *writeData = g_new0(WriteDescData, 1); |
| writeData->value = g_variant_ref(value); |
| writeData->descriptor = descriptor; |
| |
| guint16 offset = 0; |
| GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); |
| g_variant_builder_add(builder, "{sv}", "offset", g_variant_new_uint16(offset)); |
| GVariant *options = g_variant_builder_end(builder); |
| g_variant_builder_unref(builder); |
| |
| g_dbus_connection_call(descriptor->connection, |
| BLUEZ_DBUS, |
| descriptor->path, |
| INTERFACE_DESCRIPTOR, |
| DESCRIPTOR_METHOD_WRITE_VALUE, |
| g_variant_new("(@ay@a{sv})", value, options), |
| NULL, |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| (GAsyncReadyCallback) binc_internal_descriptor_write_cb, |
| writeData); |
| } |
| |
| void binc_descriptor_set_read_cb(Descriptor *descriptor, OnDescReadCallback callback) { |
| g_assert(descriptor != NULL); |
| g_assert(callback != NULL); |
| |
| descriptor->on_read_cb = callback; |
| } |
| |
| void binc_descriptor_set_write_cb(Descriptor *descriptor, OnDescWriteCallback callback) { |
| g_assert(descriptor != NULL); |
| g_assert(callback != NULL); |
| |
| descriptor->on_write_cb = callback; |
| } |
| |
| Device *binc_descriptor_get_device(const Descriptor *descriptor) { |
| g_assert(descriptor != NULL); |
| return descriptor->device; |
| } |
| |
| Characteristic *binc_descriptor_get_char(const Descriptor *descriptor) { |
| g_assert(descriptor != NULL); |
| return descriptor->characteristic; |
| } |