blob: bd7dafb2a8cd284a27459af131083707b33d7fc8 [file] [log] [blame]
// 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;
}