summaryrefslogtreecommitdiff
path: root/profiles/iap
diff options
context:
space:
mode:
authorMarcel Holtmann <marcel@holtmann.org>2012-12-27 23:57:04 -0800
committerMarcel Holtmann <marcel@holtmann.org>2012-12-27 23:57:04 -0800
commit8fa10450c89a6a9c53599cedf9553079738eee9b (patch)
treeb626f1fb61c51e0d48a0aba2ccf542dc93a49cb4 /profiles/iap
parent54fa756e52fc2140b804b1a347ccacddc01e2d71 (diff)
downloadbluez-8fa10450c89a6a9c53599cedf9553079738eee9b.tar.gz
profiles: Add experimental daemon for Apple's Wireless iAP
Diffstat (limited to 'profiles/iap')
-rw-r--r--profiles/iap/main.c465
1 files changed, 465 insertions, 0 deletions
diff --git a/profiles/iap/main.c b/profiles/iap/main.c
new file mode 100644
index 000000000..5ba9d2b91
--- /dev/null
+++ b/profiles/iap/main.c
@@ -0,0 +1,465 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * 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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/signalfd.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#define IAP_PATH "/org/bluez/iap"
+
+#define IAP_UUID "00000000-deca-fade-deca-deafdecacafe"
+
+#define IAP_RECORD \
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \
+ <record> \
+ <attribute id=\"0x0001\"> \
+ <sequence> \
+ <uuid value=\"%s\" /> \
+ </sequence> \
+ </attribute> \
+ <attribute id=\"0x0002\"> \
+ <uint32 value=\"0x00000000\" /> \
+ </attribute> \
+ <attribute id=\"0x0004\"> \
+ <sequence> \
+ <sequence> \
+ <uuid value=\"0x0100\" /> \
+ </sequence> \
+ <sequence> \
+ <uuid value=\"0x0003\" /> \
+ <uint8 value=\"0x%02x\" /> \
+ </sequence> \
+ </sequence> \
+ </attribute> \
+ <attribute id=\"0x0005\"> \
+ <sequence> \
+ <uuid value=\"0x1002\" /> \
+ </sequence> \
+ </attribute> \
+ <attribute id=\"0x0006\"> \
+ <sequence> \
+ <uint16 value=\"0x656e\" /> \
+ <uint16 value=\"0x006a\" /> \
+ <uint16 value=\"0x0100\" /> \
+ <uint16 value=\"0x6672\" /> \
+ <uint16 value=\"0x006a\" /> \
+ <uint16 value=\"0x0110\" /> \
+ <uint16 value=\"0x6465\" /> \
+ <uint16 value=\"0x006a\" /> \
+ <uint16 value=\"0x0120\" /> \
+ <uint16 value=\"0x6a61\" /> \
+ <uint16 value=\"0x006a\" /> \
+ <uint16 value=\"0x0130\" /> \
+ </sequence> \
+ </attribute> \
+ <attribute id=\"0x0008\"> \
+ <uint8 value=\"0xff\" /> \
+ </attribute> \
+ <attribute id=\"0x0009\"> \
+ <sequence> \
+ <sequence> \
+ <uuid value=\"0x1101\" /> \
+ <uint16 value=\"0x0100\" /> \
+ </sequence> \
+ </sequence> \
+ </attribute> \
+ <attribute id=\"0x0100\"> \
+ <text value=\"Wireless iAP\" /> \
+ </attribute> \
+ </record>"
+
+static GMainLoop *main_loop;
+
+static guint iap_source = 0;
+
+static gboolean iap_handler(GIOChannel *channel, GIOCondition condition,
+ gpointer user_data)
+{
+ unsigned char buf[512];
+ ssize_t len;
+ int fd;
+
+ if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ iap_source = 0;
+ return FALSE;
+ }
+
+ fd = g_io_channel_unix_get_fd(channel);
+
+ len = read(fd, buf, sizeof(buf));
+ if (len < 0) {
+ iap_source = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static guint create_source(int fd, GIOFunc func)
+{
+ GIOChannel *channel;
+ guint source;
+
+ 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, func, NULL);
+
+ g_io_channel_unref(channel);
+
+ return source;
+}
+
+static void remove_source(const char *path)
+{
+ if (iap_source > 0) {
+ g_source_remove(iap_source);
+ iap_source = 0;
+ }
+}
+
+static DBusMessage *release_profile(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ g_print("Profile released\n");
+
+ remove_source(IAP_PATH);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *new_connection(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ const char *path, *device;
+ int fd;
+
+ g_print("New connection\n");
+
+ path = dbus_message_get_path(msg);
+
+ dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device,
+ DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID);
+
+ g_print(" from %s\n", path);
+ g_print(" for device %s with fd %d\n", device, fd);
+
+ if (iap_source == 0)
+ iap_source = create_source(fd, iap_handler);
+ else
+ close(fd);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *request_disconnection(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ DBusMessageIter iter;
+ const char *path, *device;
+
+ g_print("Request disconnection\n");
+
+ path = dbus_message_get_path(msg);
+
+ dbus_message_iter_init(msg, &iter);
+ dbus_message_iter_get_basic(&iter, &device);
+
+ g_print(" from %s\n", path);
+ g_print(" for device %s\n", device);
+
+ remove_source(path);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *cancel_request(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ const char *path;
+
+ g_print("Request canceled\n");
+
+ path = dbus_message_get_path(msg);
+
+ g_print(" from %s\n", path);
+
+ remove_source(path);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static const GDBusMethodTable methods[] = {
+ { GDBUS_METHOD("Release", NULL, NULL, release_profile) },
+ { GDBUS_METHOD("NewConnection",
+ GDBUS_ARGS({ "device", "o" },
+ { "fd", "h"}, { "opts", "a{sv}"}),
+ NULL, new_connection) },
+ { GDBUS_METHOD("RequestDisconnection",
+ GDBUS_ARGS({ "device", "o" }),
+ NULL, request_disconnection) },
+ { GDBUS_METHOD("Cancel", NULL, NULL, cancel_request) },
+ { }
+};
+
+static void register_profile_setup(DBusMessageIter *iter, void *user_data)
+{
+ const char *path = IAP_PATH;
+ const char *uuid = IAP_UUID;
+ DBusMessageIter dict, entry, value;
+ dbus_uint16_t channel;
+ char *record;
+ const char *str;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid);
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY,
+ NULL, &entry);
+ str = "Role";
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_STRING_AS_STRING, &value);
+ str = "server";
+ dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &str);
+ dbus_message_iter_close_container(&entry, &value);
+ dbus_message_iter_close_container(&dict, &entry);
+
+ dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY,
+ NULL, &entry);
+ str = "Channel";
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_UINT16_AS_STRING, &value);
+ channel = 23;
+ dbus_message_iter_append_basic(&value, DBUS_TYPE_UINT16, &channel);
+ dbus_message_iter_close_container(&entry, &value);
+ dbus_message_iter_close_container(&dict, &entry);
+
+ dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY,
+ NULL, &entry);
+ str = "ServiceRecord";
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str);
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_STRING_AS_STRING, &value);
+ record = g_strdup_printf(IAP_RECORD, IAP_UUID, channel);
+ dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &record);
+ g_free(record);
+ dbus_message_iter_close_container(&entry, &value);
+ dbus_message_iter_close_container(&dict, &entry);
+
+ dbus_message_iter_close_container(iter, &dict);
+}
+
+static void register_profile_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ g_print("Failed to register profile\n");
+ return;
+ }
+
+ g_print("Profile registered\n");
+}
+
+static void connect_handler(DBusConnection *connection, void *user_data)
+{
+ GDBusClient *client = user_data;
+ GDBusProxy *proxy;
+
+ g_print("Bluetooth connected\n");
+
+ proxy = g_dbus_proxy_new(client, "/org/bluez",
+ "org.bluez.ProfileManager1");
+ if (!proxy)
+ return;
+
+ g_dbus_register_interface(connection, IAP_PATH,
+ "org.bluez.Profile1",
+ methods, NULL, NULL, NULL, NULL);
+
+ g_dbus_proxy_method_call(proxy, "RegisterProfile",
+ register_profile_setup,
+ register_profile_reply, NULL, NULL);
+
+ g_dbus_proxy_unref(proxy);
+}
+
+static void disconnect_handler(DBusConnection *connection, void *user_data)
+{
+ g_print("Bluetooth disconnected\n");
+
+ g_dbus_unregister_interface(connection, IAP_PATH,
+ "org.bluez.Profile1");
+}
+
+static gboolean signal_handler(GIOChannel *channel, GIOCondition condition,
+ gpointer user_data)
+{
+ static unsigned int __terminated = 0;
+ struct signalfd_siginfo si;
+ ssize_t result;
+ int fd;
+
+ if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ g_main_loop_quit(main_loop);
+ 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 == 0)
+ g_main_loop_quit(main_loop);
+
+ __terminated = 1;
+ 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;
+}
+
+static gboolean option_version = FALSE;
+
+static GOptionEntry options[] = {
+ { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version,
+ "Show version information and exit" },
+ { NULL },
+};
+
+int main(int argc, char *argv[])
+{
+ GOptionContext *context;
+ GError *error = NULL;
+ DBusConnection *dbus_conn;
+ GDBusClient *client;
+ guint signal;
+
+ context = g_option_context_new(NULL);
+ g_option_context_add_main_entries(context, options, NULL);
+
+ if (g_option_context_parse(context, &argc, &argv, &error) == FALSE) {
+ if (error != NULL) {
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("An unknown error occurred\n");
+ exit(1);
+ }
+
+ g_option_context_free(context);
+
+ if (option_version == TRUE) {
+ g_print("%s\n", VERSION);
+ exit(0);
+ }
+
+ main_loop = g_main_loop_new(NULL, FALSE);
+ dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);
+
+ signal = setup_signalfd();
+
+ client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez");
+
+ g_dbus_client_set_connect_watch(client, connect_handler, client);
+ g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL);
+
+ g_main_loop_run(main_loop);
+
+ g_dbus_client_unref(client);
+
+ g_source_remove(signal);
+
+ dbus_connection_unref(dbus_conn);
+ g_main_loop_unref(main_loop);
+
+ return 0;
+}