aboutsummaryrefslogtreecommitdiffstats
path: root/binding
diff options
context:
space:
mode:
Diffstat (limited to 'binding')
-rw-r--r--binding/CMakeLists.txt37
-rw-r--r--binding/network-api.c1886
-rw-r--r--binding/network-api.h257
-rw-r--r--binding/network-common.h183
-rw-r--r--binding/network-connman.c555
-rw-r--r--binding/network-util.c1015
6 files changed, 3933 insertions, 0 deletions
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
new file mode 100644
index 0000000..4da88ce
--- /dev/null
+++ b/binding/CMakeLists.txt
@@ -0,0 +1,37 @@
+###########################################################################
+# 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.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(network-binding)
+
+ # Define project Targets
+ add_library(${TARGET_NAME} MODULE
+ network-api.c
+ network-connman.c
+ network-util.c)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX "afb-"
+ 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/network-api.c b/binding/network-api.c
new file mode 100644
index 0000000..3c4cf29
--- /dev/null
+++ b/binding/network-api.c
@@ -0,0 +1,1886 @@
+/*
+ * 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 2
+#include <afb/afb-binding.h>
+
+#include "network-api.h"
+#include "network-common.h"
+
+struct network_state *global_ns;
+
+/**
+ * The global thread
+ */
+static GThread *global_thread;
+
+struct init_data {
+ GCond cond;
+ GMutex mutex;
+ gboolean init_done;
+ struct network_state *ns; /* before setting global_ns */
+ int rc;
+};
+
+static void signal_init_done(struct init_data *id, int rc);
+
+static void call_work_lock(struct network_state *ns)
+{
+ g_mutex_lock(&ns->cw_mutex);
+}
+
+static void call_work_unlock(struct network_state *ns)
+{
+ g_mutex_unlock(&ns->cw_mutex);
+}
+
+struct call_work *call_work_lookup_unlocked(
+ struct network_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 network_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 network_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 network_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 network_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 network_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, const char *connman_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->connman_method = g_strdup(connman_method);
+
+ ns->cw_pending = g_slist_prepend(ns->cw_pending, cw);
+
+ return cw;
+}
+
+struct call_work *call_work_create(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, const char *connman_method,
+ GError **error)
+{
+
+ struct call_work *cw;
+
+ g_mutex_lock(&ns->cw_mutex);
+ cw = call_work_create_unlocked(ns,
+ access_type, type_arg, method, connman_method,
+ error);
+ g_mutex_unlock(&ns->cw_mutex);
+
+ return cw;
+}
+
+void call_work_destroy_unlocked(struct call_work *cw)
+{
+ struct network_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);
+
+ g_free(cw->access_type);
+ g_free(cw->type_arg);
+ g_free(cw->method);
+ g_free(cw->connman_method);
+}
+
+void call_work_destroy(struct call_work *cw)
+{
+ struct network_state *ns = cw->ns;
+
+ g_mutex_lock(&ns->cw_mutex);
+ call_work_destroy_unlocked(cw);
+ g_mutex_unlock(&ns->cw_mutex);
+}
+
+static struct afb_event *get_event_from_value(struct network_state *ns,
+ const char *value)
+{
+ if (!g_strcmp0(value, "global_state"))
+ return &ns->global_state_event;
+
+ if (!g_strcmp0(value, "technologies"))
+ return &ns->technologies_event;
+
+ if (!g_strcmp0(value, "technology_properties"))
+ return &ns->technology_properties_event;
+
+ if (!g_strcmp0(value, "services"))
+ return &ns->services_event;
+
+ if (!g_strcmp0(value, "service_properties"))
+ return &ns->service_properties_event;
+
+ if (!g_strcmp0(value, "counter"))
+ return &ns->counter_event;
+
+ if (!g_strcmp0(value, "agent"))
+ return &ns->agent_event;
+
+ return NULL;
+}
+
+static void network_manager_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 network_state *ns = user_data;
+ GVariantIter *array1, *array2, *array3;
+ GError *error = NULL;
+ GVariant *var = NULL;
+ const gchar *path = NULL;
+ const gchar *key = NULL;
+ const gchar *basename;
+ json_object *jresp = NULL, *jobj, *jprop;
+ struct afb_event *event = NULL;
+ 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, "TechnologyAdded")) {
+
+ g_variant_get(parameters, "o(a{sv})", &path, &array);
+
+ basename = connman_strip_path(path);
+ g_assert(basename); /* guaranteed by dbus */
+
+ jresp = json_object_new_object();
+
+ json_object_object_add(jresp, "technology",
+ json_object_new_string(basename));
+ json_object_object_add(jresp, "action",
+ json_object_new_string("added"));
+
+ jobj = json_object_new_object();
+ while (g_variant_iter_loop(array, "{sv}", &key, &var)) {
+ ret = technology_property_dbus2json(jobj,
+ key, var, &is_config, &error);
+ if (!ret) {
+ AFB_WARNING("%s property %s - %s",
+ "technology",
+ key, error->message);
+ g_clear_error(&error);
+ }
+ }
+ g_variant_iter_free(array);
+
+ json_object_object_add(jresp, "properties", jobj);
+
+ event = &ns->technologies_event;
+
+
+ } else if (!g_strcmp0(signal_name, "TechnologyRemoved")) {
+
+ g_variant_get(parameters, "o", &path);
+
+ basename = connman_strip_path(path);
+ g_assert(basename); /* guaranteed by dbus */
+
+ jresp = json_object_new_object();
+
+ json_object_object_add(jresp, "technology",
+ json_object_new_string(basename));
+ json_object_object_add(jresp, "action",
+ json_object_new_string("removed"));
+
+ event = &ns->technologies_event;
+
+ } else if (!g_strcmp0(signal_name, "ServicesChanged")) {
+
+ jresp = json_object_new_array();
+
+ g_variant_get(parameters, "(a(oa{sv})ao)", &array1, &array2);
+
+ while (g_variant_iter_loop(array1, "(oa{sv})", &path, &array3)) {
+
+ basename = connman_strip_path(path);
+ g_assert(basename); /* guaranteed by dbus */
+
+ jobj = json_object_new_object();
+
+ json_object_object_add(jobj, "service",
+ json_object_new_string(basename));
+
+ jprop = NULL;
+ while (g_variant_iter_loop(array3,
+ "{sv}", &key, &var)) {
+ if (!jprop)
+ jprop = json_object_new_object();
+ ret = service_property_dbus2json(jprop, key, var,
+ &is_config, &error);
+ if (!ret) {
+ AFB_WARNING("%s property %s - %s",
+ "service",
+ key, error->message);
+ g_clear_error(&error);
+ }
+ }
+
+ json_object_object_add(jobj, "action",
+ json_object_new_string(jprop ?
+ "changed" : "unchanged"));
+
+ if (jprop)
+ json_object_object_add(jobj, "properties",
+ jprop);
+
+ json_object_array_add(jresp, jobj);
+ }
+
+ while (g_variant_iter_loop(array2, "o", &path)) {
+
+ basename = connman_strip_path(path);
+ g_assert(basename); /* guaranteed by dbus */
+
+ jobj = json_object_new_object();
+
+ json_object_object_add(jobj, "service",
+ json_object_new_string(basename));
+
+ json_object_object_add(jobj, "action",
+ json_object_new_string("removed"));
+
+ json_object_array_add(jresp, jobj);
+ }
+
+ g_variant_iter_free(array2);
+ g_variant_iter_free(array1);
+
+ event = &ns->services_event;
+
+ } else if (!g_strcmp0(signal_name, "PropertyChanged")) {
+
+ jresp = json_object_new_object();
+
+ g_variant_get(parameters, "(sv)", &key, &var);
+ g_clear_error(&error);
+ ret = manager_property_dbus2json(jresp,
+ key, var, &is_config, &error);
+ if (!ret) {
+ AFB_WARNING("%s property %s - %s",
+ "manager",
+ key, error->message);
+ g_clear_error(&error);
+ json_object_put(jresp);
+ jresp = NULL;
+ } else
+ event = &ns->global_state_event;
+ }
+
+ /* if (jresp)
+ printf("manager-signal\n%s\n",
+ json_object_to_json_string_ext(jresp,
+ JSON_C_TO_STRING_PRETTY)); */
+
+ if (event && jresp) {
+ afb_event_push(*event, jresp);
+ jresp = NULL;
+ }
+
+ json_object_put(jresp);
+}
+
+static void network_technology_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 network_state *ns = user_data;
+ GError *error = NULL;
+ GVariant *var = NULL;
+ const gchar *key = NULL;
+ const gchar *basename;
+ json_object *jresp = NULL, *jobj;
+ struct afb_event *event = NULL;
+ 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); */
+
+ /* a basename must exist and be at least 1 character wide */
+ basename = connman_strip_path(object_path);
+ g_assert(basename);
+
+ if (!g_strcmp0(signal_name, "PropertyChanged")) {
+
+ jobj = json_object_new_object();
+
+ g_variant_get(parameters, "(sv)", &key, &var);
+
+ g_clear_error(&error);
+ ret = technology_property_dbus2json(jobj, key, var, &is_config,
+ &error) ;
+ g_variant_unref(var);
+ var = NULL;
+
+ if (!ret) {
+ AFB_ERROR("unhandled manager property %s - %s",
+ key, error->message);
+ json_object_put(jobj);
+ return;
+ }
+
+ jresp = json_object_new_object();
+
+ json_object_object_add(jresp, "technology",
+ json_object_new_string(basename));
+
+ json_object_object_add(jresp, "properties", jobj);
+ event = &ns->technology_properties_event;
+ }
+
+ /* if (jresp)
+ printf("technology-signal\n%s\n",
+ json_object_to_json_string_ext(jresp,
+ JSON_C_TO_STRING_PRETTY)); */
+
+ if (event && jresp) {
+ afb_event_push(*event, jresp);
+ jresp = NULL;
+ }
+
+ json_object_put(jresp);
+}
+
+static void network_service_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 network_state *ns = user_data;
+ GError *error = NULL;
+ GVariant *var = NULL;
+ const gchar *key = NULL;
+ const gchar *basename;
+ json_object *jresp = NULL, *jobj;
+ struct afb_event *event = NULL;
+ 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); */
+
+ /* a basename must exist and be at least 1 character wide */
+ basename = connman_strip_path(object_path);
+ g_assert(basename);
+
+ if (!g_strcmp0(signal_name, "PropertyChanged")) {
+
+ g_variant_get(parameters, "(sv)", &key, &var);
+
+ jobj = json_object_new_object();
+ ret = service_property_dbus2json(jobj,
+ key, var, &is_config, &error);
+
+ g_variant_unref(var);
+ var = NULL;
+
+ if (!ret) {
+ AFB_ERROR("unhandled %s property %s - %s",
+ "service", key, error->message);
+ json_object_put(jobj);
+ return;
+ }
+
+ jresp = json_object_new_object();
+
+ json_object_object_add(jresp, "service",
+ json_object_new_string(basename));
+
+ json_object_object_add(jresp, "properties",
+ jobj);
+
+ event = &ns->service_properties_event;
+ }
+
+ /* if (jresp)
+ printf("service-signal\n%s\n",
+ json_object_to_json_string_ext(jresp,
+ JSON_C_TO_STRING_PRETTY)); */
+
+ if (event && jresp) {
+ afb_event_push(*event, jresp);
+ jresp = NULL;
+ }
+
+ json_object_put(jresp);
+}
+
+/* Introspection data for the agent service */
+static const gchar introspection_xml[] =
+"<node>"
+" <interface name='net.connman.Agent'>"
+" <method name='RequestInput'>"
+" <arg type='o' name='service' direction='in'/>"
+" <arg type='a{sv}' name='fields' direction='in'/>"
+" <arg type='a{sv}' name='fields' direction='out'/>"
+" </method>"
+" <method name='ReportError'>"
+" <arg type='o' name='service' direction='in'/>"
+" <arg type='s' name='error' direction='in'/>"
+" </method>"
+" </interface>"
+"</node>";
+
+static const struct property_info agent_request_input_props[] = {
+ {
+ .name = "Name",
+ .fmt = "{sv}",
+ .sub = (const struct property_info []) {
+ { .name = "Type", .fmt = "s", },
+ { .name = "Requirement", .fmt = "s", },
+ { .name = "Alternates", .fmt = "s", },
+ { },
+ },
+ }, {
+ .name = "SSID",
+ .fmt = "{sv}",
+ .sub = (const struct property_info []) {
+ { .name = "Type", .fmt = "s", },
+ { .name = "Requirement", .fmt = "s", },
+ { },
+ },
+ }, {
+ .name = "Identity",
+ .fmt = "{sv}",
+ .sub = (const struct property_info []) {
+ { .name = "Type", .fmt = "s", },
+ { .name = "Requirement", .fmt = "s", },
+ { },
+ },
+ }, {
+ .name = "Passphrase",
+ .fmt = "{sv}",
+ .sub = (const struct property_info []) {
+ { .name = "Type", .fmt = "s", },
+ { .name = "Requirement", .fmt = "s", },
+ { },
+ },
+ }, {
+ .name = "WPS",
+ .fmt = "{sv}",
+ .sub = (const struct property_info []) {
+ { .name = "Type", .fmt = "s", },
+ { .name = "Requirement", .fmt = "s", },
+ { },
+ },
+ },
+ { }
+};
+
+static const struct property_info agent_request_input_out_props[] = {
+ {
+ .name = "Name",
+ .fmt = "s",
+ }, {
+ .name = "SSID",
+ .fmt = "s",
+ }, {
+ .name = "Identity",
+ .fmt = "s",
+ }, {
+ .name = "Passphrase",
+ .fmt = "s",
+ }, {
+ .name = "WPS",
+ .fmt = "s",
+ },
+ { }
+};
+
+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 network_state *ns = user_data;
+ struct call_work *cw;
+ json_object *jev = NULL, *jprop;
+ GVariantIter *array;
+ const gchar *path = NULL;
+ const gchar *service = NULL;
+ const gchar *key = NULL;
+ const gchar *strerr = NULL;
+ GVariant *var = NULL;
+ gboolean is_config;
+
+ /* 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, "RequestInput")) {
+
+ jev = json_object_new_object();
+ jprop = json_object_new_object();
+ g_variant_get(parameters, "(oa{sv})", &path, &array);
+ while (g_variant_iter_loop(array, "{sv}", &key, &var)) {
+ root_property_dbus2json(jprop, agent_request_input_props,
+ key, var, &is_config);
+ }
+ g_variant_iter_free(array);
+
+ service = connman_strip_path(path);
+
+ call_work_lock(ns);
+
+ /* can only occur while a connect is issued */
+ cw = call_work_lookup_unlocked(ns, "service", service,
+ "connect_service");
+
+ /* if nothing is pending return an error */
+ if (!cw) {
+ call_work_unlock(ns);
+ json_object_put(jprop);
+ g_dbus_method_invocation_return_dbus_error(invocation,
+ "net.connman.Agent.Error.Canceled",
+ "No connection pending");
+ return;
+ }
+
+ json_object_object_add(jev, "id",
+ json_object_new_int(cw->id));
+ json_object_object_add(jev, "method",
+ json_object_new_string("request-input"));
+ json_object_object_add(jev, "service",
+ json_object_new_string(service));
+ json_object_object_add(jev, "fields", jprop);
+
+ cw->agent_method = "RequestInput";
+ cw->invocation = invocation;
+
+ call_work_unlock(ns);
+
+ /* AFB_INFO("request-input: jev=%s",
+ json_object_to_json_string(jev)); */
+
+ afb_event_push(ns->agent_event, jev);
+
+ return;
+ }
+
+ if (!g_strcmp0(method_name, "ReportError")) {
+
+ g_variant_get(parameters, "(os)", &path, &strerr);
+
+ AFB_INFO("report-error: service_path=%s error=%s",
+ path, strerr);
+
+ 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 network_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 = manager_call(ns, "RegisterAgent",
+ g_variant_new("(o)", ns->agent_path),
+ &error);
+ if (!result) {
+ AFB_ERROR("failed to register agent to connman");
+ goto err_unable_to_register_connman;
+ }
+ 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_register_connman:
+ g_dbus_connection_unregister_object(ns->conn, ns->registration_id);
+ ns->registration_id = 0;
+err_unable_to_register_bus:
+ signal_init_done(id, -1);
+}
+
+static int network_register_agent(struct init_data *id)
+{
+ struct network_state *ns = id->ns;
+
+ ns->agent_path = g_strdup_printf("%s/agent%d",
+ CONNMAN_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, AGENT_SERVICE,
+ 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;
+}
+
+static void network_unregister_agent(struct network_state *ns)
+{
+ g_bus_unown_name(ns->agent_id);
+ g_dbus_node_info_unref(ns->introspection_data);
+ g_free(ns->agent_path);
+}
+
+static struct network_state *network_init(GMainLoop *loop)
+{
+ struct network_state *ns;
+ GError *error = NULL;
+
+ ns = g_try_malloc0(sizeof(*ns));
+ if (!ns) {
+ AFB_ERROR("out of memory allocating network 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->global_state_event =
+ afb_daemon_make_event("global_state");
+ ns->technologies_event =
+ afb_daemon_make_event("tecnologies_event");
+ ns->technology_properties_event =
+ afb_daemon_make_event("tecnology_properties");
+ ns->services_event =
+ afb_daemon_make_event("services_event");
+ ns->service_properties_event =
+ afb_daemon_make_event("service_properties");
+ ns->counter_event =
+ afb_daemon_make_event("counter");
+ ns->agent_event =
+ afb_daemon_make_event("agent");
+
+ if (!afb_event_is_valid(ns->global_state_event) ||
+ !afb_event_is_valid(ns->technologies_event) ||
+ !afb_event_is_valid(ns->technology_properties_event) ||
+ !afb_event_is_valid(ns->services_event) ||
+ !afb_event_is_valid(ns->service_properties_event) ||
+ !afb_event_is_valid(ns->counter_event) ||
+ !afb_event_is_valid(ns->agent_event)) {
+ AFB_ERROR("Cannot create events");
+ goto err_no_events;
+ }
+
+ ns->manager_sub = g_dbus_connection_signal_subscribe(
+ ns->conn,
+ NULL, /* sender */
+ CONNMAN_MANAGER_INTERFACE,
+ NULL, /* member */
+ NULL, /* object path */
+ NULL, /* arg0 */
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ network_manager_signal_callback,
+ ns,
+ NULL);
+ if (!ns->manager_sub) {
+ AFB_ERROR("Unable to subscribe to manager signal");
+ goto err_no_manager_sub;
+ }
+
+ ns->technology_sub = g_dbus_connection_signal_subscribe(
+ ns->conn,
+ NULL, /* sender */
+ CONNMAN_TECHNOLOGY_INTERFACE,
+ NULL, /* member */
+ NULL, /* object path */
+ NULL, /* arg0 */
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ network_technology_signal_callback,
+ ns,
+ NULL);
+ if (!ns->technology_sub) {
+ AFB_ERROR("Unable to subscribe to technology signal");
+ goto err_no_technology_sub;
+ }
+
+ ns->service_sub = g_dbus_connection_signal_subscribe(
+ ns->conn,
+ NULL, /* sender */
+ CONNMAN_SERVICE_INTERFACE,
+ NULL, /* member */
+ NULL, /* object path */
+ NULL, /* arg0 */
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ network_service_signal_callback,
+ ns,
+ NULL);
+ if (!ns->service_sub) {
+ AFB_ERROR("Unable to subscribe to service signal");
+ goto err_no_service_sub;
+ }
+
+ g_mutex_init(&ns->cw_mutex);
+ ns->next_cw_id = 1;
+
+ return ns;
+
+err_no_service_sub:
+ g_dbus_connection_signal_unsubscribe(ns->conn, ns->technology_sub);
+err_no_technology_sub:
+ g_dbus_connection_signal_unsubscribe(ns->conn, ns->manager_sub);
+err_no_manager_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 network_cleanup(struct network_state *ns)
+{
+ g_dbus_connection_signal_unsubscribe(ns->conn, ns->service_sub);
+ g_dbus_connection_signal_unsubscribe(ns->conn, ns->technology_sub);
+ g_dbus_connection_signal_unsubscribe(ns->conn, ns->manager_sub);
+ g_dbus_connection_close(ns->conn, NULL, NULL, NULL);
+ g_free(ns);
+}
+
+static 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);
+}
+
+static gpointer network_func(gpointer ptr)
+{
+ struct init_data *id = ptr;
+ struct network_state *ns;
+ GMainLoop *loop;
+ int rc;
+
+ loop = g_main_loop_new(NULL, FALSE);
+ if (!loop) {
+ AFB_ERROR("Unable to create main loop");
+ goto err_no_loop;
+ }
+
+ /* real network init */
+ ns = network_init(loop);
+ if (!ns) {
+ AFB_ERROR("network_init() failed");
+ goto err_no_ns;
+ }
+
+ id->ns = ns;
+ rc = network_register_agent(id);
+ if (rc) {
+ AFB_ERROR("network_register_agent() failed");
+ goto err_no_agent;
+ }
+
+ /* note that we wait for agent registration to signal done */
+
+ global_ns = ns;
+ g_main_loop_run(loop);
+
+ g_main_loop_unref(ns->loop);
+
+ network_unregister_agent(ns);
+
+ network_cleanup(ns);
+ global_ns = NULL;
+
+ return NULL;
+
+err_no_agent:
+ network_cleanup(ns);
+
+err_no_ns:
+ g_main_loop_unref(loop);
+
+err_no_loop:
+ signal_init_done(id, -1);
+
+ return NULL;
+}
+
+static int init(void)
+{
+ struct init_data init_data, *id = &init_data;
+ gint64 end_time;
+
+ memset(id, 0, sizeof(*id));
+ id->init_done = FALSE;
+ id->rc = 0;
+ g_cond_init(&id->cond);
+ g_mutex_init(&id->mutex);
+
+ global_thread = g_thread_new("agl-service-network",
+ network_func,
+ id);
+
+ AFB_INFO("network-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("network-binding init timeout");
+ return -1;
+ }
+
+ if (id->rc)
+ AFB_ERROR("network-binding init thread returned %d",
+ id->rc);
+ else
+ AFB_INFO("network-binding operational");
+
+ return id->rc;
+}
+
+static void network_ping(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ json_object *jresp = json_object_new_object();
+ json_object *jev = json_object_new_object();
+
+ /* push fake counter event */
+ ns->ping_counter++;
+ jev = json_object_new_object();
+ json_object_object_add(jev, "counter",
+ json_object_new_int(ns->ping_counter));
+ afb_event_push(ns->counter_event, jev);
+
+ afb_req_success(request, jresp, "Network binding - pong");
+}
+
+static void network_subscribe_unsubscribe(struct afb_req request,
+ gboolean unsub)
+{
+ struct network_state *ns = global_ns;
+ json_object *jresp = json_object_new_object();
+ const char *value;
+ struct afb_event *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, "Network %s to event \"%s\"",
+ !unsub ? "subscribed" : "unsubscribed",
+ value);
+}
+
+static void network_subscribe(struct afb_req request)
+{
+ network_subscribe_unsubscribe(request, FALSE);
+}
+
+static void network_unsubscribe(struct afb_req request)
+{
+ network_subscribe_unsubscribe(request, TRUE);
+}
+
+static void network_state(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ GError *error = NULL;
+ json_object *jresp;
+
+ jresp = manager_get_property(ns, FALSE, "State", &error);
+ if (!jresp) {
+ afb_req_fail_f(request, "failed", "property %s error %s",
+ "State", error->message);
+ return;
+ }
+
+ afb_req_success(request, jresp, "Network - state");
+}
+
+static void network_offline(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ GError *error = NULL;
+ json_object *jresp = NULL;
+ const char *value;
+ int set_to;
+ gboolean ret;
+
+ /* if value exists means to set offline mode */
+ value = afb_req_value(request, "value");
+ if (!value) {
+ jresp = manager_get_property(ns, FALSE, "OfflineMode", &error);
+ if (!jresp) {
+ afb_req_fail_f(request, "failed", "property %s error %s",
+ "OfflineMode", error->message);
+ g_error_free(error);
+ return;
+ }
+ } else {
+ set_to = str2boolean(value);
+ if (set_to < 0) {
+ afb_req_fail_f(request, "failed", "bad value \"%s\"",
+ value);
+ return;
+ }
+ ret = manager_set_property(ns, FALSE, "OfflineMode",
+ json_object_new_boolean(set_to), &error);
+ if (!ret) {
+ afb_req_fail_f(request, "failed",
+ "Network - offline mode set to %s failed - %s",
+ set_to ? "true" : "false", error->message);
+ g_error_free(error);
+ return;
+ }
+ }
+
+ afb_req_success(request, jresp, "Network - offline mode");
+}
+
+static void network_technologies(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ json_object *jresp;
+ GError *error = NULL;
+
+ /* if value exists means to set offline mode */
+ jresp = technology_properties(ns, &error, NULL);
+ if (!jresp) {
+ afb_req_fail_f(request, "failed", "technology properties error %s",
+ error->message);
+ g_error_free(error);
+ return;
+ }
+
+ afb_req_success(request, jresp, "Network - Network Technologies");
+}
+
+static void network_services(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ json_object *jresp;
+ GError *error = NULL;
+
+ jresp = service_properties(ns, &error, NULL);
+ if (!jresp) {
+ afb_req_fail_f(request, "failed", "service properties error %s",
+ error->message);
+ g_error_free(error);
+ return;
+ }
+
+ afb_req_success(request, jresp, "Network - Network Services");
+}
+
+static void network_technology_set_powered(struct afb_req request,
+ gboolean powered)
+{
+ struct network_state *ns = global_ns;
+ GError *error = NULL;
+ const char *technology;
+ json_object *jpowered;
+ gboolean ret, this_powered;
+
+ /* if value exists means to set offline mode */
+ technology = afb_req_value(request, "technology");
+ if (!technology) {
+ afb_req_fail(request, "failed",
+ "technology argument missing");
+ return;
+ }
+
+ jpowered = technology_get_property(ns, technology,
+ FALSE, "Powered", &error);
+ if (!jpowered) {
+ afb_req_fail_f(request, "failed",
+ "Network - failed to get current Powered state - %s",
+ error->message);
+ g_error_free(error);
+ return;
+ }
+ this_powered = json_object_get_boolean(jpowered);
+ json_object_put(jpowered);
+ jpowered = NULL;
+
+ if (this_powered == powered) {
+ afb_req_success_f(request, NULL,
+ "Network - Technology %s already %s",
+ technology, powered ? "enabled" : "disabled");
+ return;
+ }
+
+ ret = technology_set_property(ns, technology,
+ FALSE, "Powered",
+ json_object_new_boolean(powered), &error);
+ if (!ret) {
+ afb_req_fail_f(request, "failed",
+ "Network - failed to set Powered state - %s",
+ error->message);
+ g_error_free(error);
+ return;
+ }
+
+ afb_req_success_f(request, NULL, "Network - Technology %s %s",
+ technology, powered ? "enabled" : "disabled");
+}
+
+static void network_enable_technology(struct afb_req request)
+{
+ return network_technology_set_powered(request, TRUE);
+}
+
+static void network_disable_technology(struct afb_req request)
+{
+ return network_technology_set_powered(request, FALSE);
+}
+
+static void network_scan_services(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ const char *technology;
+
+ /* if value exists means to set offline mode */
+ technology = afb_req_value(request, "technology");
+ if (!technology) {
+ afb_req_fail(request, "failed", "No technology given to enable");
+ return;
+ }
+
+ reply = technology_call(ns, technology, "Scan", NULL, &error);
+ if (!reply) {
+ afb_req_fail_f(request, "failed",
+ "technology %s method %s error %s",
+ technology, "Scan", error->message);
+ g_error_free(error);
+ return;
+ }
+ g_variant_unref(reply);
+
+ afb_req_success_f(request, json_object_new_object(),
+ "Network - technology %s scan complete",
+ technology);
+}
+
+static void network_move_service(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ const char *service;
+ const char *other_service;
+ const char *method_name;
+ gboolean before_after; /* false=before, true=after */
+
+ /* first service */
+ service = afb_req_value(request, "service");
+ if (!service) {
+ afb_req_fail(request, "failed", "No service given to move");
+ return;
+ }
+
+ before_after = FALSE;
+ other_service = afb_req_value(request, "before_service");
+ if (!other_service) {
+ before_after = TRUE;
+ other_service = afb_req_value(request, "after_service");
+ }
+
+ if (!other_service) {
+ afb_req_fail(request, "failed", "No other service given for move");
+ return;
+ }
+
+ method_name = before_after ? "MoveAfter" : "MoveBefore";
+
+ reply = service_call(ns, service, method_name,
+ g_variant_new("o", CONNMAN_SERVICE_PATH(other_service)),
+ &error);
+ if (!reply) {
+ afb_req_fail_f(request, "failed", "%s error %s",
+ method_name,
+ error ? error->message : "unspecified");
+ g_error_free(error);
+ return;
+ }
+ g_variant_unref(reply);
+
+ afb_req_success_f(request, NULL, "Network - service %s moved %s service %s",
+ service, before_after ? "before" : "after", other_service);
+}
+
+static void network_remove_service(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ const char *service;
+
+ /* first service */
+ service = afb_req_value(request, "service");
+ if (!service) {
+ afb_req_fail(request, "failed", "No service");
+ return;
+ }
+
+ reply = service_call(ns, service, "Remove", NULL, &error);
+ if (!reply) {
+ afb_req_fail_f(request, "failed", "Remove error %s",
+ error ? error->message : "unspecified");
+ g_error_free(error);
+ return;
+ }
+ g_variant_unref(reply);
+
+ afb_req_success_f(request, NULL, "Network - service %s removed",
+ service);
+}
+
+/* reset_counters as async implementation for illustrative purposes */
+
+static void reset_counters_callback(void *user_data,
+ GVariant *result, GError **error)
+{
+ struct call_work *cw = user_data;
+ struct network_state *ns = cw->ns;
+
+ connman_decode_call_error(ns,
+ cw->access_type, cw->type_arg, cw->connman_method,
+ error);
+ if (error && *error) {
+ afb_req_fail_f(cw->request, "failed", "%s error %s",
+ cw->method, (*error)->message);
+ goto out_free;
+ }
+
+ if (result)
+ g_variant_unref(result);
+
+ afb_req_success_f(cw->request, NULL, "Network - service %s %s",
+ cw->type_arg, cw->method);
+out_free:
+ afb_req_unref(cw->request);
+ call_work_destroy(cw);
+}
+
+static void network_reset_counters(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ GError *error = NULL;
+ const char *service;
+ struct call_work *cw;
+
+ /* first service */
+ service = afb_req_value(request, "service");
+ if (!service) {
+ afb_req_fail(request, "failed",
+ "No service given");
+ return;
+ }
+
+ cw = call_work_create(ns, "service", service,
+ "reset_counters", "ResetCounters", &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 = connman_call_async(ns, "service", service,
+ "ResetCounters", NULL, &error,
+ reset_counters_callback, cw);
+ if (!cw->cpw) {
+ afb_req_fail_f(request, "failed", "reset counters error %s",
+ error->message);
+ call_work_destroy(cw);
+ g_error_free(error);
+ return;
+ }
+}
+
+static void connect_service_callback(void *user_data,
+ GVariant *result, GError **error)
+{
+ struct call_work *cw = user_data;
+ struct network_state *ns = cw->ns;
+ struct json_object *jerr;
+ GError *sub_error = NULL;
+
+ connman_decode_call_error(ns,
+ cw->access_type, cw->type_arg, cw->connman_method,
+ error);
+ if (error && *error) {
+ /* read the Error property (if available to be specific) */
+
+ jerr = service_get_property(ns, cw->type_arg, FALSE,
+ "Error", &sub_error);
+ g_clear_error(&sub_error);
+ if (jerr) {
+ /* clear property error */
+ service_call(ns, cw->type_arg, "ClearProperty",
+ NULL, &sub_error);
+ g_clear_error(&sub_error);
+
+ afb_req_fail_f(cw->request, "failed", "Connect error: %s",
+ json_object_get_string(jerr));
+ json_object_put(jerr);
+ jerr = NULL;
+ } else
+ 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, "Network - service %s connected",
+ cw->type_arg);
+out_free:
+ afb_req_unref(cw->request);
+ call_work_destroy(cw);
+}
+
+static void network_connect_service(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ GError *error = NULL;
+ const char *service;
+ struct call_work *cw;
+
+ /* first service */
+ service = afb_req_value(request, "service");
+ if (!service) {
+ afb_req_fail(request, "failed",
+ "No service given");
+ return;
+ }
+
+ cw = call_work_create(ns, "service", service,
+ "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);
+ cw->cpw = connman_call_async(ns, "service", service,
+ "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 network_disconnect_service(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ json_object *jresp;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ const char *service;
+
+ /* first service */
+ service = afb_req_value(request, "service");
+ if (!service) {
+ afb_req_fail(request, "failed", "No service given to move");
+ return;
+ }
+
+ reply = service_call(ns, service, "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, "Network - service %s disconnected",
+ service);
+}
+
+static void network_get_property(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ json_object *jobj = afb_req_json(request);
+ const char *technology, *service;
+ const char *access_type;
+ const char *type_arg;
+ GError *error = NULL;
+ json_object *jprop, *jrespprop = NULL, *jreqprop = NULL;
+
+ (void)ns;
+
+ /* printf("%s\n", json_object_to_json_string_ext(jobj,
+ JSON_C_TO_STRING_PRETTY)); */
+
+ /* either or both may be NULL */
+ technology = afb_req_value(request, "technology");
+ service = afb_req_value(request, "service");
+
+ if (technology) {
+ access_type = CONNMAN_AT_TECHNOLOGY;
+ type_arg = technology;
+ } else if (service) {
+ access_type = CONNMAN_AT_SERVICE;
+ type_arg = service;
+ } else {
+ access_type = CONNMAN_AT_MANAGER;
+ type_arg = NULL;
+ }
+
+ jprop = connman_get_properties(ns, access_type, type_arg, &error);
+ if (!jprop) {
+ afb_req_fail_f(request, "failed", "%s property error %s",
+ access_type, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ /* if properties exist, copy out only those */
+ if (json_object_object_get_ex(jobj, "properties", &jreqprop)) {
+ /* and now copy (if found) */
+ jrespprop = get_property_collect(jreqprop, jprop,
+ &error);
+ json_object_put(jprop);
+
+ if (!jrespprop) {
+ afb_req_fail_f(request, "failed", "error - %s",
+ error->message);
+ g_error_free(error);
+ return;
+ }
+ } else /* otherwise everything */
+ jrespprop = jprop;
+
+ afb_req_success_f(request, jrespprop, "Network - %s property get",
+ access_type);
+}
+
+static void network_set_property(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ json_object *jobj = afb_req_json(request);
+ const char *technology, *service;
+ const char *access_type;
+ const char *type_arg;
+ json_object *jprop = NULL;
+ GError *error = NULL;
+ gboolean ret;
+ int count;
+
+ /* printf("%s\n", json_object_to_json_string_ext(jobj,
+ JSON_C_TO_STRING_PRETTY)); */
+
+ if (!json_object_object_get_ex(jobj, "properties", &jprop)) {
+ afb_req_fail_f(request, "failed", "Network - property set no properties");
+ return;
+ }
+
+ /* either or both may be NULL */
+ technology = afb_req_value(request, "technology");
+ service = afb_req_value(request, "service");
+
+ if (technology) {
+ access_type = CONNMAN_AT_TECHNOLOGY;
+ type_arg = technology;
+ } else if (service) {
+ access_type = CONNMAN_AT_SERVICE;
+ type_arg = service;
+ } else {
+ access_type = CONNMAN_AT_MANAGER;
+ type_arg = NULL;
+ }
+
+ /* iterate */
+ count = 0;
+ json_object_object_foreach(jprop, key, jval) {
+ ret = FALSE;
+
+ /* keep jval from being consumed */
+ json_object_get(jval);
+ ret = connman_set_property(ns, access_type, type_arg,
+ TRUE, key, jval, &error);
+ if (!ret) {
+ afb_req_fail_f(request, "failed",
+ "Network - set property %s to %s failed - %s",
+ key, json_object_to_json_string(jval),
+ error->message);
+ g_error_free(error);
+ return;
+ }
+ count++;
+ }
+
+ if (!count) {
+ afb_req_fail_f(request, "failed", "Network - property set empty");
+ return;
+ }
+
+ afb_req_success_f(request, NULL, "Network - %s %d propert%s set",
+ access_type, count, count > 1 ? "ies" : "y");
+}
+
+static void network_agent_response(struct afb_req request)
+{
+ struct network_state *ns = global_ns;
+ json_object *jobj = afb_req_json(request);
+ json_object *jfields;
+ const char *id_str;
+ int id;
+ const struct property_info *pi, *pi_sub;
+ GError *error = NULL;
+ gboolean ret;
+ struct call_work *cw;
+ GVariant *parameters = NULL, *item;
+ GVariantBuilder builder, builder2;
+ gboolean is_config;
+ gchar *dbus_name;
+
+ /* printf("%s\n", json_object_to_json_string(jobj)); */
+
+ /* either or both may be NULL */
+ id_str = afb_req_value(request, "id");
+ id = id_str ? (int)strtol(id_str, NULL, 10) : -1;
+ if (id <= 0) {
+ afb_req_fail_f(request, "failed",
+ "Network - agent response missing arguments");
+ return;
+ }
+
+ call_work_lock(ns);
+ cw = call_work_lookup_by_id_unlocked(ns, id);
+ if (!cw || !cw->invocation) {
+ call_work_unlock(ns);
+ afb_req_fail_f(request, "failed",
+ "Network - can't find request with id=%d", id);
+ return;
+ }
+
+ ret = FALSE;
+ parameters = NULL;
+ if (!g_strcmp0(cw->agent_method, "RequestInput") &&
+ json_object_object_get_ex(jobj, "fields", &jfields)) {
+
+ /* AFB_INFO("request-input response fields=%s",
+ json_object_to_json_string(jfields)); */
+
+ pi = agent_request_input_out_props;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ json_object_object_foreach(jfields, key_o, jval_o) {
+ pi_sub = property_by_json_name(pi, key_o, &is_config);
+ if (!pi_sub) {
+ AFB_ERROR("no property %s", key_o);
+ continue;
+ }
+
+ g_clear_error(&error);
+ item = property_json_to_gvariant(pi_sub, NULL, NULL,
+ jval_o, &error);
+ if (!item) {
+ AFB_ERROR("on %s", key_o);
+ continue;
+ }
+
+ dbus_name = property_get_name(pi, key_o);
+ g_assert(dbus_name); /* can't fail; but check */
+
+ g_variant_builder_add(&builder, "{sv}", dbus_name, item);
+
+ g_free(dbus_name);
+ }
+
+ /* dbus requires response to be wrapped as a tuple */
+ g_variant_builder_init(&builder2, G_VARIANT_TYPE_TUPLE);
+ g_variant_builder_add_value(&builder2,
+ g_variant_builder_end(&builder));
+
+ parameters = g_variant_builder_end(&builder2);
+ ret = TRUE;
+ }
+
+
+ if (!ret) {
+ afb_req_fail_f(request, "failed",
+ "Network - unhandled agent method %s",
+ cw->agent_method);
+ g_dbus_method_invocation_return_dbus_error(cw->invocation,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ "Uknown method");
+ cw->invocation = NULL;
+ call_work_unlock(ns);
+ return;
+ }
+
+ g_dbus_method_invocation_return_value(cw->invocation, parameters);
+ cw->invocation = NULL;
+
+ call_work_unlock(ns);
+
+ afb_req_success_f(request, NULL, "Network - agent response sent");
+}
+
+static const struct afb_verb_v2 network_verbs[] = {
+ {
+ .verb = "ping",
+ .session = AFB_SESSION_NONE,
+ .callback = network_ping,
+ .info ="Check if binding is alive"
+ }, {
+ .verb = "subscribe",
+ .session = AFB_SESSION_NONE,
+ .callback = network_subscribe,
+ .info ="Subscribe to the event of 'value'",
+ }, {
+ .verb = "unsubscribe",
+ .session = AFB_SESSION_NONE,
+ .callback = network_unsubscribe,
+ .info ="Unsubscribe to the event of 'value'",
+ }, {
+ .verb = "state",
+ .session = AFB_SESSION_NONE,
+ .callback = network_state,
+ .info ="Return global network state"
+ }, {
+ .verb = "offline",
+ .session = AFB_SESSION_NONE,
+ .callback = network_offline,
+ .info ="Return global network state"
+ }, {
+ .verb = "technologies",
+ .session = AFB_SESSION_NONE,
+ .callback = network_technologies,
+ .info ="Return list of network technologies"
+ }, {
+ .verb = "services",
+ .session = AFB_SESSION_NONE,
+ .callback = network_services,
+ .info ="Return list of network services"
+ }, {
+ .verb = "enable_technology",
+ .session = AFB_SESSION_NONE,
+ .callback = network_enable_technology,
+ .info ="Enable a technology"
+ }, {
+ .verb = "disable_technology",
+ .session = AFB_SESSION_NONE,
+ .callback = network_disable_technology,
+ .info ="Disable a technology"
+ }, {
+ .verb = "scan_services",
+ .session = AFB_SESSION_NONE,
+ .callback = network_scan_services,
+ .info ="Scan services"
+ }, {
+ .verb = "move_service",
+ .session = AFB_SESSION_NONE,
+ .callback = network_move_service,
+ .info ="Arrange service order"
+ }, {
+ .verb = "remove_service",
+ .session = AFB_SESSION_NONE,
+ .callback = network_remove_service,
+ .info ="Remove service"
+ }, {
+ .verb = "reset_counters",
+ .session = AFB_SESSION_NONE,
+ .callback = network_reset_counters,
+ .info ="Reset service counters"
+ }, {
+ .verb = "connect_service",
+ .session = AFB_SESSION_NONE,
+ .callback = network_connect_service,
+ .info ="Connect service"
+ }, {
+ .verb = "disconnect_service",
+ .session = AFB_SESSION_NONE,
+ .callback = network_disconnect_service,
+ .info ="Disconnect service"
+ }, {
+ .verb = "get_property",
+ .session = AFB_SESSION_NONE,
+ .callback = network_get_property,
+ .info ="Get property"
+ }, {
+ .verb = "set_property",
+ .session = AFB_SESSION_NONE,
+ .callback = network_set_property,
+ .info ="Set property"
+ }, {
+ .verb = "agent_response",
+ .session = AFB_SESSION_NONE,
+ .callback = network_agent_response,
+ .info ="Agent response"
+ },
+
+ { } /* marker for end of the array */
+};
+
+/*
+ * description of the binding for afb-daemon
+ */
+const struct afb_binding_v2 afbBindingV2 = {
+ .api = "network-manager", /* the API name (or binding name or prefix) */
+ .specification = "networking API", /* short description of of the binding */
+ .verbs = network_verbs, /* the array describing the verbs of the API */
+ .init = init,
+};
diff --git a/binding/network-api.h b/binding/network-api.h
new file mode 100644
index 0000000..e01216b
--- /dev/null
+++ b/binding/network-api.h
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+
+#ifndef NETWORK_API_H
+#define NETWORK_API_H
+
+#include <alloca.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <json-c/json.h>
+
+#define CONNMAN_SERVICE "net.connman"
+#define CONNMAN_MANAGER_INTERFACE CONNMAN_SERVICE ".Manager"
+#define CONNMAN_TECHNOLOGY_INTERFACE CONNMAN_SERVICE ".Technology"
+#define CONNMAN_SERVICE_INTERFACE CONNMAN_SERVICE ".Service"
+#define CONNMAN_PROFILE_INTERFACE CONNMAN_SERVICE ".Profile"
+#define CONNMAN_COUNTER_INTERFACE CONNMAN_SERVICE ".Counter"
+#define CONNMAN_ERROR_INTERFACE CONNMAN_SERVICE ".Error"
+#define CONNMAN_AGENT_INTERFACE CONNMAN_SERVICE ".Agent"
+
+#define CONNMAN_MANAGER_PATH "/"
+#define CONNMAN_PATH "/net/connman"
+#define CONNMAN_TECHNOLOGY_PREFIX CONNMAN_PATH "/technology"
+#define CONNMAN_SERVICE_PREFIX CONNMAN_PATH "/service"
+
+#define CONNMAN_TECHNOLOGY_PATH(_t) \
+ ({ \
+ const char *__t = (_t); \
+ size_t __len = strlen(CONNMAN_TECHNOLOGY_PREFIX) + 1 + \
+ strlen(__t) + 1; \
+ char *__tpath; \
+ __tpath = alloca(__len + 1 + 1); \
+ snprintf(__tpath, __len + 1, \
+ CONNMAN_TECHNOLOGY_PREFIX "/%s", __t); \
+ __tpath; \
+ })
+
+#define CONNMAN_SERVICE_PATH(_s) \
+ ({ \
+ const char *__s = (_s); \
+ size_t __len = strlen(CONNMAN_SERVICE_PREFIX) + 1 + \
+ strlen(__s) + 1; \
+ char *__spath; \
+ __spath = alloca(__len + 1 + 1); \
+ snprintf(__spath, __len + 1, \
+ CONNMAN_SERVICE_PREFIX "/%s", __s); \
+ __spath; \
+ })
+
+#define AGENT_PATH "/net/connman/Agent"
+#define AGENT_SERVICE "org.agent"
+
+#define DBUS_REPLY_TIMEOUT (120 * 1000)
+#define DBUS_REPLY_TIMEOUT_SHORT (10 * 1000)
+
+#define CONNMAN_AT_MANAGER "manager"
+#define CONNMAN_AT_TECHNOLOGY "technology"
+#define CONNMAN_AT_SERVICE "service"
+
+struct network_state;
+
+static inline const char *connman_strip_path(const char *path)
+{
+ const char *basename;
+
+ basename = strrchr(path, '/');
+ if (!basename)
+ return NULL;
+ basename++;
+ /* at least one character */
+ return *basename ? basename : NULL;
+}
+
+const struct property_info *connman_get_property_info(
+ const char *access_type, GError **error);
+
+gboolean connman_property_dbus2json(const char *access_type,
+ json_object *jprop, const gchar *key, GVariant *var,
+ gboolean *is_config,
+ GError **error);
+
+GVariant *connman_call(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, GVariant *params, GError **error);
+
+json_object *connman_get_properties(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ GError **error);
+
+json_object *connman_get_property(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ gboolean is_json_name, const char *name, GError **error);
+
+gboolean connman_set_property(struct network_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 manager_property_dbus2json(json_object *jprop,
+ const gchar *key, GVariant *var, gboolean *is_config,
+ GError **error)
+{
+ return connman_property_dbus2json(CONNMAN_AT_MANAGER,
+ jprop, key, var, is_config, error);
+}
+
+static inline gboolean technology_property_dbus2json(json_object *jprop,
+ const gchar *key, GVariant *var, gboolean *is_config,
+ GError **error)
+{
+ return connman_property_dbus2json(CONNMAN_AT_TECHNOLOGY,
+ jprop, key, var, is_config, error);
+}
+
+static inline gboolean service_property_dbus2json(json_object *jprop,
+ const gchar *key, GVariant *var, gboolean *is_config,
+ GError **error)
+{
+ return connman_property_dbus2json(CONNMAN_AT_SERVICE,
+ jprop, key, var, is_config, error);
+}
+
+static inline GVariant *manager_call(struct network_state *ns,
+ const char *method, GVariant *params, GError **error)
+{
+ return connman_call(ns, CONNMAN_AT_MANAGER, NULL,
+ method, params, error);
+}
+
+static inline GVariant *technology_call(struct network_state *ns,
+ const char *technology, const char *method,
+ GVariant *params, GError **error)
+{
+ return connman_call(ns, CONNMAN_AT_TECHNOLOGY, technology,
+ method, params, error);
+}
+
+static inline GVariant *service_call(struct network_state *ns,
+ const char *service, const char *method,
+ GVariant *params, GError **error)
+{
+ return connman_call(ns, CONNMAN_AT_SERVICE, service,
+ method, params, error);
+}
+
+static inline json_object *manager_properties(struct network_state *ns, GError **error)
+{
+ return connman_get_properties(ns,
+ CONNMAN_AT_MANAGER, NULL, error);
+}
+
+static inline json_object *technology_properties(struct network_state *ns,
+ GError **error, const gchar *technology)
+{
+ return connman_get_properties(ns,
+ CONNMAN_AT_TECHNOLOGY, technology, error);
+}
+
+static inline json_object *service_properties(struct network_state *ns,
+ GError **error, const gchar *service)
+{
+ return connman_get_properties(ns,
+ CONNMAN_AT_SERVICE, service, error);
+}
+
+static inline json_object *manager_get_property(struct network_state *ns,
+ gboolean is_json_name, const char *name, GError **error)
+{
+ return connman_get_property(ns, CONNMAN_AT_MANAGER, NULL,
+ is_json_name, name, error);
+}
+
+static inline json_object *technology_get_property(struct network_state *ns,
+ const char *technology,
+ gboolean is_json_name, const char *name, GError **error)
+{
+ return connman_get_property(ns, CONNMAN_AT_TECHNOLOGY, technology,
+ is_json_name, name, error);
+}
+
+static inline json_object *service_get_property(struct network_state *ns,
+ const char *service,
+ gboolean is_json_name, const char *name, GError **error)
+{
+ return connman_get_property(ns, CONNMAN_AT_SERVICE, service,
+ is_json_name, name, error);
+}
+
+static inline gboolean manager_set_property(struct network_state *ns,
+ gboolean is_json_name, const char *name, json_object *jval,
+ GError **error)
+{
+ return connman_set_property(ns, CONNMAN_AT_MANAGER, NULL,
+ is_json_name, name, jval, error);
+
+}
+
+static inline gboolean technology_set_property(struct network_state *ns,
+ const char *technology,
+ gboolean is_json_name, const char *name, json_object *jval,
+ GError **error)
+{
+ return connman_set_property(ns, CONNMAN_AT_TECHNOLOGY, technology,
+ is_json_name, name, jval, error);
+}
+
+static inline gboolean service_set_property(struct network_state *ns,
+ const char *service,
+ gboolean is_json_name, const char *name, json_object *jval,
+ GError **error)
+{
+ return connman_set_property(ns, CONNMAN_AT_SERVICE, service,
+ is_json_name, name, jval, error);
+}
+
+struct connman_pending_work {
+ struct network_state *ns;
+ void *user_data;
+ GCancellable *cancel;
+ void (*callback)(void *user_data, GVariant *result, GError **error);
+};
+
+void connman_cancel_call(struct network_state *ns,
+ struct connman_pending_work *cpw);
+
+struct connman_pending_work *
+connman_call_async(struct network_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 connman_decode_call_error(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method,
+ GError **error);
+
+#endif /* NETWORK_API_H */
diff --git a/binding/network-common.h b/binding/network-common.h
new file mode 100644
index 0000000..83c2e0f
--- /dev/null
+++ b/binding/network-common.h
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+#ifndef NETWORK_COMMON_H
+#define NETWORK_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 2
+#include <afb/afb-binding.h>
+
+struct call_work;
+
+struct network_state {
+ GMainLoop *loop;
+ GDBusConnection *conn;
+ guint manager_sub;
+ guint technology_sub;
+ guint service_sub;
+ struct afb_event global_state_event;
+ struct afb_event technologies_event;
+ struct afb_event technology_properties_event;
+ struct afb_event services_event;
+ struct afb_event service_properties_event;
+ struct afb_event counter_event;
+ struct afb_event agent_event;
+ int ping_counter;
+
+ /* 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 call_work {
+ struct network_state *ns;
+ int id;
+ gchar *access_type;
+ gchar *type_arg;
+ gchar *method;
+ gchar *connman_method;
+ struct connman_pending_work *cpw;
+ struct afb_req request;
+ gchar *agent_method;
+ GDBusMethodInvocation *invocation;
+};
+
+/* in network-api.c */
+
+/*
+ * Unfortunately we can't completely avoid a global here.
+ * appfw API does not pass around a user pointer to do so.
+ */
+extern struct network_state *global_ns;
+
+/* utility methods in network-util.c */
+
+extern gboolean auto_lowercase_keys;
+
+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/network-connman.c b/binding/network-connman.c
new file mode 100644
index 0000000..a9e0b74
--- /dev/null
+++ b/binding/network-connman.c
@@ -0,0 +1,555 @@
+/*
+ * 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 2
+#include <afb/afb-binding.h>
+
+#include "network-api.h"
+#include "network-common.h"
+
+static const struct property_info manager_props[] = {
+ { .name = "State", .fmt = "s", },
+ { .name = "OfflineMode", .fmt = "b", },
+ { .name = "SessionMode", .fmt = "b", },
+ { },
+};
+
+static const struct property_info technology_props[] = {
+ { .name = "Name", .fmt = "s", },
+ { .name = "Type", .fmt = "s", },
+ { .name = "Powered", .fmt = "b", },
+ { .name = "Connected", .fmt = "b", },
+ { .name = "Tethering", .fmt = "b", },
+ { .name = "TetheringIdentifier",.fmt = "s", },
+ { .name = "TetheringPassphrase",.fmt = "s", },
+ { },
+};
+
+static const struct property_info service_props[] = {
+ /* simple types */
+ { .name = "State", .fmt = "s", },
+ { .name = "Error", .fmt = "s", },
+ { .name = "Type", .fmt = "s", },
+ { .name = "Name", .fmt = "s", },
+ { .name = "Favorite", .fmt = "b", },
+ { .name = "Immutable", .fmt = "b", },
+ { .name = "AutoConnect", .fmt = "b", },
+ { .name = "Strength", .fmt = "y", },
+ { .name = "Security", .fmt = "as", },
+ { .name = "Roaming", .fmt = "b", },
+
+ /* complex types with configuration (but no subtype) */
+ {
+ .name = "Nameservers",
+ .fmt = "as",
+ .flags = PI_CONFIG,
+ }, {
+ .name = "Timeservers",
+ .fmt = "as",
+ .flags = PI_CONFIG,
+ }, {
+ .name = "Domains",
+ .fmt = "as",
+ .flags = PI_CONFIG,
+ }, {
+ .name = "mDNS",
+ .fmt = "b",
+ .flags = PI_CONFIG,
+ },
+
+ /* complex types with subtypes */
+ {
+ .name = "IPv4",
+ .fmt = "{sv}",
+ .flags = PI_CONFIG,
+ .sub = (const struct property_info []) {
+ { .name = "Method", .fmt = "s", },
+ { .name = "Address", .fmt = "s", },
+ { .name = "Netmask", .fmt = "s", },
+ { .name = "Gateway", .fmt = "s", },
+ { },
+ },
+ }, {
+ .name = "IPv6",
+ .fmt = "{sv}",
+ .flags = PI_CONFIG,
+ .sub = (const struct property_info []) {
+ { .name = "Method", .fmt = "s", },
+ { .name = "Address", .fmt = "s", },
+ { .name = "PrefixLength", .fmt = "y", },
+ { .name = "Gateway", .fmt = "s", },
+ { .name = "Privacy", .fmt = "s", },
+ { },
+ },
+ }, {
+ .name = "Proxy",
+ .fmt = "{sv}",
+ .flags = PI_CONFIG,
+ .sub = (const struct property_info []) {
+ { .name = "Method", .fmt = "s", },
+ { .name = "URL", .fmt = "s", },
+ { .name = "Servers", .fmt = "as", },
+ { .name = "Excludes", .fmt = "as", },
+ { },
+ },
+ }, {
+ .name = "Ethernet",
+ .fmt = "{sv}",
+ .sub = (const struct property_info []) {
+ { .name = "Method", .fmt = "s", },
+ { .name = "Interface", .fmt = "s", },
+ { .name = "Address", .fmt = "s", },
+ { .name = "MTU", .fmt = "q", },
+ { },
+ },
+ }, {
+ .name = "Provider",
+ .fmt = "{sv}",
+ .flags = PI_CONFIG,
+ .sub = (const struct property_info []) {
+ { .name = "Host", .fmt = "s", },
+ { .name = "Domain", .fmt = "s", },
+ { .name = "Name", .fmt = "s", },
+ { .name = "Type", .fmt = "s", },
+ { },
+ },
+ },
+ { }
+};
+
+const struct property_info *connman_get_property_info(
+ const char *access_type, GError **error)
+{
+ const struct property_info *pi = NULL;
+
+ if (!strcmp(access_type, CONNMAN_AT_MANAGER))
+ pi = manager_props;
+ else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY))
+ pi = technology_props;
+ else if (!strcmp(access_type, CONNMAN_AT_SERVICE))
+ pi = service_props;
+ else
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "illegal %s argument", access_type);
+ return pi;
+}
+
+gboolean connman_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 = connman_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 connman_decode_call_error(struct network_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, "SetProperty") ||
+ !strcmp(method, "GetProperty") ||
+ !strcmp(method, "ClearProperty")) {
+
+ 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, "Disconnect") ||
+ !strcmp(method, "Remove") ||
+ !strcmp(method, "ResetCounters") ||
+ !strcmp(method, "MoveAfter") ||
+ !strcmp(method, "MoveBefore")) {
+
+ g_clear_error(error);
+ g_set_error(error, NB_ERROR,
+ NB_ERROR_UNKNOWN_SERVICE,
+ "unknown service %s",
+ type_arg);
+
+ } else if (!strcmp(method, "Scan")) {
+
+ g_clear_error(error);
+ g_set_error(error, NB_ERROR,
+ NB_ERROR_UNKNOWN_TECHNOLOGY,
+ "unknown technology %s",
+ type_arg);
+ }
+ }
+}
+
+GVariant *connman_call(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ const char *method, GVariant *params, GError **error)
+{
+ const char *path;
+ const char *interface;
+ GVariant *reply;
+
+ if (!type_arg && (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY) ||
+ !strcmp(access_type, CONNMAN_AT_SERVICE))) {
+ g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT,
+ "missing %s argument",
+ access_type);
+ return NULL;
+ }
+
+ if (!strcmp(access_type, CONNMAN_AT_MANAGER)) {
+ path = CONNMAN_MANAGER_PATH;
+ interface = CONNMAN_MANAGER_INTERFACE;
+ } else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY)) {
+ path = CONNMAN_TECHNOLOGY_PATH(type_arg);
+ interface = CONNMAN_TECHNOLOGY_INTERFACE;
+ } else if (!strcmp(access_type, CONNMAN_AT_SERVICE)) {
+ path = CONNMAN_SERVICE_PATH(type_arg);
+ interface = CONNMAN_SERVICE_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,
+ CONNMAN_SERVICE, path, interface, method, params,
+ NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT,
+ NULL, error);
+ connman_decode_call_error(ns, access_type, type_arg, 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,
+ type_arg ? "/" : "",
+ type_arg ? type_arg : "",
+ method,
+ error && *error ? (*error)->message :
+ "unspecified");
+ }
+
+ return reply;
+}
+
+static void connman_call_async_ready(GObject *source_object,
+ GAsyncResult *res, gpointer user_data)
+{
+ struct connman_pending_work *cpw = user_data;
+ struct network_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 connman_cancel_call(struct network_state *ns,
+ struct connman_pending_work *cpw)
+{
+ g_cancellable_cancel(cpw->cancel);
+}
+
+struct connman_pending_work *
+connman_call_async(struct network_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 connman_pending_work *cpw;
+
+ if (!type_arg && (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY) ||
+ !strcmp(access_type, CONNMAN_AT_SERVICE))) {
+ g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT,
+ "missing %s argument",
+ access_type);
+ return NULL;
+ }
+
+ if (!strcmp(access_type, CONNMAN_AT_MANAGER)) {
+ path = CONNMAN_MANAGER_PATH;
+ interface = CONNMAN_MANAGER_INTERFACE;
+ } else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY)) {
+ path = CONNMAN_TECHNOLOGY_PATH(type_arg);
+ interface = CONNMAN_TECHNOLOGY_INTERFACE;
+ } else if (!strcmp(access_type, CONNMAN_AT_SERVICE)) {
+ path = CONNMAN_SERVICE_PATH(type_arg);
+ interface = CONNMAN_SERVICE_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,
+ CONNMAN_SERVICE, path, interface, method, params,
+ NULL, /* reply type */
+ G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT,
+ cpw->cancel, /* cancellable? */
+ connman_call_async_ready,
+ cpw);
+
+ return cpw;
+}
+
+json_object *connman_get_properties(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ GError **error)
+{
+ const struct property_info *pi = NULL;
+ const char *method = NULL;
+ GVariant *reply = NULL;
+ GVariantIter *array, *array2;
+ GVariant *var = NULL;
+ const gchar *path = NULL;
+ const gchar *key = NULL;
+ const gchar *basename;
+ json_object *jprop = NULL, *jresp = NULL, *jtype = NULL;
+ gboolean is_config;
+
+ pi = connman_get_property_info(access_type, error);
+ if (!pi)
+ return NULL;
+
+ method = NULL;
+ if (!strcmp(access_type, CONNMAN_AT_MANAGER))
+ method = "GetProperties";
+ else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY))
+ method = "GetTechnologies";
+ else if (!strcmp(access_type, CONNMAN_AT_SERVICE))
+ method = "GetServices";
+
+ if (!method) {
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "illegal %s argument",
+ access_type);
+ return NULL;
+ }
+
+ reply = connman_call(ns, CONNMAN_AT_MANAGER, NULL,
+ method, NULL, error);
+ if (!reply)
+ return NULL;
+
+ if (!strcmp(access_type, CONNMAN_AT_MANAGER)) {
+ 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 (!type_arg)
+ jresp = json_object_new_array();
+
+ g_variant_get(reply, "(a(oa{sv}))", &array);
+ while (g_variant_iter_loop(array, "(oa{sv})", &path, &array2)) {
+
+ /* a basename must exist and be at least 1 character wide */
+ basename = strrchr(path, '/');
+ if (!basename || strlen(basename) <= 1)
+ continue;
+ basename++;
+
+ if (!type_arg) {
+ jtype = json_object_new_object();
+ json_object_object_add(jtype, access_type,
+ json_object_new_string(basename));
+ } else if (g_strcmp0(basename, type_arg))
+ continue;
+
+ jprop = json_object_new_object();
+ while (g_variant_iter_loop(array2, "{sv}", &key, &var)) {
+ root_property_dbus2json(jprop, pi,
+ key, var, &is_config);
+ }
+
+ if (!type_arg) {
+ json_object_object_add(jtype, "properties", jprop);
+ json_object_array_add(jresp, jtype);
+ }
+ }
+ g_variant_iter_free(array);
+ g_variant_unref(reply);
+
+ if (type_arg && jprop)
+ jresp = jprop;
+
+ }
+
+ if (!jresp) {
+ if (type_arg)
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "Bad %s %s", access_type, type_arg);
+ else
+ g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT,
+ "No %s", access_type);
+ }
+
+ return jresp;
+}
+
+json_object *connman_get_property(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ gboolean is_json_name, const char *name, GError **error)
+{
+ const struct property_info *pi;
+ json_object *jprop, *jval;
+
+ pi = connman_get_property_info(access_type, error);
+ if (!pi)
+ return NULL;
+
+ jprop = connman_get_properties(ns, access_type, type_arg, 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,
+ type_arg ? "/" : "",
+ type_arg ? type_arg : "");
+ return jval;
+}
+
+/* NOTE: jval is consumed */
+gboolean connman_set_property(struct network_state *ns,
+ const char *access_type, const char *type_arg,
+ gboolean is_json_name, const char *name, json_object *jval,
+ GError **error)
+{
+ const struct property_info *pi;
+ GVariant *reply, *arg;
+ gboolean is_config;
+ gchar *propname;
+
+ /* get start of properties */
+ pi = connman_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);
+
+ reply = connman_call(ns, access_type, type_arg,
+ "SetProperty",
+ g_variant_new("(sv)", propname, arg),
+ error);
+
+ g_free(propname);
+
+ if (!reply)
+ return FALSE;
+
+ g_variant_unref(reply);
+
+ return TRUE;
+}
diff --git a/binding/network-util.c b/binding/network-util.c
new file mode 100644
index 0000000..b487501
--- /dev/null
+++ b/binding/network-util.c
@@ -0,0 +1,1015 @@
+/*
+ * 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 2
+#include <afb/afb-binding.h>
+
+#include "network-api.h"
+#include "network-common.h"
+
+G_DEFINE_QUARK(network-binding-error-quark, nb_error)
+
+/* convert dbus key to lower case */
+gboolean auto_lowercase_keys = TRUE;
+
+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_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;
+}