ble-fast-pair: add package of ble fast pair. [2/2]
PD#SWPL-176346
Problem:
Implement LE Audio fast pair application.
Solution:
add application of ble fast pair.
Verify:
A113L2_BA401
Change-Id: I1d6517fc4775bfff6d381d1c2671f26b31188100
Signed-off-by: ye.he <ye.he@amlogic.com>
diff --git a/ble-fast-pair/Makefile b/ble-fast-pair/Makefile
new file mode 100644
index 0000000..9041c2f
--- /dev/null
+++ b/ble-fast-pair/Makefile
@@ -0,0 +1,61 @@
+#
+## sample Makefile for aml ble fast pair
+#
+#
+
+# Define the source files
+SOURCES = adapter.c \
+ advertisement.c \
+ agent.c \
+ application.c \
+ characteristic.c \
+ descriptor.c \
+ device.c \
+ logger.c \
+ parser.c \
+ service.c \
+ utility.c \
+ ble_audio_source.c \
+ ble_audio_sink.c \
+ ble_fast_pair.c
+
+# Convert source files to object files
+OBJ = $(SOURCES:.c=.o)
+
+# Include paths
+INCLUDES = -I$(STAGING_DIR)/usr/include/bluez \
+ -I$(STAGING_DIR)/usr/include/glib-2.0 \
+ -I$(STAGING_DIR)/usr/lib/glib-2.0/include \
+ -I$(STAGING_DIR)/usr/include/dbus-1.0 \
+ -I$(STAGING_DIR)/usr/lib/dbus-1.0/include
+
+# Libraries to link against
+LIBS = -lbluetooth
+
+# Compilation flags
+CFLAGS = -Wall -Wextra $(INCLUDES) -fpermissive
+#CFLAGS += $(shell $(TARGET_CONFIGURE_OPTS) pkg-config --cflags gio-2.0 dbus-1 gobject-2.0 glib-2.0)
+
+# Linker flags
+LDFLAGS = $(LIBS)
+LDFLAGS += $(shell $(TARGET_CONFIGURE_OPTS) pkg-config --libs gio-2.0 dbus-1 gobject-2.0 glib-2.0)
+
+TARGET = blefastpair
+
+# Rules
+all: $(TARGET)
+
+$(TARGET): $(OBJ)
+ $(CC) $(OBJ) -o $@ $(LDFLAGS)
+
+%.o: %.c
+ $(CC) -c $(CFLAGS) $< -o $@
+
+.PHONY: clean
+
+clean:
+ rm -f $(OBJ) $(TARGET)
+
+install:
+ cp blefastpair $(DESTDIR)/bin/blefastpair
+
diff --git a/ble-fast-pair/adapter.c b/ble-fast-pair/adapter.c
new file mode 100644
index 0000000..bd7dafb
--- /dev/null
+++ b/ble-fast-pair/adapter.c
@@ -0,0 +1,1128 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "adapter.h"
+#include "device.h"
+#include "device_internal.h"
+#include "logger.h"
+#include "utility.h"
+#include "advertisement.h"
+#include "application.h"
+
+static const char *const TAG = "Adapter";
+static const char *const BLUEZ_DBUS = "org.bluez";
+static const char *const INTERFACE_ADAPTER = "org.bluez.Adapter1";
+static const char *const INTERFACE_DEVICE = "org.bluez.Device1";
+static const char *const INTERFACE_OBJECT_MANAGER = "org.freedesktop.DBus.ObjectManager";
+static const char *const INTERFACE_GATT_MANAGER = "org.bluez.GattManager1";
+static const char *const INTERFACE_PROPERTIES = "org.freedesktop.DBus.Properties";
+
+static const char *const METHOD_START_DISCOVERY = "StartDiscovery";
+static const char *const METHOD_STOP_DISCOVERY = "StopDiscovery";
+static const char *const METHOD_REMOVE_DEVICE = "RemoveDevice";
+static const char *const METHOD_SET_DISCOVERY_FILTER = "SetDiscoveryFilter";
+
+static const char *const ADAPTER_PROPERTY_POWERED = "Powered";
+static const char *const ADAPTER_PROPERTY_DISCOVERING = "Discovering";
+static const char *const ADAPTER_PROPERTY_ADDRESS = "Address";
+static const char *const ADAPTER_PROPERTY_DISCOVERABLE = "Discoverable";
+
+static const char *const DEVICE_PROPERTY_RSSI = "RSSI";
+static const char *const DEVICE_PROPERTY_UUIDS = "UUIDs";
+static const char *const DEVICE_PROPERTY_MANUFACTURER_DATA = "ManufacturerData";
+static const char *const DEVICE_PROPERTY_SERVICE_DATA = "ServiceData";
+
+static const char *const SIGNAL_PROPERTIES_CHANGED = "PropertiesChanged";
+
+static const guint MAC_ADDRESS_LENGTH = 17;
+
+static const char *discovery_state_names[] = {
+ [BINC_DISCOVERY_STOPPED] = "stopped",
+ [BINC_DISCOVERY_STARTED] = "started",
+ [BINC_DISCOVERY_STARTING] = "starting",
+ [BINC_DISCOVERY_STOPPING] = "stopping"
+};
+
+typedef struct binc_discovery_filter {
+ short rssi;
+ GPtrArray *services;
+ const char *pattern;
+} DiscoveryFilter;
+
+struct binc_adapter {
+ const char *path; // Owned
+ const char *address; // Owned
+ gboolean powered;
+ gboolean discoverable;
+ gboolean discovering;
+ DiscoveryState discovery_state;
+ DiscoveryFilter discovery_filter;
+
+ GDBusConnection *connection; // Borrowed
+ guint device_prop_changed;
+ guint adapter_prop_changed;
+ guint iface_added;
+ guint iface_removed;
+
+ AdapterDiscoveryResultCallback discoveryResultCallback;
+ AdapterDiscoveryStateChangeCallback discoveryStateCallback;
+ AdapterPoweredStateChangeCallback poweredStateCallback;
+ RemoteCentralConnectionStateCallback centralStateCallback;
+ void *user_data; // Borrowed
+ GHashTable *devices_cache; // Owned
+
+ Advertisement *advertisement; // Borrowed
+};
+
+static void remove_signal_subscribers(Adapter *adapter) {
+ g_assert(adapter != NULL);
+
+ g_dbus_connection_signal_unsubscribe(adapter->connection, adapter->device_prop_changed);
+ adapter->device_prop_changed = 0;
+ g_dbus_connection_signal_unsubscribe(adapter->connection, adapter->adapter_prop_changed);
+ adapter->device_prop_changed = 0;
+ g_dbus_connection_signal_unsubscribe(adapter->connection, adapter->iface_added);
+ adapter->iface_added = 0;
+ g_dbus_connection_signal_unsubscribe(adapter->connection, adapter->iface_removed);
+ adapter->iface_removed = 0;
+}
+
+static void free_discovery_filter(Adapter *adapter) {
+ g_assert(adapter != NULL);
+
+ for (guint i = 0; i < adapter->discovery_filter.services->len; i++) {
+ char *uuid_filter = g_ptr_array_index(adapter->discovery_filter.services, i);
+ g_free(uuid_filter);
+ }
+ g_ptr_array_free(adapter->discovery_filter.services, TRUE);
+ adapter->discovery_filter.services = NULL;
+
+ g_free((char *) adapter->discovery_filter.pattern);
+ adapter->discovery_filter.pattern = NULL;
+}
+
+void binc_adapter_free(Adapter *adapter) {
+ g_assert(adapter != NULL);
+
+ remove_signal_subscribers(adapter);
+
+ if (adapter->discovery_filter.services != NULL) {
+ free_discovery_filter(adapter);
+ adapter->discovery_filter.services = NULL;
+ }
+
+ if (adapter->devices_cache != NULL) {
+ g_hash_table_destroy(adapter->devices_cache);
+ adapter->devices_cache = NULL;
+ }
+
+ g_free((char *) adapter->path);
+ adapter->path = NULL;
+
+ g_free((char *) adapter->address);
+ adapter->address = NULL;
+
+ adapter->connection = NULL;
+ g_free(adapter);
+}
+
+static void binc_internal_adapter_call_method_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to call adapter method (error %d: %s)", error->code, error->message);
+ g_clear_error(&error);
+ }
+}
+
+static void binc_internal_adapter_call_method(Adapter *adapter, const char *method, GVariant *parameters) {
+ g_assert(adapter != NULL);
+ g_assert(method != NULL);
+
+ g_dbus_connection_call(adapter->connection,
+ BLUEZ_DBUS,
+ adapter->path,
+ INTERFACE_ADAPTER,
+ method,
+ parameters,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_adapter_call_method_cb,
+ adapter);
+}
+
+static void binc_internal_set_discovery_state(Adapter *adapter, DiscoveryState discovery_state) {
+ g_assert(adapter != NULL);
+ if (adapter->discovery_state == discovery_state) return;
+
+ adapter->discovery_state = discovery_state;
+ if (adapter->discoveryStateCallback != NULL) {
+ adapter->discoveryStateCallback(adapter, adapter->discovery_state, NULL);
+ }
+}
+
+static void binc_internal_adapter_changed(__attribute__((unused)) GDBusConnection *conn,
+ __attribute__((unused)) const gchar *sender,
+ __attribute__((unused)) const gchar *path,
+ __attribute__((unused)) const gchar *interface,
+ __attribute__((unused)) const gchar *signal,
+ GVariant *parameters,
+ void *user_data) {
+
+ GVariantIter *properties_changed = NULL;
+ GVariantIter *properties_invalidated = NULL;
+ const char *iface = NULL;
+ const char *property_name = NULL;
+ GVariant *property_value = NULL;
+
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ g_assert(g_str_equal(g_variant_get_type_string(parameters), "(sa{sv}as)"));
+ g_variant_get(parameters, "(&sa{sv}as)", &iface, &properties_changed, &properties_invalidated);
+ while (g_variant_iter_loop(properties_changed, "{&sv}", &property_name, &property_value)) {
+ if (g_str_equal(property_name, ADAPTER_PROPERTY_POWERED)) {
+ adapter->powered = g_variant_get_boolean(property_value);
+ if (adapter->poweredStateCallback != NULL) {
+ adapter->poweredStateCallback(adapter, adapter->powered);
+ }
+ } else if (g_str_equal(property_name, ADAPTER_PROPERTY_DISCOVERING)) {
+ adapter->discovering = g_variant_get_boolean(property_value);
+
+ // It could be that some other app is causing discovery to be stopped, e.g. power off
+ if (adapter->discovering == FALSE) {
+ // Update discovery state to reflect discovery state
+ binc_internal_set_discovery_state(adapter, BINC_DISCOVERY_STOPPED);
+ }
+ } else if (g_str_equal(property_name, ADAPTER_PROPERTY_DISCOVERABLE)) {
+ adapter->discoverable = g_variant_get_boolean(property_value);
+ }
+ }
+
+ if (properties_changed != NULL)
+ g_variant_iter_free(properties_changed);
+
+ if (properties_invalidated != NULL)
+ g_variant_iter_free(properties_invalidated);
+}
+
+static gboolean matches_discovery_filter(Adapter *adapter, Device *device) {
+ g_assert(adapter != NULL);
+ g_assert(device != NULL);
+
+ if (binc_device_get_rssi(device) < adapter->discovery_filter.rssi) return FALSE;
+
+ const char *pattern = adapter->discovery_filter.pattern;
+ if (pattern != NULL) {
+ if (!(g_str_has_prefix(binc_device_get_name(device), pattern) ||
+ g_str_has_prefix(binc_device_get_address(device), pattern)))
+ return FALSE;
+ }
+
+ GPtrArray *services_filter = adapter->discovery_filter.services;
+ if (services_filter != NULL) {
+ guint count = services_filter->len;
+ if (count == 0) return TRUE;
+
+ for (guint i = 0; i < count; i++) {
+ const char *uuid_filter = g_ptr_array_index(services_filter, i);
+ if (binc_device_has_service(device, uuid_filter)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void deliver_discovery_result(Adapter *adapter, Device *device) {
+ g_assert(adapter != NULL);
+ g_assert(device != NULL);
+
+ if (binc_device_get_connection_state(device) == BINC_DISCONNECTED) {
+ // Double check if the device matches the discovery filter
+ if (!matches_discovery_filter(adapter, device)) return;
+
+ if (adapter->discoveryResultCallback != NULL) {
+ adapter->discoveryResultCallback(adapter, device);
+ }
+ }
+}
+
+static void binc_internal_device_disappeared(__attribute__((unused)) GDBusConnection *conn,
+ __attribute__((unused)) const gchar *sender_name,
+ __attribute__((unused)) const gchar *object_path,
+ __attribute__((unused)) const gchar *interface,
+ __attribute__((unused)) const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data) {
+
+ GVariantIter *interfaces = NULL;
+ const char *object = NULL;
+ const char *interface_name = NULL;
+
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ g_assert(g_str_equal(g_variant_get_type_string(parameters), "(oas)"));
+ g_variant_get(parameters, "(&oas)", &object, &interfaces);
+ while (g_variant_iter_loop(interfaces, "s", &interface_name)) {
+ if (g_str_equal(interface_name, INTERFACE_DEVICE)) {
+ log_debug(TAG, "Device %s removed", object);
+ if (g_hash_table_lookup(adapter->devices_cache, object) != NULL) {
+ g_hash_table_remove(adapter->devices_cache, object);
+ }
+ }
+ }
+
+ if (interfaces != NULL)
+ g_variant_iter_free(interfaces);
+}
+
+static void binc_internal_device_appeared(__attribute__((unused)) GDBusConnection *conn,
+ __attribute__((unused)) const gchar *sender_name,
+ __attribute__((unused)) const gchar *object_path,
+ __attribute__((unused)) const gchar *interface,
+ __attribute__((unused)) const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data) {
+
+ GVariantIter *interfaces = NULL;
+ const char *object = NULL;
+ const char *interface_name = NULL;
+ GVariant *properties = NULL;
+
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ g_assert(g_str_equal(g_variant_get_type_string(parameters), "(oa{sa{sv}})"));
+ g_variant_get(parameters, "(&oa{sa{sv}})", &object, &interfaces);
+ while (g_variant_iter_loop(interfaces, "{&s@a{sv}}", &interface_name, &properties)) {
+ if (g_str_equal(interface_name, INTERFACE_DEVICE)) {
+ Device *device = binc_device_create(object, adapter);
+
+ char *property_name = NULL;
+ GVariantIter iter;
+ GVariant *property_value = NULL;
+ g_variant_iter_init(&iter, properties);
+ while (g_variant_iter_loop(&iter, "{&sv}", &property_name, &property_value)) {
+ binc_internal_device_update_property(device, property_name, property_value);
+ }
+
+ g_hash_table_insert(adapter->devices_cache,
+ g_strdup(binc_device_get_path(device)),
+ device);
+
+ if (adapter->discovery_state == BINC_DISCOVERY_STARTED && binc_device_get_connection_state(device) == BINC_DISCONNECTED) {
+ deliver_discovery_result(adapter, device);
+ }
+
+ if (binc_device_get_connection_state(device) == BINC_CONNECTED &&
+ binc_device_get_rssi(device) == -255 &&
+ binc_device_get_uuids(device) == NULL) {
+ binc_device_set_is_central(device, TRUE);
+ if (adapter->centralStateCallback != NULL) {
+ adapter->centralStateCallback(adapter, device);
+ }
+ }
+ }
+ }
+
+ if (interfaces != NULL)
+ g_variant_iter_free(interfaces);
+}
+
+static void binc_internal_device_getall_properties_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ Device *device = (Device *) user_data;
+ g_assert(device != NULL);
+
+ GError *error = NULL;
+ GVariant *result = g_dbus_connection_call_finish(binc_device_get_dbus_connection(device), res, &error);
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to call '%s' (error %d: %s)", "GetAll", error->code, error->message);
+ g_clear_error(&error);
+ }
+
+ if (result != NULL) {
+ GVariantIter *iter = NULL;
+ const char *property_name = NULL;
+ GVariant *property_value = NULL;
+
+ g_assert(g_str_equal(g_variant_get_type_string(result), "(a{sv})"));
+ g_variant_get(result, "(a{sv})", &iter);
+ while (g_variant_iter_loop(iter, "{&sv}", &property_name, &property_value)) {
+ binc_internal_device_update_property(device, property_name, property_value);
+ }
+
+ if (iter != NULL) {
+ g_variant_iter_free(iter);
+ }
+ g_variant_unref(result);
+ }
+}
+
+static void binc_internal_device_getall_properties(Adapter *adapter, Device *device) {
+ g_dbus_connection_call(adapter->connection,
+ BLUEZ_DBUS,
+ binc_device_get_path(device),
+ INTERFACE_PROPERTIES,
+ "GetAll",
+ g_variant_new("(s)", INTERFACE_DEVICE),
+ G_VARIANT_TYPE("(a{sv})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_device_getall_properties_cb,
+ device);
+}
+
+
+static void binc_internal_device_changed(__attribute__((unused)) GDBusConnection *conn,
+ __attribute__((unused)) const gchar *sender,
+ const gchar *path,
+ __attribute__((unused)) const gchar *interface,
+ __attribute__((unused)) const gchar *signal,
+ GVariant *parameters,
+ void *user_data) {
+
+ GVariantIter *properties_changed = NULL;
+ GVariantIter *properties_invalidated = NULL;
+ const char *iface = NULL;
+ const char *property_name = NULL;
+ GVariant *property_value = NULL;
+
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ Device *device = g_hash_table_lookup(adapter->devices_cache, path);
+ if (device == NULL) {
+ device = binc_device_create(path, adapter);
+ g_hash_table_insert(adapter->devices_cache, g_strdup(binc_device_get_path(device)), device);
+ binc_internal_device_getall_properties(adapter, device);
+ } else {
+ gboolean isDiscoveryResult = FALSE;
+ ConnectionState oldState = binc_device_get_connection_state(device);
+ g_assert(g_str_equal(g_variant_get_type_string(parameters), "(sa{sv}as)"));
+ g_variant_get(parameters, "(&sa{sv}as)", &iface, &properties_changed, &properties_invalidated);
+ while (g_variant_iter_loop(properties_changed, "{&sv}", &property_name, &property_value)) {
+ binc_internal_device_update_property(device, property_name, property_value);
+ if (g_str_equal(property_name, DEVICE_PROPERTY_RSSI) ||
+ g_str_equal(property_name, DEVICE_PROPERTY_MANUFACTURER_DATA) ||
+ g_str_equal(property_name, DEVICE_PROPERTY_SERVICE_DATA)) {
+ isDiscoveryResult = TRUE;
+ }
+ }
+ if (adapter->discovery_state == BINC_DISCOVERY_STARTED && isDiscoveryResult) {
+ deliver_discovery_result(adapter, device);
+ }
+
+ if (binc_device_get_bonding_state(device) == BINC_BONDED && binc_device_get_rssi(device) == -255) {
+ binc_device_set_is_central(device, TRUE);
+ }
+
+ if (binc_device_is_central(device)) {
+ ConnectionState newState = binc_device_get_connection_state(device);
+ if (oldState != newState) {
+ if (adapter->centralStateCallback != NULL) {
+ adapter->centralStateCallback(adapter, device);
+ }
+ }
+ }
+ }
+
+ if (properties_changed != NULL)
+ g_variant_iter_free(properties_changed);
+
+ if (properties_invalidated != NULL)
+ g_variant_iter_free(properties_invalidated);
+}
+
+static void setup_signal_subscribers(Adapter *adapter) {
+ adapter->device_prop_changed = g_dbus_connection_signal_subscribe(adapter->connection,
+ BLUEZ_DBUS,
+ INTERFACE_PROPERTIES,
+ SIGNAL_PROPERTIES_CHANGED,
+ NULL,
+ INTERFACE_DEVICE,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ binc_internal_device_changed,
+ adapter,
+ NULL);
+
+ adapter->adapter_prop_changed = g_dbus_connection_signal_subscribe(adapter->connection,
+ BLUEZ_DBUS,
+ INTERFACE_PROPERTIES,
+ SIGNAL_PROPERTIES_CHANGED,
+ adapter->path,
+ INTERFACE_ADAPTER,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ binc_internal_adapter_changed,
+ adapter,
+ NULL);
+
+ adapter->iface_added = g_dbus_connection_signal_subscribe(adapter->connection,
+ BLUEZ_DBUS,
+ INTERFACE_OBJECT_MANAGER,
+ "InterfacesAdded",
+ NULL,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ binc_internal_device_appeared,
+ adapter,
+ NULL);
+
+ adapter->iface_removed = g_dbus_connection_signal_subscribe(adapter->connection,
+ BLUEZ_DBUS,
+ INTERFACE_OBJECT_MANAGER,
+ "InterfacesRemoved",
+ NULL,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ binc_internal_device_disappeared,
+ adapter,
+ NULL);
+}
+
+const char *binc_adapter_get_name(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ g_assert(adapter->path != NULL);
+ return strrchr(adapter->path, '/') + 1;
+}
+
+const char *binc_adapter_get_address(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return adapter->address;
+}
+
+static Adapter *binc_adapter_create(GDBusConnection *connection, const char *path) {
+ g_assert(connection != NULL);
+ g_assert(path != NULL);
+ g_assert(strlen(path) > 0);
+
+ Adapter *adapter = g_new0(Adapter, 1);
+ adapter->connection = connection;
+ adapter->path = g_strdup(path);
+ adapter->discovery_filter.rssi = -255;
+ adapter->devices_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) binc_device_free);
+ adapter->user_data = NULL;
+ setup_signal_subscribers(adapter);
+ return adapter;
+}
+
+static Adapter *binc_internal_get_adapter_by_path(GPtrArray *adapters, const char *path) {
+ g_assert(adapters != NULL);
+ g_assert(path != NULL);
+ g_assert(strlen(path) > 0);
+
+ for (guint i = 0; i < adapters->len; i++) {
+ Adapter *adapter = g_ptr_array_index(adapters, i);
+ const char *adapter_path = binc_adapter_get_path(adapter);
+ if (g_str_has_prefix(path, adapter_path)) {
+ return adapter;
+ }
+ }
+ return NULL;
+}
+
+GPtrArray *binc_adapter_find_all(GDBusConnection *dbusConnection) {
+ g_assert(dbusConnection != NULL);
+
+ GPtrArray *binc_adapters = g_ptr_array_new();
+ log_debug(TAG, "finding adapters");
+
+ GError *error = NULL;
+ GVariant *result = g_dbus_connection_call_sync(dbusConnection,
+ BLUEZ_DBUS,
+ "/",
+ INTERFACE_OBJECT_MANAGER,
+ "GetManagedObjects",
+ NULL,
+ G_VARIANT_TYPE("(a{oa{sa{sv}}})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (result) {
+ GVariantIter *iter;
+ const char *object_path;
+ GVariant *ifaces_and_properties;
+
+ g_assert(g_str_equal(g_variant_get_type_string(result), "(a{oa{sa{sv}}})"));
+ g_variant_get(result, "(a{oa{sa{sv}}})", &iter);
+ while (g_variant_iter_loop(iter, "{&o@a{sa{sv}}}", &object_path, &ifaces_and_properties)) {
+ const char *interface_name;
+ GVariant *properties;
+ GVariantIter iter2;
+
+ g_variant_iter_init(&iter2, ifaces_and_properties);
+ while (g_variant_iter_loop(&iter2, "{&s@a{sv}}", &interface_name, &properties)) {
+ if (g_str_equal(interface_name, INTERFACE_ADAPTER)) {
+ Adapter *adapter = binc_adapter_create(dbusConnection, object_path);
+ char *property_name;
+ GVariantIter iter3;
+ GVariant *property_value;
+ g_variant_iter_init(&iter3, properties);
+ while (g_variant_iter_loop(&iter3, "{&sv}", &property_name, &property_value)) {
+ if (g_str_equal(property_name, ADAPTER_PROPERTY_ADDRESS)) {
+ adapter->address = g_strdup(g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, ADAPTER_PROPERTY_POWERED)) {
+ adapter->powered = g_variant_get_boolean(property_value);
+ } else if (g_str_equal(property_name, ADAPTER_PROPERTY_DISCOVERING)) {
+ adapter->discovering = g_variant_get_boolean(property_value);
+ } else if (g_str_equal(property_name, ADAPTER_PROPERTY_DISCOVERABLE)) {
+ adapter->discoverable = g_variant_get_boolean(property_value);
+ }
+ }
+ g_ptr_array_add(binc_adapters, adapter);
+ } else if (g_str_equal(interface_name, INTERFACE_DEVICE)) {
+ Adapter *adapter = binc_internal_get_adapter_by_path(binc_adapters, object_path);
+ Device *device = binc_device_create(object_path, adapter);
+ g_hash_table_insert(adapter->devices_cache, g_strdup(binc_device_get_path(device)), device);
+
+ char *property_name;
+ GVariantIter iter4;
+ GVariant *property_value;
+ g_variant_iter_init(&iter4, properties);
+ while (g_variant_iter_loop(&iter4, "{&sv}", &property_name, &property_value)) {
+ binc_internal_device_update_property(device, property_name, property_value);
+ }
+ log_debug(TAG, "found device %s '%s'", object_path, binc_device_get_name(device));
+ }
+ }
+ }
+
+ if (iter != NULL) {
+ g_variant_iter_free(iter);
+ }
+ g_variant_unref(result);
+ }
+
+ if (error != NULL) {
+ log_error(TAG, "Error GetManagedObjects: %s", error->message);
+ g_clear_error(&error);
+ }
+
+ log_debug(TAG, "found %d adapter%s", binc_adapters->len, binc_adapters->len > 1 ? "s" : "");
+ return binc_adapters;
+}
+
+Adapter *binc_adapter_get_default(GDBusConnection *dbusConnection) {
+ g_assert(dbusConnection != NULL);
+
+ Adapter *adapter = NULL;
+ GPtrArray *adapters = binc_adapter_find_all(dbusConnection);
+ if (adapters->len > 0) {
+ // Choose the first one in the array, typically the 'hciX' with the highest X
+ adapter = g_ptr_array_index(adapters, 0);
+
+ // Free any other adapters we are not going to use
+ for (guint i = 1; i < adapters->len; i++) {
+ binc_adapter_free(g_ptr_array_index(adapters, i));
+ }
+ g_ptr_array_free(adapters, TRUE);
+ }
+ return adapter;
+}
+
+Adapter *binc_adapter_get(GDBusConnection *dbusConnection, const char *name) {
+ g_assert(dbusConnection != NULL);
+ g_assert(name != NULL && strlen(name) > 0);
+
+ Adapter *result = NULL;
+ GPtrArray *adapters = binc_adapter_find_all(dbusConnection);
+ if (adapters->len > 0) {
+ for (guint i = 0; i < adapters->len; i++) {
+ Adapter *adapter = g_ptr_array_index(adapters, i);
+ if (g_str_equal(binc_adapter_get_name(adapter), name)) {
+ result = adapter;
+ } else {
+ binc_adapter_free(g_ptr_array_index(adapters, i));
+ }
+ }
+ g_ptr_array_free(adapters, TRUE);
+ }
+ return result;
+}
+
+static void binc_internal_start_discovery_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to call '%s' (error %d: %s)", METHOD_START_DISCOVERY, error->code, error->message);
+ adapter->discovery_state = BINC_DISCOVERY_STOPPED;
+ if (adapter->discoveryStateCallback != NULL) {
+ adapter->discoveryStateCallback(adapter, adapter->discovery_state, error);
+ }
+ g_clear_error(&error);
+ } else {
+ binc_internal_set_discovery_state(adapter, BINC_DISCOVERY_STARTED);
+ }
+
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+}
+
+void binc_adapter_start_discovery(Adapter *adapter) {
+ g_assert (adapter != NULL);
+
+ if (adapter->discovery_state == BINC_DISCOVERY_STOPPED) {
+ binc_internal_set_discovery_state(adapter, BINC_DISCOVERY_STARTING);
+ g_dbus_connection_call(adapter->connection,
+ BLUEZ_DBUS,
+ adapter->path,
+ INTERFACE_ADAPTER,
+ METHOD_START_DISCOVERY,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_start_discovery_cb,
+ adapter);
+ }
+}
+
+static void binc_internal_stop_discovery_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to call '%s' (error %d: %s)", METHOD_STOP_DISCOVERY, error->code, error->message);
+ if (adapter->discoveryStateCallback != NULL) {
+ adapter->discoveryStateCallback(adapter, adapter->discovery_state, error);
+ }
+ g_clear_error(&error);
+ } else {
+ binc_internal_set_discovery_state(adapter, BINC_DISCOVERY_STOPPED);
+ }
+
+ if (value != NULL)
+ g_variant_unref(value);
+}
+
+void binc_adapter_stop_discovery(Adapter *adapter) {
+ g_assert (adapter != NULL);
+
+ if (adapter->discovery_state == BINC_DISCOVERY_STARTED) {
+ binc_internal_set_discovery_state(adapter, BINC_DISCOVERY_STOPPING);
+ g_dbus_connection_call(adapter->connection,
+ BLUEZ_DBUS,
+ adapter->path,
+ INTERFACE_ADAPTER,
+ METHOD_STOP_DISCOVERY,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_stop_discovery_cb,
+ adapter);
+ }
+}
+
+void binc_adapter_remove_device(Adapter *adapter, Device *device) {
+ g_assert(device != NULL);
+ g_assert (adapter != NULL);
+
+ log_debug(TAG, "removing %s (%s)", binc_device_get_name(device), binc_device_get_address(device));
+ binc_internal_adapter_call_method(adapter, METHOD_REMOVE_DEVICE,
+ g_variant_new("(o)", binc_device_get_path(device)));
+}
+
+GList *binc_adapter_get_devices(const Adapter *adapter) {
+ g_assert (adapter != NULL);
+ return g_hash_table_get_values(adapter->devices_cache);
+}
+
+GList *binc_adapter_get_connected_devices(const Adapter *adapter) {
+ g_assert (adapter != NULL);
+
+ GList *all_devices = binc_adapter_get_devices(adapter);
+ if (g_list_length(all_devices) <= 0)
+ return all_devices;
+
+ GList *result = NULL;
+ for (GList *iterator = all_devices; iterator; iterator = iterator->next) {
+ Device *device = (Device *) iterator->data;
+ if (binc_device_get_connection_state(device) == BINC_CONNECTED) {
+ result = g_list_append(result, device);
+ }
+ }
+
+ g_list_free(all_devices);
+ return result;
+}
+
+void binc_adapter_set_discovery_filter(Adapter *adapter, short rssi_threshold, const GPtrArray *service_uuids,
+ const char *pattern) {
+ g_assert(adapter != NULL);
+ g_assert(rssi_threshold >= -127);
+ g_assert(rssi_threshold <= 20);
+
+ // Setup discovery filter so we can double-check the results later
+ if (adapter->discovery_filter.services != NULL) {
+ free_discovery_filter(adapter);
+ }
+ adapter->discovery_filter.services = g_ptr_array_new();
+ adapter->discovery_filter.rssi = rssi_threshold;
+ adapter->discovery_filter.pattern = g_strdup(pattern);
+
+ GVariantBuilder *arguments = g_variant_builder_new(G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(arguments, "{sv}", "Transport", g_variant_new_string("le"));
+ g_variant_builder_add(arguments, "{sv}", DEVICE_PROPERTY_RSSI, g_variant_new_int16(rssi_threshold));
+ g_variant_builder_add(arguments, "{sv}", "DuplicateData", g_variant_new_boolean(TRUE));
+
+ if (pattern != NULL) {
+ g_variant_builder_add(arguments, "{sv}", "Pattern", g_variant_new_string(pattern));
+ }
+
+ if (service_uuids != NULL && service_uuids->len > 0) {
+ GVariantBuilder *uuids = g_variant_builder_new(G_VARIANT_TYPE_STRING_ARRAY);
+ for (guint i = 0; i < service_uuids->len; i++) {
+ char *uuid = g_ptr_array_index(service_uuids, i);
+ g_assert(g_uuid_string_is_valid(uuid));
+ g_variant_builder_add(uuids, "s", uuid);
+ g_ptr_array_add(adapter->discovery_filter.services, g_strdup(uuid));
+ }
+ g_variant_builder_add(arguments, "{sv}", DEVICE_PROPERTY_UUIDS, g_variant_builder_end(uuids));
+ g_variant_builder_unref(uuids);
+ }
+
+ GVariant *filter = g_variant_builder_end(arguments);
+ g_variant_builder_unref(arguments);
+ binc_internal_adapter_call_method(adapter, METHOD_SET_DISCOVERY_FILTER, g_variant_new_tuple(&filter, 1));
+}
+
+static void binc_internal_set_property_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to set adapter property (error %d: %s)", error->code, error->message);
+ g_clear_error(&error);
+ }
+}
+
+static void adapter_set_property(Adapter *adapter, const char *property, GVariant *value) {
+ g_assert(adapter != NULL);
+ g_assert(property != NULL);
+ g_assert(value != NULL);
+
+ g_dbus_connection_call(adapter->connection,
+ BLUEZ_DBUS,
+ adapter->path,
+ INTERFACE_PROPERTIES,
+ "Set",
+ g_variant_new("(ssv)", INTERFACE_ADAPTER, property, value),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_set_property_cb,
+ adapter);
+}
+
+void binc_adapter_power_on(Adapter *adapter) {
+ g_assert(adapter != NULL);
+
+ adapter_set_property(adapter, ADAPTER_PROPERTY_POWERED, g_variant_new("b", TRUE));
+}
+
+void binc_adapter_power_off(Adapter *adapter) {
+ g_assert(adapter != NULL);
+
+ adapter_set_property(adapter, ADAPTER_PROPERTY_POWERED, g_variant_new("b", FALSE));
+}
+
+void binc_adapter_discoverable_on(Adapter *adapter) {
+ g_assert(adapter != NULL);
+
+ adapter_set_property(adapter, ADAPTER_PROPERTY_DISCOVERABLE, g_variant_new("b", TRUE));
+}
+
+void binc_adapter_discoverable_off(Adapter *adapter) {
+ g_assert(adapter != NULL);
+
+ adapter_set_property(adapter, ADAPTER_PROPERTY_DISCOVERABLE, g_variant_new("b", FALSE));
+}
+
+
+void binc_adapter_set_discovery_cb(Adapter *adapter, AdapterDiscoveryResultCallback callback) {
+ g_assert(adapter != NULL);
+ g_assert(callback != NULL);
+
+ adapter->discoveryResultCallback = callback;
+}
+
+void binc_adapter_set_discovery_state_cb(Adapter *adapter, AdapterDiscoveryStateChangeCallback callback) {
+ g_assert(adapter != NULL);
+ g_assert(callback != NULL);
+
+ adapter->discoveryStateCallback = callback;
+}
+
+void binc_adapter_set_powered_state_cb(Adapter *adapter, AdapterPoweredStateChangeCallback callback) {
+ g_assert(adapter != NULL);
+ g_assert(callback != NULL);
+
+ adapter->poweredStateCallback = callback;
+}
+
+const char *binc_adapter_get_path(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return adapter->path;
+}
+
+DiscoveryState binc_adapter_get_discovery_state(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return adapter->discovery_state;
+}
+
+gboolean binc_adapter_get_powered_state(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return adapter->powered;
+}
+
+gboolean binc_adapter_is_discoverable(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return adapter->discoverable;
+}
+
+Device *binc_adapter_get_device_by_path(const Adapter *adapter, const char *path) {
+ g_assert(adapter != NULL);
+ return g_hash_table_lookup(adapter->devices_cache, path);
+}
+
+Device *binc_adapter_get_device_by_address(const Adapter *adapter, const char *address) {
+ g_assert(adapter != NULL);
+ g_assert(address != NULL);
+ g_assert(strlen(address) == MAC_ADDRESS_LENGTH);
+
+ char *path = g_strdup_printf("%s/dev_%s", adapter->path, address);
+ path = replace_char(path, ':', '_');
+ Device *device = g_hash_table_lookup(adapter->devices_cache, path);
+ g_free(path);
+ return device;
+}
+
+GDBusConnection *binc_adapter_get_dbus_connection(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return adapter->connection;
+}
+
+const char *binc_adapter_get_discovery_state_name(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return discovery_state_names[adapter->discovery_state];
+}
+
+static void binc_internal_start_advertising_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to register advertisement (error %d: %s)", error->code, error->message);
+ g_clear_error(&error);
+ } else {
+ log_debug(TAG, "started advertising (%s)", adapter->address);
+ }
+}
+
+void binc_adapter_start_advertising(Adapter *adapter, Advertisement *advertisement) {
+ g_assert(adapter != NULL);
+ g_assert(advertisement != NULL);
+
+ adapter->advertisement = advertisement;
+ binc_advertisement_register(advertisement, adapter);
+
+ g_dbus_connection_call(binc_adapter_get_dbus_connection(adapter),
+ "org.bluez",
+ adapter->path,
+ "org.bluez.LEAdvertisingManager1",
+ "RegisterAdvertisement",
+ g_variant_new("(oa{sv})", binc_advertisement_get_path(advertisement), NULL),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_start_advertising_cb, adapter);
+}
+
+static void binc_internal_stop_advertising_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to unregister advertisement (error %d: %s)", error->code, error->message);
+ g_clear_error(&error);
+ } else {
+ binc_advertisement_unregister(adapter->advertisement, adapter);
+ log_debug(TAG, "stopped advertising");
+ }
+
+}
+
+void binc_adapter_stop_advertising(Adapter *adapter, Advertisement *advertisement) {
+ g_assert(adapter != NULL);
+ g_assert(advertisement != NULL);
+
+ g_dbus_connection_call(binc_adapter_get_dbus_connection(adapter),
+ "org.bluez",
+ adapter->path,
+ "org.bluez.LEAdvertisingManager1",
+ "UnregisterAdvertisement",
+ g_variant_new("(o)", binc_advertisement_get_path(advertisement)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_stop_advertising_cb, adapter);
+}
+
+static void binc_internal_register_appl_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to register application (error %d: %s)", error->code, error->message);
+ g_clear_error(&error);
+ } else {
+ log_debug(TAG, "successfully registered application");
+ }
+}
+
+void binc_adapter_register_application(Adapter *adapter, Application *application) {
+ g_assert(adapter != NULL);
+ g_assert(application != NULL);
+
+ g_dbus_connection_call(binc_adapter_get_dbus_connection(adapter),
+ BLUEZ_DBUS,
+ adapter->path,
+ INTERFACE_GATT_MANAGER,
+ "RegisterApplication",
+ g_variant_new("(oa{sv})", binc_application_get_path(application), NULL),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_register_appl_cb, adapter);
+
+}
+
+static void binc_internal_unregister_appl_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ Adapter *adapter = (Adapter *) user_data;
+ g_assert(adapter != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(adapter->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to unregister application (error %d: %s)", error->code, error->message);
+ g_clear_error(&error);
+ } else {
+ log_debug(TAG, "successfully unregistered application");
+ }
+}
+
+void binc_adapter_unregister_application(Adapter *adapter, Application *application) {
+ g_assert(adapter != NULL);
+ g_assert(application != NULL);
+
+ g_dbus_connection_call(binc_adapter_get_dbus_connection(adapter),
+ BLUEZ_DBUS,
+ adapter->path,
+ INTERFACE_GATT_MANAGER,
+ "UnregisterApplication",
+ g_variant_new("(o)", binc_application_get_path(application)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_unregister_appl_cb, adapter);
+
+}
+
+void binc_adapter_set_remote_central_cb(Adapter *adapter, RemoteCentralConnectionStateCallback callback) {
+ g_assert(adapter != NULL);
+ adapter->centralStateCallback = callback;
+}
+
+void binc_adapter_set_user_data(Adapter *adapter, void *user_data) {
+ g_assert(adapter != NULL);
+ adapter->user_data = user_data;
+}
+
+void *binc_adapter_get_user_data(const Adapter *adapter) {
+ g_assert(adapter != NULL);
+ return adapter->user_data;
+}
diff --git a/ble-fast-pair/adapter.h b/ble-fast-pair/adapter.h
new file mode 100644
index 0000000..07afa2e
--- /dev/null
+++ b/ble-fast-pair/adapter.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_ADAPTER_H
+#define BINC_ADAPTER_H
+
+#include <gio/gio.h>
+#include "forward_decl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum DiscoveryState {
+ BINC_DISCOVERY_STOPPED = 0, BINC_DISCOVERY_STARTED = 1, BINC_DISCOVERY_STARTING = 2, BINC_DISCOVERY_STOPPING = 3
+} DiscoveryState;
+
+typedef void (*AdapterDiscoveryResultCallback)(Adapter *adapter, Device *device);
+
+typedef void (*AdapterDiscoveryStateChangeCallback)(Adapter *adapter, DiscoveryState state, const GError *error);
+
+typedef void (*AdapterPoweredStateChangeCallback)(Adapter *adapter, gboolean state);
+
+typedef void (*RemoteCentralConnectionStateCallback)(Adapter *adapter, Device *device);
+
+
+Adapter *binc_adapter_get_default(GDBusConnection *dbusConnection);
+
+Adapter *binc_adapter_get(GDBusConnection *dbusConnection, const char *name);
+
+GPtrArray *binc_adapter_find_all(GDBusConnection *dbusConnection);
+
+void binc_adapter_free(Adapter *adapter);
+
+void binc_adapter_start_discovery(Adapter *adapter);
+
+void binc_adapter_stop_discovery(Adapter *adapter);
+
+void binc_adapter_set_discovery_filter(Adapter *adapter, short rssi_threshold, const GPtrArray *service_uuids, const char *pattern);
+
+void binc_adapter_remove_device(Adapter *adapter, Device *device);
+
+GList *binc_adapter_get_devices(const Adapter *adapter);
+
+GList *binc_adapter_get_connected_devices(const Adapter *adapter);
+
+Device *binc_adapter_get_device_by_path(const Adapter *adapter, const char *path); // make this internal
+
+Device *binc_adapter_get_device_by_address(const Adapter *adapter, const char *address);
+
+void binc_adapter_power_on(Adapter *adapter);
+
+void binc_adapter_power_off(Adapter *adapter);
+
+void binc_adapter_discoverable_on(Adapter *adapter);
+
+void binc_adapter_discoverable_off(Adapter *adapter);
+
+const char *binc_adapter_get_path(const Adapter *adapter);
+
+const char *binc_adapter_get_name(const Adapter *adapter);
+
+const char *binc_adapter_get_address(const Adapter *adapter);
+
+DiscoveryState binc_adapter_get_discovery_state(const Adapter *adapter);
+
+const char *binc_adapter_get_discovery_state_name(const Adapter *adapter);
+
+gboolean binc_adapter_get_powered_state(const Adapter *adapter);
+
+void binc_adapter_set_discovery_cb(Adapter *adapter, AdapterDiscoveryResultCallback callback);
+
+void binc_adapter_set_discovery_state_cb(Adapter *adapter, AdapterDiscoveryStateChangeCallback callback);
+
+void binc_adapter_set_powered_state_cb(Adapter *adapter, AdapterPoweredStateChangeCallback callback);
+
+GDBusConnection *binc_adapter_get_dbus_connection(const Adapter *adapter);
+
+gboolean binc_adapter_is_discoverable(const Adapter *adapter);
+
+void binc_adapter_start_advertising(Adapter *adapter, Advertisement *advertisement);
+
+void binc_adapter_stop_advertising(Adapter *adapter, Advertisement *advertisement);
+
+void binc_adapter_register_application(Adapter *adapter, Application *application);
+
+void binc_adapter_unregister_application(Adapter *adapter, Application *application);
+
+void binc_adapter_set_remote_central_cb(Adapter *adapter, RemoteCentralConnectionStateCallback callback);
+
+void binc_adapter_set_user_data(Adapter *adapter, void *user_data);
+
+void *binc_adapter_get_user_data(const Adapter *adapter);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_ADAPTER_H
diff --git a/ble-fast-pair/advertisement.c b/ble-fast-pair/advertisement.c
new file mode 100644
index 0000000..4c8c1a7
--- /dev/null
+++ b/ble-fast-pair/advertisement.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "advertisement.h"
+#include "adapter.h"
+#include "logger.h"
+#include "utility.h"
+
+static const char *const TAG = "Advertisement";
+
+struct binc_advertisement {
+ char *path; // Owned
+ char *local_name; // Owned
+ GPtrArray *services; // Owned
+ GHashTable *manufacturer_data; // Owned
+ GHashTable *service_data; // Owned
+ guint registration_id;
+};
+
+static void add_manufacturer_data(gpointer key, gpointer value, gpointer userdata) {
+ GByteArray *byteArray = (GByteArray *) value;
+ GVariant *byteArrayVariant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, byteArray->data,
+ byteArray->len, sizeof(guint8));
+ guint16 manufacturer_id = *(int *) key;
+ g_variant_builder_add((GVariantBuilder *) userdata, "{qv}", manufacturer_id, byteArrayVariant);
+}
+
+static void add_service_data(gpointer key, gpointer value, gpointer userdata) {
+ GByteArray *byteArray = (GByteArray *) value;
+ GVariant *byteArrayVariant = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, byteArray->data,
+ byteArray->len, sizeof(guint8));
+ g_variant_builder_add((GVariantBuilder *) userdata, "{sv}", (char *) key, byteArrayVariant);
+}
+
+GVariant *advertisement_get_property(GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data) {
+
+ GVariant *ret = NULL;
+ Advertisement *advertisement = user_data;
+ g_assert(advertisement != NULL);
+
+ if (g_str_equal(property_name, "Type")) {
+ ret = g_variant_new_string("peripheral");
+ } else if (g_str_equal(property_name, "LocalName")) {
+ ret = advertisement->local_name ? g_variant_new_string(advertisement->local_name) : NULL;
+ } else if (g_str_equal(property_name, "ServiceUUIDs")) {
+ GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
+ if (advertisement->services != NULL) {
+ for (guint i = 0; i < advertisement->services->len; i++) {
+ char *service_uuid = g_ptr_array_index(advertisement->services, i);
+ g_variant_builder_add(builder, "s", service_uuid);
+ }
+ }
+ ret = g_variant_builder_end(builder);
+ g_variant_builder_unref(builder);
+ } else if (g_str_equal(property_name, "ManufacturerData")) {
+ GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("a{qv}"));
+ if (advertisement->manufacturer_data != NULL && g_hash_table_size(advertisement->manufacturer_data) > 0) {
+ g_hash_table_foreach(advertisement->manufacturer_data, add_manufacturer_data, builder);
+ }
+ ret = g_variant_builder_end(builder);
+ g_variant_builder_unref(builder);
+ } else if (g_str_equal(property_name, "ServiceData")) {
+ GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+ if (advertisement->service_data != NULL && g_hash_table_size(advertisement->service_data) > 0) {
+ g_hash_table_foreach(advertisement->service_data, add_service_data, builder);
+ }
+ ret = g_variant_builder_end(builder);
+ g_variant_builder_unref(builder);
+ }
+ return ret;
+}
+
+static void advertisement_method_call(__attribute__((unused)) GDBusConnection *conn,
+ __attribute__((unused)) const gchar *sender,
+ __attribute__((unused)) const gchar *path,
+ __attribute__((unused)) const gchar *interface,
+ __attribute__((unused)) const gchar *method,
+ __attribute__((unused)) GVariant *params,
+ __attribute__((unused)) GDBusMethodInvocation *invocation,
+ void *userdata) {
+ log_debug(TAG, "advertisement method called");
+}
+
+static const GDBusInterfaceVTable advertisement_method_table = {
+ .method_call = advertisement_method_call,
+ .get_property = advertisement_get_property
+};
+
+void binc_advertisement_register(Advertisement *advertisement, const Adapter *adapter) {
+ g_assert(advertisement != NULL);
+ g_assert(adapter != NULL);
+
+ static const char advertisement_xml[] =
+ "<node name='/'>"
+ " <interface name='org.bluez.LEAdvertisement1'>"
+ " <method name='Release' />"
+ " <property name='Type' type='s' access='read'/>"
+ " <property name='LocalName' type='s' access='read'/>"
+ " <property name='ManufacturerData' type='a{qv}' access='read'/>"
+ " <property name='ServiceData' type='a{sv}' access='read'/>"
+ " <property name='ServiceUUIDs' type='as' access='read'/>"
+ " </interface>"
+ "</node>";
+
+ GError *error = NULL;
+ GDBusNodeInfo *info = g_dbus_node_info_new_for_xml(advertisement_xml, &error);
+ advertisement->registration_id = g_dbus_connection_register_object(binc_adapter_get_dbus_connection(adapter),
+ advertisement->path,
+ info->interfaces[0],
+ &advertisement_method_table,
+ advertisement, NULL, &error);
+
+ if (error != NULL) {
+ log_debug(TAG, "registering advertisement failed: %s", error->message);
+ g_clear_error(&error);
+ }
+ g_dbus_node_info_unref(info);
+}
+
+void binc_advertisement_unregister(Advertisement *advertisement, const Adapter *adapter) {
+ g_assert(advertisement != NULL);
+ g_assert(adapter != NULL);
+
+ gboolean result = g_dbus_connection_unregister_object(binc_adapter_get_dbus_connection(adapter),
+ advertisement->registration_id);
+ if (!result) {
+ log_debug(TAG, "failed to unregister advertisement");
+ }
+}
+
+static void byte_array_free(GByteArray *byteArray) { g_byte_array_free(byteArray, TRUE); }
+
+Advertisement *binc_advertisement_create() {
+ Advertisement *advertisement = g_new0(Advertisement, 1);
+ advertisement->path = g_strdup("/org/bluez/bincadvertisement");
+ advertisement->manufacturer_data = g_hash_table_new_full(g_int_hash, g_int_equal, g_free,
+ (GDestroyNotify) byte_array_free);
+ advertisement->service_data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) byte_array_free);
+ return advertisement;
+}
+
+void binc_advertisement_free(Advertisement *advertisement) {
+ g_assert(advertisement != NULL);
+
+ g_free(advertisement->path);
+ advertisement->path = NULL;
+
+ g_free(advertisement->local_name);
+ advertisement->local_name = NULL;
+
+ if (advertisement->services != NULL) {
+ g_ptr_array_free(advertisement->services, TRUE);
+ advertisement->services = NULL;
+ }
+ if (advertisement->manufacturer_data != NULL) {
+ g_hash_table_destroy(advertisement->manufacturer_data);
+ advertisement->manufacturer_data = NULL;
+ }
+ if (advertisement->service_data != NULL) {
+ g_hash_table_destroy(advertisement->service_data);
+ advertisement->service_data = NULL;
+ }
+
+ g_free(advertisement);
+}
+
+void binc_advertisement_set_local_name(Advertisement *advertisement, const char *local_name) {
+ g_assert(advertisement != NULL);
+
+ g_free(advertisement->local_name);
+ advertisement->local_name = g_strdup(local_name);
+}
+
+const char *binc_advertisement_get_path(const Advertisement *advertisement) {
+ g_assert(advertisement != NULL);
+ return advertisement->path;
+}
+
+void binc_advertisement_set_services(Advertisement *advertisement, const GPtrArray *service_uuids) {
+ g_assert(advertisement != NULL);
+ g_assert(service_uuids != NULL);
+
+ if (advertisement->services != NULL) {
+ g_ptr_array_free(advertisement->services, TRUE);
+ }
+ advertisement->services = g_ptr_array_new_with_free_func(g_free);
+
+ for (guint i = 0; i < service_uuids->len; i++) {
+ g_ptr_array_add(advertisement->services, g_strdup(g_ptr_array_index(service_uuids, i)));
+ }
+}
+
+void binc_advertisement_set_manufacturer_data(Advertisement *advertisement, guint16 manufacturer_id,
+ const GByteArray *byteArray) {
+ g_assert(advertisement != NULL);
+ g_assert(advertisement->manufacturer_data != NULL);
+ g_assert(byteArray != NULL);
+
+ int man_id = manufacturer_id;
+ g_hash_table_remove(advertisement->manufacturer_data, &man_id);
+
+ int *key = g_new0 (int, 1);
+ *key = manufacturer_id;
+
+ GByteArray *value = g_byte_array_sized_new(byteArray->len);
+ g_byte_array_append(value, byteArray->data, byteArray->len);
+
+ g_hash_table_insert(advertisement->manufacturer_data, key, value);
+}
+
+void binc_advertisement_set_service_data(Advertisement *advertisement, const char *service_uuid,
+ const GByteArray *byteArray) {
+ g_assert(advertisement != NULL);
+ g_assert(advertisement->service_data != NULL);
+ g_assert(service_uuid != NULL);
+ g_assert(is_valid_uuid(service_uuid));
+ g_assert(byteArray != NULL);
+
+ g_hash_table_remove(advertisement->service_data, service_uuid);
+
+ GByteArray *value = g_byte_array_sized_new(byteArray->len);
+ g_byte_array_append(value, byteArray->data, byteArray->len);
+
+ g_hash_table_insert(advertisement->service_data, g_strdup(service_uuid), value);
+}
+
+
+
diff --git a/ble-fast-pair/advertisement.h b/ble-fast-pair/advertisement.h
new file mode 100644
index 0000000..8cbaf79
--- /dev/null
+++ b/ble-fast-pair/advertisement.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_ADVERTISEMENT_H
+#define BINC_ADVERTISEMENT_H
+
+#include <gio/gio.h>
+#include "forward_decl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+Advertisement *binc_advertisement_create();
+
+void binc_advertisement_free(Advertisement *advertisement);
+
+void binc_advertisement_set_local_name(Advertisement *advertisement, const char* local_name);
+
+void binc_advertisement_set_services(Advertisement *advertisement, const GPtrArray * service_uuids);
+
+void binc_advertisement_set_manufacturer_data(Advertisement *advertisement, guint16 manufacturer_id, const GByteArray *byteArray);
+
+void binc_advertisement_set_service_data(Advertisement *advertisement, const char* service_uuid, const GByteArray *byteArray);
+
+const char *binc_advertisement_get_path(const Advertisement *advertisement);
+
+void binc_advertisement_register(Advertisement *advertisement, const Adapter *adapter);
+
+void binc_advertisement_unregister(Advertisement *advertisement, const Adapter *adapter);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_ADVERTISEMENT_H
diff --git a/ble-fast-pair/agent.c b/ble-fast-pair/agent.c
new file mode 100644
index 0000000..72e7990
--- /dev/null
+++ b/ble-fast-pair/agent.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "agent.h"
+#include "adapter.h"
+#include "device.h"
+#include "device_internal.h"
+#include "logger.h"
+#include <errno.h>
+#include <glib.h>
+#include <stdio.h>
+
+#define TAG "Agent"
+
+struct binc_agent {
+ char *path; // Owned
+ IoCapability io_capability;
+ GDBusConnection *connection; // Borrowed
+ Adapter *adapter; // Borrowed
+ guint registration_id;
+ AgentRequestAuthorizationCallback request_authorization_callback;
+ AgentRequestPasskeyCallback request_passkey_callback;
+};
+
+void binc_agent_free(Agent *agent) {
+ g_assert (agent != NULL);
+ gboolean result = g_dbus_connection_unregister_object(agent->connection, agent->registration_id);
+ if (!result) {
+ log_debug(TAG, "could not unregister agent");
+ }
+
+ g_free((char *) agent->path);
+ agent->path = NULL;
+
+ agent->connection = NULL;
+ agent->adapter = NULL;
+
+ g_free(agent);
+}
+
+static void bluez_agent_method_call(GDBusConnection *conn,
+ const gchar *sender,
+ const gchar *path,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *params,
+ GDBusMethodInvocation *invocation,
+ void *userdata) {
+ guint32 pass;
+ guint16 entered;
+ char *object_path = NULL;
+ char *pin = NULL;
+ char *uuid = NULL;
+
+ Agent *agent = (Agent *) userdata;
+ g_assert(agent != NULL);
+
+ Adapter *adapter = agent->adapter;
+ g_assert(adapter != NULL);
+
+ if (method)
+ log_debug(TAG, "method:%s", method);
+
+ if (g_str_equal(method, "RequestPinCode")) {
+ g_variant_get(params, "(o)", &object_path);
+ log_debug(TAG, "request pincode for %s", object_path);
+ g_free(object_path);
+
+ // add code to request pin
+ pin = "123";
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", pin));
+ } else if (g_str_equal(method, "DisplayPinCode")) {
+ g_variant_get(params, "(os)", &object_path, &pin);
+ log_debug(TAG, "displaying pincode %s", pin);
+ g_free(object_path);
+ g_free(pin);
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ } else if (g_str_equal(method, "RequestPasskey")) {
+ g_variant_get(params, "(o)", &object_path);
+ Device *device = binc_adapter_get_device_by_path(adapter, object_path);
+ g_free(object_path);
+
+ if (device != NULL) {
+ binc_device_set_bonding_state(device, BINC_BONDING);
+ }
+
+ if (agent->request_passkey_callback != NULL) {
+ pass = agent->request_passkey_callback(device);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", pass));
+ } else {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.bluez.Error.Rejected", "No passkey inputted");
+ }
+ } else if (g_str_equal(method, "DisplayPasskey")) {
+ g_variant_get(params, "(ouq)", &object_path, &pass, &entered);
+ log_debug(TAG, "passkey: %u, entered: %u", pass, entered);
+ g_free(object_path);
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ } else if (g_str_equal(method, "RequestConfirmation")) {
+ g_variant_get(params, "(ou)", &object_path, &pass);
+ g_free(object_path);
+ log_debug(TAG, "request confirmation for %u", pass);
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ } else if (g_str_equal(method, "RequestAuthorization")) {
+ g_variant_get(params, "(o)", &object_path);
+ log_debug(TAG, "request for authorization %s", object_path);
+ Device *device = binc_adapter_get_device_by_path(adapter, object_path);
+ g_free(object_path);
+ if (device != NULL) {
+ binc_device_set_bonding_state(device, BINC_BONDING);
+ }
+ if (agent->request_authorization_callback != NULL) {
+ gboolean allowed = agent->request_authorization_callback(device);
+ if (allowed) {
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ } else {
+ g_dbus_method_invocation_return_dbus_error(invocation, "org.bluez.Error.Rejected", "Pairing rejected");
+ }
+ } else {
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ }
+ } else if (g_str_equal(method, "AuthorizeService")) {
+ g_variant_get(params, "(os)", &object_path, &uuid);
+ log_debug(TAG, "authorize service");
+ g_free(object_path);
+ g_free(uuid);
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ } else if (g_str_equal(method, "Cancel")) {
+ log_debug(TAG, "cancelling pairing");
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ } else if (g_str_equal(method, "Release")) {
+ log_debug(TAG, "agent released");
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ } else
+ log_error(TAG, "We should not come here, unknown method");
+}
+
+static const GDBusInterfaceVTable agent_method_table = {
+ .method_call = bluez_agent_method_call,
+};
+
+static int bluez_register_agent(Agent *agent) {
+ GError *error = NULL;
+ GDBusNodeInfo *info = NULL;
+
+ static const gchar bluez_agent_introspection_xml[] =
+ "<node name='/org/bluez/SampleAgent'>"
+ " <interface name='org.bluez.Agent1'>"
+ " <method name='Release'>"
+ " </method>"
+ " <method name='RequestPinCode'>"
+ " <arg type='o' name='device' direction='in' />"
+ " <arg type='s' name='pincode' direction='out' />"
+ " </method>"
+ " <method name='DisplayPinCode'>"
+ " <arg type='o' name='device' direction='in' />"
+ " <arg type='s' name='pincode' direction='in' />"
+ " </method>"
+ " <method name='RequestPasskey'>"
+ " <arg type='o' name='device' direction='in' />"
+ " <arg type='u' name='passkey' direction='out' />"
+ " </method>"
+ " <method name='DisplayPasskey'>"
+ " <arg type='o' name='device' direction='in' />"
+ " <arg type='u' name='passkey' direction='in' />"
+ " <arg type='q' name='entered' direction='in' />"
+ " </method>"
+ " <method name='RequestConfirmation'>"
+ " <arg type='o' name='device' direction='in' />"
+ " <arg type='u' name='passkey' direction='in' />"
+ " </method>"
+ " <method name='RequestAuthorization'>"
+ " <arg type='o' name='device' direction='in' />"
+ " </method>"
+ " <method name='AuthorizeService'>"
+ " <arg type='o' name='device' direction='in' />"
+ " <arg type='s' name='uuid' direction='in' />"
+ " </method>"
+ " <method name='Cancel'>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+ info = g_dbus_node_info_new_for_xml(bluez_agent_introspection_xml, &error);
+ if (error) {
+ log_error(TAG, "Unable to create node: %s\n", error->message);
+ g_clear_error(&error);
+ return EINVAL;
+ }
+
+ agent->registration_id = g_dbus_connection_register_object(agent->connection,
+ agent->path,
+ info->interfaces[0],
+ &agent_method_table,
+ agent, NULL, &error);
+ g_dbus_node_info_unref(info);
+ return 0;
+}
+
+static int binc_agentmanager_call_method(GDBusConnection *connection, const gchar *method, GVariant *param) {
+ g_assert(connection != NULL);
+ g_assert(method != NULL);
+
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_connection_call_sync(connection,
+ "org.bluez",
+ "/org/bluez",
+ "org.bluez.AgentManager1",
+ method,
+ param,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_variant_unref(result);
+
+ if (error != NULL) {
+ log_error(TAG, "AgentManager call failed '%s': %s\n", method, error->message);
+ g_clear_error(&error);
+ return 1;
+ }
+
+ return 0;
+}
+
+int binc_agentmanager_register_agent(Agent *agent) {
+ g_assert(agent != NULL);
+ char *capability = NULL;
+
+ switch (agent->io_capability) {
+ case DISPLAY_ONLY:
+ capability = "DisplayOnly";
+ break;
+ case DISPLAY_YES_NO:
+ capability = "DisplayYesNo";
+ break;
+ case KEYBOARD_ONLY:
+ capability = "KeyboardOnly";
+ break;
+ case NO_INPUT_NO_OUTPUT:
+ capability = "NoInputNoOutput";
+ break;
+ case KEYBOARD_DISPLAY:
+ capability = "KeyboardDisplay";
+ break;
+ }
+ int result = binc_agentmanager_call_method(agent->connection, "RegisterAgent",
+ g_variant_new("(os)", agent->path, capability));
+ if (result == EXIT_FAILURE) {
+ log_debug(TAG, "failed to register agent");
+ }
+
+ result = binc_agentmanager_call_method(agent->connection, "RequestDefaultAgent", g_variant_new("(o)", agent->path));
+ if (result == EXIT_FAILURE) {
+ log_debug(TAG, "failed to register agent as default agent");
+ }
+ return result;
+}
+
+Agent *binc_agent_create(Adapter *adapter, const char *path, IoCapability io_capability) {
+ Agent *agent = g_new0(Agent, 1);
+ agent->path = g_strdup(path);
+ agent->connection = binc_adapter_get_dbus_connection(adapter);
+ agent->adapter = adapter;
+ agent->io_capability = io_capability;
+ bluez_register_agent(agent);
+ binc_agentmanager_register_agent(agent);
+ return agent;
+}
+
+void binc_agent_set_request_authorization_cb(Agent *agent, AgentRequestAuthorizationCallback callback) {
+ g_assert(agent != NULL);
+ g_assert(callback != NULL);
+ agent->request_authorization_callback = callback;
+}
+
+void binc_agent_set_request_passkey_cb(Agent *agent, AgentRequestPasskeyCallback callback) {
+ g_assert(agent != NULL);
+ g_assert(callback != NULL);
+ agent->request_passkey_callback = callback;
+}
diff --git a/ble-fast-pair/agent.h b/ble-fast-pair/agent.h
new file mode 100644
index 0000000..1c23fab
--- /dev/null
+++ b/ble-fast-pair/agent.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_AGENT_H
+#define BINC_AGENT_H
+
+#include <gio/gio.h>
+#include "adapter.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef gboolean (*AgentRequestAuthorizationCallback)(Device *device);
+
+typedef guint32 (*AgentRequestPasskeyCallback)(Device *device);
+
+typedef enum {
+ DISPLAY_ONLY, DISPLAY_YES_NO, KEYBOARD_ONLY, NO_INPUT_NO_OUTPUT, KEYBOARD_DISPLAY
+} IoCapability;
+
+typedef struct binc_agent Agent;
+
+Agent *binc_agent_create(Adapter *adapter, const char *path, IoCapability io_capability);
+
+void binc_agent_set_request_authorization_cb(Agent *agent, AgentRequestAuthorizationCallback callback);
+
+void binc_agent_set_request_passkey_cb(Agent *agent, AgentRequestPasskeyCallback callback);
+
+void binc_agent_free(Agent *agent);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_AGENT_H
diff --git a/ble-fast-pair/application.c b/ble-fast-pair/application.c
new file mode 100644
index 0000000..ab797e5
--- /dev/null
+++ b/ble-fast-pair/application.c
@@ -0,0 +1,1295 @@
+// 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;
+}
diff --git a/ble-fast-pair/application.h b/ble-fast-pair/application.h
new file mode 100644
index 0000000..1d69828
--- /dev/null
+++ b/ble-fast-pair/application.h
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_APPLICATION_H
+#define BINC_APPLICATION_H
+
+#include <gio/gio.h>
+#include "forward_decl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Errors
+#define BLUEZ_ERROR_REJECTED "org.bluez.Error.Rejected"
+#define BLUEZ_ERROR_FAILED "org.bluez.Error.Failed"
+#define BLUEZ_ERROR_INPROGRESS "org.bluez.Error.InProgress"
+#define BLUEZ_ERROR_NOT_PERMITTED "org.bluez.Error.NotPermitted"
+#define BLUEZ_ERROR_INVALID_VALUE_LENGTH "org.bluez.Error.InvalidValueLength"
+#define BLUEZ_ERROR_NOT_AUTHORIZED "org.bluez.Error.NotAuthorized"
+#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
+
+// This callback is called just before the characteristic's value is returned.
+// Use it to update the characteristic before it is read
+// For accepting the read, return NULL, otherwise return an error (BLUEZ_ERROR_*)
+typedef const char *(*onLocalCharacteristicRead)(const Application *application, const char *address,
+ const char *service_uuid, const char *char_uuid);
+
+// This callback is called just before the characteristic's value is set.
+// Use it to accept (return NULL), or reject (return BLUEZ_ERROR_*) the byte array
+typedef const char *(*onLocalCharacteristicWrite)(const Application *application, const char *address,
+ const char *service_uuid, const char *char_uuid, GByteArray *byteArray);
+
+// This callback is called after a characteristic's value is set, e.g. because of a 'write' or 'notify'
+// Use it to act upon the new value set
+typedef void (*onLocalCharacteristicUpdated)(const Application *application, const char *service_uuid,
+ const char *char_uuid, GByteArray *byteArray);
+
+// This callback is called when notifications are enabled for a characteristic
+typedef void (*onLocalCharacteristicStartNotify)(const Application *application, const char *service_uuid,
+ const char *char_uuid);
+
+// This callback is called when notifications are disabled for a characteristic
+typedef void (*onLocalCharacteristicStopNotify)(const Application *application, const char *service_uuid,
+ const char *char_uuid);
+
+// This callback is called just before the descriptor's value is returned.
+// Use it to update the descriptor before it is read
+typedef const char *(*onLocalDescriptorRead)(const Application *application, const char *address,
+ const char *service_uuid, const char *char_uuid, const char *desc_uuid);
+
+// This callback is called just before the descriptor's value is set.
+// Use it to accept (return NULL), or reject (return BLUEZ_ERROR_*) the byte array
+typedef const char *(*onLocalDescriptorWrite)(const Application *application, const char *address,
+ const char *service_uuid, const char *char_uuid,
+ const char *desc_uuid, const GByteArray *byteArray);
+
+// Methods
+Application *binc_create_application(const Adapter *adapter);
+
+void binc_application_free(Application *application);
+
+const char *binc_application_get_path(const Application *application);
+
+int binc_application_add_service(Application *application, const char *service_uuid);
+
+int binc_application_add_characteristic(Application *application, const char *service_uuid,
+ const char *char_uuid, guint permissions);
+
+int binc_application_add_descriptor(Application *application, const char *service_uuid,
+ const char *char_uuid, const char *desc_uuid, guint permissions);
+
+void binc_application_set_char_read_cb(Application *application, onLocalCharacteristicRead callback);
+
+void binc_application_set_char_write_cb(Application *application, onLocalCharacteristicWrite callback);
+
+void binc_application_set_char_start_notify_cb(Application *application, onLocalCharacteristicStartNotify callback);
+
+void binc_application_set_char_stop_notify_cb(Application *application, onLocalCharacteristicStopNotify callback);
+
+int binc_application_set_char_value(const Application *application, const char *service_uuid,
+ const char *char_uuid, GByteArray *byteArray);
+
+GByteArray *binc_application_get_char_value(const Application *application, const char *service_uuid,
+ const char *char_uuid);
+
+void binc_application_set_desc_read_cb(Application *application, onLocalDescriptorRead callback);
+
+void binc_application_set_desc_write_cb(Application *application, onLocalDescriptorWrite callback);
+
+int binc_application_set_desc_value(const Application *application, const char *service_uuid,
+ const char *char_uuid, const char *desc_uuid, GByteArray *byteArray);
+
+int binc_application_notify(const Application *application, const char *service_uuid, const char *char_uuid,
+ const GByteArray *byteArray);
+
+gboolean binc_application_char_is_notifying(const Application *application, const char *service_uuid,
+ const char *char_uuid);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_APPLICATION_H
diff --git a/ble-fast-pair/ble_audio_sink.c b/ble-fast-pair/ble_audio_sink.c
new file mode 100644
index 0000000..5670e58
--- /dev/null
+++ b/ble-fast-pair/ble_audio_sink.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <glib.h>
+#include <stdio.h>
+#include <signal.h>
+#include "adapter.h"
+#include "device.h"
+#include "logger.h"
+#include "agent.h"
+#include "application.h"
+#include "advertisement.h"
+#include "utility.h"
+#include "parser.h"
+
+#define TAG "LEA-SINK"
+#define ADV_NAME "aml-leaudio-sink"
+#define AML_VENDOR_SERVICE_UUID "0000fba0-0000-1000-8000-00805f9b34fb"
+#define RUN_LOOP_TIMEOUT_SECONDS (60 * 20) //20 Minites
+
+static GMainLoop *loop = NULL;
+static Adapter *default_adapter = NULL;
+static Advertisement *advertisement = NULL;
+static Agent *agent = NULL;
+static Application *app = NULL;
+
+static void on_powered_state_changed(Adapter *adapter, gboolean state) {
+ log_debug(TAG, "powered '%s' (%s)", state ? "on" : "off", binc_adapter_get_path(adapter));
+}
+
+static void on_central_state_changed(Adapter *adapter, Device *device) {
+ char *deviceToString = binc_device_to_string(device);
+ log_debug(TAG, deviceToString);
+ g_free(deviceToString);
+
+ log_debug(TAG, "remote central %s is %s", binc_device_get_address(device), binc_device_get_connection_state_name(device));
+ ConnectionState state = binc_device_get_connection_state(device);
+ if (state == BINC_CONNECTED) {
+ binc_adapter_stop_advertising(adapter, advertisement);
+ } else if (state == BINC_DISCONNECTED){
+ binc_adapter_start_advertising(adapter, advertisement);
+ }
+}
+
+static const char *on_local_char_read(const Application *application, const char *address, const char *service_uuid,
+ const char *char_uuid) {
+ return BLUEZ_ERROR_REJECTED;
+}
+
+static const char *on_local_char_write(const Application *application, const char *address, const char *service_uuid,
+ const char *char_uuid, GByteArray *byteArray) {
+ return NULL;
+}
+
+static void on_local_char_start_notify(const Application *application, const char *service_uuid, const char *char_uuid) {
+ log_debug(TAG, "on start notify");
+}
+
+static void on_local_char_stop_notify(const Application *application, const char *service_uuid, const char *char_uuid) {
+ log_debug(TAG, "on stop notify");
+}
+
+static gboolean callback(gpointer data) {
+ if (app != NULL) {
+ binc_adapter_unregister_application(default_adapter, app);
+ binc_application_free(app);
+ app = NULL;
+ }
+
+ if (advertisement != NULL) {
+ binc_adapter_stop_advertising(default_adapter, advertisement);
+ binc_advertisement_free(advertisement);
+ }
+
+ if (default_adapter != NULL) {
+ binc_adapter_free(default_adapter);
+ default_adapter = NULL;
+ }
+
+ g_main_loop_quit((GMainLoop *) data);
+ return FALSE;
+}
+
+static void cleanup_handler(int signo) {
+ if (signo == SIGINT) {
+ log_error(TAG, "received SIGINT");
+ callback(loop);
+ }
+}
+
+static gboolean on_request_authorization(Device *device) {
+ log_debug(TAG, "requesting authorization for '%s", binc_device_get_name(device));
+ return TRUE;
+}
+
+static guint32 on_request_passkey(Device *device) {
+ guint32 pass = 000000;
+ log_debug(TAG, "requesting passkey for '%s", binc_device_get_name(device));
+ log_debug(TAG, "Enter 6 digit pin code: ");
+ int result = fscanf(stdin, "%d", &pass);
+ if (result != 1) {
+ log_error(TAG, "didn't read a pin code");
+ } else {
+ log_debug(TAG, "pass key:%d", pass);
+ }
+ return pass;
+}
+
+int ble_audio_sink_go(void) {
+
+ log_enabled(TRUE);
+ log_set_level(LOG_DEBUG);
+ // log_set_filename("./ble_fast_pair_sink.txt", 1024 * 64, 5);
+ // Get a DBus connection
+ GDBusConnection *dbusConnection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
+
+ // Setup handler for CTRL+C
+ if (signal(SIGINT, cleanup_handler) == SIG_ERR)
+ log_error(TAG, "can't catch SIGINT");
+
+ // Setup mainloop
+ loop = g_main_loop_new(NULL, FALSE);
+
+ // Get the default default_adapter
+ default_adapter = binc_adapter_get_default(dbusConnection);
+
+ if (default_adapter != NULL) {
+ log_debug(TAG, "using default_adapter '%s'", binc_adapter_get_path(default_adapter));
+
+ // Register an agent and set callbacks
+ agent = binc_agent_create(default_adapter, "/org/bluez/BincAgent", DISPLAY_YES_NO);
+ binc_agent_set_request_authorization_cb(agent, &on_request_authorization);
+ binc_agent_set_request_passkey_cb(agent, &on_request_passkey);
+
+ // Make sure the adapter is on
+ binc_adapter_set_powered_state_cb(default_adapter, &on_powered_state_changed);
+ if (!binc_adapter_get_powered_state(default_adapter)) {
+ binc_adapter_power_on(default_adapter);
+ }
+
+ GList *device_list = NULL;
+ GList *iterator = NULL;
+ Device *device = NULL;
+
+ device_list = binc_adapter_get_devices(default_adapter);
+ for (iterator = device_list; iterator != NULL; iterator = iterator->next) {
+ device = (Device *)iterator->data;
+ log_debug(TAG, "Device Name: %s\n", binc_device_get_name(device));
+ log_debug(TAG, "Device Address: %s\n", binc_device_get_address(device));
+ if (device) {
+ binc_adapter_remove_device(default_adapter, device);
+ }
+ }
+
+ // Setup remote central connection state callback
+ binc_adapter_set_remote_central_cb(default_adapter, &on_central_state_changed);
+
+ // Setup advertisement
+ GPtrArray *adv_service_uuids = g_ptr_array_new();
+ g_ptr_array_add(adv_service_uuids, (gpointer)AML_VENDOR_SERVICE_UUID);
+
+ advertisement = binc_advertisement_create();
+ binc_advertisement_set_local_name(advertisement, ADV_NAME);
+ binc_advertisement_set_services(advertisement, adv_service_uuids);
+ g_ptr_array_free(adv_service_uuids, TRUE);
+ binc_adapter_start_advertising(default_adapter, advertisement);
+ } else {
+ log_error(TAG, "No default_adapter found");
+ }
+
+ // Bail out after some time
+ g_timeout_add_seconds(RUN_LOOP_TIMEOUT_SECONDS, callback, loop);
+
+ // Start the mainloop
+ g_main_loop_run(loop);
+
+ // Disconnect from DBus
+ g_dbus_connection_close_sync(dbusConnection, NULL, NULL);
+ g_object_unref(dbusConnection);
+
+ // Clean up mainloop
+ g_main_loop_unref(loop);
+ return 0;
+}
diff --git a/ble-fast-pair/ble_audio_source.c b/ble-fast-pair/ble_audio_source.c
new file mode 100644
index 0000000..a23321f
--- /dev/null
+++ b/ble-fast-pair/ble_audio_source.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <glib.h>
+#include <stdio.h>
+#include <signal.h>
+#include "adapter.h"
+#include "device.h"
+#include "logger.h"
+#include "agent.h"
+#include "parser.h"
+
+#define TAG "LEA-SOUR"
+#define AML_VENDOR_SERVICE_UUID "0000fba0-0000-1000-8000-00805f9b34fb"
+#define RUN_LOOP_TIMEOUT_SECONDS (60 * 20) //20 Minites
+
+static GMainLoop *loop = NULL;
+static Adapter *default_adapter = NULL;
+static Agent *agent = NULL;
+
+static void on_connection_state_changed(Device *device, ConnectionState state, const GError *error) {
+ if (error != NULL) {
+ log_warn(TAG, "%s failed (error %d: %s)", binc_device_get_connection_state_name(device), error->code, error->message);
+ return;
+ }
+
+ log_debug(TAG, "'%s' (%s) bond_state: %d, con_state: %s (%d)",
+ binc_device_get_name(device), binc_device_get_address(device),
+ binc_device_get_bonding_state(device),
+ binc_device_get_connection_state_name(device), state);
+
+ if (state == BINC_DISCONNECTED) {
+ // Remove devices immediately of they are not bonded
+ if (binc_device_get_bonding_state(device) != BINC_BONDED) {
+ binc_adapter_remove_device(default_adapter, device);
+ }
+ }
+
+ if (state == BINC_CONNECTED) {
+ if (binc_device_get_bonding_state(device) == BINC_BOND_NONE) {
+ binc_device_pair(device);
+ }
+ }
+}
+
+static void on_bonding_state_changed(Device *device, BondingState new_state, BondingState old_state, const GError *error) {
+ log_debug(TAG, "bonding state changed from %d to %d", old_state, new_state);
+}
+
+static void on_notification_state_changed(Device *device, Characteristic *characteristic, const GError *error) {
+ const char *uuid = binc_characteristic_get_uuid(characteristic);
+
+ if (error != NULL) {
+ log_debug(TAG, "notifying <%s> failed (error %d: %s)", uuid, error->code, error->message);
+ return;
+ }
+
+ log_debug(TAG, "<%s> notifying %s", uuid, binc_characteristic_is_notifying(characteristic) ? "true" : "false");
+}
+
+static void on_notify(Device *device, Characteristic *characteristic, const GByteArray *byteArray) {
+}
+
+static void on_read(Device *device, Characteristic *characteristic, const GByteArray *byteArray, const GError *error) {
+}
+
+static void on_write(Device *device, Characteristic *characteristic, const GByteArray *byteArray, const GError *error) {
+ log_debug(TAG, "on write");
+}
+
+static void on_desc_read(Device *device, Descriptor *descriptor, const GByteArray *byteArray, const GError *error) {
+ log_debug(TAG, "on descriptor read");
+ Parser *parser = parser_create(byteArray, LITTLE_ENDIAN);
+ GString *parsed_string = parser_get_string(parser);
+ log_debug(TAG, "CUD %s", parsed_string->str);
+ parser_free(parser);
+}
+
+static void on_services_resolved(Device *device) {
+ log_debug(TAG, "'%s' services resolved", binc_device_get_name(device));
+}
+
+static gboolean on_request_authorization(Device *device) {
+ log_debug(TAG, "requesting authorization for '%s", binc_device_get_name(device));
+ return TRUE;
+}
+
+static guint32 on_request_passkey(Device *device) {
+ guint32 pass = 000000;
+ log_debug(TAG, "requesting passkey for '%s", binc_device_get_name(device));
+ log_debug(TAG, "Enter 6 digit pin code: ");
+ int result = fscanf(stdin, "%d", &pass);
+ if (result != 1) {
+ log_error(TAG, "didn't read a pin code");
+ } else {
+ log_debug(TAG, "pass key:%d", pass);
+ }
+ return pass;
+}
+
+static void on_scan_result(Adapter *adapter, Device *device) {
+ char *deviceToString = binc_device_to_string(device);
+ log_debug(TAG, deviceToString);
+ g_free(deviceToString);
+
+ binc_device_set_connection_state_change_cb(device, &on_connection_state_changed);
+ binc_device_set_services_resolved_cb(device, &on_services_resolved);
+ binc_device_set_bonding_state_changed_cb(device, &on_bonding_state_changed);
+ binc_device_set_read_char_cb(device, &on_read);
+ binc_device_set_write_char_cb(device, &on_write);
+ binc_device_set_notify_char_cb(device, &on_notify);
+ binc_device_set_notify_state_cb(device, &on_notification_state_changed);
+ binc_device_set_read_desc_cb(device, &on_desc_read);
+ binc_adapter_stop_discovery(default_adapter);
+ binc_device_connect(device);
+}
+
+static void on_discovery_state_changed(Adapter *adapter, DiscoveryState state, const GError *error) {
+ if (error != NULL) {
+ log_debug(TAG, "discovery error (error %d: %s)", error->code, error->message);
+ return;
+ }
+ log_debug(TAG, "discovery '%s' (%s)", binc_adapter_get_discovery_state_name(adapter),
+ binc_adapter_get_path(adapter));
+}
+
+static void on_powered_state_changed(Adapter *adapter, gboolean state) {
+ log_debug(TAG, "powered '%s' (%s)", state ? "on" : "off", binc_adapter_get_path(adapter));
+ if (state) {
+ binc_adapter_start_discovery(default_adapter);
+ }
+}
+
+static gboolean callback(gpointer data) {
+ if (agent != NULL) {
+ binc_agent_free(agent);
+ agent = NULL;
+ }
+
+ if (default_adapter != NULL) {
+ binc_adapter_free(default_adapter);
+ default_adapter = NULL;
+ }
+
+ g_main_loop_quit((GMainLoop *) data);
+ return FALSE;
+}
+
+static void cleanup_handler(int signo) {
+ if (signo == SIGINT) {
+ log_error(TAG, "received SIGINT");
+ callback(loop);
+ }
+}
+
+int ble_audio_source_go(void) {
+ log_enabled(TRUE);
+ log_set_level(LOG_DEBUG);
+ // log_set_filename("./ble_fast_pair_source.txt", 1024 * 64, 5);
+
+ // Get a DBus connection
+ GDBusConnection *dbusConnection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
+
+ // Setup signal handler
+ if (signal(SIGINT, cleanup_handler) == SIG_ERR)
+ log_error(TAG, "can't catch SIGINT");
+
+ // Setup mainloop
+ loop = g_main_loop_new(NULL, FALSE);
+
+ // Get the default adapter
+ default_adapter = binc_adapter_get_default(dbusConnection);
+
+ if (default_adapter != NULL) {
+ log_debug(TAG, "using default_adapter '%s'", binc_adapter_get_path(default_adapter));
+
+ // Register an agent and set callbacks
+ agent = binc_agent_create(default_adapter, "/org/bluez/BincAgent", KEYBOARD_DISPLAY);
+ binc_agent_set_request_authorization_cb(agent, &on_request_authorization);
+ binc_agent_set_request_passkey_cb(agent, &on_request_passkey);
+
+ // Make sure the adapter is on
+ binc_adapter_set_powered_state_cb(default_adapter, &on_powered_state_changed);
+ if (!binc_adapter_get_powered_state(default_adapter)) {
+ binc_adapter_power_on(default_adapter);
+ }
+
+ GList *device_list = NULL;
+ GList *iterator = NULL;
+ Device *device = NULL;
+
+ device_list = binc_adapter_get_devices(default_adapter);
+ for (iterator = device_list; iterator != NULL; iterator = iterator->next) {
+ device = (Device *)iterator->data;
+ log_debug(TAG, "Device Name: %s\n", binc_device_get_name(device));
+ log_debug(TAG, "Device Address: %s\n", binc_device_get_address(device));
+ if (device) {
+ binc_adapter_remove_device(default_adapter, device);
+ }
+ }
+
+ // Build UUID array so we can use it in the discovery filter
+ GPtrArray *service_uuids = g_ptr_array_new();
+ g_ptr_array_add(service_uuids, AML_VENDOR_SERVICE_UUID);
+
+ // Set discovery callbacks and start discovery
+ binc_adapter_set_discovery_cb(default_adapter, &on_scan_result);
+ binc_adapter_set_discovery_state_cb(default_adapter, &on_discovery_state_changed);
+ binc_adapter_set_discovery_filter(default_adapter, -100, service_uuids, NULL);
+ g_ptr_array_free(service_uuids, TRUE);
+ binc_adapter_start_discovery(default_adapter);
+ } else {
+ log_error(TAG, "No default_adapter found");
+ }
+
+ // Bail out after some time
+ g_timeout_add_seconds(RUN_LOOP_TIMEOUT_SECONDS, callback, loop);
+
+ // Start the mainloop
+ g_main_loop_run(loop);
+
+ // Disconnect from DBus
+ g_dbus_connection_close_sync(dbusConnection, NULL, NULL);
+ g_object_unref(dbusConnection);
+
+ // Clean up mainloop
+ g_main_loop_unref(loop);
+ return 0;
+}
diff --git a/ble-fast-pair/ble_fast_pair.c b/ble-fast-pair/ble_fast_pair.c
new file mode 100644
index 0000000..e0ac973
--- /dev/null
+++ b/ble-fast-pair/ble_fast_pair.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+extern int ble_audio_source_go(void);
+extern int ble_audio_sink_go(void);
+
+int main(int argc, char *argv[]) {
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <sink|source>\n", argv[0]);
+ return 1;
+ }
+
+ if (strcmp(argv[1], "sink") == 0) {
+ ble_audio_sink_go();
+ } else if (strcmp(argv[1], "source") == 0) {
+ ble_audio_source_go();
+ } else {
+ fprintf(stderr, "Invalid argument: %s. Use 'sink' or 'source'.\n", argv[1]);
+ return 1;
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ble-fast-pair/ble_fast_pair.h b/ble-fast-pair/ble_fast_pair.h
new file mode 100644
index 0000000..6281efb
--- /dev/null
+++ b/ble-fast-pair/ble_fast_pair.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BLE_FAST_PAIR_H
+#define BLE_FAST_PAIR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int leaudio_source_go(void);
+
+int leaudio_sink_go(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BLE_FAST_PAIR_H
diff --git a/ble-fast-pair/characteristic.c b/ble-fast-pair/characteristic.c
new file mode 100644
index 0000000..9754110
--- /dev/null
+++ b/ble-fast-pair/characteristic.c
@@ -0,0 +1,596 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "characteristic.h"
+#include "characteristic_internal.h"
+#include "logger.h"
+#include "utility.h"
+#include "device_internal.h"
+
+static const char *const TAG = "Characteristic";
+static const char *const INTERFACE_CHARACTERISTIC = "org.bluez.GattCharacteristic1";
+static const char *const BLUEZ_DBUS = "org.bluez";
+
+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_PROPERTY_NOTIFYING = "Notifying";
+static const char *const CHARACTERISTIC_PROPERTY_VALUE = "Value";
+
+typedef struct binc_write_data {
+ GVariant *value;
+ Characteristic *characteristic;
+} WriteData;
+
+struct binc_characteristic {
+ Device *device; // Borrowed
+ Service *service; // Borrowed
+ GDBusConnection *connection; // Borrowed
+ const char *path; // Owned
+ const char *uuid; // Owned
+ const char *service_path; // Owned
+ gboolean notifying;
+ GList *flags; // Owned
+ guint properties;
+ GList *descriptors; // Owned
+ guint mtu;
+
+ guint characteristic_prop_changed;
+ OnNotifyingStateChangedCallback notify_state_callback;
+ OnReadCallback on_read_callback;
+ OnWriteCallback on_write_callback;
+ OnNotifyCallback on_notify_callback;
+};
+
+Characteristic *binc_characteristic_create(Device *device, const char *path) {
+ g_assert(device != NULL);
+ g_assert(path != NULL);
+ g_assert(strlen(path) > 0);
+
+ Characteristic *characteristic = g_new0(Characteristic, 1);
+ characteristic->device = device;
+ characteristic->connection = binc_device_get_dbus_connection(device);
+ characteristic->path = g_strdup(path);
+ characteristic->mtu = 23;
+ return characteristic;
+}
+
+void binc_characteristic_free(Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+
+ if (characteristic->characteristic_prop_changed != 0) {
+ g_dbus_connection_signal_unsubscribe(characteristic->connection, characteristic->characteristic_prop_changed);
+ characteristic->characteristic_prop_changed = 0;
+ }
+
+ if (characteristic->flags != NULL) {
+ g_list_free_full(characteristic->flags, g_free);
+ characteristic->flags = NULL;
+ }
+
+ if (characteristic->descriptors != NULL) {
+ g_list_free(characteristic->descriptors);
+ characteristic->descriptors = NULL;
+ }
+
+ g_free((char *) characteristic->uuid);
+ characteristic->uuid = NULL;
+
+ g_free((char *) characteristic->path);
+ characteristic->path = NULL;
+
+ g_free((char *) characteristic->service_path);
+ characteristic->service_path = NULL;
+
+ characteristic->device = NULL;
+ characteristic->connection = NULL;
+ characteristic->service = NULL;
+ g_free(characteristic);
+}
+
+char *binc_characteristic_to_string(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+
+ GString *flags = g_string_new("[");
+ if (g_list_length(characteristic->flags) > 0) {
+ for (GList *iterator = characteristic->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(
+ "Characteristic{uuid='%s', flags='%s', properties=%d, service_uuid='%s, mtu=%d'}",
+ characteristic->uuid,
+ flags->str,
+ characteristic->properties,
+ binc_service_get_uuid(characteristic->service),
+ characteristic->mtu);
+
+ g_string_free(flags, TRUE);
+ return result;
+}
+
+static void binc_internal_char_read_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ GError *error = NULL;
+ GByteArray *byteArray = NULL;
+ GVariant *innerArray = NULL;
+ Characteristic *characteristic = (Characteristic *) user_data;
+ g_assert(characteristic != NULL);
+
+ GVariant *value = g_dbus_connection_call_finish(characteristic->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 (characteristic->on_read_callback != NULL) {
+ characteristic->on_read_callback(characteristic->device, characteristic, 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)", CHARACTERISTIC_METHOD_READ_VALUE, error->code,
+ error->message);
+ g_clear_error(&error);
+ }
+}
+
+void binc_characteristic_read(Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ g_assert((characteristic->properties & GATT_CHR_PROP_READ) > 0);
+
+ log_debug(TAG, "reading <%s>", characteristic->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(characteristic->connection,
+ BLUEZ_DBUS,
+ characteristic->path,
+ INTERFACE_CHARACTERISTIC,
+ CHARACTERISTIC_METHOD_READ_VALUE,
+ g_variant_new("(@a{sv})", options),
+ G_VARIANT_TYPE("(ay)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_char_read_cb,
+ characteristic);
+}
+
+static void binc_internal_char_write_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ WriteData *writeData = (WriteData*) user_data;
+ Characteristic *characteristic = writeData->characteristic;
+ g_assert(characteristic != NULL);
+
+ GByteArray *byteArray = NULL;
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(characteristic->connection, res, &error);
+
+ if (writeData->value != NULL) {
+ byteArray = g_variant_get_byte_array(writeData->value);
+ }
+
+ if (characteristic->on_write_callback != NULL) {
+ characteristic->on_write_callback(characteristic->device, characteristic, 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)", CHARACTERISTIC_METHOD_WRITE_VALUE,
+ error->code, error->message);
+ g_clear_error(&error);
+ }
+}
+
+void binc_characteristic_write(Characteristic *characteristic, const GByteArray *byteArray, WriteType writeType) {
+ g_assert(characteristic != NULL);
+ g_assert(byteArray != NULL);
+ g_assert(byteArray->len > 0);
+ g_assert(binc_characteristic_supports_write(characteristic, writeType));
+
+ GString *byteArrayStr = g_byte_array_as_hex(byteArray);
+ log_debug(TAG, "writing <%s> to <%s>", byteArrayStr->str, characteristic->uuid);
+ g_string_free(byteArrayStr, TRUE);
+
+ GVariant *value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, byteArray->data, byteArray->len, sizeof(guint8));
+
+ WriteData *writeData = g_new0(WriteData, 1);
+ writeData->value = g_variant_ref(value);
+ writeData->characteristic = characteristic;
+
+ guint16 offset = 0;
+ const char *writeTypeString = writeType == WITH_RESPONSE ? "request" : "command";
+ GVariantBuilder *optionsBuilder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(optionsBuilder, "{sv}", "offset", g_variant_new_uint16(offset));
+ g_variant_builder_add(optionsBuilder, "{sv}", "type", g_variant_new_string(writeTypeString));
+ GVariant *options = g_variant_builder_end(optionsBuilder);
+ g_variant_builder_unref(optionsBuilder);
+
+ g_dbus_connection_call(characteristic->connection,
+ BLUEZ_DBUS,
+ characteristic->path,
+ INTERFACE_CHARACTERISTIC,
+ CHARACTERISTIC_METHOD_WRITE_VALUE,
+ g_variant_new("(@ay@a{sv})", value, options),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_char_write_cb,
+ writeData);
+}
+
+static void binc_internal_signal_characteristic_changed(__attribute__((unused)) GDBusConnection *conn,
+ __attribute__((unused)) const gchar *sender,
+ __attribute__((unused)) const gchar *path,
+ __attribute__((unused)) const gchar *interface,
+ __attribute__((unused)) const gchar *signal,
+ GVariant *parameters,
+ void *user_data) {
+
+ Characteristic *characteristic = (Characteristic *) user_data;
+ g_assert(characteristic != NULL);
+
+ GVariantIter *properties = NULL;
+ GVariantIter *unknown = NULL;
+ const char *iface = NULL;
+ const char *property_name = NULL;
+ GVariant *property_value = NULL;
+
+ g_assert(g_str_equal(g_variant_get_type_string(parameters), "(sa{sv}as)"));
+ g_variant_get(parameters, "(&sa{sv}as)", &iface, &properties, &unknown);
+ while (g_variant_iter_loop(properties, "{&sv}", &property_name, &property_value)) {
+ if (g_str_equal(property_name, CHARACTERISTIC_PROPERTY_NOTIFYING)) {
+ characteristic->notifying = g_variant_get_boolean(property_value);
+ log_debug(TAG, "notifying %s <%s>", characteristic->notifying ? "true" : "false", characteristic->uuid);
+
+ if (characteristic->notify_state_callback != NULL) {
+ characteristic->notify_state_callback(characteristic->device, characteristic, NULL);
+ }
+
+ if (characteristic->notifying == FALSE) {
+ if (characteristic->characteristic_prop_changed != 0) {
+ g_dbus_connection_signal_unsubscribe(characteristic->connection,
+ characteristic->characteristic_prop_changed);
+ characteristic->characteristic_prop_changed = 0;
+ }
+ }
+ } else if (g_str_equal(property_name, CHARACTERISTIC_PROPERTY_VALUE)) {
+ GByteArray *byteArray = g_variant_get_byte_array(property_value);
+ GString *result = g_byte_array_as_hex(byteArray);
+ log_debug(TAG, "notification <%s> on <%s>", result->str, characteristic->uuid);
+ g_string_free(result, TRUE);
+
+ if (characteristic->on_notify_callback != NULL) {
+ characteristic->on_notify_callback(characteristic->device, characteristic, byteArray);
+ }
+ g_byte_array_free(byteArray, FALSE);
+ }
+ }
+
+ if (properties != NULL) {
+ g_variant_iter_free(properties);
+ }
+ if (unknown != NULL) {
+ g_variant_iter_free(unknown);
+ }
+}
+
+static void binc_internal_char_start_notify_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ Characteristic *characteristic = (Characteristic *) user_data;
+ g_assert(characteristic != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(characteristic->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to call '%s' (error %d: %s)", CHARACTERISTIC_METHOD_START_NOTIFY, error->code,
+ error->message);
+ if (characteristic->notify_state_callback != NULL) {
+ characteristic->notify_state_callback(characteristic->device, characteristic, error);
+ }
+ g_clear_error(&error);
+ }
+}
+
+static void register_for_properties_changed_signal(Characteristic *characteristic) {
+ if (characteristic->characteristic_prop_changed == 0) {
+ characteristic->characteristic_prop_changed = g_dbus_connection_signal_subscribe(characteristic->connection,
+ BLUEZ_DBUS,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ characteristic->path,
+ INTERFACE_CHARACTERISTIC,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ binc_internal_signal_characteristic_changed,
+ characteristic,
+ NULL);
+ }
+}
+
+void binc_characteristic_start_notify(Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ g_assert(binc_characteristic_supports_notify(characteristic));
+
+ log_debug(TAG, "start notify for <%s>", characteristic->uuid);
+ register_for_properties_changed_signal(characteristic);
+
+ g_dbus_connection_call(characteristic->connection,
+ BLUEZ_DBUS,
+ characteristic->path,
+ INTERFACE_CHARACTERISTIC,
+ CHARACTERISTIC_METHOD_START_NOTIFY,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_char_start_notify_cb,
+ characteristic);
+}
+
+static void binc_internal_char_stop_notify_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) {
+ Characteristic *characteristic = (Characteristic *) user_data;
+ g_assert(characteristic != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(characteristic->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_debug(TAG, "failed to call '%s' (error %d: %s)", CHARACTERISTIC_METHOD_STOP_NOTIFY, error->code,
+ error->message);
+ if (characteristic->notify_state_callback != NULL) {
+ characteristic->notify_state_callback(characteristic->device, characteristic, error);
+ }
+ g_clear_error(&error);
+ }
+}
+
+void binc_characteristic_stop_notify(Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ g_assert((characteristic->properties & GATT_CHR_PROP_INDICATE) > 0 ||
+ (characteristic->properties & GATT_CHR_PROP_NOTIFY) > 0);
+
+ g_dbus_connection_call(characteristic->connection,
+ BLUEZ_DBUS,
+ characteristic->path,
+ INTERFACE_CHARACTERISTIC,
+ CHARACTERISTIC_METHOD_STOP_NOTIFY,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_char_stop_notify_cb,
+ characteristic);
+}
+
+void binc_characteristic_set_read_cb(Characteristic *characteristic, OnReadCallback callback) {
+ g_assert(characteristic != NULL);
+ g_assert(callback != NULL);
+ characteristic->on_read_callback = callback;
+}
+
+void binc_characteristic_set_write_cb(Characteristic *characteristic, OnWriteCallback callback) {
+ g_assert(characteristic != NULL);
+ g_assert(callback != NULL);
+ characteristic->on_write_callback = callback;
+}
+
+void binc_characteristic_set_notify_cb(Characteristic *characteristic, OnNotifyCallback callback) {
+ g_assert(characteristic != NULL);
+ g_assert(callback != NULL);
+ characteristic->on_notify_callback = callback;
+}
+
+void binc_characteristic_set_notifying_state_change_cb(Characteristic *characteristic,
+ OnNotifyingStateChangedCallback callback) {
+ g_assert(characteristic != NULL);
+ g_assert(callback != NULL);
+
+ characteristic->notify_state_callback = callback;
+}
+
+void binc_characteristic_set_notifying(Characteristic *characteristic, gboolean notifying) {
+ g_assert(characteristic != NULL);
+ characteristic->notifying = notifying;
+
+ if (characteristic->notifying) {
+ register_for_properties_changed_signal(characteristic);
+ }
+}
+
+const char *binc_characteristic_get_uuid(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->uuid;
+}
+
+void binc_characteristic_set_uuid(Characteristic *characteristic, const char *uuid) {
+ g_assert(characteristic != NULL);
+ g_assert(uuid != NULL);
+
+ g_free((char *) characteristic->uuid);
+ characteristic->uuid = g_strdup(uuid);
+}
+
+void binc_characteristic_set_mtu(Characteristic *characteristic, guint mtu) {
+ g_assert(characteristic != NULL);
+ characteristic->mtu = mtu;
+}
+
+Device *binc_characteristic_get_device(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->device;
+}
+
+Service *binc_characteristic_get_service(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->service;
+}
+
+void binc_characteristic_set_service(Characteristic *characteristic, Service *service) {
+ g_assert(characteristic != NULL);
+ g_assert(service != NULL);
+ characteristic->service = service;
+}
+
+const char *binc_characteristic_get_service_path(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->service_path;
+}
+
+void binc_characteristic_set_service_path(Characteristic *characteristic, const char *service_path) {
+ g_assert(characteristic != NULL);
+ g_assert(service_path != NULL);
+
+ if (characteristic->service_path != NULL) {
+ g_free((char *) characteristic->service_path);
+ }
+ characteristic->service_path = g_strdup(service_path);
+}
+
+GList *binc_characteristic_get_flags(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->flags;
+}
+
+static guint binc_characteristic_flags_to_int(GList *flags) {
+ guint result = 0;
+ if (g_list_length(flags) > 0) {
+ for (GList *iterator = flags; iterator; iterator = iterator->next) {
+ char *property = (char *) iterator->data;
+ if (g_str_equal(property, "broadcast")) {
+ result += GATT_CHR_PROP_BROADCAST;
+ } else if (g_str_equal(property, "read")) {
+ result += GATT_CHR_PROP_READ;
+ } else if (g_str_equal(property, "write-without-response")) {
+ result += GATT_CHR_PROP_WRITE_WITHOUT_RESP;
+ } else if (g_str_equal(property, "write")) {
+ result += GATT_CHR_PROP_WRITE;
+ } else if (g_str_equal(property, "notify")) {
+ result += GATT_CHR_PROP_NOTIFY;
+ } else if (g_str_equal(property, "indicate")) {
+ result += GATT_CHR_PROP_INDICATE;
+ } else if (g_str_equal(property, "authenticated-signed-writes")) {
+ result += GATT_CHR_PROP_AUTH;
+ } else if (g_str_equal(property, "encrypt-read")) {
+ result += GATT_CHR_PROP_ENCRYPT_READ;
+ } else if (g_str_equal(property, "encrypt-write")) {
+ result += GATT_CHR_PROP_ENCRYPT_WRITE;
+ } else if (g_str_equal(property, "encrypt-notify")) {
+ result += GATT_CHR_PROP_ENCRYPT_NOTIFY;
+ } else if (g_str_equal(property, "encrypt-indicate")) {
+ result += GATT_CHR_PROP_ENCRYPT_INDICATE;
+ }
+ }
+ }
+ return result;
+}
+
+void binc_characteristic_set_flags(Characteristic *characteristic, GList *flags) {
+ g_assert(characteristic != NULL);
+ g_assert(flags != NULL);
+
+ if (characteristic->flags != NULL) {
+ g_list_free_full(characteristic->flags, g_free);
+ }
+ characteristic->flags = flags;
+ characteristic->properties = binc_characteristic_flags_to_int(flags);
+}
+
+guint binc_characteristic_get_properties(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->properties;
+}
+
+gboolean binc_characteristic_is_notifying(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->notifying;
+}
+
+gboolean binc_characteristic_supports_write(const Characteristic *characteristic, WriteType writeType) {
+ if (writeType == WITH_RESPONSE) {
+ return (characteristic->properties & GATT_CHR_PROP_WRITE) > 0;
+ } else {
+ return (characteristic->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP) > 0;
+ }
+}
+
+gboolean binc_characteristic_supports_read(const Characteristic *characteristic) {
+ return (characteristic->properties & GATT_CHR_PROP_READ) > 0;
+}
+
+gboolean binc_characteristic_supports_notify(const Characteristic *characteristic) {
+ return ((characteristic->properties & GATT_CHR_PROP_INDICATE) > 0 ||
+ (characteristic->properties & GATT_CHR_PROP_NOTIFY) > 0);
+}
+
+void binc_characteristic_add_descriptor(Characteristic *characteristic, Descriptor *descriptor) {
+ g_assert(characteristic != NULL);
+ g_assert(descriptor != NULL);
+
+ characteristic->descriptors = g_list_append(characteristic->descriptors, descriptor);
+}
+
+Descriptor *binc_characteristic_get_descriptor(const Characteristic *characteristic, const char* desc_uuid) {
+ g_assert(characteristic != NULL);
+ g_assert(is_valid_uuid(desc_uuid));
+
+ if (characteristic->descriptors != NULL) {
+ for (GList *iterator = characteristic->descriptors; iterator; iterator = iterator->next) {
+ Descriptor *descriptor = (Descriptor *) iterator->data;
+ if (g_str_equal(desc_uuid, binc_descriptor_get_uuid(descriptor))) {
+ return descriptor;
+ }
+ }
+ }
+ return NULL;
+}
+
+GList *binc_characteristic_get_descriptors(const Characteristic *characteristic) {
+ g_assert(characteristic != NULL);
+ return characteristic->descriptors;
+}
diff --git a/ble-fast-pair/characteristic.h b/ble-fast-pair/characteristic.h
new file mode 100644
index 0000000..f791c5b
--- /dev/null
+++ b/ble-fast-pair/characteristic.h
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_CHARACTERISTIC_H
+#define BINC_CHARACTERISTIC_H
+
+#include <gio/gio.h>
+#include "service.h"
+#include "forward_decl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * GATT Characteristic Property bit field
+ * Reference: Core SPEC 4.1 page 2183 (Table 3.5: Characteristic Properties
+ * bit field) defines how the Characteristic Value can be used, or how the
+ * characteristic descriptors (see Section 3.3.3 - page 2184) can be accessed.
+ * In the core spec, regular properties are included in the characteristic
+ * declaration, and the extended properties are defined as descriptor.
+ */
+#define GATT_CHR_PROP_BROADCAST 0x01
+#define GATT_CHR_PROP_READ 0x02
+#define GATT_CHR_PROP_WRITE_WITHOUT_RESP 0x04
+#define GATT_CHR_PROP_WRITE 0x08
+#define GATT_CHR_PROP_NOTIFY 0x10
+#define GATT_CHR_PROP_INDICATE 0x20
+#define GATT_CHR_PROP_AUTH 0x40
+#define GATT_CHR_PROP_EXT_PROP 0x80
+#define GATT_CHR_PROP_ENCRYPT_READ 0x0100
+#define GATT_CHR_PROP_ENCRYPT_WRITE 0x0200
+#define GATT_CHR_PROP_ENCRYPT_NOTIFY 0x0400
+#define GATT_CHR_PROP_ENCRYPT_INDICATE 0x0800
+#define GATT_CHR_PROP_ENCRYPT_AUTH_READ 0x1000
+#define GATT_CHR_PROP_ENCRYPT_AUTH_WRITE 0x2000
+#define GATT_CHR_PROP_ENCRYPT_AUTH_NOTIFY 0x4000
+#define GATT_CHR_PROP_ENCRYPT_AUTH_INDICATE 0x8000
+#define GATT_CHR_PROP_SECURE_READ 0x100000
+#define GATT_CHR_PROP_SECURE_WRITE 0x200000
+#define GATT_CHR_PROP_SECURE_NOTIFY 0x400000
+#define GATT_CHR_PROP_SECURE_INDICATE 0x800000
+
+typedef enum WriteType {
+ WITH_RESPONSE = 0, WITHOUT_RESPONSE = 1
+} WriteType;
+
+typedef void (*OnNotifyingStateChangedCallback)(Device *device, Characteristic *characteristic, const GError *error);
+
+typedef void (*OnNotifyCallback)(Device *device, Characteristic *characteristic, const GByteArray *byteArray);
+
+typedef void (*OnReadCallback)(Device *device, Characteristic *characteristic, const GByteArray *byteArray, const GError *error);
+
+typedef void (*OnWriteCallback)(Device *device, Characteristic *characteristic, const GByteArray *byteArray, const GError *error);
+
+
+void binc_characteristic_read(Characteristic *characteristic);
+
+void binc_characteristic_write(Characteristic *characteristic, const GByteArray *byteArray, WriteType writeType);
+
+void binc_characteristic_start_notify(Characteristic *characteristic);
+
+void binc_characteristic_stop_notify(Characteristic *characteristic);
+
+Service *binc_characteristic_get_service(const Characteristic *characteristic);
+
+Device *binc_characteristic_get_device(const Characteristic *characteristic);
+
+const char *binc_characteristic_get_uuid(const Characteristic *characteristic);
+
+GList *binc_characteristic_get_flags(const Characteristic *characteristic);
+
+guint binc_characteristic_get_properties(const Characteristic *characteristic);
+
+gboolean binc_characteristic_is_notifying(const Characteristic *characteristic);
+
+gboolean binc_characteristic_supports_write(const Characteristic *characteristic, WriteType writeType);
+
+gboolean binc_characteristic_supports_read(const Characteristic *characteristic);
+
+gboolean binc_characteristic_supports_notify(const Characteristic *characteristic);
+
+Descriptor *binc_characteristic_get_descriptor(const Characteristic *characteristic, const char *desc_uuid);
+
+GList *binc_characteristic_get_descriptors(const Characteristic *characteristic);
+
+/**
+ * Get a string representation of the characteristic
+ * @param characteristic
+ * @return string representation of characteristic, caller must free
+ */
+char *binc_characteristic_to_string(const Characteristic *characteristic);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_CHARACTERISTIC_H
diff --git a/ble-fast-pair/characteristic_internal.h b/ble-fast-pair/characteristic_internal.h
new file mode 100644
index 0000000..8fd5d6f
--- /dev/null
+++ b/ble-fast-pair/characteristic_internal.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_CHARACTERISTIC_INTERNAL_H
+#define BINC_CHARACTERISTIC_INTERNAL_H
+
+#include "characteristic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+Characteristic *binc_characteristic_create(Device *device, const char *path);
+
+void binc_characteristic_free(Characteristic *characteristic);
+
+void binc_characteristic_set_read_cb(Characteristic *characteristic, OnReadCallback callback);
+
+void binc_characteristic_set_write_cb(Characteristic *characteristic, OnWriteCallback callback);
+
+void binc_characteristic_set_notify_cb(Characteristic *characteristic, OnNotifyCallback callback);
+
+void binc_characteristic_set_notifying_state_change_cb(Characteristic *characteristic,
+ OnNotifyingStateChangedCallback callback);
+
+void binc_characteristic_set_service(Characteristic *characteristic, Service *service);
+
+void binc_characteristic_set_service_path(Characteristic *characteristic, const char *service_path);
+
+void binc_characteristic_set_flags(Characteristic *characteristic, GList *flags);
+
+void binc_characteristic_set_uuid(Characteristic *characteristic, const char *uuid);
+
+void binc_characteristic_set_mtu(Characteristic *characteristic, guint mtu);
+
+void binc_characteristic_set_notifying(Characteristic *characteristic, gboolean notifying);
+
+const char *binc_characteristic_get_service_path(const Characteristic *characteristic);
+
+void binc_characteristic_add_descriptor(Characteristic *characteristic, Descriptor *descriptor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_CHARACTERISTIC_INTERNAL_H
diff --git a/ble-fast-pair/descriptor.c b/ble-fast-pair/descriptor.c
new file mode 100644
index 0000000..9a9b66d
--- /dev/null
+++ b/ble-fast-pair/descriptor.c
@@ -0,0 +1,294 @@
+// 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;
+}
diff --git a/ble-fast-pair/descriptor.h b/ble-fast-pair/descriptor.h
new file mode 100644
index 0000000..1adec65
--- /dev/null
+++ b/ble-fast-pair/descriptor.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_DESCRIPTOR_H
+#define BINC_DESCRIPTOR_H
+
+#include <gio/gio.h>
+#include "forward_decl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*OnDescReadCallback)(Device *device, Descriptor *descriptor, const GByteArray *byteArray, const GError *error);
+
+typedef void (*OnDescWriteCallback)(Device *device, Descriptor *descriptor, const GByteArray *byteArray, const GError *error);
+
+void binc_descriptor_read(Descriptor *descriptor);
+
+void binc_descriptor_write(Descriptor *descriptor, const GByteArray *byteArray);
+
+const char *binc_descriptor_get_uuid(const Descriptor *descriptor);
+
+const char *binc_descriptor_to_string(const Descriptor *descriptor);
+
+Characteristic *binc_descriptor_get_char(const Descriptor *descriptor);
+
+Device *binc_descriptor_get_device(const Descriptor *descriptor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_DESCRIPTOR_H
diff --git a/ble-fast-pair/descriptor_internal.h b/ble-fast-pair/descriptor_internal.h
new file mode 100644
index 0000000..9ebf548
--- /dev/null
+++ b/ble-fast-pair/descriptor_internal.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_DESCRIPTOR_INTERNAL_H
+#define BINC_DESCRIPTOR_INTERNAL_H
+
+#include "descriptor.h"
+
+Descriptor *binc_descriptor_create(Device *device, const char *path);
+
+void binc_descriptor_free(Descriptor *descriptor);
+
+void binc_descriptor_set_read_cb(Descriptor *descriptor, OnDescReadCallback callback);
+
+void binc_descriptor_set_write_cb(Descriptor *descriptor, OnDescWriteCallback callback);
+
+void binc_descriptor_set_uuid(Descriptor *descriptor, const char *uuid);
+
+void binc_descriptor_set_char_path(Descriptor *descriptor, const char *path);
+
+void binc_descriptor_set_char(Descriptor *descriptor, Characteristic *characteristic);
+
+void binc_descriptor_set_flags(Descriptor *descriptor, GList *flags);
+
+const char *binc_descriptor_get_char_path(const Descriptor *descriptor);
+
+#endif //BINC_DESCRIPTOR_INTERNAL_H
diff --git a/ble-fast-pair/device.c b/ble-fast-pair/device.c
new file mode 100644
index 0000000..02a19d9
--- /dev/null
+++ b/ble-fast-pair/device.c
@@ -0,0 +1,1199 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include <gio/gio.h>
+#include "logger.h"
+#include "device.h"
+#include "utility.h"
+#include "service_internal.h"
+#include "characteristic_internal.h"
+#include "adapter.h"
+#include "descriptor_internal.h"
+
+static const char *const TAG = "Device";
+static const char *const BLUEZ_DBUS = "org.bluez";
+static const char *const INTERFACE_DEVICE = "org.bluez.Device1";
+
+static const char *const DEVICE_METHOD_CONNECT = "Connect";
+static const char *const DEVICE_METHOD_PAIR = "Pair";
+static const char *const DEVICE_METHOD_DISCONNECT = "Disconnect";
+
+static const char *const DEVICE_PROPERTY_ADDRESS = "Address";
+static const char *const DEVICE_PROPERTY_ADDRESS_TYPE = "AddressType";
+static const char *const DEVICE_PROPERTY_ALIAS = "Alias";
+static const char *const DEVICE_PROPERTY_NAME = "Name";
+static const char *const DEVICE_PROPERTY_PAIRED = "Paired";
+static const char *const DEVICE_PROPERTY_RSSI = "RSSI";
+static const char *const DEVICE_PROPERTY_UUIDS = "UUIDs";
+static const char *const DEVICE_PROPERTY_MANUFACTURER_DATA = "ManufacturerData";
+static const char *const DEVICE_PROPERTY_SERVICE_DATA = "ServiceData";
+static const char *const DEVICE_PROPERTY_TRUSTED = "Trusted";
+static const char *const DEVICE_PROPERTY_TXPOWER = "TxPower";
+static const char *const DEVICE_PROPERTY_CONNECTED = "Connected";
+static const char *const DEVICE_PROPERTY_SERVICES_RESOLVED = "ServicesResolved";
+
+static const char *const INTERFACE_SERVICE = "org.bluez.GattService1";
+static const char *const INTERFACE_CHARACTERISTIC = "org.bluez.GattCharacteristic1";
+static const char *const INTERFACE_DESCRIPTOR = "org.bluez.GattDescriptor1";
+
+static const char *connection_state_names[] = {
+ [BINC_DISCONNECTED] = "DISCONNECTED",
+ [BINC_CONNECTED] = "CONNECTED",
+ [BINC_CONNECTING] = "CONNECTING",
+ [BINC_DISCONNECTING] = "DISCONNECTING"
+};
+
+struct binc_device {
+ GDBusConnection *connection; // Borrowed
+ Adapter *adapter; // Borrowed
+ const char *address; // Owned
+ const char *address_type; // Owned
+ const char *alias; // Owned
+ ConnectionState connection_state;
+ gboolean services_resolved;
+ gboolean service_discovery_started;
+ gboolean paired;
+ BondingState bondingState;
+ const char *path; // Owned
+ const char *name; // Owned
+ short rssi;
+ gboolean trusted;
+ short txpower;
+ GHashTable *manufacturer_data; // Owned
+ GHashTable *service_data; // Owned
+ GList *uuids; // Owned
+ guint mtu;
+
+ guint device_prop_changed;
+ ConnectionStateChangedCallback connection_state_callback;
+ ServicesResolvedCallback services_resolved_callback;
+ BondingStateChangedCallback bonding_state_callback;
+ GHashTable *services; // Owned
+ GList *services_list; // Owned
+ GHashTable *characteristics; // Owned
+ GHashTable *descriptors; // Owned
+ gboolean is_central;
+
+ OnReadCallback on_read_callback;
+ OnWriteCallback on_write_callback;
+ OnNotifyCallback on_notify_callback;
+ OnNotifyingStateChangedCallback on_notify_state_callback;
+ OnDescReadCallback on_read_desc_cb;
+ OnDescWriteCallback on_write_desc_cb;
+ void *user_data; // Borrowed
+};
+
+
+Device *binc_device_create(const char *path, Adapter *adapter) {
+ g_assert(path != NULL);
+ g_assert(strlen(path) > 0);
+ g_assert(adapter != NULL);
+
+ Device *device = g_new0(Device, 1);
+ device->path = g_strdup(path);
+ device->adapter = adapter;
+ device->connection = binc_adapter_get_dbus_connection(adapter);
+ device->bondingState = BINC_BOND_NONE;
+ device->connection_state = BINC_DISCONNECTED;
+ device->rssi = -255;
+ device->txpower = -255;
+ device->mtu = 23;
+ device->user_data = NULL;
+ return device;
+}
+
+static void binc_device_free_uuids(Device *device) {
+ if (device->uuids != NULL) {
+ g_list_free_full(device->uuids, g_free);
+ device->uuids = NULL;
+ }
+}
+
+static void byte_array_free(GByteArray *byteArray) { g_byte_array_free(byteArray, TRUE); }
+
+static void binc_device_free_manufacturer_data(Device *device) {
+ g_assert(device != NULL);
+
+ if (device->manufacturer_data != NULL) {
+ g_hash_table_destroy(device->manufacturer_data);
+ device->manufacturer_data = NULL;
+ }
+}
+
+static void binc_device_free_service_data(Device *device) {
+ g_assert(device != NULL);
+
+ if (device->service_data != NULL) {
+ g_hash_table_destroy(device->service_data);
+ device->service_data = NULL;
+ }
+}
+
+void binc_device_free(Device *device) {
+ g_assert(device != NULL);
+
+ log_debug(TAG, "freeing %s", device->path);
+
+ if (device->device_prop_changed != 0) {
+ g_dbus_connection_signal_unsubscribe(device->connection, device->device_prop_changed);
+ device->device_prop_changed = 0;
+ }
+
+ g_free((char *) device->path);
+ device->path = NULL;
+ g_free((char *) device->address_type);
+ device->address_type = NULL;
+ g_free((char *) device->address);
+ device->address = NULL;
+ g_free((char *) device->alias);
+ device->alias = NULL;
+ g_free((char *) device->name);
+ device->name = NULL;
+
+ if (device->descriptors != NULL) {
+ g_hash_table_destroy(device->descriptors);
+ device->descriptors = NULL;
+ }
+
+ if (device->characteristics != NULL) {
+ g_hash_table_destroy(device->characteristics);
+ device->characteristics = NULL;
+ }
+
+ if (device->services != NULL) {
+ g_hash_table_destroy(device->services);
+ device->services = NULL;
+ }
+
+ binc_device_free_manufacturer_data(device);
+ binc_device_free_service_data(device);
+ binc_device_free_uuids(device);
+
+ if (device->services_list != NULL) {
+ g_list_free(device->services_list);
+ device->services_list = NULL;
+ }
+
+ device->connection = NULL;
+ device->adapter = NULL;
+ g_free(device);
+}
+
+char *binc_device_to_string(const Device *device) {
+ g_assert(device != NULL);
+
+ // First build up uuids string
+ GString *uuids = g_string_new("[");
+ if (g_list_length(device->uuids) > 0) {
+ for (GList *iterator = device->uuids; iterator; iterator = iterator->next) {
+ g_string_append_printf(uuids, "%s, ", (char *) iterator->data);
+ }
+ g_string_truncate(uuids, uuids->len - 2);
+ }
+ g_string_append(uuids, "]");
+
+ // Build up manufacturer data string
+ GString *manufacturer_data = g_string_new("[");
+ if (device->manufacturer_data != NULL && g_hash_table_size(device->manufacturer_data) > 0) {
+ GHashTableIter iter;
+ int *key;
+ gpointer value;
+ g_hash_table_iter_init(&iter, device->manufacturer_data);
+ while (g_hash_table_iter_next(&iter, (gpointer) &key, &value)) {
+ GByteArray *byteArray = (GByteArray *) value;
+ GString *byteArrayString = g_byte_array_as_hex(byteArray);
+ gint keyInt = *key;
+ g_string_append_printf(manufacturer_data, "%04X -> %s, ", keyInt, byteArrayString->str);
+ g_string_free(byteArrayString, TRUE);
+ }
+ g_string_truncate(manufacturer_data, manufacturer_data->len - 2);
+ }
+ g_string_append(manufacturer_data, "]");
+
+ // Build up service data string
+ GString *service_data = g_string_new("[");
+ if (device->service_data != NULL && g_hash_table_size(device->service_data) > 0) {
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init(&iter, device->service_data);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ GByteArray *byteArray = (GByteArray *) value;
+ GString *byteArrayString = g_byte_array_as_hex(byteArray);
+ g_string_append_printf(service_data, "%s -> %s, ", (char *) key, byteArrayString->str);
+ g_string_free(byteArrayString, TRUE);
+ }
+ g_string_truncate(service_data, service_data->len - 2);
+ }
+ g_string_append(service_data, "]");
+
+ char *result = g_strdup_printf(
+ "Device{name='%s', address='%s', address_type=%s, rssi=%d, uuids=%s, manufacturer_data=%s, service_data=%s, paired=%s, txpower=%d path='%s' }",
+ device->name,
+ device->address,
+ device->address_type,
+ device->rssi,
+ uuids->str,
+ manufacturer_data->str,
+ service_data->str,
+ device->paired ? "true" : "false",
+ device->txpower,
+ device->path
+ );
+
+ g_string_free(uuids, TRUE);
+ g_string_free(manufacturer_data, TRUE);
+ g_string_free(service_data, TRUE);
+ return result;
+}
+
+static void
+binc_on_characteristic_read(Device *device, Characteristic *characteristic, const GByteArray *byteArray, const GError *error) {
+ if (device->on_read_callback != NULL) {
+ device->on_read_callback(device, characteristic, byteArray, error);
+ }
+}
+
+static void
+binc_on_characteristic_write(Device *device, Characteristic *characteristic, const GByteArray *byteArray, const GError *error) {
+ if (device->on_write_callback != NULL) {
+ device->on_write_callback(device, characteristic, byteArray, error);
+ }
+}
+
+static void binc_on_characteristic_notify(Device *device, Characteristic *characteristic, const GByteArray *byteArray) {
+ if (device->on_notify_callback != NULL) {
+ device->on_notify_callback(device, characteristic, byteArray);
+ }
+}
+
+static void binc_on_characteristic_notification_state_changed(Device *device, Characteristic *characteristic, const GError *error) {
+ if (device->on_notify_state_callback != NULL) {
+ device->on_notify_state_callback(device, characteristic, error);
+ }
+}
+
+static void binc_on_descriptor_read(Device *device, Descriptor *descriptor, const GByteArray *byteArray, const GError *error) {
+ if (device->on_read_desc_cb != NULL) {
+ device->on_read_desc_cb(device, descriptor, byteArray, error);
+ }
+}
+
+static void binc_on_descriptor_write(Device *device, Descriptor *descriptor, const GByteArray *byteArray, const GError *error) {
+ if (device->on_write_desc_cb != NULL) {
+ device->on_write_desc_cb(device, descriptor, byteArray, error);
+ }
+}
+
+static void binc_device_internal_set_conn_state(Device *device, ConnectionState state, GError *error) {
+ ConnectionState old_state = device->connection_state;
+ device->connection_state = state;
+ if (device->connection_state_callback != NULL) {
+ if (device->connection_state != old_state) {
+ device->connection_state_callback(device, state, error);
+ }
+ }
+}
+
+static void binc_internal_extract_service(Device *device, const char *object_path, GVariant *properties) {
+ g_assert(device != NULL);
+ g_assert(object_path != NULL);
+ g_assert(properties != NULL);
+
+ char *uuid = NULL;
+ const char *property_name;
+ GVariantIter iter;
+ GVariant *property_value;
+
+ g_variant_iter_init(&iter, properties);
+ while (g_variant_iter_loop(&iter, "{&sv}", &property_name, &property_value)) {
+ if (g_str_equal(property_name, "UUID")) {
+ uuid = g_strdup(g_variant_get_string(property_value, NULL));
+ }
+ }
+
+ Service *service = binc_service_create(device, object_path, uuid);
+ g_hash_table_insert(device->services, g_strdup(object_path), service);
+ g_free(uuid);
+}
+
+static void binc_internal_extract_characteristic(Device *device, const char *object_path, GVariant *properties) {
+ g_assert(device != NULL);
+ g_assert(object_path != NULL);
+ g_assert(properties != NULL);
+
+ Characteristic *characteristic = binc_characteristic_create(device, object_path);
+ binc_characteristic_set_read_cb(characteristic, &binc_on_characteristic_read);
+ binc_characteristic_set_write_cb(characteristic, &binc_on_characteristic_write);
+ binc_characteristic_set_notify_cb(characteristic, &binc_on_characteristic_notify);
+ binc_characteristic_set_notifying_state_change_cb(characteristic,
+ &binc_on_characteristic_notification_state_changed);
+
+ const char *property_name;
+ GVariantIter iter;
+ GVariant *property_value;
+
+ g_variant_iter_init(&iter, properties);
+ while (g_variant_iter_loop(&iter, "{&sv}", &property_name, &property_value)) {
+ if (g_str_equal(property_name, "UUID")) {
+ binc_characteristic_set_uuid(characteristic,
+ g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, "Service")) {
+ binc_characteristic_set_service_path(characteristic,
+ g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, "Flags")) {
+ binc_characteristic_set_flags(characteristic,
+ g_variant_string_array_to_list(property_value));
+ } else if (g_str_equal(property_name, "Notifying")) {
+ binc_characteristic_set_notifying(characteristic,
+ g_variant_get_boolean(property_value));
+ } else if (g_str_equal(property_name, "MTU")) {
+ device->mtu = g_variant_get_uint16(property_value);
+ binc_characteristic_set_mtu(characteristic, g_variant_get_uint16(property_value));
+ }
+ }
+
+ // Get service and link the characteristic to the service
+ Service *service = g_hash_table_lookup(device->services,
+ binc_characteristic_get_service_path(characteristic));
+ if (service != NULL) {
+ binc_service_add_characteristic(service, characteristic);
+ binc_characteristic_set_service(characteristic, service);
+ g_hash_table_insert(device->characteristics, g_strdup(object_path), characteristic);
+
+ char *charString = binc_characteristic_to_string(characteristic);
+ log_debug(TAG, charString);
+ g_free(charString);
+ } else {
+ log_error(TAG, "could not find service %s",
+ binc_characteristic_get_service_path(characteristic));
+ }
+}
+
+static void binc_internal_extract_descriptor(Device *device, const char *object_path, GVariant *properties) {
+ g_assert(device != NULL);
+ g_assert(object_path != NULL);
+ g_assert(properties != NULL);
+
+ Descriptor *descriptor = binc_descriptor_create(device, object_path);
+ binc_descriptor_set_read_cb(descriptor, &binc_on_descriptor_read);
+ binc_descriptor_set_write_cb(descriptor, &binc_on_descriptor_write);
+
+ const char *property_name;
+ GVariantIter iter;
+ GVariant *property_value;
+ g_variant_iter_init(&iter, properties);
+ while (g_variant_iter_loop(&iter, "{&sv}", &property_name, &property_value)) {
+ if (g_str_equal(property_name, "UUID")) {
+ binc_descriptor_set_uuid(descriptor, g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, "Characteristic")) {
+ binc_descriptor_set_char_path(descriptor,
+ g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, "Flags")) {
+ binc_descriptor_set_flags(descriptor, g_variant_string_array_to_list(property_value));
+ }
+ }
+
+ // Look up characteristic
+ Characteristic *characteristic = g_hash_table_lookup(device->characteristics,
+ binc_descriptor_get_char_path(descriptor));
+ if (characteristic != NULL) {
+ binc_characteristic_add_descriptor(characteristic, descriptor);
+ binc_descriptor_set_char(descriptor, characteristic);
+ g_hash_table_insert(device->descriptors, g_strdup(object_path), descriptor);
+
+ const char *descString = binc_descriptor_to_string(descriptor);
+ log_debug(TAG, descString);
+ g_free((char *) descString);
+ } else {
+ log_error(TAG, "could not find characteristic %s",
+ binc_descriptor_get_char_path(descriptor));
+ }
+}
+
+static void binc_internal_collect_gatt_tree_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ GError *error = NULL;
+ Device *device = (Device *) user_data;
+ g_assert(device != NULL);
+
+ GVariant *result = g_dbus_connection_call_finish(device->connection, res, &error);
+
+ if (result == NULL) {
+ log_error(TAG, "Unable to get result for GetManagedObjects");
+ if (error != NULL) {
+ log_error(TAG, "call failed (error %d: %s)", error->code, error->message);
+ g_clear_error(&error);
+ return;
+ }
+ }
+
+ GVariantIter *iter;
+ const char *object_path;
+ GVariant *ifaces_and_properties;
+ if (result) {
+ if (device->services != NULL) {
+ g_hash_table_destroy(device->services);
+ }
+ device->services = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) binc_service_free);
+
+ if (device->characteristics != NULL) {
+ g_hash_table_destroy(device->characteristics);
+ }
+ device->characteristics = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) binc_characteristic_free);
+
+ if (device->descriptors != NULL) {
+ g_hash_table_destroy(device->descriptors);
+ }
+ device->descriptors = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) binc_descriptor_free);
+
+ g_assert(g_str_equal(g_variant_get_type_string(result), "(a{oa{sa{sv}}})"));
+ g_variant_get(result, "(a{oa{sa{sv}}})", &iter);
+ while (g_variant_iter_loop(iter, "{&o@a{sa{sv}}}", &object_path, &ifaces_and_properties)) {
+ if (g_str_has_prefix(object_path, device->path)) {
+ const char *interface_name;
+ GVariant *properties;
+ GVariantIter iter2;
+ g_variant_iter_init(&iter2, ifaces_and_properties);
+ while (g_variant_iter_loop(&iter2, "{&s@a{sv}}", &interface_name, &properties)) {
+ if (g_str_equal(interface_name, INTERFACE_SERVICE)) {
+ binc_internal_extract_service(device, object_path, properties);
+ } else if (g_str_equal(interface_name, INTERFACE_CHARACTERISTIC)) {
+ binc_internal_extract_characteristic(device, object_path, properties);
+ } else if (g_str_equal(interface_name, INTERFACE_DESCRIPTOR)) {
+ binc_internal_extract_descriptor(device, object_path, properties);
+
+ }
+ }
+ }
+ }
+
+ if (iter != NULL) {
+ g_variant_iter_free(iter);
+ }
+ g_variant_unref(result);
+ }
+
+ if (device->services_list != NULL) {
+ g_list_free(device->services_list);
+ }
+ device->services_list = g_hash_table_get_values(device->services);
+
+ log_debug(TAG, "found %d services", g_list_length(device->services_list));
+ if (device->services_resolved_callback != NULL) {
+ device->services_resolved_callback(device);
+ }
+}
+
+static void binc_collect_gatt_tree(Device *device) {
+ g_assert(device != NULL);
+
+ device->service_discovery_started = TRUE;
+ g_dbus_connection_call(device->connection,
+ BLUEZ_DBUS,
+ "/",
+ "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects",
+ NULL,
+ G_VARIANT_TYPE("(a{oa{sa{sv}}})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_collect_gatt_tree_cb,
+ device);
+}
+
+void binc_device_set_bonding_state_changed_cb(Device *device, BondingStateChangedCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+
+ device->bonding_state_callback = callback;
+}
+
+void binc_device_set_bonding_state(Device *device, BondingState bonding_state) {
+ g_assert(device != NULL);
+
+ BondingState old_state = device->bondingState;
+ device->bondingState = bonding_state;
+ if (device->bonding_state_callback != NULL) {
+ if (device->bondingState != old_state) {
+ device->bonding_state_callback(device, device->bondingState, old_state, NULL);
+ }
+ }
+}
+
+static void binc_device_changed(__attribute__((unused)) GDBusConnection *conn,
+ __attribute__((unused)) const gchar *sender,
+ __attribute__((unused)) const gchar *path,
+ __attribute__((unused)) const gchar *interface,
+ __attribute__((unused)) const gchar *signal,
+ GVariant *params,
+ void *userdata) {
+
+ GVariantIter *properties_changed = NULL;
+ GVariantIter *properties_invalidated = NULL;
+ const char *iface = NULL;
+ const char *property_name = NULL;
+ GVariant *property_value = NULL;
+
+ Device *device = (Device *) userdata;
+ g_assert(device != NULL);
+
+ g_assert(g_str_equal(g_variant_get_type_string(params), "(sa{sv}as)"));
+ g_variant_get(params, "(&sa{sv}as)", &iface, &properties_changed, &properties_invalidated);
+ while (g_variant_iter_loop(properties_changed, "{&sv}", &property_name, &property_value)) {
+ if (g_str_equal(property_name, DEVICE_PROPERTY_CONNECTED)) {
+ binc_device_internal_set_conn_state(device, g_variant_get_boolean(property_value), NULL);
+ if (device->connection_state == BINC_DISCONNECTED) {
+ g_dbus_connection_signal_unsubscribe(device->connection, device->device_prop_changed);
+ device->device_prop_changed = 0;
+ }
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_SERVICES_RESOLVED)) {
+ device->services_resolved = g_variant_get_boolean(property_value);
+ log_debug(TAG, "ServicesResolved %s", device->services_resolved ? "true" : "false");
+ if (device->services_resolved == TRUE && device->bondingState != BINC_BONDING) {
+ binc_collect_gatt_tree(device);
+ }
+
+ if (device->services_resolved == FALSE && device->connection_state == BINC_CONNECTED) {
+ binc_device_internal_set_conn_state(device, BINC_DISCONNECTING, NULL);
+ }
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_PAIRED)) {
+ device->paired = g_variant_get_boolean(property_value);
+ log_debug(TAG, "Paired %s", device->paired ? "true" : "false");
+ binc_device_set_bonding_state(device, device->paired ? BINC_BONDED : BINC_BOND_NONE);
+
+ // If gatt-tree has not been built yet, start building it
+ if (device->services == NULL && device->services_resolved && !device->service_discovery_started) {
+ binc_collect_gatt_tree(device);
+ }
+ }
+ }
+
+ if (properties_changed != NULL)
+ g_variant_iter_free(properties_changed);
+
+ if (properties_invalidated != NULL)
+ g_variant_iter_free(properties_invalidated);
+}
+
+static void binc_internal_device_connect_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ GError *error = NULL;
+ Device *device = (Device *) user_data;
+ g_assert(device != NULL);
+
+ GVariant *value = g_dbus_connection_call_finish(device->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_error(TAG, "Connect failed (error %d: %s)", error->code, error->message);
+
+ // Maybe don't do this because connection changes may com later? See A&D scale testing
+ // Or send the current connection state?
+ binc_device_internal_set_conn_state(device, BINC_DISCONNECTED, error);
+
+ g_clear_error(&error);
+ }
+}
+
+static void subscribe_prop_changed(Device *device) {
+ if (device->device_prop_changed == 0) {
+ device->device_prop_changed = g_dbus_connection_signal_subscribe(device->connection,
+ BLUEZ_DBUS,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ device->path,
+ INTERFACE_DEVICE,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ binc_device_changed,
+ device,
+ NULL);
+ }
+}
+
+void binc_device_connect(Device *device) {
+ g_assert(device != NULL);
+ g_assert(device->path != NULL);
+
+ // Don't do anything if we are not disconnected
+ if (device->connection_state != BINC_DISCONNECTED) return;
+
+ log_debug(TAG, "Connecting to '%s' (%s) (%s)", device->name, device->address,
+ device->paired ? "BINC_BONDED" : "BINC_BOND_NONE");
+
+ binc_device_internal_set_conn_state(device, BINC_CONNECTING, NULL);
+ subscribe_prop_changed(device);
+ g_dbus_connection_call(device->connection,
+ BLUEZ_DBUS,
+ device->path,
+ INTERFACE_DEVICE,
+ DEVICE_METHOD_CONNECT,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_device_connect_cb,
+ device);
+}
+
+static void binc_internal_device_pair_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ Device *device = (Device *) user_data;
+ g_assert(device != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(device->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_error(TAG, "failed to call '%s' (error %d: %s)", DEVICE_METHOD_PAIR, error->code, error->message);
+ binc_device_internal_set_conn_state(device, BINC_DISCONNECTED, error);
+ g_clear_error(&error);
+ }
+}
+
+void binc_device_pair(Device *device) {
+ g_assert(device != NULL);
+ g_assert(device->path != NULL);
+
+ log_debug(TAG, "pairing device '%s'", device->address);
+
+ if (device->connection_state == BINC_DISCONNECTING) {
+ return;
+ }
+
+ if (device->connection_state == BINC_DISCONNECTED) {
+ binc_device_internal_set_conn_state(device, BINC_CONNECTING, NULL);
+ }
+
+ subscribe_prop_changed(device);
+ g_dbus_connection_call(device->connection,
+ BLUEZ_DBUS,
+ device->path,
+ INTERFACE_DEVICE,
+ DEVICE_METHOD_PAIR,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_device_pair_cb,
+ device);
+}
+
+static void binc_internal_device_disconnect_cb(__attribute__((unused)) GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+
+ Device *device = (Device *) user_data;
+ g_assert(device != NULL);
+
+ GError *error = NULL;
+ GVariant *value = g_dbus_connection_call_finish(device->connection, res, &error);
+ if (value != NULL) {
+ g_variant_unref(value);
+ }
+
+ if (error != NULL) {
+ log_error(TAG, "failed to call '%s' (error %d: %s)", DEVICE_METHOD_DISCONNECT, error->code, error->message);
+ binc_device_internal_set_conn_state(device, BINC_CONNECTED, error);
+ g_clear_error(&error);
+ }
+}
+
+void binc_device_disconnect(Device *device) {
+ g_assert(device != NULL);
+ g_assert(device->path != NULL);
+
+ // Don't do anything if we are not connected
+ if (device->connection_state != BINC_CONNECTED) return;
+
+ log_debug(TAG, "Disconnecting '%s' (%s)", device->name, device->address);
+
+ binc_device_internal_set_conn_state(device, BINC_DISCONNECTING, NULL);
+ g_dbus_connection_call(device->connection,
+ BLUEZ_DBUS,
+ device->path,
+ INTERFACE_DEVICE,
+ DEVICE_METHOD_DISCONNECT,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ (GAsyncReadyCallback) binc_internal_device_disconnect_cb,
+ device);
+}
+
+
+void binc_device_set_connection_state_change_cb(Device *device, ConnectionStateChangedCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+
+ device->connection_state_callback = callback;
+}
+
+GList *binc_device_get_services(const Device *device) {
+ g_assert(device != NULL);
+ return device->services_list;
+}
+
+void binc_device_set_services_resolved_cb(Device *device, ServicesResolvedCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+
+ device->services_resolved_callback = callback;
+}
+
+Service *binc_device_get_service(const Device *device, const char *service_uuid) {
+ g_assert(device != NULL);
+ g_assert(service_uuid != NULL);
+ g_assert(g_uuid_string_is_valid(service_uuid));
+
+ if (device->services_list != NULL) {
+ for (GList *iterator = device->services_list; iterator; iterator = iterator->next) {
+ Service *service = (Service *) iterator->data;
+ if (g_str_equal(service_uuid, binc_service_get_uuid(service))) {
+ return service;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+Characteristic *
+binc_device_get_characteristic(const Device *device, const char *service_uuid, const char *characteristic_uuid) {
+ g_assert(device != NULL);
+ g_assert(service_uuid != NULL);
+ g_assert(characteristic_uuid != NULL);
+ g_assert(g_uuid_string_is_valid(service_uuid));
+ g_assert(g_uuid_string_is_valid(characteristic_uuid));
+
+ Service *service = binc_device_get_service(device, service_uuid);
+ if (service != NULL) {
+ return binc_service_get_characteristic(service, characteristic_uuid);
+ }
+
+ return NULL;
+}
+
+void binc_device_set_read_char_cb(Device *device, OnReadCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+ device->on_read_callback = callback;
+}
+
+gboolean binc_device_read_char(const Device *device, const char *service_uuid, const char *characteristic_uuid) {
+ g_assert(is_valid_uuid(service_uuid));
+ g_assert(is_valid_uuid(characteristic_uuid));
+
+ Characteristic *characteristic = binc_device_get_characteristic(device, service_uuid, characteristic_uuid);
+ if (characteristic != NULL && binc_characteristic_supports_read(characteristic)) {
+ binc_characteristic_read(characteristic);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean binc_device_read_desc(const Device *device, const char *service_uuid,
+ const char *characteristic_uuid, const char *desc_uuid) {
+ g_assert(is_valid_uuid(service_uuid));
+ g_assert(is_valid_uuid(characteristic_uuid));
+ g_assert(is_valid_uuid(desc_uuid));
+
+ Characteristic *characteristic = binc_device_get_characteristic(device, service_uuid, characteristic_uuid);
+ if (characteristic == NULL) {
+ return FALSE;
+ }
+
+ Descriptor *descriptor = binc_characteristic_get_descriptor(characteristic, desc_uuid);
+ if (descriptor == NULL) {
+ return FALSE;
+ }
+
+ binc_descriptor_read(descriptor);
+ return TRUE;
+}
+
+gboolean binc_device_write_desc(const Device *device, const char *service_uuid,
+ const char *characteristic_uuid, const char *desc_uuid, const GByteArray *byteArray) {
+ g_assert(is_valid_uuid(service_uuid));
+ g_assert(is_valid_uuid(characteristic_uuid));
+ g_assert(is_valid_uuid(desc_uuid));
+
+ Characteristic *characteristic = binc_device_get_characteristic(device, service_uuid, characteristic_uuid);
+ if (characteristic == NULL) {
+ return FALSE;
+ }
+
+ Descriptor *descriptor = binc_characteristic_get_descriptor(characteristic, desc_uuid);
+ if (descriptor == NULL) {
+ return FALSE;
+ }
+
+ binc_descriptor_write(descriptor, byteArray);
+ return TRUE;
+}
+
+void binc_device_set_write_char_cb(Device *device, OnWriteCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+ device->on_write_callback = callback;
+}
+
+gboolean binc_device_write_char(const Device *device, const char *service_uuid, const char *characteristic_uuid,
+ const GByteArray *byteArray, WriteType writeType) {
+ g_assert(device != NULL);
+ g_assert(is_valid_uuid(service_uuid));
+ g_assert(is_valid_uuid(characteristic_uuid));
+
+ Characteristic *characteristic = binc_device_get_characteristic(device, service_uuid, characteristic_uuid);
+ if (characteristic != NULL && binc_characteristic_supports_write(characteristic, writeType)) {
+ binc_characteristic_write(characteristic, byteArray, writeType);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void binc_device_set_notify_char_cb(Device *device, OnNotifyCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+ device->on_notify_callback = callback;
+}
+
+void binc_device_set_notify_state_cb(Device *device, OnNotifyingStateChangedCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+ device->on_notify_state_callback = callback;
+}
+
+gboolean binc_device_start_notify(const Device *device, const char *service_uuid, const char *characteristic_uuid) {
+ g_assert(device != NULL);
+ g_assert(is_valid_uuid(service_uuid));
+ g_assert(is_valid_uuid(characteristic_uuid));
+
+ Characteristic *characteristic = binc_device_get_characteristic(device, service_uuid, characteristic_uuid);
+ if (characteristic != NULL && binc_characteristic_supports_notify(characteristic)) {
+ binc_characteristic_start_notify(characteristic);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean binc_device_stop_notify(const Device *device, const char *service_uuid, const char *characteristic_uuid) {
+ g_assert(device != NULL);
+ g_assert(is_valid_uuid(service_uuid));
+ g_assert(is_valid_uuid(characteristic_uuid));
+
+ Characteristic *characteristic = binc_device_get_characteristic(device, service_uuid, characteristic_uuid);
+ if (characteristic != NULL && binc_characteristic_supports_notify(characteristic) && binc_characteristic_is_notifying(characteristic)) {
+ binc_characteristic_stop_notify(characteristic);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+void binc_device_set_read_desc_cb(Device *device, OnDescReadCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+ device->on_read_desc_cb = callback;
+}
+
+void binc_device_set_write_desc_cb(Device *device, OnDescWriteCallback callback) {
+ g_assert(device != NULL);
+ g_assert(callback != NULL);
+ device->on_write_desc_cb = callback;
+}
+
+ConnectionState binc_device_get_connection_state(const Device *device) {
+ g_assert(device != NULL);
+ return device->connection_state;
+}
+
+const char *binc_device_get_connection_state_name(const Device *device) {
+ g_assert(device != NULL);
+ return connection_state_names[device->connection_state];
+}
+
+const char *binc_device_get_address(const Device *device) {
+ g_assert(device != NULL);
+ return device->address;
+}
+
+void binc_device_set_address(Device *device, const char *address) {
+ g_assert(device != NULL);
+ g_assert(address != NULL);
+
+ g_free((char *) device->address);
+ device->address = g_strdup(address);
+}
+
+const char *binc_device_get_address_type(const Device *device) {
+ g_assert(device != NULL);
+ return device->address_type;
+}
+
+void binc_device_set_address_type(Device *device, const char *address_type) {
+ g_assert(device != NULL);
+ g_assert(address_type != NULL);
+
+ g_free((char *) device->address_type);
+ device->address_type = g_strdup(address_type);
+}
+
+const char *binc_device_get_alias(const Device *device) {
+ g_assert(device != NULL);
+ return device->alias;
+}
+
+void binc_device_set_alias(Device *device, const char *alias) {
+ g_assert(device != NULL);
+ g_assert(alias != NULL);
+
+ g_free((char *) device->alias);
+ device->alias = g_strdup(alias);
+}
+
+const char *binc_device_get_name(const Device *device) {
+ g_assert(device != NULL);
+ return device->name;
+}
+
+void binc_device_set_name(Device *device, const char *name) {
+ g_assert(device != NULL);
+ g_assert(name != NULL);
+ g_assert(strlen(name) > 0);
+
+ g_free((char *) device->name);
+ device->name = g_strdup(name);
+}
+
+const char *binc_device_get_path(const Device *device) {
+ g_assert(device != NULL);
+ return device->path;
+}
+
+void binc_device_set_path(Device *device, const char *path) {
+ g_assert(device != NULL);
+ g_assert(path != NULL);
+
+ g_free((char *) device->path);
+ device->path = g_strdup(path);
+}
+
+gboolean binc_device_get_paired(const Device *device) {
+ g_assert(device != NULL);
+ return device->paired;
+}
+
+void binc_device_set_paired(Device *device, gboolean paired) {
+ g_assert(device != NULL);
+ device->paired = paired;
+ binc_device_set_bonding_state(device, paired ? BINC_BONDED : BINC_BOND_NONE);
+}
+
+short binc_device_get_rssi(const Device *device) {
+ g_assert(device != NULL);
+ return device->rssi;
+}
+
+void binc_device_set_rssi(Device *device, short rssi) {
+ g_assert(device != NULL);
+ device->rssi = rssi;
+}
+
+gboolean binc_device_get_trusted(const Device *device) {
+ g_assert(device != NULL);
+ return device->trusted;
+}
+
+void binc_device_set_trusted(Device *device, gboolean trusted) {
+ g_assert(device != NULL);
+ device->trusted = trusted;
+}
+
+short binc_device_get_txpower(const Device *device) {
+ g_assert(device != NULL);
+ return device->txpower;
+}
+
+void binc_device_set_txpower(Device *device, short txpower) {
+ g_assert(device != NULL);
+ device->txpower = txpower;
+}
+
+GList *binc_device_get_uuids(const Device *device) {
+ g_assert(device != NULL);
+ return device->uuids;
+}
+
+void binc_device_set_uuids(Device *device, GList *uuids) {
+ g_assert(device != NULL);
+
+ binc_device_free_uuids(device);
+ device->uuids = uuids;
+}
+
+GHashTable *binc_device_get_manufacturer_data(const Device *device) {
+ g_assert(device != NULL);
+ return device->manufacturer_data;
+}
+
+void binc_device_set_manufacturer_data(Device *device, GHashTable *manufacturer_data) {
+ g_assert(device != NULL);
+
+ binc_device_free_manufacturer_data(device);
+ device->manufacturer_data = manufacturer_data;
+}
+
+GHashTable *binc_device_get_service_data(const Device *device) {
+ g_assert(device != NULL);
+ return device->service_data;
+}
+
+void binc_device_set_service_data(Device *device, GHashTable *service_data) {
+ g_assert(device != NULL);
+
+ binc_device_free_service_data(device);
+ device->service_data = service_data;
+}
+
+void binc_device_set_is_central(Device *device, gboolean is_central) {
+ g_assert(device != NULL);
+ device->is_central = is_central;
+}
+
+gboolean binc_device_is_central(const Device *device) {
+ g_assert(device != NULL);
+ return device->is_central;
+}
+
+GDBusConnection *binc_device_get_dbus_connection(const Device *device) {
+ g_assert(device != NULL);
+ return device->connection;
+}
+
+BondingState binc_device_get_bonding_state(const Device *device) {
+ g_assert(device != NULL);
+ return device->bondingState;
+}
+
+Adapter *binc_device_get_adapter(const Device *device) {
+ g_assert(device != NULL);
+ return device->adapter;
+}
+
+guint binc_device_get_mtu(const Device *device) {
+ g_assert(device != NULL);
+ return device->mtu;
+}
+
+gboolean binc_device_has_service(const Device *device, const char *service_uuid) {
+ g_assert(device != NULL);
+ g_assert(g_uuid_string_is_valid(service_uuid));
+
+ if (device->uuids != NULL && g_list_length(device->uuids) > 0) {
+ for (GList *iterator = device->uuids; iterator; iterator = iterator->next) {
+ if (g_str_equal(service_uuid, (char *) iterator->data)) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+void binc_internal_device_update_property(Device *device, const char *property_name, GVariant *property_value) {
+ if (g_str_equal(property_name, DEVICE_PROPERTY_ADDRESS)) {
+ binc_device_set_address(device, g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_ADDRESS_TYPE)) {
+ binc_device_set_address_type(device, g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_ALIAS)) {
+ binc_device_set_alias(device, g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_CONNECTED)) {
+ binc_device_internal_set_conn_state(device, g_variant_get_boolean(property_value) ? BINC_CONNECTED : BINC_DISCONNECTED,
+ NULL);
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_NAME)) {
+ binc_device_set_name(device, g_variant_get_string(property_value, NULL));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_PAIRED)) {
+ binc_device_set_paired(device, g_variant_get_boolean(property_value));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_RSSI)) {
+ binc_device_set_rssi(device, g_variant_get_int16(property_value));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_TRUSTED)) {
+ binc_device_set_trusted(device, g_variant_get_boolean(property_value));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_TXPOWER)) {
+ binc_device_set_txpower(device, g_variant_get_int16(property_value));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_UUIDS)) {
+ binc_device_set_uuids(device, g_variant_string_array_to_list(property_value));
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_MANUFACTURER_DATA)) {
+ GVariantIter *iter;
+ g_variant_get(property_value, "a{qv}", &iter);
+
+ GVariant *array;
+ guint16 key;
+ GHashTable *manufacturer_data = g_hash_table_new_full(g_int_hash, g_int_equal,
+ g_free, (GDestroyNotify) byte_array_free);
+ while (g_variant_iter_loop(iter, "{qv}", &key, &array)) {
+ size_t data_length = 0;
+ guint8 *data = (guint8 *) g_variant_get_fixed_array(array, &data_length, sizeof(guint8));
+ GByteArray *byteArray = g_byte_array_sized_new(data_length);
+ g_byte_array_append(byteArray, data, data_length);
+
+ int *keyCopy = g_new0 (gint, 1);
+ *keyCopy = key;
+
+ g_hash_table_insert(manufacturer_data, keyCopy, byteArray);
+ }
+ binc_device_set_manufacturer_data(device, manufacturer_data);
+ g_variant_iter_free(iter);
+ } else if (g_str_equal(property_name, DEVICE_PROPERTY_SERVICE_DATA)) {
+ GVariantIter *iter;
+ g_variant_get(property_value, "a{sv}", &iter);
+
+ GVariant *array;
+ char *key;
+
+ GHashTable *service_data = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) byte_array_free);
+ while (g_variant_iter_loop(iter, "{sv}", &key, &array)) {
+ size_t data_length = 0;
+ guint8 *data = (guint8 *) g_variant_get_fixed_array(array, &data_length, sizeof(guint8));
+ GByteArray *byteArray = g_byte_array_sized_new(data_length);
+ g_byte_array_append(byteArray, data, data_length);
+
+ char *keyCopy = g_strdup(key);
+
+ g_hash_table_insert(service_data, keyCopy, byteArray);
+ }
+ binc_device_set_service_data(device, service_data);
+ g_variant_iter_free(iter);
+ }
+}
+
+void binc_device_set_user_data(Device *device, void *user_data) {
+ g_assert(device != NULL);
+ device->user_data = user_data;
+}
+
+void *binc_device_get_user_data(const Device *device) {
+ g_assert(device != NULL);
+ return device->user_data;
+}
+
diff --git a/ble-fast-pair/device.h b/ble-fast-pair/device.h
new file mode 100644
index 0000000..7b15826
--- /dev/null
+++ b/ble-fast-pair/device.h
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_DEVICE_H
+#define BINC_DEVICE_H
+
+#include <glib.h>
+#include "forward_decl.h"
+#include "characteristic.h"
+#include "descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum ConnectionState {
+ BINC_DISCONNECTED = 0, BINC_CONNECTED = 1, BINC_CONNECTING = 2, BINC_DISCONNECTING = 3
+} ConnectionState;
+
+typedef enum BondingState {
+ BINC_BOND_NONE = 0, BINC_BONDING = 1, BINC_BONDED = 2
+} BondingState;
+
+typedef void (*ConnectionStateChangedCallback)(Device *device, ConnectionState state, const GError *error);
+
+typedef void (*ServicesResolvedCallback)(Device *device);
+
+typedef void (*BondingStateChangedCallback)(Device *device, BondingState new_state, BondingState old_state,
+ const GError *error);
+
+
+/**
+ * Connect to a device asynchronously
+ *
+ * When a connection is attempted, established or failed, the ConnectionStateChangedCallback is called. Times out in 25 seconds.
+ *
+ * @param device the device to connect to. Must be non-null and not already connected.
+ */
+void binc_device_connect(Device *device);
+
+void binc_device_pair(Device *device);
+
+void binc_device_disconnect(Device *device);
+
+void binc_device_set_read_char_cb(Device *device, OnReadCallback callback);
+
+gboolean binc_device_read_char(const Device *device, const char *service_uuid, const char *characteristic_uuid);
+
+void binc_device_set_write_char_cb(Device *device, OnWriteCallback callback);
+
+gboolean binc_device_write_char(const Device *device, const char *service_uuid,
+ const char *characteristic_uuid, const GByteArray *byteArray, WriteType writeType);
+
+void binc_device_set_notify_char_cb(Device *device, OnNotifyCallback callback);
+
+void binc_device_set_notify_state_cb(Device *device, OnNotifyingStateChangedCallback callback);
+
+gboolean binc_device_start_notify(const Device *device, const char *service_uuid, const char *characteristic_uuid);
+
+gboolean binc_device_stop_notify(const Device *device, const char *service_uuid, const char *characteristic_uuid);
+
+gboolean binc_device_read_desc(const Device *device, const char *service_uuid,
+ const char *characteristic_uuid, const char *desc_uuid);
+
+gboolean binc_device_write_desc(const Device *device, const char *service_uuid,
+ const char *characteristic_uuid, const char *desc_uuid, const GByteArray *byteArray);
+
+void binc_device_set_read_desc_cb(Device *device, OnDescReadCallback callback);
+
+void binc_device_set_write_desc_cb(Device *device, OnDescWriteCallback callback);
+
+void binc_device_set_connection_state_change_cb(Device *device, ConnectionStateChangedCallback callback);
+
+void binc_device_set_services_resolved_cb(Device *device, ServicesResolvedCallback callback);
+
+void binc_device_set_bonding_state_changed_cb(Device *device, BondingStateChangedCallback callback);
+
+gboolean binc_device_has_service(const Device *device, const char *service_uuid);
+
+GList *binc_device_get_services(const Device *device);
+
+Service *binc_device_get_service(const Device *device, const char *service_uuid);
+
+Characteristic *binc_device_get_characteristic(const Device *device,
+ const char *service_uuid, const char *characteristic_uuid);
+
+ConnectionState binc_device_get_connection_state(const Device *device);
+
+const char *binc_device_get_connection_state_name(const Device *device);
+
+const char *binc_device_get_address(const Device *device);
+
+const char *binc_device_get_address_type(const Device *device);
+
+const char *binc_device_get_alias(const Device *device);
+
+const char *binc_device_get_name(const Device *device);
+
+const char *binc_device_get_path(const Device *device);
+
+gboolean binc_device_get_paired(const Device *device);
+
+short binc_device_get_rssi(const Device *device);
+
+gboolean binc_device_get_trusted(const Device *device);
+
+short binc_device_get_txpower(const Device *device);
+
+GList *binc_device_get_uuids(const Device *device);
+
+GHashTable *binc_device_get_manufacturer_data(const Device *device);
+
+GHashTable *binc_device_get_service_data(const Device *device);
+
+BondingState binc_device_get_bonding_state(const Device *device);
+
+Adapter *binc_device_get_adapter(const Device *device);
+
+guint binc_device_get_mtu(const Device *device);
+
+gboolean binc_device_is_central(const Device *device);
+
+char *binc_device_to_string(const Device *device);
+
+void binc_device_set_user_data(Device *device, void *user_data);
+
+void *binc_device_get_user_data(const Device *device);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_DEVICE_H
diff --git a/ble-fast-pair/device_internal.h b/ble-fast-pair/device_internal.h
new file mode 100644
index 0000000..f911b04
--- /dev/null
+++ b/ble-fast-pair/device_internal.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_DEVICE_INTERNAL_H
+#define BINC_DEVICE_INTERNAL_H
+
+#include "device.h"
+
+Device *binc_device_create(const char *path, Adapter *adapter);
+
+void binc_device_free(Device *device);
+
+GDBusConnection *binc_device_get_dbus_connection(const Device *device);
+
+void binc_device_set_address(Device *device, const char *address);
+
+void binc_device_set_address_type(Device *device, const char *address_type);
+
+void binc_device_set_alias(Device *device, const char *alias);
+
+void binc_device_set_adapter_path(Device *device, const char *adapter_path);
+
+void binc_device_set_name(Device *device, const char *name);
+
+void binc_device_set_path(Device *device, const char *path);
+
+void binc_device_set_paired(Device *device, gboolean paired);
+
+void binc_device_set_rssi(Device *device, short rssi);
+
+void binc_device_set_trusted(Device *device, gboolean trusted);
+
+void binc_device_set_txpower(Device *device, short txpower);
+
+void binc_device_set_uuids(Device *device, GList *uuids);
+
+void binc_device_set_manufacturer_data(Device *device, GHashTable *manufacturer_data);
+
+void binc_device_set_service_data(Device *device, GHashTable *service_data);
+
+void binc_device_set_bonding_state(Device *device, BondingState bonding_state);
+
+void binc_device_set_is_central(Device *device, gboolean is_central);
+
+void binc_internal_device_update_property(Device *device, const char *property_name, GVariant *property_value);
+
+#endif //BINC_DEVICE_INTERNAL_H
diff --git a/ble-fast-pair/forward_decl.h b/ble-fast-pair/forward_decl.h
new file mode 100644
index 0000000..568a9f4
--- /dev/null
+++ b/ble-fast-pair/forward_decl.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_FORWARD_DECL_H
+#define BINC_FORWARD_DECL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct binc_adapter Adapter;
+typedef struct binc_device Device;
+typedef struct binc_service Service;
+typedef struct binc_characteristic Characteristic;
+typedef struct binc_descriptor Descriptor;
+typedef struct binc_service_handler_manager ServiceHandlerManager;
+typedef struct binc_advertisement Advertisement;
+typedef struct binc_application Application;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_FORWARD_DECL_H
diff --git a/ble-fast-pair/logger.c b/ble-fast-pair/logger.c
new file mode 100644
index 0000000..44a148e
--- /dev/null
+++ b/ble-fast-pair/logger.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "logger.h"
+#include <glib.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+#define TAG "Logger"
+#define BUFFER_SIZE 1024
+#define MAX_FILE_SIZE 1024 * 64
+#define MAX_LOGS 5
+
+static struct {
+ gboolean enabled;
+ LogLevel level;
+ FILE *fout;
+ char filename[256];
+ unsigned long maxFileSize;
+ unsigned int maxFiles;
+ size_t currentSize;
+ LogEventCallback logCallback;
+} LogSettings = {TRUE, LOG_DEBUG, NULL, "", MAX_FILE_SIZE, MAX_LOGS, 0, NULL};
+
+static const char *log_level_names[] = {
+ [LOG_DEBUG] = "DEBUG",
+ [LOG_INFO] = "INFO",
+ [LOG_WARN] = "WARN",
+ [LOG_ERROR] = "ERROR"
+};
+
+void log_set_level(LogLevel level) {
+ LogSettings.level = level;
+}
+
+void log_set_handler(LogEventCallback callback) {
+ LogSettings.logCallback = callback;
+}
+
+void log_enabled(gboolean enabled) {
+ LogSettings.enabled = enabled;
+}
+
+static void open_log_file() {
+ LogSettings.fout = fopen(LogSettings.filename, "a");
+ if (LogSettings.fout == NULL) {
+ LogSettings.fout = stdout;
+ return;
+ }
+
+ struct stat finfo;
+ fstat(fileno(LogSettings.fout), &finfo);
+ LogSettings.currentSize = finfo.st_size;
+}
+
+void log_set_filename(const char *filename, long max_size, int max_files) {
+ g_assert(filename != NULL);
+ g_assert(strlen(filename) > 0);
+
+ LogSettings.maxFileSize = max_size ? max_size : MAX_FILE_SIZE;
+ LogSettings.maxFiles = max_files ? max_files : MAX_LOGS;
+ strncpy(LogSettings.filename, filename, sizeof(LogSettings.filename) - 1);
+ open_log_file();
+}
+
+/**
+ * Get the current UTC time in milliseconds since epoch
+ * @return
+ */
+static long long current_timestamp_in_millis() {
+ struct timeval te;
+ gettimeofday(&te, NULL); // get current time
+ long long milliseconds = te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds
+ return milliseconds;
+}
+
+/**
+ * Returns a string representation of the current time year-month-day hours:minutes:seconds
+ * @return newly allocated string, must be freed using g_free()
+ */
+static char *current_time_string() {
+ GDateTime *now = g_date_time_new_now_local();
+ char *time_string = g_date_time_format(now, "%F %R:%S");
+ g_date_time_unref(now);
+
+ char *result = g_strdup_printf("%s:%03lld", time_string, current_timestamp_in_millis() % 1000);
+ g_free(time_string);
+ return result;
+}
+
+static void log_log(const char *tag, const char *level, const char *message) {
+ char *timestamp = current_time_string();
+ int bytes_written;
+ if ((bytes_written = fprintf(LogSettings.fout, "%s [%s] %s\n", level, tag, message)) > 0) {
+ LogSettings.currentSize += bytes_written;
+ fflush(LogSettings.fout);
+ }
+
+ g_free(timestamp);
+}
+
+static char *get_log_name(int index) {
+ if (index > 0) {
+ return g_strdup_printf("%s.%d", LogSettings.filename, index);
+ } else {
+ return g_strdup(LogSettings.filename);
+ }
+}
+
+static gboolean fileExists(const char *filename) {
+ FILE *fp;
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ return FALSE;
+ } else {
+ fclose(fp);
+ return TRUE;
+ }
+}
+
+static void rotate_log_files() {
+ for (int i = LogSettings.maxFiles; i > 0; i--) {
+ char *src = get_log_name(i - 1);
+ char *dst = get_log_name(i);
+ if (fileExists(dst)) {
+ remove(dst);
+ }
+
+ if (fileExists(src)) {
+ rename(src, dst);
+ }
+
+ g_free(src);
+ g_free(dst);
+ }
+}
+
+static void rotate_log_file_if_needed() {
+ if ((LogSettings.currentSize < LogSettings.maxFileSize) ||
+ LogSettings.fout == stdout || LogSettings.logCallback != NULL)
+ return;
+
+ g_assert(LogSettings.fout != NULL);
+ fclose(LogSettings.fout);
+ rotate_log_files();
+ open_log_file();
+}
+
+void log_log_at_level(LogLevel level, const char *tag, const char *format, ...) {
+ // Init fout to stdout if needed
+ if (LogSettings.fout == NULL && LogSettings.logCallback == NULL) {
+ LogSettings.fout = stdout;
+ }
+
+ rotate_log_file_if_needed();
+
+ if (LogSettings.level <= level && LogSettings.enabled) {
+ char buf[BUFFER_SIZE];
+ va_list arg;
+ va_start(arg, format);
+ g_vsnprintf(buf, BUFFER_SIZE, format, arg);
+ if (LogSettings.logCallback) {
+ LogSettings.logCallback(level, tag, buf);
+ } else {
+ log_log(tag, log_level_names[level], buf);
+ }
+ va_end(arg);
+ }
+}
+
diff --git a/ble-fast-pair/logger.h b/ble-fast-pair/logger.h
new file mode 100644
index 0000000..21264e4
--- /dev/null
+++ b/ble-fast-pair/logger.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_LOGGER_H
+#define BINC_LOGGER_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum LogLevel {
+ LOG_DEBUG = 0, LOG_INFO = 1, LOG_WARN = 2, LOG_ERROR = 3
+} LogLevel;
+
+#define log_debug(tag, format, ...) log_log_at_level(LOG_DEBUG, tag, format, ##__VA_ARGS__)
+#define log_info(tag, format, ...) log_log_at_level(LOG_INFO, tag, format, ##__VA_ARGS__)
+#define log_warn(tag, format, ...) log_log_at_level(LOG_WARN, tag, format, ##__VA_ARGS__)
+#define log_error(tag, format, ...) log_log_at_level(LOG_ERROR, tag, format, ##__VA_ARGS__)
+
+void log_log_at_level(LogLevel level, const char* tag, const char *format, ...);
+
+void log_set_level(LogLevel level);
+
+void log_set_filename(const char* filename, long max_size, int max_files);
+
+typedef void (*LogEventCallback)(LogLevel level, const char *tag, const char *message);
+
+void log_set_handler(LogEventCallback callback);
+
+void log_enabled(gboolean enabled);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_LOGGER_H
diff --git a/ble-fast-pair/parser.c b/ble-fast-pair/parser.c
new file mode 100644
index 0000000..7221f26
--- /dev/null
+++ b/ble-fast-pair/parser.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "parser.h"
+#include "math.h"
+#include <time.h>
+
+// IEEE 11073 Reserved float values
+typedef enum {
+ MDER_POSITIVE_INFINITY = 0x007FFFFE,
+ MDER_NaN = 0x007FFFFF,
+ MDER_NRes = 0x00800000,
+ MDER_RESERVED_VALUE = 0x00800001,
+ MDER_NEGATIVE_INFINITY = 0x00800002
+} ReservedFloatValues;
+
+struct parser_instance {
+ const GByteArray *bytes;
+ guint offset;
+ int byteOrder;
+};
+
+static const double reserved_float_values[5] = {MDER_POSITIVE_INFINITY, MDER_NaN, MDER_NaN, MDER_NaN,
+ MDER_NEGATIVE_INFINITY};
+
+
+#define BINARY32_MASK_SIGN 0x80000000
+#define BINARY32_MASK_EXPO 0x7FE00000
+#define BINARY32_MASK_SNCD 0x007FFFFF
+#define BINARY32_IMPLIED_BIT 0x800000
+#define BINARY32_SHIFT_EXPO 23
+
+Parser *parser_create(const GByteArray *bytes, int byteOrder) {
+ Parser *parser = g_new0(Parser, 1);
+ parser->bytes = bytes;
+ parser->offset = 0;
+ parser->byteOrder = byteOrder;
+ return parser;
+}
+
+void parser_free(Parser *parser) {
+ g_assert(parser != NULL);
+ parser->bytes = NULL;
+ g_free(parser);
+}
+
+void parser_set_offset(Parser *parser, guint offset) {
+ g_assert(parser != NULL);
+ parser->offset = offset;
+}
+
+guint8 parser_get_uint8(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert(parser->offset < parser->bytes->len);
+
+ guint8 result = parser->bytes->data[parser->offset];
+ parser->offset = parser->offset + 1;
+ return result;
+}
+
+gint8 parser_get_sint8(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert(parser->offset < parser->bytes->len);
+
+ gint8 result = parser->bytes->data[parser->offset];
+ parser->offset = parser->offset + 1;
+ return result;
+}
+
+guint16 parser_get_uint16(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert((parser->offset + 1) < parser->bytes->len);
+
+ guint8 byte1, byte2;
+ byte1 = parser->bytes->data[parser->offset];
+ byte2 = parser->bytes->data[parser->offset + 1];
+ parser->offset = parser->offset + 2;
+ if (parser->byteOrder == LITTLE_ENDIAN) {
+ return (byte2 << 8) + byte1;
+ } else {
+ return (byte1 << 8) + byte2;
+ }
+}
+
+gint16 parser_get_sint16(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert((parser->offset + 1) < parser->bytes->len);
+
+ guint8 byte1, byte2;
+ byte1 = parser->bytes->data[parser->offset];
+ byte2 = parser->bytes->data[parser->offset + 1];
+ parser->offset = parser->offset + 2;
+ if (parser->byteOrder == LITTLE_ENDIAN) {
+ return (byte2 << 8) + byte1;
+ } else {
+ return (byte1 << 8) + byte2;
+ }
+}
+
+guint32 parser_get_uint24(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert((parser->offset + 2) < parser->bytes->len);
+
+ guint8 byte1, byte2, byte3;
+ byte1 = parser->bytes->data[parser->offset];
+ byte2 = parser->bytes->data[parser->offset+1];
+ byte3 = parser->bytes->data[parser->offset+2];
+ parser->offset = parser->offset + 3;
+ if (parser->byteOrder == LITTLE_ENDIAN) {
+ return (byte3 << 16) + (byte2 << 8) + byte1;
+ } else {
+ return (byte1 << 16) + (byte2 << 8) + byte3;
+ }
+}
+
+guint32 parser_get_uint32(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert((parser->offset + 3) < parser->bytes->len);
+
+ guint8 byte1, byte2, byte3, byte4;
+ byte1 = parser->bytes->data[parser->offset];
+ byte2 = parser->bytes->data[parser->offset + 1];
+ byte3 = parser->bytes->data[parser->offset + 2];
+ byte4 = parser->bytes->data[parser->offset + 3];
+ parser->offset = parser->offset + 4;
+ if (parser->byteOrder == LITTLE_ENDIAN) {
+ return (byte4 << 24) + (byte3 << 16) + (byte2 << 8) + byte1;
+ } else {
+ return (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + byte4;
+ }
+}
+
+double parser_get_sfloat(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert(parser->offset < parser->bytes->len);
+
+ guint16 sfloat = parser_get_uint16(parser);
+
+ int mantissa = sfloat & 0xfff;
+ if (mantissa >= 0x800) {
+ mantissa = mantissa - 0x1000;
+ }
+ int exponent = sfloat >> 12;
+ if (exponent >= 0x8) {
+ exponent = exponent - 0x10;
+ }
+ return (mantissa * pow(10.0, exponent));
+}
+
+/* round number n to d decimal points */
+float fround(float n, int d) {
+ int rounded = floor(n * pow(10.0f, d) + 0.5f);
+ int divider = (int) pow(10.0f, d);
+ return (float) rounded / (float) divider;
+}
+
+double parser_get_float(Parser *parser) {
+ g_assert(parser != NULL);
+ guint32 int_data = parser_get_uint32(parser);
+
+ guint32 mantissa = int_data & 0xFFFFFF;
+ gint8 exponent = int_data >> 24;
+ double output = 0;
+
+ if (mantissa >= MDER_POSITIVE_INFINITY &&
+ mantissa <= MDER_NEGATIVE_INFINITY) {
+ output = reserved_float_values[mantissa - MDER_POSITIVE_INFINITY];
+ } else {
+ if (mantissa >= 0x800000) {
+ mantissa = -((0xFFFFFF + 1) - mantissa);
+ }
+ output = (mantissa * pow(10.0f, exponent));
+ }
+
+ return output;
+}
+
+double parser_get_754float(Parser *parser) {
+ g_assert(parser != NULL);
+ guint32 int_data = parser_get_uint32(parser);
+
+ // Break up into 3 parts
+ gboolean sign = int_data & BINARY32_MASK_SIGN;
+ guint32 biased_expo = (int_data & BINARY32_MASK_EXPO) >> BINARY32_SHIFT_EXPO;
+ int32_t significand = int_data & BINARY32_MASK_SNCD;
+
+ float result;
+ if (biased_expo == 0xFF) {
+ result = significand ? NAN : INFINITY; // For simplicity, NaN payload not copied
+ } else {
+ guint32 expo;
+
+ if (biased_expo > 0) {
+ significand |= BINARY32_IMPLIED_BIT;
+ expo = biased_expo - 127;
+ } else {
+ expo = 126;
+ }
+
+ result = ldexpf((float)significand, expo - BINARY32_SHIFT_EXPO);
+ }
+
+ if (sign) result = -result;
+
+ return result;
+}
+
+double parser_get_754half(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert(parser->offset < parser->bytes->len);
+
+ guint16 value = parser_get_uint16(parser);
+
+ gboolean sign = ((value & 0x8000) != 0);
+ guint16 exponent = (value & 0x7c00) >> 10;
+ guint16 fraction = value & 0x300;
+
+ float result = 0.0;
+
+ if (exponent == 0) {
+ if (fraction == 0) {
+ return (0.0);
+ }
+ else {
+ result = pow(-1, sign) * pow(2, -14) * ((float) fraction / 1024);
+ }
+ }
+ else if (exponent == 0x1f) {
+ if (fraction == 0) return (INFINITY);
+ else return (NAN);
+ }
+ else {
+ result = pow(-1, sign) * pow(2, exponent - 15) * (1.0 + (float) fraction / 1024);
+ }
+
+ return (result);
+}
+
+GString *parser_get_string(Parser *parser) {
+ g_assert(parser != NULL);
+ g_assert(parser->bytes != NULL);
+
+ return g_string_new_len((const char *) parser->bytes->data + parser->offset,
+ parser->bytes->len - parser->offset);
+}
+
+GDateTime *parser_get_date_time(Parser *parser) {
+ g_assert(parser != NULL);
+
+ guint16 year = parser_get_uint16(parser);
+ guint8 month = parser_get_uint8(parser);
+ guint8 day = parser_get_uint8(parser);
+ guint8 hour = parser_get_uint8(parser);
+ guint8 min = parser_get_uint8(parser);
+ guint8 sec = parser_get_uint8(parser);
+
+ return g_date_time_new_local(year, month, day, hour, min, sec);
+}
+
+GByteArray *binc_get_current_time() {
+ GByteArray *byteArray = g_byte_array_new();
+
+ GDateTime *now = g_date_time_new_now_local();
+ guint year = g_date_time_get_year(now);
+ guint8 yearLsb = year & 0xFF;
+ guint8 yearMsb = year >> 8;
+ guint8 month = g_date_time_get_month(now);
+ guint8 day = g_date_time_get_day_of_month(now);
+ guint8 hours = g_date_time_get_hour(now);
+ guint8 minutes = g_date_time_get_minute(now);
+ guint8 seconds = g_date_time_get_second(now);
+ guint8 dayOfWeek = g_date_time_get_day_of_week(now);
+ guint8 miliseconds = (g_date_time_get_microsecond(now) / 1000) * 256 / 1000;
+ guint8 reason = 1;
+ g_date_time_unref(now);
+
+ g_byte_array_append(byteArray, &yearLsb, 1);
+ g_byte_array_append(byteArray, &yearMsb, 1);
+ g_byte_array_append(byteArray, &month, 1);
+ g_byte_array_append(byteArray, &day, 1);
+ g_byte_array_append(byteArray, &hours, 1);
+ g_byte_array_append(byteArray, &minutes, 1);
+ g_byte_array_append(byteArray, &seconds, 1);
+ g_byte_array_append(byteArray, &dayOfWeek, 1);
+ g_byte_array_append(byteArray, &miliseconds, 1);
+ g_byte_array_append(byteArray, &reason, 1);
+ return byteArray;
+}
+
+GByteArray *binc_get_date_time() {
+ GByteArray *byteArray = g_byte_array_new();
+
+ GDateTime *now = g_date_time_new_now_local();
+ guint year = g_date_time_get_year(now);
+ guint8 yearLsb = year & 0xFF;
+ guint8 yearMsb = year >> 8;
+ guint8 month = g_date_time_get_month(now);
+ guint8 day = g_date_time_get_day_of_month(now);
+ guint8 hours = g_date_time_get_hour(now);
+ guint8 minutes = g_date_time_get_minute(now);
+ guint8 seconds = g_date_time_get_second(now);
+ g_date_time_unref(now);
+
+ g_byte_array_append(byteArray, &yearLsb, 1);
+ g_byte_array_append(byteArray, &yearMsb, 1);
+ g_byte_array_append(byteArray, &month, 1);
+ g_byte_array_append(byteArray, &day, 1);
+ g_byte_array_append(byteArray, &hours, 1);
+ g_byte_array_append(byteArray, &minutes, 1);
+ g_byte_array_append(byteArray, &seconds, 1);
+ return byteArray;
+}
diff --git a/ble-fast-pair/parser.h b/ble-fast-pair/parser.h
new file mode 100644
index 0000000..9d62f7a
--- /dev/null
+++ b/ble-fast-pair/parser.h
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_PARSER_H
+#define BINC_PARSER_H
+
+#include <glib.h>
+#include <endian.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct parser_instance Parser;
+
+/**
+ * Create a parser for a byte array
+ *
+ * @param bytes the byte array
+ * @param byteOrder either LITTLE_ENDIAN or BIG_ENDIAN
+ * @return parser object
+ */
+Parser *parser_create(const GByteArray *bytes, int byteOrder);
+
+void parser_set_offset(Parser *parser, guint offset);
+
+void parser_free(Parser *parser);
+
+guint8 parser_get_uint8(Parser *parser);
+
+gint8 parser_get_sint8(Parser *parser);
+
+guint16 parser_get_uint16(Parser *parser);
+
+gint16 parser_get_sint16(Parser *parser);
+
+guint32 parser_get_uint24(Parser *parser);
+
+guint32 parser_get_uint32(Parser *parser);
+
+double parser_get_sfloat(Parser *parser);
+
+double parser_get_float(Parser *parser);
+
+double parser_get_754half(Parser *parser);
+
+double parser_get_754float(Parser *parser);
+
+GDateTime* parser_get_date_time(Parser *parser);
+
+GByteArray* binc_get_date_time();
+
+GByteArray *binc_get_current_time();
+
+GString *parser_get_string(Parser *parser);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_PARSER_H
diff --git a/ble-fast-pair/service.c b/ble-fast-pair/service.c
new file mode 100644
index 0000000..111bd42
--- /dev/null
+++ b/ble-fast-pair/service.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "service.h"
+#include "characteristic.h"
+#include "utility.h"
+
+struct binc_service {
+ Device *device; // Borrowed
+ const char *path; // Owned
+ const char* uuid; // Owned
+ GList *characteristics; // Owned
+};
+
+Service* binc_service_create(Device *device, const char* path, const char* uuid) {
+ g_assert(device != NULL);
+ g_assert(path != NULL);
+ g_assert(is_valid_uuid(uuid));
+
+ Service *service = g_new0(Service, 1);
+ service->device = device;
+ service->path = g_strdup(path);
+ service->uuid = g_strdup(uuid);
+ service->characteristics = NULL;
+ return service;
+}
+
+void binc_service_free(Service *service) {
+ g_assert(service != NULL);
+
+ g_free((char*) service->path);
+ service->path = NULL;
+
+ g_free((char*) service->uuid);
+ service->uuid = NULL;
+
+ g_list_free(service->characteristics);
+ service->characteristics = NULL;
+
+ service->device = NULL;
+ g_free(service);
+}
+
+const char* binc_service_get_uuid(const Service *service) {
+ g_assert(service != NULL);
+ return service->uuid;
+}
+
+Device *binc_service_get_device(const Service *service) {
+ g_assert(service != NULL);
+ return service->device;
+}
+
+void binc_service_add_characteristic(Service *service, Characteristic *characteristic) {
+ g_assert(service != NULL);
+ g_assert(characteristic != NULL);
+
+ service->characteristics = g_list_append(service->characteristics, characteristic);
+}
+
+GList *binc_service_get_characteristics(const Service *service) {
+ g_assert(service != NULL);
+ return service->characteristics;
+}
+
+Characteristic *binc_service_get_characteristic(const Service *service, const char* char_uuid) {
+ g_assert(service != NULL);
+ g_assert(char_uuid != NULL);
+ g_assert(is_valid_uuid(char_uuid));
+
+ if (service->characteristics != NULL) {
+ for (GList *iterator = service->characteristics; iterator; iterator = iterator->next) {
+ Characteristic *characteristic = (Characteristic *) iterator->data;
+ if (g_str_equal(char_uuid, binc_characteristic_get_uuid(characteristic))) {
+ return characteristic;
+ }
+ }
+ }
+ return NULL;
+}
diff --git a/ble-fast-pair/service.h b/ble-fast-pair/service.h
new file mode 100644
index 0000000..55e9b2c
--- /dev/null
+++ b/ble-fast-pair/service.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_SERVICE_H
+#define BINC_SERVICE_H
+
+#include <gio/gio.h>
+#include "forward_decl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char *binc_service_get_uuid(const Service *service);
+
+Device *binc_service_get_device(const Service *service);
+
+GList *binc_service_get_characteristics(const Service *service);
+
+Characteristic *binc_service_get_characteristic(const Service *service, const char *char_uuid);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_SERVICE_H
diff --git a/ble-fast-pair/service_internal.h b/ble-fast-pair/service_internal.h
new file mode 100644
index 0000000..496dfc0
--- /dev/null
+++ b/ble-fast-pair/service_internal.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_SERVICE_INTERNAL_H
+#define BINC_SERVICE_INTERNAL_H
+
+Service *binc_service_create(Device *device, const char *path, const char *uuid);
+
+void binc_service_free(Service *service);
+
+void binc_service_add_characteristic(Service *service, Characteristic *characteristic);
+
+#endif //BINC_SERVICE_INTERNAL_H
diff --git a/ble-fast-pair/utility.c b/ble-fast-pair/utility.c
new file mode 100644
index 0000000..2e08716
--- /dev/null
+++ b/ble-fast-pair/utility.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#include "utility.h"
+#include "math.h"
+
+void bytes_to_hex(char *dest, const guint8 *src, int n) {
+ const char xx[] = "0123456789abcdef";
+ while (--n >= 0) dest[n] = xx[(src[n >> 1] >> ((1 - (n & 1)) << 2)) & 0xF];
+}
+
+GString *g_byte_array_as_hex(const GByteArray *byteArray) {
+ int hexLength = (int) byteArray->len * 2;
+ GString *result = g_string_sized_new(hexLength + 1);
+ bytes_to_hex(result->str, byteArray->data, hexLength);
+ result->str[hexLength] = 0;
+ result->len = hexLength;
+ return result;
+}
+
+GList *g_variant_string_array_to_list(GVariant *value) {
+ g_assert(value != NULL);
+ g_assert(g_str_equal(g_variant_get_type_string(value), "as"));
+
+ GList *list = NULL;
+ gchar *data;
+ GVariantIter iter;
+
+ g_variant_iter_init(&iter, value);
+ while (g_variant_iter_loop(&iter, "s", &data)) {
+ list = g_list_append(list, g_strdup(data));
+ }
+ return list;
+}
+
+float binc_round_with_precision(float value, guint8 precision) {
+ int multiplier = (int) pow(10.0, precision);
+ return roundf(value * multiplier) / multiplier;
+}
+
+gchar *binc_date_time_format_iso8601(GDateTime *datetime) {
+ GString *outstr = NULL;
+ gchar *main_date = NULL;
+ gint64 offset;
+ gchar *format = "%Y-%m-%dT%H:%M:%S";
+
+ /* Main date and time. */
+ main_date = g_date_time_format(datetime, format);
+ outstr = g_string_new(main_date);
+ g_free(main_date);
+
+ /* Timezone. Format it as `%:::z` unless the offset is zero, in which case
+ * we can simply use `Z`. */
+ offset = g_date_time_get_utc_offset(datetime);
+
+ if (offset == 0) {
+ g_string_append_c (outstr, 'Z');
+ } else {
+ gchar *time_zone = g_date_time_format(datetime, "%:z");
+ g_string_append(outstr, time_zone);
+ g_free(time_zone);
+ }
+
+ return g_string_free(outstr, FALSE);
+}
+
+gboolean is_lowercase(const char *str) {
+ for (int i = 0; str[i] != '\0'; i++) {
+ if (g_ascii_isalpha(str[i]) && g_ascii_isupper(str[i])) return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean is_valid_uuid(const char *uuid) {
+ if (uuid == NULL) {
+ g_critical("uuid is NULL");
+ return FALSE;
+ }
+
+ if (!g_uuid_string_is_valid(uuid)) {
+ g_critical("%s is not a valid UUID", uuid);
+ return FALSE;
+ }
+
+ if (!is_lowercase(uuid)) {
+ g_critical("%s is not entirely lowercase", uuid);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+char* replace_char(char* str, char find, char replace){
+ char *current_pos = strchr(str,find);
+ while (current_pos) {
+ *current_pos = replace;
+ current_pos = strchr(current_pos,find);
+ }
+ return str;
+}
+
+char *path_to_address(const char *path) {
+ char *address = g_strdup_printf("%s", path+(strlen(path)-17));
+ return replace_char(address, '_', ':');
+}
+
+/**
+ * Get a byte array that wraps the data inside the variant.
+ *
+ * Only the GByteArray should be freed but not the content as it doesn't make a copy.
+ * Content will be freed automatically when the variant is unref-ed.
+ *
+ * @param variant byte array of format 'ay'
+ * @return GByteArray wrapping the data in the variant
+ */
+GByteArray *g_variant_get_byte_array(GVariant *variant) {
+ g_assert(variant != NULL);
+ g_assert(g_str_equal(g_variant_get_type_string(variant), "ay"));
+
+ size_t data_length = 0;
+ guint8 *data = (guint8 *) g_variant_get_fixed_array(variant, &data_length, sizeof(guint8));
+ return g_byte_array_new_take(data, data_length);
+}
\ No newline at end of file
diff --git a/ble-fast-pair/utility.h b/ble-fast-pair/utility.h
new file mode 100644
index 0000000..1ba7638
--- /dev/null
+++ b/ble-fast-pair/utility.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2024 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef BINC_UTILITY_H
+#define BINC_UTILITY_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+GString *g_byte_array_as_hex(const GByteArray *byteArray);
+
+GList *g_variant_string_array_to_list(GVariant *value);
+
+float binc_round_with_precision(float value, guint8 precision);
+
+gchar *binc_date_time_format_iso8601(GDateTime *datetime);
+
+gboolean is_lowercase(const char *str);
+
+gboolean is_valid_uuid(const char *uuid);
+
+char *path_to_address(const char *path);
+
+GByteArray *g_variant_get_byte_array(GVariant *variant);
+
+char* replace_char(char* str, char find, char replace);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINC_UTILITY_H