diff options
author | Pantelis Antoniou <pantelis.antoniou@konsulko.com> | 2018-03-08 20:39:36 +0200 |
---|---|---|
committer | Matt Porter <mporter@konsulko.com> | 2018-07-10 08:32:25 -0400 |
commit | 916843c373bc653bc472e6353631134b42d490f2 (patch) | |
tree | 35e89581b8dcdf82d93c064b42b6c7ec9eab3bb3 | |
parent | 8f3e1a6cd811b6b2ccecf4d192249800c521466a (diff) |
initial network service binding
The AGL network service binding exposes connman apis via the
AGL application framework. All network connectivity technologies
are supported via the binding, limited only by the underlying
connman daemon.
Bug-AGL: SPEC-1540
Change-Id: Id73cfc98c7abe97cb655a4fc40d440422fa75803
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
[Cleanups to whitespace and naming to match other services]
Signed-off-by: Matt Porter <mporter@konsulko.com>
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | .gitreview | 6 | ||||
-rw-r--r-- | CMakeLists.txt | 21 | ||||
-rw-r--r-- | LICENSE | 54 | ||||
-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 | ||||
m--------- | conf.d/app-templates | 0 | ||||
-rwxr-xr-x | conf.d/autobuild/agl/autobuild | 67 | ||||
-rwxr-xr-x | conf.d/autobuild/linux/autobuild | 67 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 162 | ||||
-rw-r--r-- | conf.d/wgt/config.xml.in | 24 | ||||
-rw-r--r-- | test/CMakeLists.txt | 16 | ||||
-rw-r--r-- | test/agl-service-network-ctl.c | 881 |
17 files changed, 5234 insertions, 0 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b545da1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "conf.d/app-templates"] + path = conf.d/app-templates + url = https://gerrit.automotivelinux.org/gerrit/apps/app-templates diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..a864610 --- /dev/null +++ b/.gitreview @@ -0,0 +1,6 @@ +[gerrit] +host=gerrit.automotivelinux.org +port=29418 +project=apps/agl-service-network +defaultbranch=master + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d1f194f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +########################################################################### +# 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. +########################################################################### + +CMAKE_MINIMUM_REQUIRED(VERSION 3.3) + +include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake) @@ -0,0 +1,54 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + You must give any other recipients of the Work or Derivative Works a copy of this License; and + You must cause any modified files to carry prominent notices stating that You changed the files; and + You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS 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; +} diff --git a/conf.d/app-templates b/conf.d/app-templates new file mode 160000 +Subproject 8c2b05967a3237e624a2cc78e13fcd1c5e72991 diff --git a/conf.d/autobuild/agl/autobuild b/conf.d/autobuild/agl/autobuild new file mode 100755 index 0000000..3a1ba5f --- /dev/null +++ b/conf.d/autobuild/agl/autobuild @@ -0,0 +1,67 @@ +#!/usr/bin/make -f +# Copyright (C) 2015, 2016 "IoT.bzh" +# Author "Romain Forlot" <romain.forlot@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build) +DEST := ${BUILD_DIR}/target + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "" + @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: ${BUILD_DIR}/Makefile + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/data + @cmake --build ${BUILD_DIR} --target widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST} + +install: build + @cmake --build ${BUILD_DIR} --target install + +${BUILD_DIR}/Makefile: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) diff --git a/conf.d/autobuild/linux/autobuild b/conf.d/autobuild/linux/autobuild new file mode 100755 index 0000000..3a1ba5f --- /dev/null +++ b/conf.d/autobuild/linux/autobuild @@ -0,0 +1,67 @@ +#!/usr/bin/make -f +# Copyright (C) 2015, 2016 "IoT.bzh" +# Author "Romain Forlot" <romain.forlot@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build) +DEST := ${BUILD_DIR}/target + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build: compilation, link and prepare files for package into a widget" + @echo "- package: output a widget file '*.wgt'" + @echo "- install: install in your ${CMAKE_INSTALL_DIR} directory" + @echo "" + @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: ${BUILD_DIR}/Makefile + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/data + @cmake --build ${BUILD_DIR} --target widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST} + +install: build + @cmake --build ${BUILD_DIR} --target install + +${BUILD_DIR}/Makefile: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake new file mode 100644 index 0000000..337bb2b --- /dev/null +++ b/conf.d/cmake/config.cmake @@ -0,0 +1,162 @@ +########################################################################### +# 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. +########################################################################### + +# Project Info +# ------------------ +set(PROJECT_NAME agl-service-network) +set(PROJECT_VERSION "1.0") +set(PROJECT_PRETTY_NAME "AFM binding for networking") +set(PROJECT_DESCRIPTION "Binding for Networking") +set(PROJECT_ICON "icon.png") +set(PROJECT_AUTHOR "Pantelis Antoniou") +set(PROJECT_AUTHOR_MAIL "pantelis.antoniou@konsulko.com") +set(PROJECT_LICENSE "APL2.0") +set(PROJECT_LANGUAGES,"C") + +# Where are stored default templates files from submodule or subtree app-templates in your project tree +# relative to the root project directory +set(PROJECT_APP_TEMPLATES_DIR "conf.d/app-templates") + +# Where are stored your external libraries for your project. This is 3rd party library that you don't maintain +# but used and must be built and linked. +# set(PROJECT_LIBDIR "libs") + +# Where are stored data for your application. Pictures, static resources must be placed in that folder. +# set(PROJECT_RESOURCES "data") + +# Which directories inspect to find CMakeLists.txt target files +# set(PROJECT_SRC_DIR_PATTERN "*") + +# Compilation Mode (DEBUG, RELEASE) +# ---------------------------------- +set(CMAKE_BUILD_TYPE "DEBUG") + +# Kernel selection if needed. You can choose between a +# mandatory version to impose a minimal version. +# Or check Kernel minimal version and just print a Warning +# about missing features and define a preprocessor variable +# to be used as preprocessor condition in code to disable +# incompatibles features. Preprocessor define is named +# KERNEL_MINIMAL_VERSION_OK. +# +# NOTE*** FOR NOW IT CHECKS KERNEL Yocto environment and +# Yocto SDK Kernel version. +# ----------------------------------------------- +#set(kernel_mandatory_version 4.8) + +# Compiler selection if needed. Impose a minimal version. +# ----------------------------------------------- +set (gcc_minimal_version 4.9) + +# PKG_CONFIG required packages +# ----------------------------- +set (PKG_REQUIRED_LIST + json-c + libsystemd>=222 + afb-daemon + json-c + glib-2.0 + gio-2.0 + gobject-2.0 + zlib +) + +# Static constante definition +# ----------------------------- +add_compile_options(-DPB_FIELD_16BIT) +add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-pthread>) + +# Customize link option +# ----------------------------- +list (APPEND link_libraries -pthread) + +# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable] +# --------------------------------------------------------------------- +set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt) +set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) +set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib) + +# Optional location for config.xml.in +# ----------------------------------- +set(WIDGET_CONFIG_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/conf.d/wgt/config.xml.in) + +# Mandatory widget Mimetype specification of the main unit +# -------------------------------------------------------------------------- +# Choose between : +#- text/html : HTML application, +# content.src designates the home page of the application +# +#- application/vnd.agl.native : AGL compatible native, +# content.src designates the relative path of the binary. +# +# - application/vnd.agl.service: AGL service, content.src is not used. +# +#- ***application/x-executable***: Native application, +# content.src designates the relative path of the binary. +# For such application, only security setup is made. +# +set(WIDGET_TYPE application/vnd.agl.service) + +# Mandatory Widget entry point file of the main unit +# -------------------------------------------------------------- +# This is the file that will be executed, loaded, +# at launch time by the application framework. +# +set(WIDGET_ENTRY_POINT lib/afb-network-binding.so) + +# Print a helper message when every thing is finished +# ---------------------------------------------------- +set(CLOSING_MESSAGE "Test with: afb-daemon --rootdir=\$\$(pwd)/package --binding=\$\$(pwd)/package/${WIDGET_ENTRY_POINT} --port=1234 --tracereq=common --token=\"1\" --verbose") +set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt") + + + +# Optional dependencies order +# --------------------------- +#set(EXTRA_DEPENDENCIES_ORDER) + +# Optional Extra global include path +# ----------------------------------- +#set(EXTRA_INCLUDE_DIRS) + +# Optional extra libraries +# ------------------------- +#set(EXTRA_LINK_LIBRARIES) + +# Optional force binding installation +# ------------------------------------ +# set(BINDINGS_INSTALL_PREFIX PrefixPath ) + +# Optional force binding Linking flag +# ------------------------------------ +# set(BINDINGS_LINK_FLAG LinkOptions ) + +# Optional force package prefix generation, like widget +# ----------------------------------------------------- +# set(PKG_PREFIX DestinationPath) + +# Optional Application Framework security token +# and port use for remote debugging. +#------------------------------------------------------------ +#set(AFB_TOKEN "" CACHE PATH "Default AFB_TOKEN") +#set(AFB_REMPORT "1234" CACHE PATH "Default AFB_TOKEN") + +# This include is mandatory and MUST happens at the end +# of this file, else you expose you to unexpected behavior +# ----------------------------------------------------------- +include(${PROJECT_APP_TEMPLATES_DIR}/cmake/common.cmake) diff --git a/conf.d/wgt/config.xml.in b/conf.d/wgt/config.xml.in new file mode 100644 index 0000000..a81e394 --- /dev/null +++ b/conf.d/wgt/config.xml.in @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<widget xmlns="http://www.w3.org/ns/widgets" id="@PROJECT_NAME@" version="@PROJECT_VERSION@"> + <name>@PROJECT_NAME@</name> + <icon src="@PROJECT_ICON@"/> + <content src="@WIDGET_ENTRY_POINT@" type="@WIDGET_TYPE@"/> + <description>@PROJECT_DESCRIPTION@</description> + <author>@PROJECT_AUTHOR@ <@PROJECT_AUTHOR_MAIL@></author> + <license>@PROJECT_LICENSE@</license> + + <feature name="urn:AGL:widget:required-permission"> + <param name="urn:AGL:permission::public:hidden" value="required" /> + <param name="urn:AGL:permission::system:run-by-default" value="required" /> + <param name="http://tizen.org/privilege/internal/dbus" value="required" /> + </feature> + + <feature name="urn:AGL:widget:provided-api"> + <param name="network-manager" value="ws" /> + </feature> + + <feature name="urn:AGL:widget:required-api"> + <param name="@WIDGET_ENTRY_POINT@" value="local" /> + </feature> + +</widget> diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..79065a4 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,16 @@ +########################################### +# build and install afb-client-demo +########################################### +PKG_CHECK_MODULES(libsystemd libsystemd>=222) +PKG_CHECK_MODULES(libafbwsc libafbwsc>=5.99) + +ADD_EXECUTABLE(agl-service-network-ctl agl-service-network-ctl.c) + +TARGET_LINK_LIBRARIES(agl-service-network-ctl + ${link_libraries} + ${libsystemd_LDFLAGS} + ${libafbwsc_LDFLAGS} +) +INSTALL(TARGETS agl-service-network-ctl + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + diff --git a/test/agl-service-network-ctl.c b/test/agl-service-network-ctl.c new file mode 100644 index 0000000..0686b5e --- /dev/null +++ b/test/agl-service-network-ctl.c @@ -0,0 +1,881 @@ +/* + * Copyright (C) 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 <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <getopt.h> +#include <stdbool.h> +#include <pthread.h> +#include <ctype.h> +#include <alloca.h> +#include <assert.h> + +#include <systemd/sd-event.h> +#include <json-c/json.h> + +#include <afb/afb-wsj1.h> +#include <afb/afb-ws-client.h> +#include <afb/afb-proto-ws.h> + +struct state { + const char *url; + int port; + const char *token; + const char *api; + char *uri; + bool interactive; + bool debug; + bool noexit; + + sd_event *loop; + struct afb_wsj1 *wsj1; + const char *proto; + bool hangup; +}; + +struct cmd { + const char *verb; + int (*call)(struct state *s, const struct cmd *c, int argc, char *argv[]); + void (*reply)(void *closure, struct afb_wsj1_msg *msg); +}; + +/* print usage of the program */ +/* declaration of functions */ +static void on_wsj1_hangup(void *closure, struct afb_wsj1 *wsj1); +static void on_wsj1_call(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg); +static void on_wsj1_event(void *closure, const char *event, struct afb_wsj1_msg *msg); + +/* the callback interface for wsj1 */ +static struct afb_wsj1_itf wsj1_itf = { + .on_hangup = on_wsj1_hangup, + .on_call = on_wsj1_call, + .on_event = on_wsj1_event +}; + +static void on_reply(struct state *s, const char *verb, struct afb_wsj1_msg *msg) +{ + printf("ON-REPLY %s: %s: %s\n%s\n", + s->api, + verb, + afb_wsj1_msg_is_reply_ok(msg) ? "OK" : "ERROR", + json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), + JSON_C_TO_STRING_PRETTY)); + fflush(stdout); + + /* if non interactive terminate */ + if (!s->interactive) { + if (!s->noexit) + sd_event_exit(s->loop, + afb_wsj1_msg_is_reply_ok(msg) ? 0 : EXIT_FAILURE); + else + printf("Ctrl-C to exit\n"); + } + +} + +static int do_call(struct state *s, const struct cmd *c, json_object *jparams) +{ + printf("CALL %s(%s)\n", c->verb, + jparams ? json_object_to_json_string(jparams) : ""); + + return afb_wsj1_call_j(s->wsj1, s->api, c->verb, jparams, c->reply, s); +} + +static int call_void(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + return do_call(s, c, NULL); +} + +static void on_ping_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "ping", msg); +} + +static void on_subscribe_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "subscribe", msg); +} + +static void on_unsubscribe_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "unsubscribe", msg); +} + +static int call_subscribe_unsubscribe(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + json_object *jobj; + + /* must give event name */ + if (argc < 1) + return -1; + jobj = json_object_new_object(); + json_object_object_add(jobj, "value", json_object_new_string(argv[0])); + + return do_call(s, c, jobj); +} + +static void on_state_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "state", msg); +} + +static void on_reset_counters_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "reset_counters", msg); +} + +static void on_technologies_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "technologies", msg); +} + +static void on_services_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "services", msg); +} + +static void on_enable_technology_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "enable_technology", msg); +} + +static void on_disable_technology_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "disable_technology", msg); +} + +static int call_technology_arg(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + json_object *jobj; + + if (argc < 1) + return -1; + + jobj = json_object_new_object(); + json_object_object_add(jobj, "technology", json_object_new_string(argv[0])); + + return do_call(s, c, jobj); +} + +static void on_scan_services_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "scan_services", msg); +} + +static void on_offline_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "offline", msg); +} + +static int call_offline(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + json_object *jobj; + + /* with no arguments it's a getter */ + if (argc >= 1) { + jobj = json_object_new_object(); + json_object_object_add(jobj, "value", json_object_new_string(argv[0])); + } else + jobj = NULL; + + return do_call(s, c, jobj); +} + +static void on_connect_service_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "connect_service", msg); +} + +static int call_service_arg(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + json_object *jobj; + + if (argc < 1) + return -1; + + jobj = json_object_new_object(); + json_object_object_add(jobj, "service", json_object_new_string(argv[0])); + + return do_call(s, c, jobj); +} + +static void on_disconnect_service_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "disconnect_service", msg); +} + +static void on_remove_service_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "remove_service", msg); +} + +static void on_move_service_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "move_service", msg); +} + +static int call_move_service(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + json_object *jobj; + + /* 3 arguments and the middle must be before or after */ + if (argc < 3 || (strcmp(argv[1], "before") && strcmp(argv[1], "after"))) + return -1; + + jobj = json_object_new_object(); + json_object_object_add(jobj, "service", json_object_new_string(argv[0])); + json_object_object_add(jobj, + !strcmp(argv[1], "before") ? "before_service" : "after_service", + json_object_new_string(argv[2])); + + return do_call(s, c, jobj); +} + +static void on_set_property_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "set_property", msg); +} + +static void on_get_property_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "get_property", msg); +} + +static bool get_property_obj(json_object *jparent, int argc, char *argv[]) +{ + json_object *jobj, *jobj2; + int i, j, nest; + + for (i = 0; i < argc; i++) { + + if ((i < (argc - 1) && !strcmp(argv[i+1], "{"))) { + nest = 1; + for (j = i + 2; j < argc; j++) { + if (!strcmp(argv[j], "{")) + nest++; + else if (!strcmp(argv[j], "}")) + nest--; + + if (!nest) + break; + } + if (j >= argc) + return false; + + jobj = json_object_new_object(); + + jobj2 = json_object_new_array(); + get_property_obj(jobj2, j - i - 2, argv + i + 2); + json_object_object_add(jobj, argv[i], jobj2); + i = j; + } else + jobj = json_object_new_string(argv[i]); + + json_object_array_add(jparent, jobj); + + } + + return true; +} + +struct json_object *get_property_value(const char *str) +{ + char *le, *de; + long long ll; + double d; + + if (!strcmp(str, "null")) + return NULL; + if (!strcmp(str, "true")) + return json_object_new_boolean(true); + if (!strcmp(str, "false")) + return json_object_new_boolean(false); + + /* try both double and long and see which one is furthest */ + ll = strtoll(str, &le, 0); + d = strtod(str, &de); + + if (de > le) { + while (isspace(*de)) + de++; + if (!*de) + return json_object_new_double(d); + } else { + while (isspace(*le)) + le++; + if (!*le) + return json_object_new_int64((int64_t)ll); + } + + /* everything else is a string */ + return json_object_new_string(str); +} + +/* how many args is a single json object value */ +static int next_span(int pos, int argc, char *argv[]) +{ + const char *left, *right; + int j, nest; + + if (pos >= argc) + return 0; + + if (!strcmp(argv[pos], "{") || !strcmp(argv[pos], "[")) { + + if (!strcmp(argv[pos], "{")) { + left = "{"; + right = "}"; + } else { + left = "["; + right = "]"; + } + nest = 1; + for (j = pos + 1; nest > 0 && j < argc; j++) { + if (!strcmp(argv[j], left)) + nest++; + else if (!strcmp(argv[j], right)) + nest--; + } + if (nest && j >= argc) { + fprintf(stderr, "nesting error\n"); + return -1; + } + + return j - pos; + } + return 1; +} + +bool add_property_value(json_object *jparent, const char *key, int argc, char *argv[]) +{ + json_object *jobj; + const char *key2; + int i, span; + + if (!strcmp(argv[0], "[")) { + assert(!strcmp(argv[argc - 1], "]")); + + argc -= 2; + argv++; + + jobj = json_object_new_array(); + + for (i = 0; i < argc; ) { + span = next_span(i, argc, argv); + if (span < 0) { + fprintf(stderr, "bad nesting\n"); + return NULL; + } + + if (!add_property_value(jobj, NULL, span, argv + i)) { + fprintf(stderr, "error adding array\n"); + return false; + } + i += span; + + } + } else if (!strcmp(argv[0], "{")) { + assert(!strcmp(argv[argc - 1], "}")); + + argc -= 2; + argv++; + + jobj = json_object_new_object(); + + for (i = 0; i < argc; ) { + key2 = argv[i]; + + span = next_span(i + 1, argc, argv); + if (span < 0) { + fprintf(stderr, "bad nesting\n"); + return NULL; + } + + if (!add_property_value(jobj, key2, span, argv + i + 1)) { + fprintf(stderr, "error adding object\n"); + return false; + } + i += 1 + span; + } + } else { + jobj = get_property_value(argv[0]); + } + + if (key) + json_object_object_add(jparent, key, jobj); + else + json_object_array_add(jparent, jobj); + + return true; +} + +bool set_property_obj(json_object *jparent, int argc, char *argv[]) +{ + int i, span; + const char *key; + + for (i = 0; i < argc; ) { + + key = argv[i]; + if (!strcmp(key, "{") || !strcmp(key, "[")) { + fprintf(stderr, "Bad object start {\n"); + return false; + } + i++; + + if (i > (argc - 1)) { + fprintf(stderr, "out of arguments on key %s\n", key); + return false; + } + + span = next_span(i, argc, argv); + if (span < 0) { + fprintf(stderr, "bad nesting\n"); + return false; + } + + if (!add_property_value(jparent, key, span, argv + i)) { + fprintf(stderr, "error adding property\n"); + return false; + } + i += span; + } + + return true; +} + +static int call_get_set_property(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + json_object *jobj, *jprop = NULL; + const char *type; + int min_argc; + + if (argc < 1) + return -1; + + type = argv[0]; + /* global or technology or service */ + if (strcmp(type, "global") && strcmp(type, "technology") && + strcmp(type, "service")) { + fprintf(stderr, "unknown property type %s\n", type); + return -1; + } + + /* minimum is get global */ + min_argc = 1; + + /* technology or service have extra argument */ + if (!strcmp(type, "technology") || !strcmp(type, "service")) + min_argc++; + + /* set property must have an argument */ + min_argc += !strcmp(c->verb, "set_property") ? 2 : 0; + + if (argc < min_argc) { + fprintf(stderr, "not enough arguments\n"); + return -1; + } + + jobj = json_object_new_object(); + + argc--; + argv++; + + if (!strcmp(type, "technology")) { + json_object_object_add(jobj, "technology", + json_object_new_string(argv[0])); + argc--; + argv++; + } else if (!strcmp(type, "service")) { + json_object_object_add(jobj, "service", + json_object_new_string(argv[0])); + argc--; + argv++; + } + + /* get is array, set is object */ + if (!strcmp(c->verb, "get_property")) { + if (argc > 0) { + jprop = json_object_new_array(); + get_property_obj(jprop, argc, argv); + } + } else { + jprop = json_object_new_object(); + set_property_obj(jprop, argc, argv); + } + + if (jprop) + json_object_object_add(jobj, "properties", jprop); + + return do_call(s, c, jobj); +} + +static void on_agent_response_reply(void *closure, struct afb_wsj1_msg *msg) +{ + on_reply(closure, "agent_response", msg); +} + +static int call_agent_response(struct state *s, const struct cmd *c, int argc, char *argv[]) +{ + json_object *jresp = NULL, *jprop = NULL; + const char *method; + const char *propname; + int id; + + if (argc < 3) + return -1; + + method = *argv++; + argc--; + + id = (int)strtol(*argv++, NULL, 10); + argc--; + + if (id <= 0) { + fprintf(stderr, "bad agent response id %d\n", id); + return -1; + } + + propname = NULL; + if (!strcmp(method, "request-input")) { + propname = "fields"; + } else { + fprintf(stderr, "unknown agent response method '%s'\n", + method); + return -1; + } + + jresp = json_object_new_object(); + json_object_object_add(jresp, "method", + json_object_new_string(method)); + json_object_object_add(jresp, "id", + json_object_new_int(id)); + + if (argc > 0) { + jprop = json_object_new_object(); + set_property_obj(jprop, argc, argv); + json_object_object_add(jresp, propname, jprop); + } + + return do_call(s, c, jresp); +} + + +static const struct cmd cmds[] = { + { + .verb = "ping", + .call = call_void, + .reply = on_ping_reply, + }, { + .verb = "subscribe", + .call = call_subscribe_unsubscribe, + .reply = on_subscribe_reply, + }, { + .verb = "unsubscribe", + .call = call_subscribe_unsubscribe, + .reply = on_unsubscribe_reply, + }, { + .verb = "state", + .call = call_void, + .reply = on_state_reply, + }, { + .verb = "reset_counters", + .call = call_service_arg, + .reply = on_reset_counters_reply, + }, { + .verb = "technologies", + .call = call_void, + .reply = on_technologies_reply, + }, { + .verb = "services", + .call = call_void, + .reply = on_services_reply, + }, { + .verb = "enable_technology", + .call = call_technology_arg, + .reply = on_enable_technology_reply, + }, { + .verb = "disable_technology", + .call = call_technology_arg, + .reply = on_disable_technology_reply, + }, { + .verb = "scan_services", + .call = call_technology_arg, + .reply = on_scan_services_reply, + }, { + .verb = "offline", + .call = call_offline, + .reply = on_offline_reply, + }, { + .verb = "connect_service", + .call = call_service_arg, + .reply = on_connect_service_reply, + }, { + .verb = "disconnect_service", + .call = call_service_arg, + .reply = on_disconnect_service_reply, + }, { + .verb = "remove_service", + .call = call_service_arg, + .reply = on_remove_service_reply, + }, { + .verb = "move_service", + .call = call_move_service, + .reply = on_move_service_reply, + }, { + .verb = "set_property", + .call = call_get_set_property, + .reply = on_set_property_reply, + }, { + .verb = "get_property", + .call = call_get_set_property, + .reply = on_get_property_reply, + }, { + .verb = "agent_response", + .call = call_agent_response, + .reply = on_agent_response_reply, + }, + { } +}; + +static int call_cmd(struct state *s, int argc, char *argv[]) +{ + const struct cmd *c; + const char *verb; + + /* first argument is verb */ + if (argc < 1 || !argv[0]) { + if (!s->interactive) + fprintf(stderr, "No verb given\n"); + goto out_err; + } + verb = argv[0]; + argv++; + argc--; + + for (c = cmds; c->verb; c++) { + if (!strcmp(verb, c->verb)) + return c->call(s, c, argc, argv); + } + + if (!s->interactive) + fprintf(stderr, "Unknown API verb \"%s\"\n", verb); +out_err: + + if (!s->interactive) + sd_event_exit(s->loop, EXIT_FAILURE); + + return -1; +} + +static struct option opts[] = { + { "url", required_argument, 0, 'u' }, + { "port", required_argument, 0, 'p' }, + { "token", required_argument, 0, 't' }, + { "api", required_argument, 0, 'a' }, + { "noexit", no_argument, 0, 'x' }, + { "debug", no_argument, 0, 'd' }, + { "help", no_argument, 0, 'h' }, + { } +}; + +#define DEFAULT_URL "ws://localhost" +#define DEFAULT_PORT 1234 +#define DEFAULT_TOKEN "1" +#define DEFAULT_API "network-manager" +#define DEFAULT_DEBUG true + +static void usage(int status, const char *arg0) + __attribute__((__noreturn__)); + +static void usage(int status, const char *arg0) +{ + const char *name = strrchr(arg0, '/'); + FILE *outf = status ? stderr : stdout; + + name = name ? name + 1 : arg0; + + fprintf(outf, "usage: %s <options> [arguments]\n", name); + fprintf(outf, " options are:\n"); + fprintf(outf, " -u, --url=X URL to connect to (default %s)\n", DEFAULT_URL); + fprintf(outf, " -p, --port=X Port to use for connection (default %d)\n", DEFAULT_PORT); + fprintf(outf, " -t, --token=X Token to use (default %s)\n", DEFAULT_TOKEN); + fprintf(outf, " -a, --api=X API to use (default %s)\n", DEFAULT_API); + fprintf(outf, " -x, --noexit Do not exit in non-interactive mode (events)\n"); + fprintf(outf, " -d, --debug Enable debug printouts\n"); + fprintf(outf, " -h, -?, --help Display this help\n"); + + exit(status); +} + +/* entry function */ +int main(int argc, char **argv) +{ + int rc, ret, cc = 0, option_index; + const char *name; + struct state state, *s = &state; + + memset(s, 0, sizeof(*s)); + s->url = DEFAULT_URL; + s->port = DEFAULT_PORT; + s->token = DEFAULT_TOKEN; + s->api = DEFAULT_API; + s->debug = DEFAULT_DEBUG; + + s->interactive = false; + s->noexit = false; + + while ((cc = getopt_long(argc, argv, "u:p:t:a:xdh?", opts, + &option_index)) != -1) { + + if (cc == 0 && option_index >= 0) { + name = opts[option_index].name; + if (!name) + continue; + /* we don't have long options without short ones */ + usage(EXIT_FAILURE, argv[0]); + } + + switch (cc) { + case 'u': + s->url = optarg; + break; + case 'p': + s->port = atoi(optarg); + break; + case 't': + s->token = optarg; + break; + case 'a': + s->api = optarg; + break; + case 'x': + s->noexit = true; + break; + case 'd': + s->debug = true; + break; + case 'h': + case '?': + usage(0, argv[0]); + break; + } + } + + /* no arguments left, interactive mode */ + if (optind >= argc) { + printf("optind=%d\n", optind); + printf("argc=%d\n", argc); + s->interactive = true; + } + + ret = EXIT_FAILURE; + + rc = asprintf(&s->uri, "%s:%d/api?token=%s", s->url, s->port, s->token); + if (rc < 0) { + fprintf(stderr, "Unable to build URI\n"); + goto out; + } + + if (s->debug) { + fprintf(stderr, "URI: \"%s\"\n", s->uri); + fprintf(stderr, "API: \"%s\"\n", s->api); + fprintf(stderr, "interactive: %s\n", s->interactive ? "true" : "false"); + fprintf(stderr, "noexit: %s\n", s->noexit ? "true" : "false"); + } + + /* get the default event loop */ + rc = sd_event_default(&s->loop); + if (rc < 0) { + fprintf(stderr, "connection to default event loop failed: %s\n", strerror(-rc)); + goto out_no_event; + } + + /* connect the websocket wsj1 to the uri given by the first argument */ + s->wsj1 = afb_ws_client_connect_wsj1(s->loop, s->uri, &wsj1_itf, s); + if (s->wsj1 == NULL) { + fprintf(stderr, "connection to %s failed: %m\n", argv[1]); + goto out_no_wsj1; + } + + if (!s->interactive) { + ret = call_cmd(s, argc - optind, argv + optind); + if (ret < 0) { + fprintf(stderr, "command failed\n"); + sd_event_exit(s->loop, EXIT_FAILURE); + } + } else { + fprintf(stderr, "interactive mode not yet supported\n"); + sd_event_exit(s->loop, EXIT_FAILURE); + } + + ret = sd_event_loop(s->loop); + + /* cleanup */ + + afb_wsj1_unref(s->wsj1); +out_no_wsj1: + sd_event_unref(s->loop); +out_no_event: + free(s->uri); +out: + return ret; +} + +/* called when wsj1 hangsup */ +static void on_wsj1_hangup(void *closure, struct afb_wsj1 *wsj1) +{ + struct state *s = closure; + + printf("ON-HANGUP\n"); + fflush(stdout); + + s->hangup = true; + sd_event_exit(s->loop, 0); +} + +/* called when wsj1 receives a method invocation */ +static void on_wsj1_call(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg) +{ + int rc; + + printf("ON-CALL %s/%s:\n%s\n", api, verb, + json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), + JSON_C_TO_STRING_PRETTY)); + fflush(stdout); + rc = afb_wsj1_reply_error_s(msg, "\"unimplemented\"", NULL); + if (rc < 0) + fprintf(stderr, "replying failed: %m\n"); +} + +/* called when wsj1 receives an event */ +static void on_wsj1_event(void *closure, const char *event, struct afb_wsj1_msg *msg) +{ + printf("ON-EVENT %s:\n%s\n", event, + json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), + JSON_C_TO_STRING_PRETTY)); + fflush(stdout); +} |