mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-16 16:54:38 +08:00
508 lines
12 KiB
C
508 lines
12 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
|
|
* Authors:
|
|
* Santiago Carot Nemesio <sancane at gmail.com>
|
|
* Jose Antonio Santos-Cadenas <santoscadenas at gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include <gdbus.h>
|
|
|
|
#include "log.h"
|
|
#include "error.h"
|
|
#include <stdint.h>
|
|
#include <hdp_types.h>
|
|
#include <hdp_util.h>
|
|
#include <adapter.h>
|
|
#include <device.h>
|
|
#include <hdp.h>
|
|
#include <mcap.h>
|
|
#include <btio.h>
|
|
#include <mcap_lib.h>
|
|
|
|
#include <sdpd.h>
|
|
#include "../src/dbus-common.h"
|
|
|
|
static DBusConnection *connection = NULL;
|
|
|
|
static GSList *applications = NULL;
|
|
static GSList *devices = NULL;
|
|
static uint8_t next_app_id = HDP_MDEP_INITIAL;
|
|
|
|
static GSList *adapters;
|
|
|
|
static gboolean update_adapter(struct hdp_adapter *adapter);
|
|
|
|
static int cmp_app_id(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct hdp_application *app = a;
|
|
const uint8_t *id = b;
|
|
|
|
return app->id - *id;
|
|
}
|
|
|
|
static int cmp_adapter(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct hdp_adapter *hdp_adapter = a;
|
|
const struct btd_adapter *adapter = b;
|
|
|
|
if (hdp_adapter->btd_adapter == adapter)
|
|
return 0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int cmp_device(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct hdp_device *hdp_device = a;
|
|
const struct btd_device *device = b;
|
|
|
|
if (hdp_device->dev == device)
|
|
return 0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static uint8_t get_app_id()
|
|
{
|
|
GSList *l;
|
|
uint8_t id = next_app_id;
|
|
|
|
do {
|
|
l = g_slist_find_custom(applications, &id, cmp_app_id);
|
|
if (!l) {
|
|
next_app_id = (id % HDP_MDEP_FINAL) + 1;
|
|
return id;
|
|
} else
|
|
id = (id % HDP_MDEP_FINAL) + 1;
|
|
} while (id != next_app_id);
|
|
|
|
/* No more ids available */
|
|
return 0;
|
|
}
|
|
|
|
static int cmp_app(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct hdp_application *app = a;
|
|
|
|
return g_strcmp0(app->path, b);
|
|
}
|
|
|
|
static gboolean set_app_path(struct hdp_application *app)
|
|
{
|
|
app->id = get_app_id();
|
|
if (!app->id)
|
|
return FALSE;
|
|
app->path = g_strdup_printf(MANAGER_PATH "/health_app_%d", app->id);
|
|
|
|
return TRUE;
|
|
};
|
|
|
|
static void free_health_device(struct hdp_device *device)
|
|
{
|
|
if (device->conn) {
|
|
dbus_connection_unref(device->conn);
|
|
device->conn = NULL;
|
|
}
|
|
|
|
if (device->dev) {
|
|
btd_device_unref(device->dev);
|
|
device->dev = NULL;
|
|
}
|
|
|
|
g_free(device);
|
|
}
|
|
|
|
static void free_application(struct hdp_application *app)
|
|
{
|
|
if (app->dbus_watcher)
|
|
g_dbus_remove_watch(connection, app->dbus_watcher);
|
|
|
|
g_free(app->oname);
|
|
g_free(app->description);
|
|
g_free(app->path);
|
|
g_free(app);
|
|
}
|
|
|
|
static void remove_application(struct hdp_application *app)
|
|
{
|
|
DBG("Application %s deleted", app->path);
|
|
free_application(app);
|
|
|
|
g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
|
|
}
|
|
|
|
static void client_disconnected(DBusConnection *conn, void *user_data)
|
|
{
|
|
struct hdp_application *app = user_data;
|
|
|
|
DBG("Client disconnected from the bus, deleting hdp application");
|
|
applications = g_slist_remove(applications, app);
|
|
|
|
app->dbus_watcher = 0; /* Watcher shouldn't be freed in this case */
|
|
remove_application(app);
|
|
}
|
|
|
|
static DBusMessage *manager_create_application(DBusConnection *conn,
|
|
DBusMessage *msg, void *user_data)
|
|
{
|
|
struct hdp_application *app;
|
|
const char *name;
|
|
DBusMessageIter iter;
|
|
GError *err = NULL;
|
|
DBusMessage *reply;
|
|
|
|
dbus_message_iter_init(msg, &iter);
|
|
app = hdp_get_app_config(&iter, &err);
|
|
if (err) {
|
|
reply = g_dbus_create_error(msg,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments: %s", err->message);
|
|
g_error_free(err);
|
|
return reply;
|
|
}
|
|
|
|
name = dbus_message_get_sender(msg);
|
|
if (!name) {
|
|
free_application(app);
|
|
return g_dbus_create_error(msg,
|
|
ERROR_INTERFACE ".HealthError",
|
|
"Can't get sender name");
|
|
}
|
|
|
|
if (!set_app_path(app)){
|
|
free_application(app);
|
|
return g_dbus_create_error(msg,
|
|
ERROR_INTERFACE ".HealthError",
|
|
"Can't get a valid id for the application");
|
|
}
|
|
|
|
app->oname = g_strdup(name);
|
|
|
|
applications = g_slist_prepend(applications, app);
|
|
|
|
app->dbus_watcher = g_dbus_add_disconnect_watch(conn, name,
|
|
client_disconnected, app, NULL);
|
|
g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
|
|
|
|
DBG("Health application created with id %s", app->path);
|
|
|
|
return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &app->path,
|
|
DBUS_TYPE_INVALID);
|
|
}
|
|
|
|
static DBusMessage *manager_destroy_application(DBusConnection *conn,
|
|
DBusMessage *msg, void *user_data)
|
|
{
|
|
const char *path;
|
|
struct hdp_application *app;
|
|
GSList *l;
|
|
|
|
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
|
|
DBUS_TYPE_INVALID)){
|
|
return g_dbus_create_error(msg,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
}
|
|
|
|
l = g_slist_find_custom(applications, path, cmp_app);
|
|
|
|
app = l->data;
|
|
applications = g_slist_remove(applications, app);
|
|
|
|
remove_application(app);
|
|
|
|
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
|
|
}
|
|
|
|
static void manager_path_unregister(gpointer data)
|
|
{
|
|
g_slist_foreach(applications, (GFunc) free_application, NULL);
|
|
|
|
g_slist_free(applications);
|
|
applications = NULL;
|
|
|
|
g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
|
|
}
|
|
|
|
static GDBusMethodTable health_manager_methods[] = {
|
|
{"CreateApplication", "a{sv}", "o", manager_create_application},
|
|
{"DestroyApplication", "o", "", manager_destroy_application},
|
|
{ NULL }
|
|
};
|
|
|
|
static void mcl_connected(struct mcap_mcl *mcl, gpointer data)
|
|
{
|
|
/* struct hdp_adapter *hdp_adapter = data; */
|
|
/* TODO: Implement mcl_connected */
|
|
}
|
|
|
|
static void mcl_reconnected(struct mcap_mcl *mcl, gpointer data)
|
|
{
|
|
/* struct hdp_adapter *hdp_adapter = data; */
|
|
/* TODO: Implement mcl_reconnected */
|
|
}
|
|
|
|
static void mcl_disconnected(struct mcap_mcl *mcl, gpointer data)
|
|
{
|
|
/* struct hdp_adapter *hdp_adapter = data; */
|
|
/* TODO: Implement mcl_disconnected */
|
|
}
|
|
|
|
static void mcl_uncached(struct mcap_mcl *mcl, gpointer data)
|
|
{
|
|
/* struct hdp_adapter *hdp_adapter = data; */
|
|
/* TODO: Implement mcl_uncached */
|
|
}
|
|
|
|
static gboolean update_adapter(struct hdp_adapter *hdp_adapter)
|
|
{
|
|
GError *err = NULL;
|
|
bdaddr_t addr;
|
|
|
|
if (!applications) {
|
|
if (hdp_adapter->mi) {
|
|
mcap_release_instance(hdp_adapter->mi);
|
|
hdp_adapter->mi = NULL;
|
|
}
|
|
goto update;
|
|
}
|
|
|
|
if (hdp_adapter->mi)
|
|
goto update;
|
|
|
|
adapter_get_address(hdp_adapter->btd_adapter, &addr);
|
|
hdp_adapter->mi = mcap_create_instance(&addr, BT_IO_SEC_HIGH, 0, 0,
|
|
mcl_connected, mcl_reconnected,
|
|
mcl_disconnected, mcl_uncached,
|
|
NULL, /* CSP is not used by now */
|
|
hdp_adapter, &err);
|
|
|
|
if (!hdp_adapter->mi) {
|
|
error("Error creating the MCAP instance: %s", err->message);
|
|
g_error_free(err);
|
|
return FALSE;
|
|
}
|
|
|
|
hdp_adapter->ccpsm = mcap_get_ctrl_psm(hdp_adapter->mi, &err);
|
|
if (err) {
|
|
error("Error getting MCAP control PSM: %s", err->message);
|
|
goto fail;
|
|
}
|
|
|
|
hdp_adapter->dcpsm = mcap_get_data_psm(hdp_adapter->mi, &err);
|
|
if (err) {
|
|
error("Error getting MCAP data PSM: %s", err->message);
|
|
goto fail;
|
|
}
|
|
|
|
update:
|
|
if (hdp_update_sdp_record(hdp_adapter, applications))
|
|
return TRUE;
|
|
error("Error updating the SDP record");
|
|
|
|
fail:
|
|
if (hdp_adapter->mi)
|
|
mcap_release_instance(hdp_adapter->mi);
|
|
if (err)
|
|
g_error_free(err);
|
|
return FALSE;
|
|
}
|
|
|
|
int hdp_adapter_register(DBusConnection *conn, struct btd_adapter *adapter)
|
|
{
|
|
struct hdp_adapter *hdp_adapter;
|
|
|
|
hdp_adapter = g_new0(struct hdp_adapter, 1);
|
|
hdp_adapter->btd_adapter = btd_adapter_ref(adapter);
|
|
|
|
if(!update_adapter(hdp_adapter))
|
|
goto fail;
|
|
|
|
adapters = g_slist_append(adapters, hdp_adapter);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
btd_adapter_unref(hdp_adapter->btd_adapter);
|
|
g_free(hdp_adapter);
|
|
return -1;
|
|
}
|
|
|
|
void hdp_adapter_unregister(struct btd_adapter *adapter)
|
|
{
|
|
struct hdp_adapter *hdp_adapter;
|
|
GSList *l;
|
|
|
|
l = g_slist_find_custom(adapters, adapter, cmp_adapter);
|
|
|
|
if (!l)
|
|
return;
|
|
|
|
hdp_adapter = l->data;
|
|
adapters = g_slist_remove(adapters, hdp_adapter);
|
|
if (hdp_adapter->sdp_handler)
|
|
remove_record_from_server(hdp_adapter->sdp_handler);
|
|
if (hdp_adapter->mi)
|
|
mcap_release_instance(hdp_adapter->mi);
|
|
btd_adapter_unref(hdp_adapter->btd_adapter);
|
|
g_free(hdp_adapter);
|
|
}
|
|
|
|
static DBusMessage *device_echo(DBusConnection *conn,
|
|
DBusMessage *msg, void *user_data)
|
|
{
|
|
return g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
|
|
"Echo function not implemented");
|
|
}
|
|
|
|
static DBusMessage *device_create_channel(DBusConnection *conn,
|
|
DBusMessage *msg, void *user_data)
|
|
{
|
|
return g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
|
|
"CreateChannel function not implemented");
|
|
}
|
|
|
|
static DBusMessage *device_destroy_channel(DBusConnection *conn,
|
|
DBusMessage *msg, void *user_data)
|
|
{
|
|
return g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
|
|
"DestroyChannel function not implemented");
|
|
}
|
|
|
|
static void health_device_destroy(void *data)
|
|
{
|
|
struct hdp_device *device = data;
|
|
|
|
DBG("Unregistered interface %s on path %s", HEALTH_DEVICE,
|
|
device_get_path(device->dev));
|
|
devices = g_slist_remove(devices, device);
|
|
free_health_device(device);
|
|
}
|
|
|
|
static GDBusMethodTable health_device_methods[] = {
|
|
{"Echo", "", "b", device_echo,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{"CreateChannel", "os", "o", device_create_channel,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{"DestroyChannel", "o", "", device_destroy_channel,
|
|
G_DBUS_METHOD_FLAG_ASYNC },
|
|
{ NULL }
|
|
};
|
|
|
|
static GDBusSignalTable health_device_signals[] = {
|
|
{"ChannelConnected", "o" },
|
|
{"ChannelDeleted", "o" },
|
|
{"PropertyChanged", "sv" },
|
|
{ NULL }
|
|
};
|
|
|
|
static struct hdp_device *create_health_device(DBusConnection *conn,
|
|
struct btd_device *device)
|
|
{
|
|
struct btd_adapter *adapter = device_get_adapter(device);
|
|
const gchar *path = device_get_path(device);
|
|
struct hdp_device *dev;
|
|
GSList *l;
|
|
|
|
dev = g_new0(struct hdp_device, 1);
|
|
dev->conn = dbus_connection_ref(conn);
|
|
dev->dev = btd_device_ref(device);
|
|
l = g_slist_find_custom(adapters, adapter, cmp_adapter);
|
|
|
|
if (!l)
|
|
goto fail;
|
|
|
|
dev->hdp_adapter = l->data;
|
|
|
|
if (!g_dbus_register_interface(conn, path,
|
|
HEALTH_DEVICE,
|
|
health_device_methods,
|
|
health_device_signals, NULL,
|
|
dev, health_device_destroy)) {
|
|
error("D-Bus failed to register %s interface", HEALTH_DEVICE);
|
|
goto fail;
|
|
}
|
|
|
|
DBG("Registered interface %s on path %s", HEALTH_DEVICE, path);
|
|
return dev;
|
|
|
|
fail:
|
|
free_health_device(dev);
|
|
return NULL;
|
|
}
|
|
|
|
int hdp_device_register(DBusConnection *conn, struct btd_device *device)
|
|
{
|
|
struct hdp_device *hdev;
|
|
GSList *l;
|
|
|
|
l = g_slist_find_custom(devices, device, cmp_device);
|
|
if (l)
|
|
return 0;
|
|
|
|
hdev = create_health_device(conn, device);
|
|
if (!hdev)
|
|
return -1;
|
|
|
|
devices = g_slist_prepend(devices, hdev);
|
|
return 0;
|
|
}
|
|
|
|
void hdp_device_unregister(struct btd_device *device)
|
|
{
|
|
struct hdp_device *hdp_dev;
|
|
const char *path;
|
|
GSList *l;
|
|
|
|
l = g_slist_find_custom(devices, device, cmp_device);
|
|
if (!l)
|
|
return;
|
|
|
|
hdp_dev = l->data;
|
|
path = device_get_path(hdp_dev->dev);
|
|
g_dbus_unregister_interface(hdp_dev->conn, path, HEALTH_DEVICE);
|
|
}
|
|
|
|
int hdp_manager_start(DBusConnection *conn)
|
|
{
|
|
DBG("Starting Health manager");
|
|
|
|
if (!g_dbus_register_interface(conn, MANAGER_PATH,
|
|
HEALTH_MANAGER,
|
|
health_manager_methods, NULL, NULL,
|
|
NULL, manager_path_unregister)) {
|
|
error("D-Bus failed to register %s interface", HEALTH_MANAGER);
|
|
return -1;
|
|
}
|
|
|
|
connection = dbus_connection_ref(conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void hdp_manager_stop()
|
|
{
|
|
g_dbus_unregister_interface(connection, MANAGER_PATH, HEALTH_MANAGER);
|
|
|
|
dbus_connection_unref(connection);
|
|
DBG("Stopped Health manager");
|
|
}
|