blob: 02267c71143191b813b0dfe5c58fbd6f1af0eca2 [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2025 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 <stdint.h>
#include <glib.h>
#include <dbus/dbus.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/mgmt.h"
#include "bluetooth/uuid.h"
#include "gdbus/gdbus.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
#include "src/shared/timeout.h"
#include "log.h"
#include "error.h"
#include "adapter.h"
#include "device.h"
#include "profile.h"
#include "service.h"
#include "dbus-common.h"
#include "bearer.h"
#define DISCONNECT_TIMER 2
struct btd_bearer {
struct btd_device *device;
uint8_t type;
const char *path;
unsigned int disconn_timer;
struct queue *disconnects; /* disconnects message */
/* Connect() is defined as a single in-flight operation. To preserve
* the API semantics of org.bluez.Device1.Connect(), we do not queue
* additional connect messages.
*/
DBusMessage *connect; /* connect message */
};
static void bearer_free_dbus_message(void *data)
{
dbus_message_unref((DBusMessage *)data);
}
static void bearer_free(void *data)
{
struct btd_bearer *bearer = data;
free(bearer);
}
static void bearer_disconnect_service(struct btd_service *service,
void *user_data)
{
uint8_t bdaddr_type = *(uint8_t *)user_data;
struct btd_profile *profile = btd_service_get_profile(service);
struct btd_device *device = btd_service_get_device(service);
if (!profile || !device)
return;
if (bdaddr_type == BDADDR_BREDR) {
if (profile->bearer == BTD_PROFILE_BEARER_LE)
return;
} else {
if (profile->bearer == BTD_PROFILE_BEARER_BREDR)
return;
}
DBG("Disconnecting profile %s for bearer addr type %u",
profile->name ?: "(unknown)", bdaddr_type);
btd_service_disconnect(service);
}
static bool bearer_disconnect_link(gpointer user_data)
{
struct btd_bearer *bearer = user_data;
struct btd_device *device = bearer->device;
bearer->disconn_timer = 0;
if (btd_device_bdaddr_type_connected(device, bearer->type))
btd_adapter_disconnect_device(device_get_adapter(device),
device_get_address(device),
bearer->type);
return FALSE;
}
static DBusMessage *bearer_connect(DBusConnection *conn, DBusMessage *msg,
void *user_data)
{
struct btd_bearer *bearer = user_data;
struct btd_device *device = bearer->device;
int err;
if (btd_device_bdaddr_type_connected(device, bearer->type)) {
if (msg)
return btd_error_already_connected(msg);
return NULL;
}
if (device_is_bonding(device, NULL)) {
if (msg)
return btd_error_in_progress(msg);
return NULL;
}
if (device_is_connecting(device) ||
bearer->connect) {
if (msg)
return btd_error_in_progress(msg);
return NULL;
}
if (msg)
bearer->connect = dbus_message_ref(msg);
if (bearer->type == BDADDR_BREDR)
return device_connect_profiles(device, BDADDR_BREDR,
msg, NULL);
else {
btd_device_set_temporary(device, false);
err = device_connect_le(device);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
}
return NULL;
}
static DBusMessage *bearer_disconnect(DBusConnection *conn, DBusMessage *msg,
void *user_data)
{
struct btd_bearer *bearer = user_data;
struct btd_device *device = bearer->device;
if (!btd_device_bdaddr_type_connected(device, bearer->type)) {
if (msg)
return btd_error_not_connected(msg);
return NULL;
}
/* org.bluez.Device1.Disconnect() is in progress. Since it tears down
* both LE and BR/EDR bearers, it takes precedence over bearer-level
* disconnects. Ignore any bearer-specific disconnect requests here.
*/
if (device_is_disconnecting(device)) {
if (msg)
return btd_error_in_progress(msg);
return NULL;
}
if (msg)
queue_push_tail(bearer->disconnects, dbus_message_ref(msg));
device_cancel_bonding(device, MGMT_STATUS_DISCONNECTED);
device_cancel_browse(device, bearer->type);
btd_device_foreach_service(device, bearer_disconnect_service,
&bearer->type);
device_remove_pending_services(device, bearer->type);
if (bearer->disconn_timer)
return NULL;
bearer->disconn_timer = timeout_add_seconds(DISCONNECT_TIMER,
bearer_disconnect_link,
bearer, NULL);
return NULL;
}
static const GDBusMethodTable bearer_methods[] = {
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("Connect", NULL, NULL,
bearer_connect) },
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("Disconnect", NULL, NULL,
bearer_disconnect) },
{}
};
static gboolean bearer_get_adapter(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_bearer *bearer = data;
struct btd_adapter *adapter = device_get_adapter(bearer->device);
const char *path = adapter_get_path(adapter);
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
return TRUE;
}
static gboolean bearer_get_paired(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_bearer *bearer = data;
dbus_bool_t paired = device_is_paired(bearer->device, bearer->type);
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &paired);
return TRUE;
}
static gboolean bearer_get_bonded(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_bearer *bearer = data;
dbus_bool_t bonded = device_is_bonded(bearer->device, bearer->type);
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &bonded);
return TRUE;
}
static gboolean bearer_get_connected(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct btd_bearer *bearer = data;
dbus_bool_t connected = btd_device_bdaddr_type_connected(bearer->device,
bearer->type);
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected);
return TRUE;
}
static const GDBusSignalTable bearer_signals[] = {
{ GDBUS_SIGNAL("Disconnected",
GDBUS_ARGS({ "name", "s" }, { "message", "s" })) },
{ }
};
static const GDBusPropertyTable bearer_properties[] = {
{ "Adapter", "o", bearer_get_adapter, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Paired", "b", bearer_get_paired, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Bonded", "b", bearer_get_bonded, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{ "Connected", "b", bearer_get_connected, NULL, NULL,
G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
{}
};
static const char *bearer_interface(uint8_t type)
{
if (type == BDADDR_BREDR)
return BTD_BEARER_BREDR_INTERFACE;
else
return BTD_BEARER_LE_INTERFACE;
}
struct btd_bearer *btd_bearer_new(struct btd_device *device, uint8_t type)
{
struct btd_bearer *bearer;
bearer = new0(struct btd_bearer, 1);
bearer->device = device;
bearer->type = type;
bearer->path = device_get_path(device);
bearer->disconnects = queue_new();
if (!g_dbus_register_interface(btd_get_dbus_connection(),
bearer->path, bearer_interface(type),
bearer_methods, bearer_signals,
bearer_properties,
bearer, bearer_free)) {
error("Unable to register BREDR interface");
bearer->path = NULL;
}
return bearer;
}
void btd_bearer_destroy(struct btd_bearer *bearer)
{
if (!bearer)
return;
if (!bearer->path) {
bearer_free(bearer);
return;
}
if (bearer->disconnects) {
queue_destroy(bearer->disconnects, bearer_free_dbus_message);
bearer->disconnects = NULL;
}
if (bearer->connect) {
dbus_message_unref(bearer->connect);
bearer->connect = NULL;
}
g_dbus_unregister_interface(btd_get_dbus_connection(), bearer->path,
bearer_interface(bearer->type));
}
void btd_bearer_paired(struct btd_bearer *bearer)
{
if (!bearer || !bearer->path)
return;
g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path,
bearer_interface(bearer->type),
"Paired");
}
void btd_bearer_bonded(struct btd_bearer *bearer)
{
if (!bearer || !bearer->path)
return;
g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path,
bearer_interface(bearer->type),
"Bonded");
}
void btd_bearer_connected(struct btd_bearer *bearer, int err)
{
DBusMessage *reply;
if (!bearer || !bearer->path)
return;
if (bearer->connect) {
if (err)
reply = bearer->type == BDADDR_BREDR ?
btd_error_bredr_errno(bearer->connect, err) :
btd_error_le_errno(bearer->connect, err);
else
reply = dbus_message_new_method_return(
bearer->connect);
g_dbus_send_message(btd_get_dbus_connection(), reply);
dbus_message_unref(bearer->connect);
bearer->connect = NULL;
}
g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path,
bearer_interface(bearer->type),
"Connected");
}
void btd_bearer_disconnected(struct btd_bearer *bearer, uint8_t reason)
{
const char *name;
const char *message;
DBusMessage *msg;
const struct queue_entry *entry;
if (!bearer || !bearer->path)
return;
if (!btd_device_is_connected(bearer->device))
device_disconnect_watches_callback(bearer->device);
while (!queue_isempty(bearer->disconnects)) {
entry = queue_get_entries(bearer->disconnects);
msg = entry->data;
g_dbus_send_reply(btd_get_dbus_connection(), msg,
DBUS_TYPE_INVALID);
queue_remove(bearer->disconnects, msg);
dbus_message_unref(msg);
}
g_dbus_emit_property_changed(btd_get_dbus_connection(), bearer->path,
bearer_interface(bearer->type),
"Connected");
switch (reason) {
case MGMT_DEV_DISCONN_UNKNOWN:
name = "org.bluez.Reason.Unknown";
message = "Unspecified";
break;
case MGMT_DEV_DISCONN_TIMEOUT:
name = "org.bluez.Reason.Timeout";
message = "Connection timeout";
break;
case MGMT_DEV_DISCONN_LOCAL_HOST:
name = "org.bluez.Reason.Local";
message = "Connection terminated by local host";
break;
case MGMT_DEV_DISCONN_REMOTE:
name = "org.bluez.Reason.Remote";
message = "Connection terminated by remote user";
break;
case MGMT_DEV_DISCONN_AUTH_FAILURE:
name = "org.bluez.Reason.Authentication";
message = "Connection terminated due to authentication failure";
break;
case MGMT_DEV_DISCONN_LOCAL_HOST_SUSPEND:
name = "org.bluez.Reason.Suspend";
message = "Connection terminated by local host for suspend";
break;
default:
warn("Unknown disconnection value: %u", reason);
name = "org.bluez.Reason.Unknown";
message = "Unspecified";
}
g_dbus_emit_signal(btd_get_dbus_connection(), bearer->path,
bearer_interface(bearer->type),
"Disconnected",
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &message,
DBUS_TYPE_INVALID);
}