blob: 4ca2f78c37020fcf5192f38780240b70a91099f5 [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2023 Intel Corporation
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdbool.h>
#include <errno.h>
#include <glib.h>
#include <dbus/dbus.h>
#include "gdbus/gdbus.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
#include "src/shared/ad.h"
#include "src/shared/crypto.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "log.h"
#include "error.h"
#include "adapter.h"
#include "device.h"
#include "dbus-common.h"
#include "set.h"
static struct queue *set_list;
struct btd_device_set {
struct btd_adapter *adapter;
char *path;
uint8_t sirk[16];
uint8_t size;
bool auto_connect;
struct queue *devices;
struct btd_device *device;
};
static DBusMessage *set_disconnect(DBusConnection *conn, DBusMessage *msg,
void *user_data)
{
/* TODO */
return NULL;
}
static DBusMessage *set_connect(DBusConnection *conn, DBusMessage *msg,
void *user_data)
{
/* TODO */
return NULL;
}
static const GDBusMethodTable set_methods[] = {
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("Disconnect", NULL, NULL,
set_disconnect) },
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("Connect", NULL, NULL,
set_connect) },
{}
};
static gboolean get_adapter(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_device_set *set = data;
const char *path = adapter_get_path(set->adapter);
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
return TRUE;
}
static gboolean get_auto_connect(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_device_set *set = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN,
&set->auto_connect);
return TRUE;
}
static void set_auto_connect(const GDBusPropertyTable *property,
DBusMessageIter *iter,
GDBusPendingPropertySet id, void *data)
{
struct btd_device_set *set = data;
dbus_bool_t b;
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) {
g_dbus_pending_property_error(id,
ERROR_INTERFACE ".InvalidArguments",
"Invalid arguments in method call");
return;
}
dbus_message_iter_get_basic(iter, &b);
set->auto_connect = b ? true : false;
g_dbus_pending_property_success(id);
}
static void append_device(void *data, void *user_data)
{
struct btd_device *device = data;
const char *path = device_get_path(device);
DBusMessageIter *entry = user_data;
dbus_message_iter_append_basic(entry, DBUS_TYPE_OBJECT_PATH, &path);
}
static gboolean get_devices(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_device_set *set = data;
DBusMessageIter entry;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_OBJECT_PATH_AS_STRING,
&entry);
queue_foreach(set->devices, append_device, &entry);
dbus_message_iter_close_container(iter, &entry);
return TRUE;
}
static gboolean get_size(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_device_set *set = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &set->size);
return TRUE;
}
static const GDBusPropertyTable set_properties[] = {
{ "Adapter", "o", get_adapter, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "AutoConnect", "b", get_auto_connect, set_auto_connect, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Devices", "ao", get_devices, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Size", "y", get_size, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{}
};
static void set_free(void *data)
{
struct btd_device_set *set = data;
queue_destroy(set->devices, NULL);
g_free(set->path);
free(set);
}
static struct btd_device_set *set_new(struct btd_device *device,
const uint8_t sirk[16], uint8_t size)
{
struct btd_device_set *set;
set = new0(struct btd_device_set, 1);
set->adapter = device_get_adapter(device);
memcpy(set->sirk, sirk, sizeof(set->sirk));
set->size = size;
set->auto_connect = true;
set->devices = queue_new();
queue_push_tail(set->devices, device);
set->path = g_strdup_printf("%s/set_%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x",
adapter_get_path(set->adapter),
sirk[15], sirk[14], sirk[13], sirk[12],
sirk[11], sirk[10], sirk[9], sirk[8],
sirk[7], sirk[6], sirk[5], sirk[4],
sirk[3], sirk[2], sirk[1], sirk[0]);
DBG("Creating set %s", set->path);
if (g_dbus_register_interface(btd_get_dbus_connection(),
set->path, BTD_DEVICE_SET_INTERFACE,
set_methods, NULL,
set_properties, set,
set_free) == FALSE) {
error("Unable to register set interface");
set_free(set);
return NULL;
}
return set;
}
static struct btd_device_set *set_find(struct btd_device *device,
const uint8_t sirk[16])
{
struct btd_adapter *adapter = device_get_adapter(device);
const struct queue_entry *entry;
for (entry = queue_get_entries(set_list); entry; entry = entry->next) {
struct btd_device_set *set = entry->data;
if (set->adapter != adapter)
continue;
if (!memcmp(set->sirk, sirk, sizeof(set->sirk)))
return set;
}
return NULL;
}
static void set_connect_next(struct btd_device_set *set)
{
const struct queue_entry *entry;
for (entry = queue_get_entries(set->devices); entry;
entry = entry->next) {
struct btd_device *device = entry->data;
/* Only connect one at time(?) */
if (!device_connect_le(device))
return;
}
}
static void set_add(struct btd_device_set *set, struct btd_device *device)
{
/* Check if device is already part of the set then skip to connect */
if (queue_find(set->devices, NULL, device))
goto done;
DBG("set %s device %s", set->path, device_get_path(device));
queue_push_tail(set->devices, device);
g_dbus_emit_property_changed(btd_get_dbus_connection(), set->path,
BTD_DEVICE_SET_INTERFACE, "Devices");
done:
/* Check if set is marked to auto-connect */
if (btd_device_is_connected(device) && set->auto_connect)
set_connect_next(set);
}
static void foreach_rsi(void *data, void *user_data)
{
struct bt_ad_data *ad = data;
struct btd_device_set *set = user_data;
struct bt_crypto *crypto;
uint8_t res[3];
if (ad->type != BT_AD_CSIP_RSI || ad->len < 6)
return;
crypto = bt_crypto_new();
if (!crypto)
return;
if (!bt_crypto_sih(crypto, set->sirk, ad->data + 3, res)) {
bt_crypto_unref(crypto);
return;
}
bt_crypto_unref(crypto);
if (memcmp(ad->data, res, sizeof(res)))
return;
/* Attempt to use existing gatt_db from set if device has never been
* connected before.
*
* If dbs don't really match bt_gatt_client will attempt to rediscover
* the ranges that don't match.
*/
if (gatt_db_isempty(btd_device_get_gatt_db(set->device))) {
struct btd_device *device;
device = queue_get_entries(set->devices)->data;
btd_device_set_gatt_db(set->device,
btd_device_get_gatt_db(device));
}
device_connect_le(set->device);
}
static void foreach_device(struct btd_device *device, void *data)
{
struct btd_device_set *set = data;
/* Check if device is already part of the set then skip */
if (queue_find(set->devices, NULL, device))
return;
set->device = device;
btd_device_foreach_ad(device, foreach_rsi, set);
}
struct btd_device_set *btd_set_add_device(struct btd_device *device,
const uint8_t *key,
const uint8_t sirk_value[16],
uint8_t size)
{
struct btd_device_set *set;
uint8_t sirk[16];
memcpy(sirk, sirk_value, sizeof(sirk));
/* In case key has been set it means SIRK is encrypted */
if (key) {
struct bt_crypto *crypto = bt_crypto_new();
if (!crypto)
return NULL;
/* sef and sdf are symmetric */
bt_crypto_sef(crypto, key, sirk, sirk);
bt_crypto_unref(crypto);
}
/* Check if DeviceSet already exists */
set = set_find(device, sirk);
if (set) {
set_add(set, device);
/* Check if there are new devices with RSI found */
goto done;
}
set = set_new(device, sirk, size);
if (!set)
return NULL;
if (!set_list)
set_list = queue_new();
queue_push_tail(set_list, set);
done:
/* Attempt to add devices which have matching RSI */
btd_adapter_for_each_device(device_get_adapter(device), foreach_device,
set);
return set;
}
bool btd_set_remove_device(struct btd_device_set *set,
struct btd_device *device)
{
if (!set || !device)
return false;
if (!queue_remove_if(set->devices, NULL, device))
return false;
if (!queue_isempty(set->devices)) {
g_dbus_emit_property_changed(btd_get_dbus_connection(),
set->path,
BTD_DEVICE_SET_INTERFACE,
"Devices");
return true;
}
if (!queue_remove(set_list, set))
return false;
/* Unregister if there are no devices left in the set */
g_dbus_unregister_interface(btd_get_dbus_connection(), set->path,
BTD_DEVICE_SET_INTERFACE);
return true;
}
const char *btd_set_get_path(struct btd_device_set *set)
{
return set->path;
}