/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Instituto Nokia de Tecnologia - INdT * * * 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 * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "gdbus/gdbus.h" #include "src/error.h" #define GATT_MGR_IFACE "org.bluez.GattManager1" #define GATT_SERVICE_IFACE "org.bluez.GattService1" #define GATT_CHR_IFACE "org.bluez.GattCharacteristic1" #define GATT_DESCRIPTOR_IFACE "org.bluez.GattDescriptor1" /* Immediate Alert Service UUID */ #define IAS_UUID "00001802-0000-1000-8000-00805f9b34fb" #define ALERT_LEVEL_CHR_UUID "00002a06-0000-1000-8000-00805f9b34fb" /* Random UUID for testing purpose */ #define READ_WRITE_DESCRIPTOR_UUID "8260c653-1a54-426b-9e36-e84c238bc669" static GMainLoop *main_loop; static GSList *services; static DBusConnection *connection; struct characteristic { char *uuid; char *path; uint8_t *value; int vlen; const char **props; }; struct descriptor { char *uuid; char *path; uint8_t *value; int vlen; }; /* * Alert Level support Write Without Response only. Supported * properties are defined at doc/gatt-api.txt. See "Flags" * property of the GattCharacteristic1. */ static const char *ias_alert_level_props[] = { "write-without-response", NULL }; static gboolean desc_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct descriptor *desc = user_data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &desc->uuid); return TRUE; } static gboolean desc_get_value(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct descriptor *desc = user_data; DBusMessageIter array; printf("Descriptor(%s): Get(\"Value\")\n", desc->uuid); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); if (desc->vlen && desc->value) dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &desc->value, desc->vlen); dbus_message_iter_close_container(iter, &array); return TRUE; } static void desc_set_value(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct descriptor *desc = user_data; DBusMessageIter array; const uint8_t *value; int vlen; printf("Descriptor(%s): Set(\"Value\", ...)\n", desc->uuid); dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &value, &vlen); g_free(desc->value); desc->value = g_memdup(value, vlen); desc->vlen = vlen; g_dbus_pending_property_success(id); g_dbus_emit_property_changed(connection, desc->path, GATT_DESCRIPTOR_IFACE, "Value"); } static const GDBusPropertyTable desc_properties[] = { { "UUID", "s", desc_get_uuid }, { "Value", "ay", desc_get_value, desc_set_value, NULL }, { } }; static gboolean chr_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct characteristic *chr = user_data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &chr->uuid); return TRUE; } static gboolean chr_get_value(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { struct characteristic *chr = user_data; DBusMessageIter array; printf("Characteristic(%s): Get(\"Value\")\n", chr->uuid); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &chr->value, chr->vlen); dbus_message_iter_close_container(iter, &array); return TRUE; } static gboolean chr_get_props(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct characteristic *chr = data; DBusMessageIter array; int i; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &array); for (i = 0; chr->props[i]; i++) dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &chr->props[i]); dbus_message_iter_close_container(iter, &array); return TRUE; } static void chr_set_value(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *user_data) { struct characteristic *chr = user_data; DBusMessageIter array; uint8_t *value; int len; printf("Characteristic(%s): Set('Value', ...)\n", chr->uuid); if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) { printf("Invalid value for Set('Value'...)\n"); g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_recurse(iter, &array); dbus_message_iter_get_fixed_array(&array, &value, &len); g_free(chr->value); chr->value = g_memdup(value, len); chr->vlen = len; g_dbus_pending_property_success(id); g_dbus_emit_property_changed(connection, chr->path, GATT_CHR_IFACE, "Value"); } static const GDBusPropertyTable chr_properties[] = { { "UUID", "s", chr_get_uuid }, { "Value", "ay", chr_get_value, chr_set_value, NULL }, { "Flags", "as", chr_get_props, NULL, NULL }, { } }; static gboolean service_get_uuid(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { const char *uuid = user_data; printf("Get UUID: %s\n", uuid); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); return TRUE; } static gboolean service_get_includes(const GDBusPropertyTable *property, DBusMessageIter *iter, void *user_data) { const char *uuid = user_data; printf("Get Includes: %s\n", uuid); return TRUE; } static gboolean service_exist_includes(const GDBusPropertyTable *property, void *user_data) { const char *uuid = user_data; printf("Exist Includes: %s\n", uuid); return FALSE; } static const GDBusPropertyTable service_properties[] = { { "UUID", "s", service_get_uuid }, { "Includes", "ao", service_get_includes, NULL, service_exist_includes }, { } }; static void chr_iface_destroy(gpointer user_data) { struct characteristic *chr = user_data; g_free(chr->uuid); g_free(chr->value); g_free(chr->path); g_free(chr); } static void desc_iface_destroy(gpointer user_data) { struct descriptor *desc = user_data; g_free(desc->uuid); g_free(desc->value); g_free(desc->path); g_free(desc); } static gboolean register_characteristic(const char *chr_uuid, const uint8_t *value, int vlen, const char **props, const char *desc_uuid, const char *service_path) { struct characteristic *chr; struct descriptor *desc; static int id = 1; chr = g_new0(struct characteristic, 1); chr->uuid = g_strdup(chr_uuid); chr->value = g_memdup(value, vlen); chr->vlen = vlen; chr->props = props; chr->path = g_strdup_printf("%s/characteristic%d", service_path, id++); if (!g_dbus_register_interface(connection, chr->path, GATT_CHR_IFACE, NULL, NULL, chr_properties, chr, chr_iface_destroy)) { printf("Couldn't register characteristic interface\n"); chr_iface_destroy(chr); return FALSE; } if (!desc_uuid) return TRUE; desc = g_new0(struct descriptor, 1); desc->uuid = g_strdup(desc_uuid); desc->path = g_strdup_printf("%s/descriptor%d", chr->path, id++); if (!g_dbus_register_interface(connection, desc->path, GATT_DESCRIPTOR_IFACE, NULL, NULL, desc_properties, desc, desc_iface_destroy)) { printf("Couldn't register descriptor interface\n"); g_dbus_unregister_interface(connection, chr->path, GATT_CHR_IFACE); desc_iface_destroy(desc); return FALSE; } return TRUE; } static char *register_service(const char *uuid) { static int id = 1; char *path; path = g_strdup_printf("/service%d", id++); if (!g_dbus_register_interface(connection, path, GATT_SERVICE_IFACE, NULL, NULL, service_properties, g_strdup(uuid), g_free)) { printf("Couldn't register service interface\n"); g_free(path); return NULL; } return path; } static void create_services() { char *service_path; uint8_t level = 0; service_path = register_service(IAS_UUID); if (!service_path) return; /* Add Alert Level Characteristic to Immediate Alert Service */ if (!register_characteristic(ALERT_LEVEL_CHR_UUID, &level, sizeof(level), ias_alert_level_props, READ_WRITE_DESCRIPTOR_UUID, service_path)) { printf("Couldn't register Alert Level characteristic (IAS)\n"); g_dbus_unregister_interface(connection, service_path, GATT_SERVICE_IFACE); g_free(service_path); return; } services = g_slist_prepend(services, service_path); printf("Registered service: %s\n", service_path); } static void register_external_service_reply(DBusPendingCall *call, void *user_data) { DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError derr; dbus_error_init(&derr); dbus_set_error_from_message(&derr, reply); if (dbus_error_is_set(&derr)) printf("RegisterService: %s\n", derr.message); else printf("RegisterService: OK\n"); dbus_message_unref(reply); dbus_error_free(&derr); } static void register_external_service(gpointer a, gpointer b) { DBusConnection *conn = b; const char *path = a; DBusMessage *msg; DBusPendingCall *call; DBusMessageIter iter, dict; msg = dbus_message_new_method_call("org.bluez", "/org/bluez", GATT_MGR_IFACE, "RegisterService"); if (!msg) { printf("Couldn't allocate D-Bus message\n"); return; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); /* TODO: Add options dictionary */ dbus_message_iter_close_container(&iter, &dict); if (!g_dbus_send_message_with_reply(conn, msg, &call, -1)) { dbus_message_unref(msg); return; } dbus_pending_call_set_notify(call, register_external_service_reply, NULL, NULL); dbus_pending_call_unref(call); } static void connect_handler(DBusConnection *conn, void *user_data) { g_slist_foreach(services, register_external_service, conn); } static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, gpointer user_data) { static bool __terminated = false; struct signalfd_siginfo si; ssize_t result; int fd; if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) return FALSE; fd = g_io_channel_unix_get_fd(channel); result = read(fd, &si, sizeof(si)); if (result != sizeof(si)) return FALSE; switch (si.ssi_signo) { case SIGINT: case SIGTERM: if (!__terminated) { printf("Terminating\n"); g_main_loop_quit(main_loop); } __terminated = true; break; } return TRUE; } static guint setup_signalfd(void) { GIOChannel *channel; guint source; sigset_t mask; int fd; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { perror("Failed to set signal mask"); return 0; } fd = signalfd(-1, &mask, 0); if (fd < 0) { perror("Failed to create signal descriptor"); return 0; } channel = g_io_channel_unix_new(fd); g_io_channel_set_close_on_unref(channel, TRUE); g_io_channel_set_encoding(channel, NULL, NULL); g_io_channel_set_buffered(channel, FALSE); source = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, signal_handler, NULL); g_io_channel_unref(channel); return source; } int main(int argc, char *argv[]) { GDBusClient *client; guint signal; signal = setup_signalfd(); if (signal == 0) return -errno; connection = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); main_loop = g_main_loop_new(NULL, FALSE); g_dbus_attach_object_manager(connection); printf("gatt-service unique name: %s\n", dbus_bus_get_unique_name(connection)); create_services(); client = g_dbus_client_new(connection, "org.bluez", "/org/bluez"); g_dbus_client_set_connect_watch(client, connect_handler, NULL); g_main_loop_run(main_loop); g_dbus_client_unref(client); g_source_remove(signal); g_slist_free_full(services, g_free); dbus_connection_unref(connection); return 0; }