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