diff options
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; +} |