aboutsummaryrefslogtreecommitdiffstats
path: root/binding
diff options
context:
space:
mode:
Diffstat (limited to 'binding')
-rw-r--r--binding/CMakeLists.txt35
-rw-r--r--binding/bluetooth-agent.c272
-rw-r--r--binding/bluetooth-api.c1034
-rw-r--r--binding/bluetooth-api.h200
-rw-r--r--binding/bluetooth-bluez.c513
-rw-r--r--binding/bluetooth-common.h196
-rw-r--r--binding/bluetooth-rfkill.c129
-rw-r--r--binding/bluetooth-util.c1026
8 files changed, 3405 insertions, 0 deletions
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
new file mode 100644
index 0000000..a4a8072
--- /dev/null
+++ b/binding/CMakeLists.txt
@@ -0,0 +1,35 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# contrib: Romain Forlot <romain.forlot@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(afm-bluetooth-binding)
+
+ # Define project Targets
+ add_library(afm-bluetooth-binding MODULE bluetooth-api.c bluetooth-agent.c bluetooth-rfkill.c bluetooth-util.c bluetooth-bluez.c)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX "lib"
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME} ${link_libraries})
diff --git a/binding/bluetooth-agent.c b/binding/bluetooth-agent.c
new file mode 100644
index 0000000..b6ae5fe
--- /dev/null
+++ b/binding/bluetooth-agent.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 3
+#include <afb/afb-binding.h>
+
+#include "bluetooth-api.h"
+#include "bluetooth-common.h"
+
+/* Introspection data for the agent service */
+static const gchar introspection_xml[] =
+"<node>"
+" <interface name='org.bluez.Agent1'>"
+" <method name='RequestConfirmation'>"
+" <arg name='device' direction='in' type='o'/>"
+" <arg name='passkey' direction='in' type='u'/>"
+" </method>"
+" <method name='AuthorizeService'>"
+" <arg name='device' direction='in' type='o'/>"
+" <arg name='uuid' direction='in' type='s'/>"
+" </method>"
+" <method name='Cancel'>"
+" </method>"
+" </interface>"
+"</node>";
+
+static void handle_method_call(
+ GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ struct bluetooth_state *ns = user_data;
+ struct call_work *cw;
+ GError *error = NULL;
+ json_object *jev = NULL;
+ const gchar *path = NULL;
+
+ /* AFB_INFO("sender=%s", sender_name);
+ AFB_INFO("object_path=%s", object_path);
+ AFB_INFO("interface=%s", interface_name);
+ AFB_INFO("method=%s", method_name); */
+
+ if (!g_strcmp0(method_name, "RequestConfirmation")) {
+ int pin;
+
+ g_variant_get(parameters, "(ou)", &path, &pin);
+
+ call_work_lock(ns);
+
+ /* can only occur while a pair is issued */
+ cw = call_work_lookup_unlocked(ns, "device", NULL, "RequestConfirmation");
+
+ /* if nothing is pending return an error */
+ /* TODO: allow client side pairing */
+ if (!cw)
+ cw = call_work_create_unlocked(ns, "device", NULL,
+ "RequestConfirmation", NULL, &error);
+
+ if (!cw) {
+ call_work_unlock(ns);
+ g_dbus_method_invocation_return_dbus_error(invocation,
+ "org.bluez.Error.Rejected",
+ "No connection pending");
+ return;
+ }
+
+ jev = json_object_new_object();
+ json_object_object_add(jev, "action", json_object_new_string("request_confirmation"));
+ json_object_object_add(jev, "device", json_object_new_string(path));
+ json_object_object_add(jev, "pincode", json_object_new_int(pin));
+
+ cw->agent_data.pin_code = pin;
+ cw->agent_data.device_path = g_strdup(path);
+ cw->invocation = invocation;
+
+ call_work_unlock(ns);
+
+ afb_event_push(ns->agent_event, jev);
+
+ return;
+ } else if (!g_strcmp0(method_name, "AuthorizeService")) {
+
+ /* g_variant_get(parameters, "(os)", &path, &service);
+
+ jev = json_object_new_object();
+ json_object_object_add(jev, "action", json_object_new_string("authorize_service"));
+ json_object_object_add(jev, "path", json_object_new_string(path));
+ json_object_object_add(jev, "uuid", json_object_new_string(service));
+
+ afb_event_push(ns->agent_event, jev); */
+
+ return g_dbus_method_invocation_return_value(invocation, NULL);
+ } else if (!g_strcmp0(method_name, "Cancel")) {
+
+ call_work_lock(ns);
+
+ /* can only occur while a pair is issued */
+ cw = call_work_lookup_unlocked(ns, "device", NULL, "RequestConfirmation");
+
+ if (!cw) {
+ call_work_unlock(ns);
+ g_dbus_method_invocation_return_dbus_error(invocation,
+ "org.bluez.Error.Rejected",
+ "No connection pending");
+ return;
+ }
+
+ jev = json_object_new_object();
+ json_object_object_add(jev, "action", json_object_new_string("canceled_pairing"));
+
+ afb_event_push(ns->agent_event, jev);
+
+ call_work_destroy_unlocked(cw);
+ call_work_unlock(ns);
+
+ return g_dbus_method_invocation_return_value(invocation, NULL);
+ }
+
+ g_dbus_method_invocation_return_dbus_error(invocation,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ "Uknown method");
+}
+
+static const GDBusInterfaceVTable interface_vtable = {
+ .method_call = handle_method_call,
+ .get_property = NULL,
+ .set_property = NULL,
+};
+
+static void on_bus_acquired(GDBusConnection *connection,
+ const gchar *name, gpointer user_data)
+{
+ struct init_data *id = user_data;
+ struct bluetooth_state *ns = id->ns;
+ GVariant *result;
+ GError *error = NULL;
+
+ AFB_INFO("agent bus acquired - registering %s", ns->agent_path);
+
+ ns->registration_id = g_dbus_connection_register_object(connection,
+ ns->agent_path,
+ ns->introspection_data->interfaces[0],
+ &interface_vtable,
+ ns, /* user data */
+ NULL, /* user_data_free_func */
+ NULL);
+
+ if (!ns->registration_id) {
+ AFB_ERROR("failed to register agent to dbus");
+ goto err_unable_to_register_bus;
+
+ }
+
+ result = agentmanager_call(ns, "RegisterAgent",
+ g_variant_new("(os)", ns->agent_path, "KeyboardDisplay"),
+ &error);
+ if (!result) {
+ AFB_ERROR("failed to register agent to bluez");
+ goto err_unable_to_register_bluez;
+ }
+ g_variant_unref(result);
+
+ result = agentmanager_call(ns, "RequestDefaultAgent",
+ g_variant_new("(o)", ns->agent_path),
+ &error);
+ if (!result) {
+ AFB_ERROR("failed to request default agent to bluez");
+ goto err_unable_to_request_default_agent_bluez;
+ }
+ g_variant_unref(result);
+
+ ns->agent_registered = TRUE;
+
+ AFB_INFO("agent registered at %s", ns->agent_path);
+ signal_init_done(id, 0);
+
+ return;
+
+err_unable_to_request_default_agent_bluez:
+ agentmanager_call(ns, "UnregisterAgent",
+ g_variant_new("(o)", ns->agent_path),
+ &error);
+
+err_unable_to_register_bluez:
+ g_dbus_connection_unregister_object(ns->conn, ns->registration_id);
+ ns->registration_id = 0;
+
+err_unable_to_register_bus:
+ signal_init_done(id, -1);
+}
+
+int bluetooth_register_agent(struct init_data *id)
+{
+ struct bluetooth_state *ns = id->ns;
+
+ ns->agent_path = g_strdup_printf("%s/agent%d",
+ BLUEZ_PATH, getpid());
+ if (!ns->agent_path) {
+ AFB_ERROR("can't create agent path");
+ goto out_no_agent_path;
+ }
+
+ ns->introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
+ if (!ns->introspection_data) {
+ AFB_ERROR("can't create introspection data");
+ goto out_no_introspection_data;
+ }
+
+ ns->agent_id = g_bus_own_name(G_BUS_TYPE_SYSTEM, BLUEZ_AGENT_INTERFACE,
+ G_BUS_NAME_OWNER_FLAGS_REPLACE |
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
+ on_bus_acquired,
+ NULL,
+ NULL,
+ id,
+ NULL);
+ if (!ns->agent_id) {
+ AFB_ERROR("can't create agent bus instance");
+ goto out_no_bus_name;
+ }
+
+ return 0;
+
+out_no_bus_name:
+ g_dbus_node_info_unref(ns->introspection_data);
+out_no_introspection_data:
+ g_free(ns->agent_path);
+out_no_agent_path:
+ return -1;
+}
+
+void bluetooth_unregister_agent(struct bluetooth_state *ns)
+{
+ g_bus_unown_name(ns->agent_id);
+ g_dbus_node_info_unref(ns->introspection_data);
+ g_free(ns->agent_path);
+}
diff --git a/binding/bluetooth-api.c b/binding/bluetooth-api.c
new file mode 100644
index 0000000..633bbd6
--- /dev/null
+++ b/binding/bluetooth-api.c
@@ -0,0 +1,1034 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 3
+#include <afb/afb-binding.h>
+
+#include "bluetooth-api.h"
+#include "bluetooth-common.h"
+
+/**
+ * The global thread
+ */
+static GThread *global_thread;
+
+static struct bluetooth_state *bluetooth_get_userdata(afb_req_t request) {
+ afb_api_t api = afb_req_get_api(request);
+ return afb_api_get_userdata(api);
+}
+
+void call_work_lock(struct bluetooth_state *ns)
+{
+ g_mutex_lock(&ns->cw_mutex);
+}
+
+void call_work_unlock(struct bluetooth_state *ns)
+{
+ g_mutex_unlock(&ns->cw_mutex);
+}
+
+struct call_work *call_work_lookup_unlocked(
+ struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method)
+{
+ struct call_work *cw;
+ GSList *list;
+
+ /* we can only allow a single pending call */
+ for (list = ns->cw_pending; list; list = g_slist_next(list)) {
+ cw = list->data;
+ if (!g_strcmp0(access_type, cw->access_type) &&
+ !g_strcmp0(type_arg, cw->type_arg) &&
+ !g_strcmp0(method, cw->method))
+ return cw;
+ }
+ return NULL;
+}
+
+struct call_work *call_work_lookup(
+ struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method)
+{
+ struct call_work *cw;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return cw;
+}
+
+int call_work_pending_id(
+ struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method)
+{
+ struct call_work *cw;
+ int id = -1;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
+ if (cw)
+ id = cw->id;
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return id;
+}
+
+struct call_work *call_work_lookup_by_id_unlocked(
+ struct bluetooth_state *ns, int id)
+{
+ struct call_work *cw;
+ GSList *list;
+
+ /* we can only allow a single pending call */
+ for (list = ns->cw_pending; list; list = g_slist_next(list)) {
+ cw = list->data;
+ if (cw->id == id)
+ return cw;
+ }
+ return NULL;
+}
+
+struct call_work *call_work_lookup_by_id(
+ struct bluetooth_state *ns, int id)
+{
+ struct call_work *cw;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_lookup_by_id_unlocked(ns, id);
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return cw;
+}
+
+struct call_work *call_work_create_unlocked(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, const char *bluez_method,
+ GError **error)
+{
+
+ struct call_work *cw = NULL;
+
+ cw = call_work_lookup_unlocked(ns, access_type, type_arg, method);
+ if (cw) {
+ g_set_error(error, NB_ERROR, NB_ERROR_CALL_IN_PROGRESS,
+ "another call in progress (%s/%s/%s)",
+ access_type, type_arg, method);
+ return NULL;
+ }
+
+ /* no other pending; allocate */
+ cw = g_malloc0(sizeof(*cw));
+ cw->ns = ns;
+ do {
+ cw->id = ns->next_cw_id;
+ if (++ns->next_cw_id < 0)
+ ns->next_cw_id = 1;
+ } while (call_work_lookup_by_id_unlocked(ns, cw->id));
+
+ cw->access_type = g_strdup(access_type);
+ cw->type_arg = g_strdup(type_arg);
+ cw->method = g_strdup(method);
+ cw->bluez_method = g_strdup(bluez_method);
+
+ ns->cw_pending = g_slist_prepend(ns->cw_pending, cw);
+
+ return cw;
+}
+
+struct call_work *call_work_create(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, const char *bluez_method,
+ GError **error)
+{
+
+ struct call_work *cw;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_create_unlocked(ns,
+ access_type, type_arg, method, bluez_method,
+ error);
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return cw;
+}
+
+void call_work_destroy_unlocked(struct call_work *cw)
+{
+ struct bluetooth_state *ns = cw->ns;
+ struct call_work *cw2;
+
+ /* verify that it's something we know about */
+ cw2 = call_work_lookup_by_id_unlocked(ns, cw->id);
+ if (cw2 != cw) {
+ AFB_ERROR("Bad call work to destroy");
+ return;
+ }
+
+ /* remove it */
+ ns->cw_pending = g_slist_remove(ns->cw_pending, cw);
+
+ /* agent struct data */
+ g_free(cw->agent_data.device_path);
+
+ g_free(cw->access_type);
+ g_free(cw->type_arg);
+ g_free(cw->method);
+ g_free(cw->bluez_method);
+}
+
+void call_work_destroy(struct call_work *cw)
+{
+ struct bluetooth_state *ns = cw->ns;
+
+ g_mutex_lock(&ns->cw_mutex);
+ call_work_destroy_unlocked(cw);
+ g_mutex_unlock(&ns->cw_mutex);
+}
+
+static afb_event_t get_event_from_value(struct bluetooth_state *ns,
+ const char *value)
+{
+ if (!g_strcmp0(value, "device_changes"))
+ return ns->device_changes_event;
+
+ if (!g_strcmp0(value, "agent"))
+ return ns->agent_event;
+
+ return NULL;
+}
+
+static void bluez_devices_signal_callback(
+ GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ struct bluetooth_state *ns = user_data;
+ GVariantIter *array1 = NULL;
+ GError *error = NULL;
+ GVariant *var = NULL;
+ const gchar *path = NULL;
+ const gchar *key = NULL;
+ json_object *jresp = NULL, *jobj;
+ GVariantIter *array;
+ gboolean is_config, ret;
+
+ /* AFB_INFO("sender=%s", sender_name);
+ AFB_INFO("object_path=%s", object_path);
+ AFB_INFO("interface=%s", interface_name);
+ AFB_INFO("signal=%s", signal_name); */
+
+ if (!g_strcmp0(signal_name, "InterfacesAdded")) {
+
+ g_variant_get(parameters, "(&oa{sa{sv}})", &path, &array);
+
+ jresp = json_object_new_object();
+
+ json_object_object_add(jresp, "device",
+ json_object_new_string(path));
+ json_object_object_add(jresp, "action",
+ json_object_new_string("added"));
+
+ jobj = json_object_new_object();
+
+ while (g_variant_iter_next(array, "{&s@a{sv}}", &key, &var)) {
+ const char *name = NULL;
+ GVariant *val = NULL;
+
+ if (g_strcmp0(key, BLUEZ_DEVICE_INTERFACE) != 0)
+ continue;
+
+ array1 = g_variant_iter_new(var);
+
+ while (g_variant_iter_next(array1, "{&sv}", &name, &val)) {
+ ret = device_property_dbus2json(jobj,
+ name, val, &is_config, &error);
+ g_variant_unref(val);
+ if (!ret) {
+ AFB_WARNING("%s property %s - %s",
+ "devices",
+ key, error->message);
+ g_clear_error(&error);
+ }
+ }
+ g_variant_iter_free(array1);
+ }
+ g_variant_iter_free(array);
+
+ if (array1) {
+ json_object_object_add(jresp, "properties", jobj);
+ } else {
+ json_object_put(jresp);
+ jresp = NULL;
+ }
+
+ } else if (!g_strcmp0(signal_name, "InterfacesRemoved")) {
+
+ g_variant_get(parameters, "(&oas)", &path, &array);
+ g_variant_iter_free(array);
+
+ jresp = json_object_new_object();
+
+ json_object_object_add(jresp, "path",
+ json_object_new_string(path));
+ json_object_object_add(jresp, "action",
+ json_object_new_string("removed"));
+
+ } else if (!g_strcmp0(signal_name, "PropertiesChanged")) {
+
+ g_variant_get(parameters, "(&sa{sv}as)", &path, &array, &array1);
+
+ if (!g_strcmp0(path, BLUEZ_DEVICE_INTERFACE)) {
+
+ jresp = json_object_new_object();
+
+ json_object_object_add(jresp, "path",
+ json_object_new_string(object_path));
+ json_object_object_add(jresp, "action",
+ json_object_new_string("changed"));
+
+ jobj = json_object_new_object();
+
+ while (g_variant_iter_next(array, "{&sv}", &key, &var)) {
+ ret = device_property_dbus2json(jobj,
+ key, var, &is_config, &error);
+ g_variant_unref(var);
+ if (!ret) {
+ AFB_WARNING("%s property %s - %s",
+ "devices",
+ key, error->message);
+ g_clear_error(&error);
+ }
+ }
+
+ json_object_object_add(jresp, "properties", jobj);
+ }
+
+ g_variant_iter_free(array);
+ g_variant_iter_free(array1);
+ }
+
+ if (jresp) {
+ afb_event_push(ns->device_changes_event, jresp);
+ jresp = NULL;
+ }
+
+ json_object_put(jresp);
+}
+
+static struct bluetooth_state *bluetooth_init(GMainLoop *loop)
+{
+ struct bluetooth_state *ns;
+ GError *error = NULL;
+
+ ns = g_try_malloc0(sizeof(*ns));
+ if (!ns) {
+ AFB_ERROR("out of memory allocating bluetooth state");
+ goto err_no_ns;
+ }
+
+ AFB_INFO("connecting to dbus");
+
+ ns->loop = loop;
+ ns->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!ns->conn) {
+ if (error)
+ g_dbus_error_strip_remote_error(error);
+ AFB_ERROR("Cannot connect to D-Bus, %s",
+ error ? error->message : "unspecified");
+ g_error_free(error);
+ goto err_no_conn;
+
+ }
+
+ AFB_INFO("connected to dbus");
+
+ ns->device_changes_event =
+ afb_daemon_make_event("device_changes");
+ ns->agent_event =
+ afb_daemon_make_event("agent");
+
+ if (!afb_event_is_valid(ns->device_changes_event) ||
+ !afb_event_is_valid(ns->agent_event)) {
+ AFB_ERROR("Cannot create events");
+ goto err_no_events;
+ }
+
+ ns->device_sub = g_dbus_connection_signal_subscribe(
+ ns->conn,
+ BLUEZ_SERVICE,
+ NULL, /* interface */
+ NULL, /* member */
+ NULL, /* object path */
+ NULL, /* arg0 */
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ bluez_devices_signal_callback,
+ ns,
+ NULL);
+ if (!ns->device_sub) {
+ AFB_ERROR("Unable to subscribe to interface signals");
+ goto err_no_device_sub;
+ }
+
+ g_mutex_init(&ns->cw_mutex);
+ ns->next_cw_id = 1;
+
+ bluetooth_monitor_init();
+
+ return ns;
+
+err_no_device_sub:
+ /* no way to clear the events */
+err_no_events:
+ g_dbus_connection_close(ns->conn, NULL, NULL, NULL);
+err_no_conn:
+ g_free(ns);
+err_no_ns:
+ return NULL;
+}
+
+static void bluetooth_cleanup(struct bluetooth_state *ns)
+{
+ g_dbus_connection_signal_unsubscribe(ns->conn, ns->device_sub);
+ g_dbus_connection_close(ns->conn, NULL, NULL, NULL);
+ g_free(ns);
+}
+
+static gpointer bluetooth_func(gpointer ptr)
+{
+ struct init_data *id = ptr;
+ struct bluetooth_state *ns;
+ GMainLoop *loop;
+ int rc = 0;
+
+ loop = g_main_loop_new(NULL, FALSE);
+ if (!loop) {
+ AFB_ERROR("Unable to create main loop");
+ goto err_no_loop;
+ }
+
+ /* real bluetooth init */
+ ns = bluetooth_init(loop);
+ if (!ns) {
+ AFB_ERROR("bluetooth_init() failed");
+ goto err_no_ns;
+ }
+
+ id->ns = ns;
+ rc = bluetooth_register_agent(id);
+ if (rc) {
+ AFB_ERROR("bluetooth_register_agent() failed");
+ goto err_no_agent;
+ }
+
+ /* note that we wait for agent registration to signal done */
+
+ afb_api_set_userdata(id->api, ns);
+ g_main_loop_run(loop);
+
+ g_main_loop_unref(ns->loop);
+
+ bluetooth_unregister_agent(ns);
+
+ bluetooth_cleanup(ns);
+ afb_api_set_userdata(id->api, NULL);
+
+ return NULL;
+
+err_no_agent:
+ bluetooth_cleanup(ns);
+
+err_no_ns:
+ g_main_loop_unref(loop);
+
+err_no_loop:
+ signal_init_done(id, -1);
+
+ return NULL;
+}
+
+static int init(afb_api_t api)
+{
+ struct init_data init_data, *id = &init_data;
+ gint64 end_time;
+
+ memset(id, 0, sizeof(*id));
+ id->init_done = FALSE;
+ id->rc = 0;
+ id->api = api;
+ g_cond_init(&id->cond);
+ g_mutex_init(&id->mutex);
+
+ global_thread = g_thread_new("agl-service-bluetooth",
+ bluetooth_func,
+ id);
+
+ AFB_INFO("bluetooth-binding waiting for init done");
+
+ /* wait maximum 10 seconds for init done */
+ end_time = g_get_monotonic_time () + 10 * G_TIME_SPAN_SECOND;
+ g_mutex_lock(&id->mutex);
+ while (!id->init_done) {
+ if (!g_cond_wait_until(&id->cond, &id->mutex, end_time))
+ break;
+ }
+ g_mutex_unlock(&id->mutex);
+
+ if (!id->init_done) {
+ AFB_ERROR("bluetooth-binding init timeout");
+ return -1;
+ }
+
+ if (id->rc)
+ AFB_ERROR("bluetooth-binding init thread returned %d",
+ id->rc);
+ else
+ AFB_INFO("bluetooth-binding operational");
+
+ return id->rc;
+}
+
+static void bluetooth_subscribe_unsubscribe(afb_req_t request,
+ gboolean unsub)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ json_object *jresp = json_object_new_object();
+ const char *value;
+ afb_event_t event;
+ int rc;
+
+ /* if value exists means to set offline mode */
+ value = afb_req_value(request, "value");
+ if (!value) {
+ afb_req_fail_f(request, "failed", "Missing \"value\" event");
+ return;
+ }
+
+ event = get_event_from_value(ns, value);
+ if (!event) {
+ afb_req_fail_f(request, "failed", "Bad \"value\" event \"%s\"",
+ value);
+ return;
+ }
+
+ if (!unsub)
+ rc = afb_req_subscribe(request, event);
+ else
+ rc = afb_req_unsubscribe(request, event);
+ if (rc != 0) {
+ afb_req_fail_f(request, "failed",
+ "%s error on \"value\" event \"%s\"",
+ !unsub ? "subscribe" : "unsubscribe",
+ value);
+ return;
+ }
+
+ afb_req_success_f(request, jresp, "Bluetooth %s to event \"%s\"",
+ !unsub ? "subscribed" : "unsubscribed",
+ value);
+}
+
+static void bluetooth_subscribe(afb_req_t request)
+{
+ bluetooth_subscribe_unsubscribe(request, FALSE);
+}
+
+static void bluetooth_unsubscribe(afb_req_t request)
+{
+ bluetooth_subscribe_unsubscribe(request, TRUE);
+}
+
+static void bluetooth_list(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ GError *error = NULL;
+ json_object *jresp;
+
+ jresp = object_properties(ns, &error);
+
+ afb_req_success(request, jresp, "Bluetooth - managed objects");
+}
+
+static void bluetooth_state(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ GError *error = NULL;
+ json_object *jresp;
+ const char *adapter;
+
+ adapter = afb_req_value(request, "adapter");
+ if (!adapter) {
+ afb_req_fail(request, "failed", "No adapter give to return state");
+ return;
+ }
+
+ jresp = adapter_properties(ns, &error, adapter);
+ if (!jresp) {
+ afb_req_fail_f(request, "failed", "property %s error %s",
+ "State", error->message);
+ return;
+ }
+
+ afb_req_success(request, jresp, "Bluetooth - adapter state");
+}
+
+static void bluetooth_adapter(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ GError *error = NULL;
+ const char *adapter, *scan, *discoverable, *powered;
+
+ adapter = afb_req_value(request, "adapter");
+ if (!adapter) {
+ afb_req_fail(request, "failed", "No adapter given to configure");
+ return;
+ }
+
+ scan = afb_req_value(request, "discovery");
+ if (scan) {
+ GVariant *reply = adapter_call(ns, adapter, str2boolean(scan) ?
+ "StartDiscovery" : "StopDiscovery", NULL, &error);
+
+ if (!reply) {
+ afb_req_fail_f(request, "failed",
+ "adapter %s method %s error %s",
+ scan, "Scan", error->message);
+ g_error_free(error);
+ return;
+ }
+ g_variant_unref(reply);
+ }
+
+ discoverable = afb_req_value(request, "discoverable");
+ if (discoverable) {
+ int ret = adapter_set_property(ns, adapter, FALSE, "Discoverable",
+ json_object_new_boolean(str2boolean(discoverable)),
+ &error);
+ if (!ret) {
+ afb_req_fail_f(request, "failed",
+ "adapter %s set_property %s error %s",
+ adapter, "Discoverable", error->message);
+ g_error_free(error);
+ return;
+ }
+ }
+
+ powered = afb_req_value(request, "powered");
+ if (powered) {
+ int ret = adapter_set_property(ns, adapter, FALSE, "Powered",
+ json_object_new_boolean(str2boolean(powered)),
+ &error);
+ if (!ret) {
+ afb_req_fail_f(request, "failed",
+ "adapter %s set_property %s error %s",
+ adapter, "Powered", error->message);
+ g_error_free(error);
+ return;
+ }
+ }
+
+ bluetooth_state(request);
+}
+
+static void connect_service_callback(void *user_data,
+ GVariant *result, GError **error)
+{
+ struct call_work *cw = user_data;
+ struct bluetooth_state *ns = cw->ns;
+
+ bluez_decode_call_error(ns,
+ cw->access_type, cw->type_arg, cw->bluez_method,
+ error);
+
+ if (error && *error) {
+ afb_req_fail_f(cw->request, "failed", "Connect error: %s",
+ (*error)->message);
+ goto out_free;
+ }
+
+ if (result)
+ g_variant_unref(result);
+
+ afb_req_success_f(cw->request, NULL, "Bluetooth - device %s connected",
+ cw->type_arg);
+out_free:
+ afb_req_unref(cw->request);
+ call_work_destroy(cw);
+}
+
+static void bluetooth_connect_device(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ GError *error = NULL;
+ const char *device, *uuid;
+ struct call_work *cw;
+
+ /* first device */
+ device = afb_req_value(request, "device");
+ if (!device) {
+ afb_req_fail(request, "failed", "No path given");
+ return;
+ }
+
+ /* optional, connect single profile */
+ uuid = afb_req_value(request, "uuid");
+
+ cw = call_work_create(ns, "device", device,
+ "connect_service", "Connect", &error);
+ if (!cw) {
+ afb_req_fail_f(request, "failed", "can't queue work %s",
+ error->message);
+ g_error_free(error);
+ return;
+ }
+
+ cw->request = request;
+ afb_req_addref(request);
+
+ if (uuid)
+ cw->cpw = bluez_call_async(ns, "device", device,
+ "ConnectProfile", g_variant_new("(&s)", uuid), &error,
+ connect_service_callback, cw);
+ else
+ cw->cpw = bluez_call_async(ns, "device", device,
+ "Connect", NULL, &error,
+ connect_service_callback, cw);
+
+ if (!cw->cpw) {
+ afb_req_fail_f(request, "failed", "connection error %s",
+ error->message);
+ call_work_destroy(cw);
+ g_error_free(error);
+ return;
+ }
+}
+
+static void bluetooth_disconnect_device(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ json_object *jresp;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ const char *device, *uuid;
+
+ /* first device */
+ device = afb_req_value(request, "device");
+ if (!device) {
+ afb_req_fail(request, "failed", "No device given to disconnect");
+ return;
+ }
+
+ /* optional, disconnect single profile */
+ uuid = afb_req_value(request, "uuid");
+
+ if (uuid)
+ reply = device_call(ns, device, "DisconnectProfile",
+ g_variant_new("(&s)", uuid), &error);
+ else
+ reply = device_call(ns, device, "Disconnect", NULL, &error);
+
+ if (!reply) {
+ afb_req_fail_f(request, "failed", "Disconnect error %s",
+ error ? error->message : "unspecified");
+ g_error_free(error);
+ return;
+ }
+
+ g_variant_unref(reply);
+
+ jresp = json_object_new_object();
+ afb_req_success_f(request, jresp, "Device - Bluetooth %s disconnected",
+ device);
+}
+
+static void pair_service_callback(void *user_data,
+ GVariant *result, GError **error)
+{
+ struct call_work *cw = user_data;
+ struct bluetooth_state *ns = cw->ns;
+
+ bluez_decode_call_error(ns,
+ cw->access_type, cw->type_arg, cw->bluez_method,
+ error);
+
+ if (error && *error) {
+ afb_req_fail_f(cw->request, "failed", "Connect error: %s",
+ (*error)->message);
+ goto out_free;
+ }
+
+ if (result)
+ g_variant_unref(result);
+
+ afb_req_success_f(cw->request, NULL, "Bluetooth - device %s paired",
+ cw->type_arg);
+out_free:
+ afb_req_unref(cw->request);
+ call_work_destroy(cw);
+}
+
+static void bluetooth_pair_device(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ GError *error = NULL;
+ const char *device;
+ struct call_work *cw;
+
+ /* first device */
+ device = afb_req_value(request, "device");
+ if (!device) {
+ afb_req_fail(request, "failed", "No path given");
+ return;
+ }
+
+ cw = call_work_create(ns, "device", device,
+ "pair_device", "Pair", &error);
+ if (!cw) {
+ afb_req_fail_f(request, "failed", "can't queue work %s",
+ error->message);
+ g_error_free(error);
+ return;
+ }
+
+ cw->request = request;
+ afb_req_addref(request);
+
+ cw->cpw = bluez_call_async(ns, "device", device, "Pair", NULL, &error,
+ pair_service_callback, cw);
+
+ if (!cw->cpw) {
+ afb_req_fail_f(request, "failed", "connection error %s",
+ error->message);
+ call_work_destroy(cw);
+ g_error_free(error);
+ return;
+ }
+}
+
+static void bluetooth_cancel_pairing(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ struct call_work *cw;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ gchar *device;
+
+ call_work_lock(ns);
+
+ cw = call_work_lookup_unlocked(ns, "device", NULL, "RequestConfirmation");
+
+ if (!cw) {
+ call_work_unlock(ns);
+ afb_req_fail(request, "failed", "No pairing in progress");
+ return;
+ }
+
+ device = cw->agent_data.device_path;
+ reply = device_call(ns, device, "CancelPairing", NULL, &error);
+
+ if (!reply) {
+ call_work_unlock(ns);
+ afb_req_fail_f(request, "failed",
+ "device %s method %s error %s",
+ device, "CancelPairing", error->message);
+ return;
+ }
+
+ call_work_unlock(ns);
+
+ afb_req_success(request, json_object_new_object(),
+ "Bluetooth - pairing canceled");
+}
+
+static void bluetooth_confirm_pairing(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ struct call_work *cw;
+ int pin = -1;
+
+ const char *value = afb_req_value(request, "pincode");
+
+ if (value)
+ pin = (int)strtol(value, NULL, 10);
+
+ if (!value || !pin) {
+ afb_req_fail_f(request, "failed", "No pincode parameter");
+ return;
+ }
+
+ call_work_lock(ns);
+
+ cw = call_work_lookup_unlocked(ns, "device", NULL, "RequestConfirmation");
+
+ if (!cw) {
+ call_work_unlock(ns);
+ afb_req_fail(request, "failed", "No pairing in progress");
+ return;
+ }
+
+ if (pin == cw->agent_data.pin_code) {
+ g_dbus_method_invocation_return_value(cw->invocation, NULL);
+
+ afb_req_success(request, json_object_new_object(),
+ "Bluetooth - pairing confimed");
+ } else {
+ g_dbus_method_invocation_return_dbus_error(cw->invocation,
+ "org.bluez.Error.Rejected",
+ "No connection pending");
+
+ afb_req_fail(request, "failed", "Bluetooth - pairing failed");
+ }
+
+ call_work_destroy_unlocked(cw);
+ call_work_unlock(ns);
+}
+
+static void bluetooth_remove_device(afb_req_t request)
+{
+ struct bluetooth_state *ns = bluetooth_get_userdata(request);
+ GError *error = NULL;
+ GVariant *reply;
+ json_object *jval = NULL;
+ const char *device, *adapter;
+
+ /* first device */
+ device = afb_req_value(request, "device");
+ if (!device) {
+ afb_req_fail(request, "failed", "No path given");
+ return;
+ }
+
+ jval = bluez_get_property(ns, "device", device, FALSE, "Adapter", &error);
+
+ if (!jval) {
+ afb_req_fail_f(request, "failed",
+ " adapter not found for device %s error %s",
+ device, error->message);
+
+ g_error_free(error);
+ return;
+ }
+
+ adapter = json_object_get_string(jval);
+
+ reply = adapter_call(ns, adapter, "RemoveDevice",
+ g_variant_new("(o)", device), &error);
+
+ if (!reply) {
+ afb_req_fail_f(request, "failed",
+ " device %s method %s error %s",
+ device, "RemoveDevice", error->message);
+ g_error_free(error);
+ json_object_put(jval);
+ return;
+ }
+ g_variant_unref(reply);
+
+ afb_req_success_f(request, json_object_new_object(),
+ "Bluetooth - device %s removed", adapter);
+
+ json_object_put(jval);
+}
+
+static const struct afb_verb_v3 bluetooth_verbs[] = {
+ {
+ .verb = "subscribe",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_subscribe,
+ .info = "Subscribe to the event of 'value'",
+ }, {
+ .verb = "unsubscribe",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_unsubscribe,
+ .info = "Unsubscribe to the event of 'value'",
+ }, {
+ .verb = "managed_objects",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_list,
+ .info = "Retrieve managed bluetooth devices"
+ }, {
+ .verb = "adapter_state",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_adapter,
+ .info = "Set adapter mode and retrieve properties"
+ }, {
+ .verb = "connect",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_connect_device,
+ .info = "Connect device and/or profile"
+ }, {
+ .verb = "disconnect",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_disconnect_device,
+ .info = "Disconnect device and/or profile"
+ }, {
+ .verb = "pair",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_pair_device,
+ .info = "Pair device"
+ }, {
+ .verb = "cancel_pairing",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_cancel_pairing,
+ .info = "Cancel pairing"
+ }, {
+ .verb = "confirm_pairing",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_confirm_pairing,
+ .info = "Confirm pairing",
+ }, {
+ .verb = "remove_device",
+ .session = AFB_SESSION_NONE,
+ .callback = bluetooth_remove_device,
+ .info = "Removed paired device",
+ },
+ { } /* marker for end of the array */
+};
+
+/*
+ * description of the binding for afb-daemon
+ */
+const struct afb_binding_v3 afbBindingV3 = {
+ .api = "bluetooth-manager",
+ .specification = "bluetooth manager API",
+ .verbs = bluetooth_verbs,
+ .init = init,
+};
diff --git a/binding/bluetooth-api.h b/binding/bluetooth-api.h
new file mode 100644
index 0000000..50bf501
--- /dev/null
+++ b/binding/bluetooth-api.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BLUETOOTH_API_H
+#define BLUETOOTH_API_H
+
+#include <alloca.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <json-c/json.h>
+
+#define BLUEZ_SERVICE "org.bluez"
+#define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1"
+#define BLUEZ_AGENT_INTERFACE BLUEZ_SERVICE ".Agent1"
+#define BLUEZ_AGENTMANAGER_INTERFACE BLUEZ_SERVICE ".AgentManager1"
+#define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1"
+
+#define BLUEZ_OBJECT_PATH "/"
+#define BLUEZ_PATH "/org/bluez"
+
+#define FREEDESKTOP_INTROSPECT "org.freedesktop.DBus.Introspectable"
+#define FREEDESKTOP_PROPERTIES "org.freedesktop.DBus.Properties"
+#define FREEDESKTOP_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager"
+
+#define DBUS_REPLY_TIMEOUT (120 * 1000)
+#define DBUS_REPLY_TIMEOUT_SHORT (10 * 1000)
+
+#define BLUEZ_AT_OBJECT "object"
+#define BLUEZ_AT_ADAPTER "adapter"
+#define BLUEZ_AT_DEVICE "device"
+#define BLUEZ_AT_AGENT "agent"
+#define BLUEZ_AT_AGENTMANAGER "agent-manager"
+
+struct bluetooth_state;
+
+static inline const char *bluez_strip_path(const char *path)
+{
+ const char *basename;
+
+ basename = strrchr(path, '/');
+ if (!basename)
+ return NULL;
+ basename++;
+ /* at least one character */
+ return *basename ? basename : NULL;
+}
+
+
+struct call_work *call_work_create_unlocked(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, const char *bluez_method,
+ GError **error);
+
+void call_work_destroy_unlocked(struct call_work *cw);
+
+void call_work_lock(struct bluetooth_state *ns);
+
+void call_work_unlock(struct bluetooth_state *ns);
+
+struct call_work *call_work_lookup_unlocked(
+ struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method);
+
+const struct property_info *bluez_get_property_info(
+ const char *access_type, GError **error);
+
+gboolean bluez_property_dbus2json(const char *access_type,
+ json_object *jprop, const gchar *key, GVariant *var,
+ gboolean *is_config,
+ GError **error);
+
+GVariant *bluez_call(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, GVariant *params, GError **error);
+
+json_object *bluez_get_properties(struct bluetooth_state *ns,
+ const char *access_type, const char *path,
+ GError **error);
+
+json_object *bluez_get_property(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ gboolean is_json_name, const char *name, GError **error);
+
+gboolean bluez_set_property(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ gboolean is_json_name, const char *name, json_object *jval,
+ GError **error);
+
+/* convenience access methods */
+static inline gboolean device_property_dbus2json(json_object *jprop,
+ const gchar *key, GVariant *var, gboolean *is_config,
+ GError **error)
+{
+ return bluez_property_dbus2json(BLUEZ_AT_DEVICE,
+ jprop, key, var, is_config, error);
+}
+
+static inline gboolean agent_property_dbus2json(json_object *jprop,
+ const gchar *key, GVariant *var, gboolean *is_config,
+ GError **error)
+{
+ return bluez_property_dbus2json(BLUEZ_AT_AGENT,
+ jprop, key, var, is_config, error);
+}
+
+static inline GVariant *device_call(struct bluetooth_state *ns,
+ const char *device, const char *method,
+ GVariant *params, GError **error)
+{
+ return bluez_call(ns, BLUEZ_AT_DEVICE, device,
+ method, params, error);
+}
+
+static inline GVariant *adapter_call(struct bluetooth_state *ns,
+ const char *adapter, const char *method,
+ GVariant *params, GError **error)
+{
+ return bluez_call(ns, BLUEZ_AT_ADAPTER, adapter,
+ method, params, error);
+}
+
+static inline GVariant *agentmanager_call(struct bluetooth_state *ns,
+ const char *method, GVariant *params, GError **error)
+{
+ return bluez_call(ns, BLUEZ_AT_AGENTMANAGER, NULL,
+ method, params, error);
+}
+
+static inline gboolean adapter_set_property(struct bluetooth_state *ns,
+ const char *adapter, gboolean is_json_name, const char *name,
+ json_object *jval, GError **error)
+{
+ return bluez_set_property(ns, BLUEZ_AT_ADAPTER, adapter,
+ is_json_name, name, jval, error);
+}
+
+static inline json_object *adapter_get_property(struct bluetooth_state *ns,
+ gboolean is_json_name, const char *name, GError **error)
+{
+ return bluez_get_property(ns, BLUEZ_AT_ADAPTER, NULL,
+ is_json_name, name, error);
+}
+
+static inline json_object *adapter_properties(struct bluetooth_state *ns,
+ GError **error, const gchar *adapter)
+{
+ return bluez_get_properties(ns,
+ BLUEZ_AT_ADAPTER, adapter, error);
+}
+
+static inline json_object *object_properties(struct bluetooth_state *ns,
+ GError **error)
+{
+ return bluez_get_properties(ns,
+ BLUEZ_AT_OBJECT, BLUEZ_OBJECT_PATH, error);
+}
+
+struct bluez_pending_work {
+ struct bluetooth_state *ns;
+ void *user_data;
+ GCancellable *cancel;
+ void (*callback)(void *user_data, GVariant *result, GError **error);
+};
+
+void bluez_cancel_call(struct bluetooth_state *ns,
+ struct bluez_pending_work *cpw);
+
+struct bluez_pending_work *
+bluez_call_async(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, GVariant *params, GError **error,
+ void (*callback)(void *user_data, GVariant *result, GError **error),
+ void *user_data);
+
+void bluez_decode_call_error(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method,
+ GError **error);
+
+#endif /* BLUETOOTH_API_H */
diff --git a/binding/bluetooth-bluez.c b/binding/bluetooth-bluez.c
new file mode 100644
index 0000000..990a244
--- /dev/null
+++ b/binding/bluetooth-bluez.c
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 3
+#include <afb/afb-binding.h>
+
+#include "bluetooth-api.h"
+#include "bluetooth-common.h"
+
+static const struct property_info adapter_props[] = {
+ { .name = "UUIDs", .fmt = "as", },
+ { .name = "Discoverable", .fmt = "b", },
+ { .name = "Discovering", .fmt = "b", },
+ { .name = "Pairable", .fmt = "b", },
+ { .name = "Powered", .fmt = "b", },
+ { .name = "Address", .fmt = "s", },
+ { .name = "AddressType", .fmt = "s", },
+ { .name = "DiscoverableTimeout",.fmt = "u", },
+ { .name = "PairableTimeout", .fmt = "u", },
+ { },
+};
+
+static const struct property_info device_props[] = {
+ { .name = "Address", .fmt = "s", },
+ { .name = "AddressType", .fmt = "s", },
+ { .name = "Name", .fmt = "s", },
+ { .name = "Adapter", .fmt = "s", },
+ { .name = "Alias", .fmt = "s", },
+ { .name = "Class", .fmt = "u", },
+ { .name = "Icon", .fmt = "s", },
+ { .name = "Modalias", .fmt = "s", },
+ { .name = "Paired", .fmt = "b", },
+ { .name = "Trusted", .fmt = "b", },
+ { .name = "Blocked", .fmt = "b", },
+ { .name = "LegacyPairing", .fmt = "b", },
+ { .name = "TxPower", .fmt = "n", },
+ { .name = "RSSI", .fmt = "n", },
+ { .name = "Connected", .fmt = "b", },
+ { .name = "UUIDs", .fmt = "as", },
+ { .name = "Adapter", .fmt = "s", },
+ { .name = "ServicesResolved", .fmt = "b", },
+ { },
+};
+
+const struct property_info *bluez_get_property_info(
+ const char *access_type, GError **error)
+{
+ const struct property_info *pi = NULL;
+
+ if (!strcmp(access_type, BLUEZ_AT_ADAPTER))
+ pi = adapter_props;
+ else if (!strcmp(access_type, BLUEZ_AT_DEVICE))
+ pi = device_props;
+ else
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "illegal %s argument", access_type);
+ return pi;
+}
+
+gboolean bluez_property_dbus2json(const char *access_type,
+ json_object *jprop, const gchar *key, GVariant *var,
+ gboolean *is_config,
+ GError **error)
+{
+ const struct property_info *pi;
+ gboolean ret;
+
+ *is_config = FALSE;
+ pi = bluez_get_property_info(access_type, error);
+ if (!pi)
+ return FALSE;
+
+ ret = root_property_dbus2json(jprop, pi, key, var, is_config);
+ if (!ret)
+ g_set_error(error, NB_ERROR, NB_ERROR_UNKNOWN_PROPERTY,
+ "unknown %s property %s",
+ access_type, key);
+
+ return ret;
+}
+
+
+void bluez_decode_call_error(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method,
+ GError **error)
+{
+ if (!error || !*error)
+ return;
+
+ if (strstr((*error)->message,
+ "org.freedesktop.DBus.Error.UnknownObject")) {
+
+ if (!strcmp(method, "Set") ||
+ !strcmp(method, "Get") ||
+ !strcmp(method, "GetAll")) {
+
+ g_clear_error(error);
+ g_set_error(error, NB_ERROR,
+ NB_ERROR_UNKNOWN_PROPERTY,
+ "unknown %s property on %s",
+ access_type, type_arg);
+
+ } else if (!strcmp(method, "Connect") ||
+ !strcmp(method, "ConnectProfile") ||
+ !strcmp(method, "Disconnect") ||
+ !strcmp(method, "DisconnectProfile") ||
+ !strcmp(method, "Pair") ||
+ !strcmp(method, "Unpair") ||
+ !strcmp(method, "RemoveDevice")) {
+
+ g_clear_error(error);
+ g_set_error(error, NB_ERROR,
+ NB_ERROR_UNKNOWN_SERVICE,
+ "unknown service %s",
+ type_arg);
+
+ } else if (!strcmp(method, "StartDiscovery") ||
+ !strcmp(method, "StopDiscovery") ||
+ !strcmp(method, "RegisterAgent")) {
+
+ g_clear_error(error);
+ g_set_error(error, NB_ERROR,
+ NB_ERROR_UNKNOWN_SERVICE,
+ "unknown service %s",
+ type_arg);
+ }
+ }
+}
+
+GVariant *bluez_call(struct bluetooth_state *ns,
+ const char *access_type, const char *path,
+ const char *method, GVariant *params, GError **error)
+{
+ const char *interface;
+ GVariant *reply;
+
+ if (!path && (!strcmp(access_type, BLUEZ_AT_DEVICE) ||
+ !strcmp(access_type, BLUEZ_AT_ADAPTER))) {
+ g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT,
+ "missing %s argument",
+ access_type);
+ return NULL;
+ }
+
+ if (!strcmp(access_type, BLUEZ_AT_DEVICE)) {
+ interface = BLUEZ_DEVICE_INTERFACE;
+ } else if (!strcmp(access_type, BLUEZ_AT_ADAPTER)) {
+ interface = BLUEZ_ADAPTER_INTERFACE;
+ } else if (!strcmp(access_type, BLUEZ_AT_AGENTMANAGER)) {
+ path = BLUEZ_PATH;
+ interface = BLUEZ_AGENTMANAGER_INTERFACE;
+ } else {
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "illegal %s argument",
+ access_type);
+ return NULL;
+ }
+
+ reply = g_dbus_connection_call_sync(ns->conn,
+ BLUEZ_SERVICE, path, interface, method, params,
+ NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT,
+ NULL, error);
+ bluez_decode_call_error(ns, access_type, path, method,
+ error);
+ if (!reply) {
+ if (error && *error)
+ g_dbus_error_strip_remote_error(*error);
+ AFB_ERROR("Error calling %s%s%s %s method %s",
+ access_type,
+ path ? "/" : "",
+ path ? path : "",
+ method,
+ error && *error ? (*error)->message :
+ "unspecified");
+ }
+
+ return reply;
+}
+
+static void bluez_call_async_ready(GObject *source_object,
+ GAsyncResult *res, gpointer user_data)
+{
+ struct bluez_pending_work *cpw = user_data;
+ struct bluetooth_state *ns = cpw->ns;
+ GVariant *result;
+ GError *error = NULL;
+
+ result = g_dbus_connection_call_finish(ns->conn, res, &error);
+
+ cpw->callback(cpw->user_data, result, &error);
+
+ g_clear_error(&error);
+ g_cancellable_reset(cpw->cancel);
+ g_free(cpw);
+}
+
+void bluez_cancel_call(struct bluetooth_state *ns,
+ struct bluez_pending_work *cpw)
+{
+ g_cancellable_cancel(cpw->cancel);
+}
+
+struct bluez_pending_work *
+bluez_call_async(struct bluetooth_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, GVariant *params, GError **error,
+ void (*callback)(void *user_data, GVariant *result, GError **error),
+ void *user_data)
+{
+ const char *path;
+ const char *interface;
+ struct bluez_pending_work *cpw;
+
+ if (!type_arg && (!strcmp(access_type, BLUEZ_AT_DEVICE) ||
+ !strcmp(access_type, BLUEZ_AT_ADAPTER))) {
+ g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT,
+ "missing %s argument",
+ access_type);
+ return NULL;
+ }
+
+ if (!strcmp(access_type, BLUEZ_AT_DEVICE)) {
+ path = type_arg;
+ interface = BLUEZ_DEVICE_INTERFACE;
+ } else if (!strcmp(access_type, BLUEZ_AT_ADAPTER)) {
+ path = type_arg;
+ interface = BLUEZ_ADAPTER_INTERFACE;
+ } else {
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "illegal %s argument",
+ access_type);
+ return NULL;
+ }
+
+ cpw = g_malloc(sizeof(*cpw));
+ if (!cpw) {
+ g_set_error(error, NB_ERROR, NB_ERROR_OUT_OF_MEMORY,
+ "out of memory");
+ return NULL;
+ }
+ cpw->ns = ns;
+ cpw->user_data = user_data;
+ cpw->cancel = g_cancellable_new();
+ if (!cpw->cancel) {
+ g_free(cpw);
+ g_set_error(error, NB_ERROR, NB_ERROR_OUT_OF_MEMORY,
+ "out of memory");
+ return NULL;
+ }
+ cpw->callback = callback;
+
+ g_dbus_connection_call(ns->conn,
+ BLUEZ_SERVICE, path, interface, method, params,
+ NULL, /* reply type */
+ G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT,
+ cpw->cancel, /* cancellable? */
+ bluez_call_async_ready,
+ cpw);
+
+ return cpw;
+}
+
+json_object *bluez_get_properties(struct bluetooth_state *ns,
+ const char *access_type, const char *path,
+ GError **error)
+{
+ const struct property_info *pi = NULL;
+ const char *method = NULL;
+ GVariant *reply = NULL;
+ GVariantIter *array, *array2, *array3;
+ GVariant *var = NULL;
+ const char *interface, *interface2, *path2 = NULL;
+ const gchar *key = NULL;
+ json_object *jarray = NULL, *jarray2 = NULL;
+ json_object *jprop = NULL, *jresp = NULL, *jtype = NULL;
+ gboolean is_config;
+
+ if (!strcmp(access_type, BLUEZ_AT_DEVICE) ||
+ !strcmp(access_type, BLUEZ_AT_ADAPTER)) {
+
+ pi = bluez_get_property_info(access_type, error);
+ if (!pi)
+ return NULL;
+
+ interface = FREEDESKTOP_PROPERTIES;
+ method = "GetAll";
+ } else if (!strcmp(access_type, BLUEZ_AT_OBJECT)) {
+ interface = FREEDESKTOP_OBJECTMANAGER;
+ method = "GetManagedObjects";
+ } else {
+ return FALSE;
+ }
+
+ if (!strcmp(access_type, BLUEZ_AT_DEVICE))
+ interface2 = BLUEZ_DEVICE_INTERFACE;
+ else if (!strcmp(access_type, BLUEZ_AT_ADAPTER))
+ interface2 = BLUEZ_ADAPTER_INTERFACE;
+ else if (!strcmp(access_type, BLUEZ_AT_OBJECT))
+ interface2 = NULL;
+ else
+ return FALSE;
+
+ if (!method) {
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "illegal %s argument",
+ access_type);
+ return NULL;
+ }
+
+ reply = g_dbus_connection_call_sync(ns->conn,
+ BLUEZ_SERVICE, path, interface, method,
+ interface2 ? g_variant_new("(s)", interface2) : NULL,
+ NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT,
+ NULL, error);
+
+ if (!reply)
+ return NULL;
+
+ if (!strcmp(access_type, BLUEZ_AT_DEVICE) ||
+ !strcmp(access_type, BLUEZ_AT_ADAPTER)) {
+ jprop = json_object_new_object();
+ g_variant_get(reply, "(a{sv})", &array);
+ while (g_variant_iter_loop(array, "{sv}", &key, &var)) {
+ root_property_dbus2json(jprop, pi,
+ key, var, &is_config);
+ }
+ g_variant_iter_free(array);
+ g_variant_unref(reply);
+ jresp = jprop;
+ } else if (!strcmp(access_type, BLUEZ_AT_OBJECT)) {
+
+ jarray = json_object_new_array();
+ jarray2 = json_object_new_array();
+
+ jresp = json_object_new_object();
+ json_object_object_add(jresp, "adapters", jarray);
+ json_object_object_add(jresp, "devices", jarray2);
+
+ g_variant_get(reply, "(a{oa{sa{sv}}})", &array);
+ while (g_variant_iter_loop(array, "{oa{sa{sv}}}", &path2, &array2)) {
+
+ while (g_variant_iter_loop(array2, "{&sa{sv}}", &interface, &array3)) {
+ json_object *array = NULL;
+
+ if (!strcmp(interface, BLUEZ_ADAPTER_INTERFACE)) {
+ access_type = BLUEZ_AT_ADAPTER;
+ array = jarray;
+ } else if (!strcmp(interface, BLUEZ_DEVICE_INTERFACE)) {
+ access_type = BLUEZ_AT_DEVICE;
+ array = jarray2;
+ } else {
+ continue; /* TODO: Maybe display other interfaces */
+ }
+
+ pi = bluez_get_property_info(access_type, error);
+ jtype = json_object_new_object();
+
+ json_object_object_add(jtype, "path",
+ json_object_new_string(path2));
+
+ while (g_variant_iter_loop(array3, "{sv}", &key, &var)) {
+ if (!jprop)
+ jprop = json_object_new_object();
+
+ root_property_dbus2json(jprop, pi,
+ key, var, &is_config);
+ }
+
+ json_object_object_add(jtype, "properties", jprop);
+ json_object_array_add(array, jtype);
+ jprop = NULL;
+ }
+
+ }
+
+ g_variant_iter_free(array);
+ g_variant_unref(reply);
+ }
+
+ if (!jresp) {
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "No %s", access_type);
+ }
+
+ return jresp;
+}
+
+json_object *bluez_get_property(struct bluetooth_state *ns,
+ const char *access_type, const char *path,
+ gboolean is_json_name, const char *name, GError **error)
+{
+ const struct property_info *pi;
+ json_object *jprop, *jval;
+
+ pi = bluez_get_property_info(access_type, error);
+ if (!pi)
+ return NULL;
+
+ jprop = bluez_get_properties(ns, access_type, path, error);
+ if (!jprop)
+ return NULL;
+
+ jval = get_named_property(pi, is_json_name, name, jprop);
+ json_object_put(jprop);
+
+ if (!jval)
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s on %s%s%s", name,
+ access_type,
+ path ? "/" : "",
+ path ? path : "");
+ return jval;
+}
+
+/* NOTE: jval is consumed */
+gboolean bluez_set_property(struct bluetooth_state *ns,
+ const char *access_type, const char *path,
+ gboolean is_json_name, const char *name, json_object *jval,
+ GError **error)
+{
+ const struct property_info *pi;
+ GVariant *reply, *arg;
+ const char *interface;
+ gboolean is_config;
+ gchar *propname;
+
+ g_assert(path);
+
+ /* get start of properties */
+ pi = bluez_get_property_info(access_type, error);
+ if (!pi)
+ return FALSE;
+
+ /* get actual property */
+ pi = property_by_name(pi, is_json_name, name, &is_config);
+ if (!pi) {
+ g_set_error(error, NB_ERROR, NB_ERROR_UNKNOWN_PROPERTY,
+ "unknown property with name %s", name);
+ json_object_put(jval);
+ return FALSE;
+ }
+
+ /* convert to gvariant */
+ arg = property_json_to_gvariant(pi, NULL, NULL, jval, error);
+
+ /* we don't need this anymore */
+ json_object_put(jval);
+ jval = NULL;
+
+ /* no variant? error */
+ if (!arg)
+ return FALSE;
+
+ if (!is_config)
+ propname = g_strdup(pi->name);
+ else
+ propname = configuration_dbus_name(pi->name);
+
+ if (!strcmp(access_type, BLUEZ_AT_DEVICE))
+ interface = BLUEZ_DEVICE_INTERFACE;
+ else if (!strcmp(access_type, BLUEZ_AT_ADAPTER))
+ interface = BLUEZ_ADAPTER_INTERFACE;
+ else if (!strcmp(access_type, BLUEZ_AT_AGENT))
+ interface = BLUEZ_AGENT_INTERFACE;
+ else
+ return FALSE;
+
+ reply = g_dbus_connection_call_sync(ns->conn,
+ BLUEZ_SERVICE, path, FREEDESKTOP_PROPERTIES, "Set",
+ g_variant_new("(ssv)", interface, propname, arg),
+ NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT,
+ NULL, error);
+
+ g_free(propname);
+
+ if (!reply)
+ return FALSE;
+
+ g_variant_unref(reply);
+
+ return TRUE;
+}
diff --git a/binding/bluetooth-common.h b/binding/bluetooth-common.h
new file mode 100644
index 0000000..159c5f4
--- /dev/null
+++ b/binding/bluetooth-common.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BLUETOOTH_COMMON_H
+#define BLUETOOTH_COMMON_H
+
+#include <stddef.h>
+
+#define _GNU_SOURCE
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 3
+#include <afb/afb-binding.h>
+
+struct call_work;
+
+struct bluetooth_state {
+ GMainLoop *loop;
+ GDBusConnection *conn;
+ guint device_sub;
+ guint autoconnect_sub;
+
+ afb_event_t device_changes_event;
+ afb_event_t agent_event;
+
+ /* NOTE: single connection allowed for now */
+ /* NOTE: needs locking and a list */
+ GMutex cw_mutex;
+ int next_cw_id;
+ GSList *cw_pending;
+ struct call_work *cw;
+
+ /* agent */
+ GDBusNodeInfo *introspection_data;
+ guint agent_id;
+ guint registration_id;
+ gchar *agent_path;
+ gboolean agent_registered;
+};
+
+struct init_data {
+ GCond cond;
+ GMutex mutex;
+ gboolean init_done;
+ afb_api_t api;
+ struct bluetooth_state *ns; /* before setting afb_api_set_userdata() */
+ int rc;
+};
+
+struct agent_data {
+ int pin_code;
+ gchar *device_path;
+};
+
+struct call_work {
+ struct bluetooth_state *ns;
+ int id;
+ gchar *access_type;
+ gchar *type_arg;
+ gchar *method;
+ gchar *bluez_method;
+ struct bluez_pending_work *cpw;
+ afb_req_t request;
+ struct agent_data agent_data;
+ GDBusMethodInvocation *invocation;
+};
+
+/* init methods in bluetooth-rfkill.c */
+
+int bluetooth_monitor_init(void);
+
+/* agent methods in bluetooth-agent.c */
+
+int bluetooth_register_agent(struct init_data *id);
+
+void bluetooth_unregister_agent(struct bluetooth_state *ns);
+
+/* utility methods in bluetooth-util.c */
+
+extern gboolean auto_lowercase_keys;
+
+void signal_init_done(struct init_data *id, int rc);
+
+int str2boolean(const char *value);
+
+json_object *json_object_copy(json_object *jval);
+
+gchar *key_dbus_to_json(const gchar *key, gboolean auto_lower);
+
+json_object *simple_gvariant_to_json(GVariant *var, json_object *parent,
+ gboolean recurse);
+
+/**
+ * Structure for converting from dbus properties to json
+ * and vice-versa.
+ * Note this is _not_ a generic dbus json bridge since
+ * some constructs are not easily mapped.
+ */
+struct property_info {
+ const char *name; /* the connman property name */
+ const char *json_name; /* the json name (if NULL autoconvert) */
+ const char *fmt;
+ unsigned int flags;
+ const struct property_info *sub;
+};
+
+#define PI_CONFIG (1U << 0)
+
+const struct property_info *property_by_dbus_name(
+ const struct property_info *pi,
+ const gchar *dbus_name,
+ gboolean *is_config);
+const struct property_info *property_by_json_name(
+ const struct property_info *pi,
+ const gchar *json_name,
+ gboolean *is_config);
+const struct property_info *property_by_name(
+ const struct property_info *pi,
+ gboolean is_json_name, const gchar *name,
+ gboolean *is_config);
+
+gchar *property_get_json_name(const struct property_info *pi,
+ const gchar *name);
+gchar *property_get_name(const struct property_info *pi,
+ const gchar *json_name);
+
+gchar *configuration_dbus_name(const gchar *dbus_name);
+gchar *configuration_json_name(const gchar *json_name);
+
+gchar *property_name_dbus2json(const struct property_info *pi,
+ gboolean is_config);
+
+json_object *property_dbus2json(
+ const struct property_info **pip,
+ const gchar *key, GVariant *var,
+ gboolean *is_config);
+
+gboolean root_property_dbus2json(
+ json_object *jparent,
+ const struct property_info *pi,
+ const gchar *key, GVariant *var,
+ gboolean *is_config);
+
+GVariant *property_json_to_gvariant(
+ const struct property_info *pi,
+ const char *fmt, /* if NULL use pi->fmt */
+ const struct property_info *pi_parent,
+ json_object *jval,
+ GError **error);
+
+typedef enum {
+ NB_ERROR_BAD_TECHNOLOGY,
+ NB_ERROR_BAD_SERVICE,
+ NB_ERROR_OUT_OF_MEMORY,
+ NB_ERROR_NO_TECHNOLOGIES,
+ NB_ERROR_NO_SERVICES,
+ NB_ERROR_BAD_PROPERTY,
+ NB_ERROR_UNIMPLEMENTED,
+ NB_ERROR_UNKNOWN_PROPERTY,
+ NB_ERROR_UNKNOWN_TECHNOLOGY,
+ NB_ERROR_UNKNOWN_SERVICE,
+ NB_ERROR_MISSING_ARGUMENT,
+ NB_ERROR_ILLEGAL_ARGUMENT,
+ NB_ERROR_CALL_IN_PROGRESS,
+} NBError;
+
+#define NB_ERROR (nb_error_quark())
+
+extern GQuark nb_error_quark(void);
+
+json_object *get_property_collect(json_object *jreqprop, json_object *jprop,
+ GError **error);
+json_object *get_named_property(const struct property_info *pi,
+ gboolean is_json_name, const char *name, json_object *jprop);
+
+#endif
diff --git a/binding/bluetooth-rfkill.c b/binding/bluetooth-rfkill.c
new file mode 100644
index 0000000..5419e8f
--- /dev/null
+++ b/binding/bluetooth-rfkill.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <linux/rfkill.h>
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#define AFB_BINDING_VERSION 3
+#include <afb/afb-binding.h>
+
+#define HCIDEVUP _IOW('H', 201, int)
+#define BTPROTO_HCI 1
+
+
+
+static int hci_interface_enable(int hdev)
+{
+ int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+ int ret;
+
+ if (ctl < 0)
+ return ctl;
+
+ ret = ioctl(ctl, HCIDEVUP, hdev);
+
+ close(ctl);
+
+ return ret;
+}
+
+static gboolean bluetooth_rfkill_event(GIOChannel *chan, GIOCondition cond, gpointer ptr)
+{
+ struct rfkill_event event;
+ gchar *name, buf[8];
+ ssize_t len;
+ int fd;
+
+ fd = g_io_channel_unix_get_fd(chan);
+ len = read(fd, &event, sizeof(struct rfkill_event));
+
+ if (len != sizeof(struct rfkill_event))
+ return TRUE;
+
+ if (event.type != RFKILL_TYPE_BLUETOOTH)
+ return TRUE;
+
+ if (event.op == RFKILL_OP_DEL)
+ return TRUE;
+
+ name = g_strdup_printf("/sys/class/rfkill/rfkill%u/soft", event.idx);
+
+ fd = g_open(name, O_WRONLY);
+ len = write(fd, "0", 1);
+ g_close(fd, NULL);
+
+ g_free(name);
+
+ memset(&buf, 0, sizeof(buf));
+
+ name = g_strdup_printf("/sys/class/rfkill/rfkill%u/name", event.idx);
+ fd = g_open(name, O_RDONLY);
+ len = read(fd, &buf, sizeof(buf) - 1);
+
+ if (len > 0)
+ {
+ int idx = 0;
+ sscanf(buf, "hci%d", &idx);
+
+ /*
+ * 50 millisecond delay to allow time for rfkill to unblock before
+ * attempting to bring up HCI interface
+ */
+ g_usleep(50000);
+
+ hci_interface_enable(idx);
+ }
+
+ g_free(name);
+
+ return TRUE;
+}
+
+int bluetooth_monitor_init(void)
+{
+ int fd = g_open("/dev/rfkill", O_RDWR);
+ GIOChannel *chan = NULL;
+ int ret = -EINVAL;
+
+ if (fd < 0)
+ {
+ AFB_ERROR("Cannot open /dev/rfkill");
+ return ret;
+ }
+
+ chan = g_io_channel_unix_new(fd);
+ g_io_channel_set_close_on_unref(chan, TRUE);
+
+ ret = g_io_add_watch(chan, G_IO_IN, bluetooth_rfkill_event, NULL);
+
+ g_io_channel_unref(chan);
+
+ return 0;
+}
diff --git a/binding/bluetooth-util.c b/binding/bluetooth-util.c
new file mode 100644
index 0000000..0af4898
--- /dev/null
+++ b/binding/bluetooth-util.c
@@ -0,0 +1,1026 @@
+/*
+ * Copyright 2018 Konsulko Group
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 3
+#include <afb/afb-binding.h>
+
+#include "bluetooth-api.h"
+#include "bluetooth-common.h"
+
+G_DEFINE_QUARK(bluetooth-binding-error-quark, nb_error)
+
+/* convert dbus key to lower case */
+gboolean auto_lowercase_keys = TRUE;
+
+void signal_init_done(struct init_data *id, int rc)
+{
+ g_mutex_lock(&id->mutex);
+ id->init_done = TRUE;
+ id->rc = rc;
+ g_cond_signal(&id->cond);
+ g_mutex_unlock(&id->mutex);
+}
+
+int str2boolean(const char *value)
+{
+ if (!strcmp(value, "1") || !g_ascii_strcasecmp(value, "true") ||
+ !g_ascii_strcasecmp(value, "on") || !g_ascii_strcasecmp(value, "enabled") ||
+ !g_ascii_strcasecmp(value, "yes"))
+ return TRUE;
+ if (!strcmp(value, "0") || !g_ascii_strcasecmp(value, "false") ||
+ !g_ascii_strcasecmp(value, "off") || !g_ascii_strcasecmp(value, "disabled") ||
+ !g_ascii_strcasecmp(value, "no"))
+ return FALSE;
+ return -1;
+}
+
+json_object *json_object_copy(json_object *jval)
+{
+ json_object *jobj;
+ int i, len;
+
+ /* handle NULL */
+ if (!jval)
+ return NULL;
+
+ switch (json_object_get_type(jval)) {
+ case json_type_object:
+ jobj = json_object_new_object();
+ json_object_object_foreach(jval, key, jval2)
+ json_object_object_add(jobj, key,
+ json_object_copy(jval2));
+
+ return jobj;
+
+ case json_type_array:
+ jobj = json_object_new_array();
+ len = json_object_array_length(jval);
+ for (i = 0; i < len; i++)
+ json_object_array_add(jobj,
+ json_object_copy(
+ json_object_array_get_idx(jval, i)));
+ return jobj;
+
+ case json_type_null:
+ return NULL;
+
+ case json_type_boolean:
+ return json_object_new_boolean(
+ json_object_get_boolean(jval));
+
+ case json_type_double:
+ return json_object_new_double(
+ json_object_get_double(jval));
+
+ case json_type_int:
+ return json_object_new_int64(
+ json_object_get_int64(jval));
+
+ case json_type_string:
+ return json_object_new_string(
+ json_object_get_string(jval));
+ }
+
+ g_assert(0);
+ /* should never happen */
+ return NULL;
+}
+
+gchar *key_dbus_to_json(const gchar *key, gboolean auto_lower)
+{
+ gchar *lower, *s;
+
+ lower = g_strdup(key);
+ g_assert(lower);
+
+ if (!auto_lower)
+ return lower;
+
+ /* convert to lower case */
+ for (s = lower; *s; s++)
+ *s = g_ascii_tolower(*s);
+
+ return lower;
+}
+
+json_object *simple_gvariant_to_json(GVariant *var, json_object *parent,
+ gboolean recurse)
+{
+ json_object *obj = NULL, *item;
+ gint32 i32;
+ gint64 i64;
+ guint32 ui32;
+ guint64 ui64;
+ GVariantIter iter;
+ GVariant *key, *value;
+ gchar *json_key;
+ gsize nitems;
+ gboolean is_dict;
+
+ obj = NULL;
+
+ /* AFB_DEBUG("g_variant_classify(var)=%c", g_variant_classify(var)); */
+
+ /* we only handle simple types */
+ switch (g_variant_classify(var)) {
+ case G_VARIANT_CLASS_BOOLEAN:
+ obj = json_object_new_boolean(g_variant_get_boolean(var));
+ break;
+ case G_VARIANT_CLASS_INT16:
+ obj = json_object_new_int(g_variant_get_int16(var));
+ break;
+ case G_VARIANT_CLASS_INT32:
+ i32 = g_variant_get_int32(var);
+ obj = json_object_new_int(i32);
+ break;
+ case G_VARIANT_CLASS_INT64:
+ i64 = g_variant_get_int64(var);
+ if (i64 >= -(1L << 31) && i64 < (1L << 31))
+ obj = json_object_new_int((int)i64);
+ else
+ obj = json_object_new_int64(i64);
+ break;
+ case G_VARIANT_CLASS_BYTE:
+ obj = json_object_new_int((int)g_variant_get_byte(var));
+ break;
+ case G_VARIANT_CLASS_UINT16:
+ obj = json_object_new_int((int)g_variant_get_uint16(var));
+ break;
+ case G_VARIANT_CLASS_UINT32:
+ ui32 = g_variant_get_uint32(var);
+ if (ui32 < (1U << 31))
+ obj = json_object_new_int(ui32);
+ else
+ obj = json_object_new_int64(ui32);
+ break;
+ case G_VARIANT_CLASS_UINT64:
+ ui64 = g_variant_get_uint64(var);
+ if (ui64 < (1U << 31))
+ obj = json_object_new_int((int)ui64);
+ else if (ui64 < (1LLU << 63))
+ obj = json_object_new_int64(ui64);
+ else {
+ AFB_WARNING("U64 value %llu clamped to %llu",
+ (unsigned long long)ui64,
+ (unsigned long long)((1LLU << 63) - 1));
+ obj = json_object_new_int64((1LLU << 63) - 1);
+ }
+ break;
+ case G_VARIANT_CLASS_DOUBLE:
+ obj = json_object_new_double(g_variant_get_double(var));
+ break;
+ case G_VARIANT_CLASS_OBJECT_PATH:
+ case G_VARIANT_CLASS_STRING:
+ obj = json_object_new_string(g_variant_get_string(var, NULL));
+ break;
+
+ case G_VARIANT_CLASS_ARRAY:
+
+ if (!recurse)
+ break;
+
+ /* detect dictionaries which are arrays of dict entries */
+ g_variant_iter_init(&iter, var);
+
+ nitems = g_variant_iter_n_children(&iter);
+ /* remove completely empty arrays */
+ if (nitems == 0)
+ break;
+
+ is_dict = nitems > 0;
+ while (is_dict && (value = g_variant_iter_next_value(&iter))) {
+ is_dict = g_variant_classify(value) == G_VARIANT_CLASS_DICT_ENTRY;
+ g_variant_unref(value);
+ }
+
+ if (is_dict)
+ obj = json_object_new_object();
+ else
+ obj = json_object_new_array();
+
+ g_variant_iter_init(&iter, var);
+ while ((value = g_variant_iter_next_value(&iter))) {
+
+ item = simple_gvariant_to_json(value, obj, TRUE);
+ if (!is_dict && item)
+ json_object_array_add(obj, item);
+
+ g_variant_unref(value);
+ }
+ break;
+
+ case G_VARIANT_CLASS_DICT_ENTRY:
+
+ if (!recurse)
+ break;
+
+ if (!parent) {
+ AFB_WARNING("#### dict new object without a parent");
+ break;
+ }
+
+ g_variant_iter_init(&iter, var);
+ while ((key = g_variant_iter_next_value(&iter))) {
+
+ value = g_variant_iter_next_value(&iter);
+ if (!value) {
+ AFB_WARNING("Out of values with a key");
+ g_variant_unref(key);
+ break;
+ }
+
+ json_key = key_dbus_to_json(
+ g_variant_get_string(key, NULL),
+ auto_lowercase_keys);
+
+ /* only handle dict values with string keys */
+ if (g_variant_classify(key) == G_VARIANT_CLASS_STRING) {
+ item = simple_gvariant_to_json(value, obj, TRUE);
+ if (item)
+ json_object_object_add(parent, json_key, item);
+
+ } else
+ AFB_WARNING("Can't handle non-string key");
+
+ g_free(json_key);
+
+ g_variant_unref(value);
+ g_variant_unref(key);
+ }
+ break;
+
+ case G_VARIANT_CLASS_VARIANT:
+
+ /* NOTE: recurse allowed because we only allow a single encapsulated variant */
+
+ g_variant_iter_init(&iter, var);
+ nitems = g_variant_iter_n_children(&iter);
+ if (nitems != 1) {
+ AFB_WARNING("Can't handle variants with more than one children (%lu)", nitems);
+ break;
+ }
+
+ while ((value = g_variant_iter_next_value(&iter))) {
+ obj = simple_gvariant_to_json(value, parent, TRUE);
+ g_variant_unref(value);
+ break;
+ }
+ break;
+
+ default:
+ AFB_WARNING("############ class is %c", g_variant_classify(var));
+ obj = NULL;
+ break;
+ }
+
+ return obj;
+}
+
+gchar *property_name_dbus2json(const struct property_info *pi,
+ gboolean is_config)
+{
+ gchar *json_name;
+ gchar *cfgname;
+
+ if (pi->json_name)
+ json_name = g_strdup(pi->json_name);
+ else
+ json_name = key_dbus_to_json(pi->name, auto_lowercase_keys);
+
+ if (!json_name)
+ return NULL;
+
+ if (!is_config)
+ return json_name;
+
+ cfgname = g_strdup_printf("%s.%configuration",
+ json_name,
+ auto_lowercase_keys ? 'c' : 'C');
+ g_free(json_name);
+ return cfgname;
+}
+
+json_object *property_dbus2json(
+ const struct property_info **pip,
+ const gchar *key, GVariant *var,
+ gboolean *is_config)
+{
+ const struct property_info *pi = *pip, *pi2, *pi_sub;
+ GVariantIter iter, iter2;
+ json_object *obj = NULL, *obji;
+ const char *fmt;
+ GVariant *value, *dict_value, *dict_key;
+ const gchar *sub_key;
+ gchar *json_key;
+ gboolean is_subconfig;
+
+ if (key) {
+ pi = property_by_dbus_name(pi, key, is_config);
+ if (!pi)
+ return NULL;
+ *pip = pi;
+ }
+
+ fmt = pi->fmt;
+
+ obj = simple_gvariant_to_json(var, NULL, FALSE);
+ if (obj) {
+ /* TODO check fmt for matching type */
+ return obj;
+ }
+
+ switch (*fmt) {
+ case 'a': /* array */
+ obj = json_object_new_array();
+
+ g_variant_iter_init(&iter, var);
+ while ((value = g_variant_iter_next_value(&iter))) {
+ pi2 = pi;
+ obji = property_dbus2json(&pi2, NULL, value,
+ &is_subconfig);
+ if (obji)
+ json_object_array_add(obj, obji);
+
+ g_variant_unref(value);
+ }
+ break;
+ case '{':
+ /* we only support {sX} */
+
+ /* there must be a sub property entry */
+ g_assert(pi->sub);
+
+ obj = json_object_new_object();
+
+ g_variant_iter_init(&iter, var);
+ while ((value = g_variant_iter_next_value(&iter))) {
+
+ if (g_variant_classify(value) != G_VARIANT_CLASS_DICT_ENTRY) {
+ AFB_WARNING("Expecting dict got '%c'", g_variant_classify(value));
+ g_variant_unref(value);
+ break;
+ }
+
+ g_variant_iter_init(&iter2, value);
+ while ((dict_key = g_variant_iter_next_value(&iter2))) {
+ if (g_variant_classify(dict_key) != G_VARIANT_CLASS_STRING) {
+ AFB_WARNING("Can't handle non-string dict keys '%c'",
+ g_variant_classify(dict_key));
+ g_variant_unref(dict_key);
+ g_variant_unref(value);
+ continue;
+ }
+
+ dict_value = g_variant_iter_next_value(&iter2);
+ if (!dict_value) {
+ AFB_WARNING("Out of values with a dict_key");
+ g_variant_unref(dict_key);
+ g_variant_unref(value);
+ break;
+ }
+
+ sub_key = g_variant_get_string(dict_key, NULL);
+
+ pi_sub = pi->sub;
+ while (pi_sub->name) {
+ if (!g_strcmp0(sub_key, pi_sub->name))
+ break;
+ pi_sub++;
+ }
+
+ if (pi_sub->name) {
+ pi2 = pi_sub;
+ obji = property_dbus2json(&pi2,
+ sub_key, dict_value,
+ &is_subconfig);
+ if (obji) {
+ json_key = property_name_dbus2json(pi2, FALSE);
+ json_object_object_add(obj, json_key, obji);
+ g_free(json_key);
+ }
+ } else
+ AFB_INFO("Unhandled %s/%s property", key, sub_key);
+
+ g_variant_unref(dict_value);
+ g_variant_unref(dict_key);
+ }
+
+ g_variant_unref(value);
+ }
+
+ break;
+ }
+
+ if (!obj)
+ AFB_INFO("# %s not a type we can handle", key ? key : "<NULL>");
+
+ return obj;
+}
+
+const struct property_info *property_by_dbus_name(
+ const struct property_info *pi,
+ const gchar *dbus_name,
+ gboolean *is_config)
+{
+ const struct property_info *pit;
+ const gchar *suffix;
+ gchar *tmpname;
+ size_t len;
+
+ /* direct match first */
+ pit = pi;
+ while (pit->name) {
+ if (!g_strcmp0(dbus_name, pit->name)) {
+ if (is_config)
+ *is_config = FALSE;
+ return pit;
+ }
+ pit++;
+ }
+
+ /* try to see if a matching config property exists */
+ suffix = strrchr(dbus_name, '.');
+ if (!suffix || g_ascii_strcasecmp(suffix, ".Configuration"))
+ return NULL;
+
+ /* it's a (possible) .config property */
+ len = suffix - dbus_name;
+ tmpname = alloca(len + 1);
+ memcpy(tmpname, dbus_name, len);
+ tmpname[len] = '\0';
+
+ /* match with config */
+ pit = pi;
+ while (pit->name) {
+ if (!(pit->flags & PI_CONFIG)) {
+ pit++;
+ continue;
+ }
+ if (!g_strcmp0(tmpname, pit->name)) {
+ if (is_config)
+ *is_config = TRUE;
+ return pit;
+ }
+ pit++;
+ }
+
+ return NULL;
+}
+
+const struct property_info *property_by_json_name(
+ const struct property_info *pi,
+ const gchar *json_name,
+ gboolean *is_config)
+{
+ const struct property_info *pit;
+ gchar *this_json_name;
+ const gchar *suffix;
+ gchar *tmpname;
+ size_t len;
+
+ /* direct match */
+ pit = pi;
+ while (pit->name) {
+ this_json_name = property_name_dbus2json(pit, FALSE);
+ if (!g_strcmp0(this_json_name, json_name)) {
+ g_free(this_json_name);
+ if (is_config)
+ *is_config = FALSE;
+ return pit;
+ }
+ g_free(this_json_name);
+ pit++;
+ }
+
+ /* try to see if a matching config property exists */
+ suffix = strrchr(json_name, '.');
+ if (!suffix || g_ascii_strcasecmp(suffix, ".configuration"))
+ return NULL;
+
+ /* it's a (possible) .config property */
+ len = suffix - json_name;
+ tmpname = alloca(len + 1);
+ memcpy(tmpname, json_name, len);
+ tmpname[len] = '\0';
+
+ /* match with config */
+ pit = pi;
+ while (pit->name) {
+ if (!(pit->flags & PI_CONFIG)) {
+ pit++;
+ continue;
+ }
+ this_json_name = property_name_dbus2json(pit, FALSE);
+ if (!g_strcmp0(this_json_name, tmpname)) {
+ g_free(this_json_name);
+ if (is_config)
+ *is_config = TRUE;
+ return pit;
+ }
+ g_free(this_json_name);
+ pit++;
+ }
+
+ return NULL;
+}
+
+const struct property_info *property_by_name(
+ const struct property_info *pi,
+ gboolean is_json_name, const gchar *name,
+ gboolean *is_config)
+{
+ return is_json_name ?
+ property_by_json_name(pi, name, is_config) :
+ property_by_dbus_name(pi, name, is_config);
+}
+
+gchar *property_get_json_name(const struct property_info *pi,
+ const gchar *name)
+{
+ gboolean is_config;
+
+ pi = property_by_dbus_name(pi, name, &is_config);
+ if (!pi)
+ return NULL;
+ return property_name_dbus2json(pi, is_config);
+}
+
+gchar *configuration_dbus_name(const gchar *dbus_name)
+{
+ return g_strdup_printf("%s.Configuration", dbus_name);
+}
+
+gchar *configuration_json_name(const gchar *json_name)
+{
+ return g_strdup_printf("%s.configuration", json_name);
+}
+
+gchar *property_get_name(const struct property_info *pi,
+ const gchar *json_name)
+{
+ gboolean is_config;
+
+ pi = property_by_json_name(pi, json_name, &is_config);
+ if (!pi)
+ return NULL;
+
+ return !is_config ? g_strdup(pi->name) :
+ configuration_dbus_name(pi->name);
+}
+
+gboolean root_property_dbus2json(
+ json_object *jparent,
+ const struct property_info *pi,
+ const gchar *key, GVariant *var,
+ gboolean *is_config)
+{
+ json_object *obj;
+ gchar *json_key;
+
+ obj = property_dbus2json(&pi, key, var, is_config);
+ if (!obj)
+ return FALSE;
+
+ switch (json_object_get_type(jparent)) {
+ case json_type_object:
+ json_key = property_name_dbus2json(pi, *is_config);
+ json_object_object_add(jparent, json_key, obj);
+ g_free(json_key);
+ break;
+ case json_type_array:
+ json_object_array_add(jparent, obj);
+ break;
+ default:
+ json_object_put(obj);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static const GVariantType *type_from_fmt(const char *fmt)
+{
+ switch (*fmt) {
+ case 'b': /* gboolean */
+ return G_VARIANT_TYPE_BOOLEAN;
+ case 'y': /* guchar */
+ return G_VARIANT_TYPE_BYTE;
+ case 'n': /* gint16 */
+ return G_VARIANT_TYPE_INT16;
+ case 'q': /* guint16 */
+ return G_VARIANT_TYPE_UINT16;
+ case 'h':
+ return G_VARIANT_TYPE_HANDLE;
+ case 'i': /* gint32 */
+ return G_VARIANT_TYPE_INT32;
+ case 'u': /* guint32 */
+ return G_VARIANT_TYPE_UINT32;
+ case 'x': /* gint64 */
+ return G_VARIANT_TYPE_INT64;
+ case 't': /* gint64 */
+ return G_VARIANT_TYPE_UINT64;
+ case 'd': /* double */
+ return G_VARIANT_TYPE_DOUBLE;
+ case 's': /* string */
+ return G_VARIANT_TYPE_STRING;
+ case 'o': /* object */
+ return G_VARIANT_TYPE_OBJECT_PATH;
+ case 'g': /* signature */
+ return G_VARIANT_TYPE_SIGNATURE;
+ case 'v': /* variant */
+ return G_VARIANT_TYPE_VARIANT;
+ }
+ /* nothing complex */
+ return NULL;
+}
+
+GVariant *property_json_to_gvariant(
+ const struct property_info *pi,
+ const char *fmt,
+ const struct property_info *pi_parent,
+ json_object *jval,
+ GError **error)
+{
+ const struct property_info *pi_sub;
+ GVariant *arg, *item;
+ GVariantBuilder builder;
+ json_object *jitem;
+ json_bool b;
+ gchar *dbus_name;
+ int64_t i64;
+ double d;
+ const char *jvalstr, *str;
+ char c;
+ int i, len;
+ gboolean is_config;
+
+ if (!fmt)
+ fmt = pi->fmt;
+
+ if (!jval) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "can't encode json NULL type");
+ return NULL;
+ }
+
+ jvalstr = json_object_to_json_string(jval);
+
+ arg = NULL;
+
+ b = FALSE;
+ i64 = 0;
+ d = 0.0;
+ str = NULL;
+
+ /* conversion and type check */
+ c = *fmt++;
+ if (c == 'a') {
+ if (!json_object_is_type(jval, json_type_array)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not an array)",
+ jvalstr);
+ return NULL;
+ }
+
+ len = json_object_array_length(jval);
+ /* special case for empty array */
+ if (!len) {
+ arg = g_variant_new_array(type_from_fmt(fmt), NULL, 0);
+ if (!arg)
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't create empty array on \"%s\"",
+ jvalstr);
+ return arg;
+ }
+
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+ for (i = 0; i < len; i++) {
+ jitem = json_object_array_get_idx(jval, i);
+ item = property_json_to_gvariant(pi, fmt, NULL, jitem, error);
+ if (!item) {
+ g_variant_builder_clear(&builder);
+ return NULL;
+ }
+ g_variant_builder_add_value(&builder, item);
+ }
+
+ arg = g_variant_builder_end(&builder);
+
+ if (!arg)
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't handle array on \"%s\"",
+ jvalstr);
+
+ return arg;
+ }
+ if (c == '{') {
+ g_assert(pi->sub);
+
+ c = *fmt++;
+ /* we only handle string keys */
+ if (c != 's') {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't handle non-string keys on \"%s\"",
+ jvalstr);
+ return NULL;
+ }
+ c = *fmt++;
+
+ /* this is arguably wrong */
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ pi_sub = pi->sub;
+ json_object_object_foreach(jval, key_o, jval_o) {
+ pi_sub = property_by_json_name(pi->sub, key_o, &is_config);
+ if (!pi_sub) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Unknown sub-property %s in \"%s\"",
+ key_o, json_object_to_json_string(jval_o));
+ return NULL;
+ }
+ item = property_json_to_gvariant(pi_sub, NULL, pi, jval_o, error);
+ if (!item)
+ return NULL;
+
+ dbus_name = property_get_name(pi->sub, key_o);
+ g_assert(dbus_name); /* can't fail; but check */
+
+ g_variant_builder_add(&builder, pi->fmt, dbus_name, item);
+
+ g_free(dbus_name);
+ }
+
+ arg = g_variant_builder_end(&builder);
+
+ if (!arg)
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Can't handle object on \"%s\"",
+ jvalstr);
+ return arg;
+ }
+
+ switch (c) {
+ case 'b': /* gboolean */
+ if (!json_object_is_type(jval, json_type_boolean)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not a boolean)",
+ jvalstr);
+ return NULL;
+ }
+ b = json_object_get_boolean(jval);
+ break;
+ case 'y': /* guchar */
+ case 'n': /* gint16 */
+ case 'q': /* guint16 */
+ case 'h':
+ case 'i': /* gint32 */
+ case 'u': /* guint32 */
+ case 'x': /* gint64 */
+ case 't': /* gint64 */
+ if (!json_object_is_type(jval, json_type_int)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not an integer)",
+ jvalstr);
+ return NULL;
+ }
+ /* note unsigned 64 bit values shall be truncated */
+ i64 = json_object_get_int64(jval);
+ break;
+
+ case 'd': /* double */
+ if (!json_object_is_type(jval, json_type_double)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not a double)",
+ jvalstr);
+ return NULL;
+ }
+ d = json_object_get_double(jval);
+ break;
+ case 's': /* string */
+ case 'o': /* object */
+ case 'g': /* signature */
+ if (!json_object_is_type(jval, json_type_string)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property \"%s\" (not a string)",
+ jvalstr);
+ return NULL;
+ }
+ str = json_object_get_string(jval);
+ break;
+ case 'v': /* variant */
+ AFB_WARNING("Can't handle variant yet");
+ break;
+ }
+
+ /* build gvariant */
+ switch (c) {
+ case 'b': /* gboolean */
+ arg = g_variant_new_boolean(b);
+ break;
+ case 'y': /* guchar */
+ if (i64 < 0 || i64 > 255) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of byte range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_byte((guchar)i64);
+ break;
+ case 'n': /* gint16 */
+ if (i64 < -(1LL << 15) || i64 >= (1LL << 15)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of int16 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_int16((gint16)i64);
+ break;
+ case 'q': /* guint16 */
+ if (i64 < 0 || i64 >= (1LL << 16)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of uint16 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_uint16((guint16)i64);
+ break;
+ case 'h':
+ case 'i': /* gint32 */
+ if (i64 < -(1LL << 31) || i64 >= (1LL << 31)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of int32 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_int32((gint32)i64);
+ break;
+ case 'u': /* guint32 */
+ if (i64 < 0 || i64 >= (1LL << 32)) {
+ g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "Bad property %s (out of uint32 range)",
+ jvalstr);
+ return FALSE;
+ }
+ arg = g_variant_new_uint32((guint32)i64);
+ break;
+ case 'x': /* gint64 */
+ arg = g_variant_new_int64(i64);
+ break;
+ case 't': /* gint64 */
+ arg = g_variant_new_uint64(i64);
+ break;
+ case 'd': /* double */
+ arg = g_variant_new_double(d);
+ break;
+ case 's': /* string */
+ arg = g_variant_new_string(str);
+ break;
+ case 'o': /* object */
+ arg = g_variant_new_object_path(str);
+ break;
+ case 'g': /* signature */
+ arg = g_variant_new_signature(str);
+ break;
+ case 'v': /* variant */
+ AFB_WARNING("Can't handle variant yet");
+ break;
+ }
+
+ return arg;
+}
+
+json_object *get_property_collect(json_object *jreqprop, json_object *jprop,
+ GError **error)
+{
+ int i, len;
+ json_object *jkey, *jval, *jobj = NULL, *jobjval;
+ const char *key;
+
+
+ /* printf("jreqprop=%s\n", json_object_to_json_string_ext(jreqprop,
+ JSON_C_TO_STRING_SPACED));
+ printf("jprop=%s\n", json_object_to_json_string_ext(jprop,
+ JSON_C_TO_STRING_SPACED)); */
+
+ /* get is an array of strings (or an object for subtype */
+ g_assert(json_object_get_type(jreqprop) == json_type_array);
+
+ len = json_object_array_length(jreqprop);
+ if (len == 0)
+ return NULL;
+
+ for (i = 0; i < len; i++) {
+ jkey = json_object_array_get_idx(jreqprop, i);
+
+ /* string key */
+ if (json_object_is_type(jkey, json_type_string)) {
+ key = json_object_get_string(jkey);
+ if (!json_object_object_get_ex(jprop, key, &jval)) {
+ g_set_error(error,
+ NB_ERROR, NB_ERROR_BAD_PROPERTY,
+ "can't find key %s", key);
+ json_object_put(jobj);
+ return NULL;
+ }
+
+ if (!jobj)
+ jobj = json_object_new_object();
+
+ json_object_object_add(jobj, key,
+ json_object_copy(jval));
+
+ } else if (json_object_is_type(jkey, json_type_object)) {
+ /* recursing into an object */
+
+ json_object_object_foreach(jkey, key_o, jval_o) {
+ if (!json_object_object_get_ex(jprop, key_o,
+ &jval)) {
+ g_set_error(error, NB_ERROR,
+ NB_ERROR_BAD_PROPERTY,
+ "can't find key %s", key_o);
+ json_object_put(jobj);
+ return NULL;
+ }
+
+ /* jval_o is on jreqprop */
+ /* jval is on jprop */
+
+ jobjval = get_property_collect(jval_o, jval,
+ error);
+
+ if (!jobjval && error && *error) {
+ json_object_put(jobj);
+ return NULL;
+ }
+
+ if (jobjval) {
+ if (!jobj)
+ jobj = json_object_new_object();
+
+ json_object_object_add(jobj, key_o, jobjval);
+ }
+ }
+ }
+ }
+
+ /* if (jobj)
+ printf("jobj=%s\n", json_object_to_json_string_ext(jobj,
+ JSON_C_TO_STRING_SPACED)); */
+
+ return jobj;
+}
+
+json_object *get_named_property(const struct property_info *pi,
+ gboolean is_json_name, const char *name, json_object *jprop)
+{
+ json_object *jret = NULL;
+ gchar *json_name = NULL;
+
+ if (!is_json_name) {
+ json_name = property_get_json_name(pi, name);
+ if (!json_name)
+ return NULL;
+ name = json_name;
+ }
+
+ json_object_object_foreach(jprop, key, jval) {
+ if (!g_strcmp0(key, name)) {
+ jret = json_object_copy(jval);
+ break;
+ }
+ }
+
+ g_free(json_name);
+
+ return jret;
+}