diff options
author | Pantelis Antoniou <pantelis.antoniou@konsulko.com> | 2018-03-08 20:39:36 +0200 |
---|---|---|
committer | Matt Porter <mporter@konsulko.com> | 2018-07-10 08:32:25 -0400 |
commit | 916843c373bc653bc472e6353631134b42d490f2 (patch) | |
tree | 35e89581b8dcdf82d93c064b42b6c7ec9eab3bb3 /binding | |
parent | 8f3e1a6cd811b6b2ccecf4d192249800c521466a (diff) |
initial network service binding
The AGL network service binding exposes connman apis via the
AGL application framework. All network connectivity technologies
are supported via the binding, limited only by the underlying
connman daemon.
Bug-AGL: SPEC-1540
Change-Id: Id73cfc98c7abe97cb655a4fc40d440422fa75803
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
[Cleanups to whitespace and naming to match other services]
Signed-off-by: Matt Porter <mporter@konsulko.com>
Diffstat (limited to 'binding')
-rw-r--r-- | binding/CMakeLists.txt | 37 | ||||
-rw-r--r-- | binding/network-api.c | 1886 | ||||
-rw-r--r-- | binding/network-api.h | 257 | ||||
-rw-r--r-- | binding/network-common.h | 183 | ||||
-rw-r--r-- | binding/network-connman.c | 555 | ||||
-rw-r--r-- | binding/network-util.c | 1015 |
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; +} |