afm-binding: provide afm as a unique binding
authorJosé Bollo <jose.bollo@iot.bzh>
Mon, 7 Aug 2017 15:20:37 +0000 (17:20 +0200)
committerJosé Bollo <jose.bollo@iot.bzh>
Fri, 24 Nov 2017 16:44:57 +0000 (17:44 +0100)
This is the first stone (not fully tested) to
implment the afm-main as a binding. At the end,
this component will replace afm-user-daemon and
afm-system-daemon.

Change-Id: I6eafa7d26800f45ae58dd51aa9ff8008cdb3ff59
Signed-off-by: José Bollo <jose.bollo@iot.bzh>
src/CMakeLists.txt
src/afm-binding.c [new file with mode: 0644]
src/afm-binding.export-map [new file with mode: 0644]
src/wrap-json.c [new file with mode: 0644]
src/wrap-json.h [new file with mode: 0644]
src/wrap-json.md [new file with mode: 0644]

index cd3b9aa..782e154 100644 (file)
@@ -56,14 +56,12 @@ else()
        add_definitions(-DUSE_LIBZIP=0)
 endif()
 
-pkg_check_modules(libsystemd libsystemd>=222)
-if(libsystemd_FOUND)
-       add_compile_options(${libsystemd_CFLAGS})
-       include_directories(${libsystemd_INCLUDE_DIRS})
-       link_libraries(${libsystemd_LIBRARIES})
-else()
-       add_definitions(-DNO_LIBSYSTEMD)
-endif()
+pkg_check_modules(libsystemd REQUIRED libsystemd>=222)
+add_compile_options(${libsystemd_CFLAGS})
+include_directories(${libsystemd_INCLUDE_DIRS})
+link_libraries(${libsystemd_LIBRARIES})
+
+pkg_check_modules(AFB REQUIRED afb-daemon>=4.0)
 
 ###########################################################################
 
@@ -149,44 +147,37 @@ target_link_libraries(wgtpkg-installer wgtpkg wgt secwrp utils)
 
 install(TARGETS wgtpkg-sign wgtpkg-pack wgtpkg-info wgtpkg-installer DESTINATION ${CMAKE_INSTALL_BINDIR})
 
-if(libsystemd_FOUND)
-       ###########################################################################
-       # the daemons
-
-       MESSAGE(STATUS "Creating daemons")
-
-       add_library(jbus STATIC utils-jbus.c)
-
-       add_executable(afm-user-daemon afm-user-daemon.c)
-       target_link_libraries(afm-user-daemon afm secwrp wgt utils jbus)
-       install(TARGETS afm-user-daemon DESTINATION ${CMAKE_INSTALL_BINDIR})
-
-       add_executable(afm-system-daemon afm-system-daemon.c)
-       target_link_libraries(afm-system-daemon wgtpkg afm secwrp wgt utils jbus)
-       install(TARGETS afm-system-daemon DESTINATION ${CMAKE_INSTALL_BINDIR})
-
-       ###########################################################################
-       # the binding for afb
-
-       pkg_check_modules(AFB afb-daemon>=4.0)
-       if(AFB_FOUND)
-               message(STATUS "Creation afm-main-binding for AFB-DAEMON")
-               ###############################################################
-               pkg_get_variable(afb_binding_install_dir afb-daemon binding_install_dir)
-               ###############################################################
-               add_library(afm-main-binding MODULE afm-main-binding.c)
-               target_compile_options(afm-main-binding PRIVATE ${AFB_CFLAGS})
-               target_include_directories(afm-main-binding PRIVATE ${AFB_INCLUDE_DIRS})
-               target_link_libraries(afm-main-binding utils jbus ${AFB_LIBRARIES})
-               set_target_properties(afm-main-binding PROPERTIES
-                       PREFIX ""
-                       LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/afm-main-binding.export-map"
-               )
-               install(TARGETS afm-main-binding LIBRARY DESTINATION ${afb_binding_install_dir})
-       else()
-               message(STATUS "Not creating the binding for AFB-DAEMON")
-       endif()
-endif()
+###########################################################################
+# the daemons
+
+MESSAGE(STATUS "Creating daemons")
+
+add_library(jbus STATIC utils-jbus.c)
+
+add_executable(afm-user-daemon afm-user-daemon.c)
+target_link_libraries(afm-user-daemon afm secwrp wgt utils jbus)
+install(TARGETS afm-user-daemon DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+add_executable(afm-system-daemon afm-system-daemon.c)
+target_link_libraries(afm-system-daemon wgtpkg afm secwrp wgt utils jbus)
+install(TARGETS afm-system-daemon DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+###########################################################################
+# the binding for afb
+
+message(STATUS "Creation afm-binding for AFB-DAEMON")
+###############################################################
+pkg_get_variable(afb_binding_install_dir afb-daemon binding_install_dir)
+###############################################################
+add_library(afm-binding MODULE afm-binding.c)
+target_compile_options(afm-binding PRIVATE ${AFB_CFLAGS})
+target_include_directories(afm-binding PRIVATE ${AFB_INCLUDE_DIRS})
+target_link_libraries(afm-binding wgtpkg wgt secwrp utils afm ${AFB_LIBRARIES})
+set_target_properties(afm-binding PROPERTIES
+       PREFIX ""
+       LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/afm-binding.export-map"
+)
+install(TARGETS afm-binding LIBRARY DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/afm)
 
 ###########################################################################
 # the tests
diff --git a/src/afm-binding.c b/src/afm-binding.c
new file mode 100644 (file)
index 0000000..b111f09
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2015, 2016, 2017 IoT.bzh
+ * Author "Fulup Ar Foll"
+ *
+ * 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         /* See feature_test_macros(7) */
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+
+#include "verbose.h"
+#include "utils-systemd.h"
+#include "afm.h"
+#include "afm-udb.h"
+#include "afm-urun.h"
+#include "wgt-info.h"
+#include "wgtpkg-install.h"
+#include "wgtpkg-uninstall.h"
+#include "wrap-json.h"
+
+static const char _added_[]     = "added";
+static const char _a_l_c_[]     = "application-list-changed";
+static const char _detail_[]    = "detail";
+static const char _id_[]        = "id";
+static const char _install_[]   = "install";
+static const char _not_found_[] = "not-found";
+static const char _once_[]      = "once";
+static const char _pause_[]     = "pause";
+static const char _resume_[]    = "resume";
+static const char _runid_[]     = "runid";
+static const char _runnables_[] = "runnables";
+static const char _runners_[]   = "runners";
+static const char _start_[]     = "start";
+static const char _state_[]     = "state";
+static const char _terminate_[] = "terminate";
+static const char _uninstall_[] = "uninstall";
+
+static const char *rootdir = FWK_APP_DIR;
+static struct afb_event applist_changed_event;
+static struct afm_udb *afudb;
+static struct json_object *json_true;
+
+static void do_reloads()
+{
+       /* enforce daemon reload */
+       systemd_daemon_reload(0);
+       systemd_unit_restart_name(0, "sockets.target");
+}
+
+static void bad_request(struct afb_req req)
+{
+       afb_req_fail(req, "bad-request", NULL);
+}
+
+static void not_found(struct afb_req req)
+{
+       afb_req_fail(req, _not_found_, NULL);
+}
+
+static void cant_start(struct afb_req req)
+{
+       afb_req_fail(req, "cannot-start", NULL);
+}
+
+/*
+ * Broadcast the event "application-list-changed".
+ * This event is sent was the event "changed" is received from dbus.
+ */
+static void application_list_changed(const char *operation, const char *data)
+{
+       struct json_object *e = NULL;
+       wrap_json_pack(&e, "{ss ss}", "operation", operation, "data", data);
+       afb_event_broadcast(applist_changed_event, e);
+}
+
+/*
+ * retrieves the 'runid' in parameters received with the
+ * request 'req' for the 'method'.
+ *
+ * Returns 1 in case of success.
+ * Otherwise, if the 'runid' can't be retrived, an error stating
+ * the bad request is replied for 'req' and 0 is returned.
+ */
+static int onrunid(struct afb_req req, const char *method, int *runid)
+{
+       struct json_object *json;
+
+       json = afb_req_json(req);
+       if (wrap_json_unpack(json, "s", runid)
+               || wrap_json_unpack(json, "{ss}", "runid", runid)) {
+               INFO("bad request method %s: %s", method,
+                                       json_object_to_json_string(json));
+               bad_request(req);
+               return 0;
+       }
+
+       INFO("method %s called for %d", method, *runid);
+       return 1;
+}
+
+/*
+ * Sends the reply 'resp' to the request 'req' if 'resp' is not NULLzero.
+ * Otherwise, when 'resp' is NULL replies the error string 'errstr'.
+ */
+static void reply(struct afb_req req, struct json_object *resp, const char *errstr)
+{
+       if (!resp)
+               afb_req_fail(req, errstr, NULL);
+       else
+               afb_req_success(req, resp, NULL);
+}
+
+/*
+ * Sends the reply "true" to the request 'req' if 'status' is zero.
+ * Otherwise, when 'status' is not zero replies the error string 'errstr'.
+ */
+static void reply_status(struct afb_req req, int status, const char *errstr)
+{
+       reply(req, status ? NULL : json_object_get(json_true), errstr);
+}
+
+/*
+ * On query "runnables"
+ */
+static void runnables(struct afb_req req)
+{
+       struct json_object *resp;
+       INFO("method runnables called");
+       resp = afm_udb_applications_public(afudb);
+       afb_req_success(req, resp, NULL);
+}
+
+/*
+ * On query "detail"
+ */
+static void detail(struct afb_req req)
+{
+       const char *appid;
+       struct json_object *resp, *json;
+
+       /* scan the request */
+       json = afb_req_json(req);
+       if (wrap_json_unpack(json, "s", &appid)
+               || wrap_json_unpack(json, "{ss}", _id_, &appid)) {
+               bad_request(req);
+               return;
+       }
+
+       /* wants details for appid */
+       INFO("method detail called for %s", appid);
+       resp = afm_udb_get_application_public(afudb, appid);
+       if (resp)
+               afb_req_success(req, resp, NULL);
+       else
+               not_found(req);
+}
+
+/*
+ * On query "start"
+ */
+static void start(struct afb_req req)
+{
+       const char *appid;
+       struct json_object *appli, *resp, *json;
+       int runid;
+
+       /* scan the request */
+       json = afb_req_json(req);
+       if (wrap_json_unpack(json, "s", &appid)
+               || wrap_json_unpack(json, "{ss}", _id_, &appid)) {
+               bad_request(req);
+               return;
+       }
+
+       /* get the application */
+       INFO("method start called for %s", appid);
+       appli = afm_udb_get_application_private(afudb, appid);
+       if (appli == NULL) {
+               not_found(req);
+               return;
+       }
+
+       /* launch the application */
+       runid = afm_urun_start(appli);
+       if (runid <= 0) {
+               cant_start(req);
+               return;
+       }
+
+       /* returns */
+       resp = NULL;
+       wrap_json_pack(&resp, "{si}", _runid_, runid);
+       afb_req_success(req, resp, NULL);
+}
+
+/*
+ * On query "once"
+ */
+static void once(struct afb_req req)
+{
+       const char *appid;
+       struct json_object *appli, *resp, *json;
+       int runid;
+
+       /* scan the request */
+       json = afb_req_json(req);
+       if (wrap_json_unpack(json, "s", &appid)
+               || wrap_json_unpack(json, "{ss}", _id_, &appid)) {
+               bad_request(req);
+               return;
+       }
+
+       /* get the application */
+       INFO("method once called for %s", appid);
+       appli = afm_udb_get_application_private(afudb, appid);
+       if (appli == NULL) {
+               not_found(req);
+               return;
+       }
+
+       /* launch the application */
+       runid = afm_urun_once(appli);
+       if (runid <= 0) {
+               cant_start(req);
+               return;
+       }
+
+       /* returns the state */
+       resp = afm_urun_state(afudb, runid);
+       afb_req_success(req, resp, NULL);
+}
+
+/*
+ * On query "pause"
+ */
+static void pause(struct afb_req req)
+{
+       int runid, status;
+       if (onrunid(req, "pause", &runid)) {
+               status = afm_urun_pause(runid);
+               reply_status(req, status, _not_found_);
+       }
+}
+
+/*
+ * On query "resume" from 'smsg' with parameters of 'obj'.
+ */
+static void resume(struct afb_req req)
+{
+       int runid, status;
+       if (onrunid(req, "resume", &runid)) {
+               status = afm_urun_resume(runid);
+               reply_status(req, status, _not_found_);
+       }
+}
+
+/*
+ * On query "terminate"
+ */
+static void terminate(struct afb_req req)
+{
+       int runid, status;
+       if (onrunid(req, "terminate", &runid)) {
+               status = afm_urun_terminate(runid);
+               reply_status(req, status, _not_found_);
+       }
+}
+
+/*
+ * On query "runners"
+ */
+static void runners(struct afb_req req)
+{
+       struct json_object *resp;
+       INFO("method runners called");
+       resp = afm_urun_list(afudb);
+       afb_req_success(req, resp, NULL);
+}
+
+/*
+ * On query "state"
+ */
+static void state(struct afb_req req)
+{
+       int runid;
+       struct json_object *resp;
+       if (onrunid(req, "state", &runid)) {
+               resp = afm_urun_state(afudb, runid);
+               reply(req, resp, _not_found_);
+       }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+static void install(struct afb_req req)
+{
+       const char *wgtfile;
+       const char *root;
+       int force;
+       int reload;
+       struct wgt_info *ifo;
+       struct json_object *json;
+       struct json_object *resp;
+
+       /* default settings */
+       root = rootdir;
+       force = 0;
+       reload = 1;
+
+       /* scan the request */
+       json = afb_req_json(req);
+       if (wrap_json_unpack(json, "s", &wgtfile)
+               || wrap_json_unpack(json, "{ss s?s s?b s?b}",
+                               "widget", &wgtfile,
+                               "root", &root,
+                               "force", &force,
+                               "reload", &reload)) {
+               return bad_request(req);
+       }
+
+       /* install the widget */
+       ifo = install_widget(wgtfile, root, force);
+       if (ifo == NULL)
+               afb_req_fail_f(req, "failed", "installation failed: %m");
+       else {
+               /* reload if needed */
+               if (reload)
+                       do_reloads();
+
+               /* build the response */
+               wrap_json_pack(&resp, "{ss}", _added_, wgt_info_desc(ifo)->idaver);
+               afb_req_success(req, resp, NULL);
+               application_list_changed(_install_, wgt_info_desc(ifo)->idaver);
+
+               /* clean-up */
+               wgt_info_unref(ifo);
+       }
+}
+
+static void uninstall(struct afb_req req)
+{
+       const char *idaver;
+       const char *root;
+       struct json_object *json;
+       int rc;
+
+       /* default settings */
+       root = rootdir;
+
+       /* scan the request */
+       json = afb_req_json(req);
+       if (wrap_json_unpack(json, "s", &idaver)
+               || wrap_json_unpack(json, "{ss s?s}",
+                               _id_, &idaver,
+                               "root", &root)) {
+               return bad_request(req);
+       }
+
+       /* install the widget */
+       rc = uninstall_widget(idaver, root);
+       if (rc)
+               afb_req_fail_f(req, "failed", "uninstallation failed: %m");
+       else {
+               afb_req_success(req, NULL, NULL);
+               application_list_changed(_uninstall_, idaver);
+       }
+}
+
+static int init()
+{
+       /* init database */
+       afudb = afm_udb_create(1, 1, "afm-appli-");
+       if (!afudb) {
+               ERROR("afm_udb_create failed");
+               return -1;
+       }
+
+       /* set the systemd's buses */
+       systemd_set_bus(0, afb_daemon_get_system_bus());
+       systemd_set_bus(1, afb_daemon_get_user_bus());
+
+       /* create TRUE */
+       json_true = json_object_new_boolean(1);
+
+       /* create the event */
+       applist_changed_event = afb_daemon_make_event(_a_l_c_);
+       return -!afb_event_is_valid(applist_changed_event);
+}
+
+static const struct afb_auth
+       auth_install = {
+               .type = afb_auth_Permission,
+               .text = "urn:AGL:permission:afm:system:widget:install"
+       },
+       auth_uninstall = {
+               .type = afb_auth_Permission,
+               .text = "urn:AGL:permission:afm:system:widget:uninstall"
+       }
+;
+
+static const struct afb_verb_v2 verbs[] =
+{
+       {_runnables_, runnables, NULL, "Get list of runnable applications",          AFB_SESSION_CHECK_V2 },
+       {_detail_   , detail,    NULL, "Get the details for one application",        AFB_SESSION_CHECK_V2 },
+       {_start_    , start,     NULL, "Start an application",                       AFB_SESSION_CHECK_V2 },
+       {_once_     , once,      NULL, "Start once an application",                  AFB_SESSION_CHECK_V2 },
+       {_terminate_, terminate, NULL, "Terminate a running application",            AFB_SESSION_CHECK_V2 },
+       {_pause_    , pause,     NULL, "Pause a running application",                AFB_SESSION_CHECK_V2 },
+       {_resume_   , resume,    NULL, "Resume a paused application",                AFB_SESSION_CHECK_V2 },
+       {_runners_  , runners,   NULL, "Get the list of running applications",       AFB_SESSION_CHECK_V2 },
+       {_state_    , state,     NULL, "Get the state of a running application",     AFB_SESSION_CHECK_V2 },
+       {_install_  , install,   NULL, "Install an application using a widget file", AFB_SESSION_CHECK_V2 },
+       {_uninstall_, uninstall, NULL, "Uninstall an application",                   AFB_SESSION_CHECK_V2 },
+       { NULL, NULL, NULL, NULL, 0 }
+};
+
+const struct afb_binding_v2 afbBindingV2 = {
+       .api = "afm-main",
+       .specification = NULL,
+       .info = "Application Framework Master Service",
+       .verbs = verbs,
+       .preinit = NULL,
+       .init = init,
+       .onevent = NULL,
+       .noconcurrency = 1 /* relies on binder for serialisation of requests */
+};
+
diff --git a/src/afm-binding.export-map b/src/afm-binding.export-map
new file mode 100644 (file)
index 0000000..ee2f413
--- /dev/null
@@ -0,0 +1 @@
+{ global: afbBindingV*; local: *; };
diff --git a/src/wrap-json.c b/src/wrap-json.c
new file mode 100644 (file)
index 0000000..9bb8d19
--- /dev/null
@@ -0,0 +1,939 @@
+/*
+ Copyright (C) 2016, 2017 "IoT.bzh"
+
+ author: José Bollo <jose.bollo@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.
+*/
+
+#include <string.h>
+
+#include "wrap-json.h"
+
+#define STACKCOUNT  32
+#define STRCOUNT    8
+
+enum {
+       wrap_json_error_none,
+       wrap_json_error_null_object,
+       wrap_json_error_truncated,
+       wrap_json_error_internal_error,
+       wrap_json_error_out_of_memory,
+       wrap_json_error_invalid_character,
+       wrap_json_error_too_long,
+       wrap_json_error_too_deep,
+       wrap_json_error_null_spec,
+       wrap_json_error_null_key,
+       wrap_json_error_null_string,
+       wrap_json_error_out_of_range,
+       wrap_json_error_incomplete,
+       wrap_json_error_missfit_type,
+       wrap_json_error_key_not_found,
+       _wrap_json_error_count_
+};
+
+static const char ignore_all[] = " \t\n\r,:";
+static const char pack_accept_arr[] = "][{snbiIfoO";
+static const char pack_accept_key[] = "s}";
+#define pack_accept_any (&pack_accept_arr[1])
+
+static const char unpack_accept_arr[] = "*!][{snbiIfFoO";
+static const char unpack_accept_key[] = "*!s}";
+#define unpack_accept_any (&unpack_accept_arr[3])
+
+static const char *pack_errors[_wrap_json_error_count_] =
+{
+       [wrap_json_error_none] = "unknown error",
+       [wrap_json_error_null_object] = "null object",
+       [wrap_json_error_truncated] = "truncated",
+       [wrap_json_error_internal_error] = "internal error",
+       [wrap_json_error_out_of_memory] = "out of memory",
+       [wrap_json_error_invalid_character] = "invalid character",
+       [wrap_json_error_too_long] = "too long",
+       [wrap_json_error_too_deep] = "too deep",
+       [wrap_json_error_null_spec] = "spec is NULL",
+       [wrap_json_error_null_key] = "key is NULL",
+       [wrap_json_error_null_string] = "string is NULL",
+       [wrap_json_error_out_of_range] = "array too small",
+       [wrap_json_error_incomplete] = "incomplete container",
+       [wrap_json_error_missfit_type] = "missfit of type",
+       [wrap_json_error_key_not_found] = "key not found"
+};
+
+int wrap_json_get_error_position(int rc)
+{
+       if (rc < 0)
+               rc = -rc;
+       return (rc >> 4) + 1;
+}
+
+int wrap_json_get_error_code(int rc)
+{
+       if (rc < 0)
+               rc = -rc;
+       return rc & 15;
+}
+
+const char *wrap_json_get_error_string(int rc)
+{
+       rc = wrap_json_get_error_code(rc);
+       if (rc >= sizeof pack_errors / sizeof *pack_errors)
+               rc = 0;
+       return pack_errors[rc];
+}
+
+
+
+static inline const char *skip(const char *d)
+{
+       while (*d && strchr(ignore_all, *d))
+               d++;
+       return d;
+}
+
+int wrap_json_vpack(struct json_object **result, const char *desc, va_list args)
+{
+       /* TODO: the case of structs with key being single char should be optimized */
+       int nstr, notnull, nullable, rc;
+       size_t sz, dsz, ssz;
+       char *s;
+       char c;
+       const char *d;
+       char buffer[256];
+       struct { const char *str; size_t sz; } strs[STRCOUNT];
+       struct { struct json_object *cont, *key; const char *acc; char type; } stack[STACKCOUNT], *top;
+       struct json_object *obj;
+
+       ssz = sizeof buffer;
+       s = buffer;
+       top = stack;
+       top->key = NULL;
+       top->cont = NULL;
+       top->acc = pack_accept_any;
+       top->type = 0;
+       d = desc;
+       if (!d)
+               goto null_spec;
+       d = skip(d);
+       for(;;) {
+               c = *d;
+               if (!c)
+                       goto truncated;
+               if (!strchr(top->acc, c))
+                       goto invalid_character;
+               d = skip(++d);
+               switch(c) {
+               case 's':
+                       nullable = 0;
+                       notnull = 0;
+                       nstr = 0;
+                       sz = 0;
+                       for (;;) {
+                               strs[nstr].str = va_arg(args, const char*);
+                               if (strs[nstr].str)
+                                       notnull = 1;
+                               if (*d == '?') {
+                                       d = skip(++d);
+                                       nullable = 1;
+                               }
+                               switch(*d) {
+                               case '%': strs[nstr].sz = va_arg(args, size_t); d = skip(++d); break;
+                               case '#': strs[nstr].sz = (size_t)va_arg(args, int); d = skip(++d); break;
+                               default: strs[nstr].sz = strs[nstr].str ? strlen(strs[nstr].str) : 0; break;
+                               }
+                               sz += strs[nstr++].sz;
+                               if (*d == '?') {
+                                       d = skip(++d);
+                                       nullable = 1;
+                               }
+                               if (*d != '+')
+                                       break;
+                               if (nstr >= STRCOUNT)
+                                       goto too_long;
+                               d = skip(++d);
+                       }
+                       if (*d == '*')
+                               nullable = 1;
+                       if (notnull) {
+                               if (sz > ssz) {
+                                       ssz += ssz;
+                                       if (ssz < sz)
+                                               ssz = sz;
+                                       s = alloca(sz);
+                               }
+                               dsz = sz;
+                               while (nstr) {
+                                       nstr--;
+                                       dsz -= strs[nstr].sz;
+                                       memcpy(&s[dsz], strs[nstr].str, strs[nstr].sz);
+                               }
+                               obj = json_object_new_string_len(s, (int)sz);
+                               if (!obj)
+                                       goto out_of_memory;
+                       } else if (nullable)
+                               obj = NULL;
+                       else
+                               goto null_string;
+                       break;
+               case 'n':
+                       obj = NULL;
+                       break;
+               case 'b':
+                       obj = json_object_new_boolean(va_arg(args, int));
+                       if (!obj)
+                               goto out_of_memory;
+                       break;
+               case 'i':
+                       obj = json_object_new_int(va_arg(args, int));
+                       if (!obj)
+                               goto out_of_memory;
+                       break;
+               case 'I':
+                       obj = json_object_new_int64(va_arg(args, int64_t));
+                       if (!obj)
+                               goto out_of_memory;
+                       break;
+               case 'f':
+                       obj = json_object_new_double(va_arg(args, double));
+                       if (!obj)
+                               goto out_of_memory;
+                       break;
+               case 'o':
+               case 'O':
+                       obj = va_arg(args, struct json_object*);
+                       if (*d == '?')
+                               d = skip(++d);
+                       else if (*d != '*' && !obj)
+                               goto null_object;
+                       if (c == 'O')
+                               json_object_get(obj);
+                       break;
+               case '[':
+               case '{':
+                       if (++top >= &stack[STACKCOUNT])
+                               goto too_deep;
+                       top->key = NULL;
+                       if (c == '[') {
+                               top->type = ']';
+                               top->acc = pack_accept_arr;
+                               top->cont = json_object_new_array();
+                       } else {
+                               top->type = '}';
+                               top->acc = pack_accept_key;
+                               top->cont = json_object_new_object();
+                       }
+                       if (!top->cont)
+                               goto out_of_memory;
+                       continue;
+               case '}':
+               case ']':
+                       if (c != top->type || top <= stack)
+                               goto internal_error;
+                       obj = (top--)->cont;
+                       if (*d == '*' && !(c == '}' ? json_object_object_length(obj) : json_object_array_length(obj))) {
+                               json_object_put(obj);
+                               obj = NULL;
+                       }
+                       break;
+               default:
+                       goto internal_error;
+               }
+               switch (top->type) {
+               case 0:
+                       if (top != stack)
+                               goto internal_error;
+                       if (*d)
+                               goto invalid_character;
+                       *result = obj;
+                       return 0;
+               case ']':
+                       if (obj || *d != '*')
+                               json_object_array_add(top->cont, obj);
+                       if (*d == '*')
+                               d = skip(++d);
+                       break;
+               case '}':
+                       if (!obj)
+                               goto null_key;
+                       top->key = obj;
+                       top->acc = pack_accept_any;
+                       top->type = ':';
+                       break;
+               case ':':
+                       if (obj || *d != '*')
+                               json_object_object_add(top->cont, json_object_get_string(top->key), obj);
+                       if (*d == '*')
+                               d = skip(++d);
+                       json_object_put(top->key);
+                       top->key = NULL;
+                       top->acc = pack_accept_key;
+                       top->type = '}';
+                       break;
+               default:
+                       goto internal_error;
+               }
+       }
+
+null_object:
+       rc = wrap_json_error_null_object;
+       goto error;
+truncated:
+       rc = wrap_json_error_truncated;
+       goto error;
+internal_error:
+       rc = wrap_json_error_internal_error;
+       goto error;
+out_of_memory:
+       rc = wrap_json_error_out_of_memory;
+       goto error;
+invalid_character:
+       rc = wrap_json_error_invalid_character;
+       goto error;
+too_long:
+       rc = wrap_json_error_too_long;
+       goto error;
+too_deep:
+       rc = wrap_json_error_too_deep;
+       goto error;
+null_spec:
+       rc = wrap_json_error_null_spec;
+       goto error;
+null_key:
+       rc = wrap_json_error_null_key;
+       goto error;
+null_string:
+       rc = wrap_json_error_null_string;
+       goto error;
+error:
+       do {
+               json_object_put(top->key);
+               json_object_put(top->cont);
+       } while (--top >= stack);
+       *result = NULL;
+       rc = rc | (int)((d - desc) << 4);
+       return -rc;
+}
+
+int wrap_json_pack(struct json_object **result, const char *desc, ...)
+{
+       int rc;
+       va_list args;
+
+       va_start(args, desc);
+       rc = wrap_json_vpack(result, desc, args);
+       va_end(args);
+       return rc;
+}
+
+static int vunpack(struct json_object *object, const char *desc, va_list args, int store)
+{
+       int rc = 0, optionnal, ignore;
+       char c, xacc[2] = { 0, 0 };
+       const char *acc;
+       const char *d, *fit = NULL;
+       const char *key = NULL;
+       const char **ps = NULL;
+       double *pf = NULL;
+       int *pi = NULL;
+       int64_t *pI = NULL;
+       size_t *pz = NULL;
+       struct { struct json_object *parent; const char *acc; int index, count; char type; } stack[STACKCOUNT], *top;
+       struct json_object *obj;
+       struct json_object **po;
+
+       xacc[0] = 0;
+       ignore = 0;
+       top = NULL;
+       acc = unpack_accept_any;
+       d = desc;
+       if (!d)
+               goto null_spec;
+       d = skip(d);
+       obj = object;
+       for(;;) {
+               fit = d;
+               c = *d;
+               if (!c)
+                       goto truncated;
+               if (!strchr(acc, c))
+                       goto invalid_character;
+               d = skip(++d);
+               switch(c) {
+               case 's':
+                       if (xacc[0] == '}') {
+                               /* expects a key */
+                               key = va_arg(args, const char *);
+                               if (!key)
+                                       goto null_key;
+                               if (*d != '?')
+                                       optionnal = 0;
+                               else {
+                                       optionnal = 1;
+                                       d = skip(++d);
+                               }
+                               if (ignore)
+                                       ignore++;
+                               else {
+                                       if (json_object_object_get_ex(top->parent, key, &obj)) {
+                                               /* found */
+                                               top->index++;
+                                       } else {
+                                               /* not found */
+                                               if (!optionnal)
+                                                       goto key_not_found;
+                                               ignore = 1;
+                                               obj = NULL;
+                                       }
+                               }
+                               xacc[0] = ':';
+                               acc = unpack_accept_any;
+                               continue;
+                       }
+                       /* get a string */
+                       if (store)
+                               ps = va_arg(args, const char **);
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_string))
+                                       goto missfit;
+                               if (store && ps)
+                                       *ps = json_object_get_string(obj);
+                       }
+                       if (*d == '%') {
+                               d = skip(++d);
+                               if (store) {
+                                       pz = va_arg(args, size_t *);
+                                       if (!ignore && pz)
+                                               *pz = (size_t)json_object_get_string_len(obj);
+                               }
+                       }
+                       break;
+               case 'n':
+                       if (!ignore && !json_object_is_type(obj, json_type_null))
+                               goto missfit;
+                       break;
+               case 'b':
+                       if (store)
+                               pi = va_arg(args, int *);
+
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_boolean))
+                                       goto missfit;
+                               if (store && pi)
+                                       *pi = json_object_get_boolean(obj);
+                       }
+                       break;
+               case 'i':
+                       if (store)
+                               pi = va_arg(args, int *);
+
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_int))
+                                       goto missfit;
+                               if (store && pi)
+                                       *pi = json_object_get_int(obj);
+                       }
+                       break;
+               case 'I':
+                       if (store)
+                               pI = va_arg(args, int64_t *);
+
+                       if (!ignore) {
+                               if (!json_object_is_type(obj, json_type_int))
+                                       goto missfit;
+                               if (store && pI)
+                                       *pI = json_object_get_int64(obj);
+                       }
+                       break;
+               case 'f':
+               case 'F':
+                       if (store)
+                               pf = va_arg(args, double *);
+
+                       if (!ignore) {
+                               if (!(json_object_is_type(obj, json_type_double) || (c == 'F' && json_object_is_type(obj, json_type_int))))
+                                       goto missfit;
+                               if (store && pf)
+                                       *pf = json_object_get_double(obj);
+                       }
+                       break;
+               case 'o':
+               case 'O':
+                       if (store) {
+                               po = va_arg(args, struct json_object **);
+                               if (!ignore && po) {
+                                       if (c == 'O')
+                                               obj = json_object_get(obj);
+                                       *po = obj;
+                               }
+                       }
+                       break;
+
+               case '[':
+               case '{':
+                       if (!top)
+                               top = stack;
+                       else if (++top  >= &stack[STACKCOUNT])
+                               goto too_deep;
+
+                       top->acc = acc;
+                       top->type = xacc[0];
+                       top->index = 0;
+                       top->parent = obj;
+                       if (ignore)
+                               ignore++;
+                       if (c == '[') {
+                               if (!ignore) {
+                                       if (!json_object_is_type(obj, json_type_array))
+                                               goto missfit;
+                                       top->count = json_object_array_length(obj);
+                               }
+                               xacc[0] = ']';
+                               acc = unpack_accept_arr;
+                       } else {
+                               if (!ignore) {
+                                       if (!json_object_is_type(obj, json_type_object))
+                                               goto missfit;
+                                       top->count = json_object_object_length(obj);
+                               }
+                               xacc[0] = '}';
+                               acc = unpack_accept_key;
+                               continue;
+                       }
+                       break;
+               case '}':
+               case ']':
+                       if (!top || c != xacc[0])
+                               goto internal_error;
+                       acc = top->acc;
+                       xacc[0] = top->type;
+                       top = top == stack ? NULL : top - 1;
+                       if (ignore)
+                               ignore--;
+                       break;
+               case '!':
+                       if (*d != xacc[0])
+                               goto invalid_character;
+                       if (!ignore && top->index != top->count)
+                               goto incomplete;
+                       /*@fallthrough@*/
+               case '*':
+                       acc = xacc;
+                       continue;
+               default:
+                       goto internal_error;
+               }
+               switch (xacc[0]) {
+               case 0:
+                       if (top)
+                               goto internal_error;
+                       if (*d)
+                               goto invalid_character;
+                       return 0;
+               case ']':
+                       if (!ignore) {
+                               key = strchr(unpack_accept_arr, *d);
+                               if (key && key >= unpack_accept_any) {
+                                       if (top->index >= top->count)
+                                               goto out_of_range;
+                                       obj = json_object_array_get_idx(top->parent, top->index++);
+                               }
+                       }
+                       break;
+               case ':':
+                       acc = unpack_accept_key;
+                       xacc[0] = '}';
+                       if (ignore)
+                               ignore--;
+                       break;
+               default:
+                       goto internal_error;
+               }
+       }
+truncated:
+       rc = wrap_json_error_truncated;
+       goto error;
+internal_error:
+       rc = wrap_json_error_internal_error;
+       goto error;
+invalid_character:
+       rc = wrap_json_error_invalid_character;
+       goto error;
+too_deep:
+       rc = wrap_json_error_too_deep;
+       goto error;
+null_spec:
+       rc = wrap_json_error_null_spec;
+       goto error;
+null_key:
+       rc = wrap_json_error_null_key;
+       goto error;
+out_of_range:
+       rc = wrap_json_error_out_of_range;
+       goto error;
+incomplete:
+       rc = wrap_json_error_incomplete;
+       goto error;
+missfit:
+       rc = wrap_json_error_missfit_type;
+       goto errorfit;
+key_not_found:
+       rc = wrap_json_error_key_not_found;
+       goto error;
+errorfit:
+       d = fit;
+error:
+       rc = rc | (int)((d - desc) << 4);
+       return -rc;
+}
+
+int wrap_json_vcheck(struct json_object *object, const char *desc, va_list args)
+{
+       return vunpack(object, desc, args, 0);
+}
+
+int wrap_json_check(struct json_object *object, const char *desc, ...)
+{
+       int rc;
+       va_list args;
+
+       va_start(args, desc);
+       rc = vunpack(object, desc, args, 0);
+       va_end(args);
+       return rc;
+}
+
+int wrap_json_vmatch(struct json_object *object, const char *desc, va_list args)
+{
+       return !vunpack(object, desc, args, 0);
+}
+
+int wrap_json_match(struct json_object *object, const char *desc, ...)
+{
+       int rc;
+       va_list args;
+
+       va_start(args, desc);
+       rc = vunpack(object, desc, args, 0);
+       va_end(args);
+       return !rc;
+}
+
+int wrap_json_vunpack(struct json_object *object, const char *desc, va_list args)
+{
+       return vunpack(object, desc, args, 1);
+}
+
+int wrap_json_unpack(struct json_object *object, const char *desc, ...)
+{
+       int rc;
+       va_list args;
+
+       va_start(args, desc);
+       rc = vunpack(object, desc, args, 1);
+       va_end(args);
+       return rc;
+}
+
+static void object_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure)
+{
+       struct json_object_iterator it = json_object_iter_begin(object);
+       struct json_object_iterator end = json_object_iter_end(object);
+       while (!json_object_iter_equal(&it, &end)) {
+               callback(closure, json_object_iter_peek_value(&it), json_object_iter_peek_name(&it));
+               json_object_iter_next(&it);
+       }
+}
+
+static void array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure)
+{
+       int n = json_object_array_length(object);
+       int i = 0;
+       while(i < n)
+               callback(closure, json_object_array_get_idx(object, i++));
+}
+
+void wrap_json_optarray_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure)
+{
+       if (json_object_is_type(object, json_type_array))
+               array_for_all(object, callback, closure);
+       else
+               callback(closure, object);
+}
+
+void wrap_json_array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure)
+{
+       if (json_object_is_type(object, json_type_array))
+               array_for_all(object, callback, closure);
+}
+
+void wrap_json_object_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure)
+{
+       if (json_object_is_type(object, json_type_object))
+               object_for_all(object, callback, closure);
+}
+
+void wrap_json_optobject_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure)
+{
+       if (json_object_is_type(object, json_type_object))
+               object_for_all(object, callback, closure);
+       else
+               callback(closure, object, NULL);
+}
+
+void wrap_json_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure)
+{
+       if (!object)
+               /* do nothing */;
+       else if (json_object_is_type(object, json_type_object))
+               object_for_all(object, callback, closure);
+       else if (!json_object_is_type(object, json_type_array))
+               callback(closure, object, NULL);
+       else {
+               int n = json_object_array_length(object);
+               int i = 0;
+               while(i < n)
+                       callback(closure, json_object_array_get_idx(object, i++), NULL);
+       }
+}
+
+#if defined(WRAP_JSON_TEST)
+#include <stdio.h>
+
+void p(const char *desc, ...)
+{
+       int rc;
+       va_list args;
+       struct json_object *result;
+
+       va_start(args, desc);
+       rc = wrap_json_vpack(&result, desc, args);
+       va_end(args);
+       if (!rc)
+               printf("  SUCCESS %s\n\n", json_object_to_json_string(result));
+       else
+               printf("  ERROR[char %d err %d] %s\n\n", wrap_json_get_error_position(rc), wrap_json_get_error_code(rc), wrap_json_get_error_string(rc));
+       json_object_put(result);
+}
+
+const char *xs[10];
+int *xi[10];
+int64_t *xI[10];
+double *xf[10];
+struct json_object *xo[10];
+size_t xz[10];
+
+void u(const char *value, const char *desc, ...)
+{
+       unsigned m, k;
+       int rc;
+       va_list args;
+       struct json_object *obj, *o;
+
+       memset(xs, 0, sizeof xs);
+       memset(xi, 0, sizeof xi);
+       memset(xI, 0, sizeof xI);
+       memset(xf, 0, sizeof xf);
+       memset(xo, 0, sizeof xo);
+       memset(xz, 0, sizeof xz);
+       obj = json_tokener_parse(value);
+       va_start(args, desc);
+       rc = wrap_json_vunpack(obj, desc, args);
+       va_end(args);
+       if (rc)
+               printf("  ERROR[char %d err %d] %s\n\n", wrap_json_get_error_position(rc), wrap_json_get_error_code(rc), wrap_json_get_error_string(rc));
+       else {
+               value = NULL;
+               printf("  SUCCESS");
+               va_start(args, desc);
+               k = m = 0;
+               while(*desc) {
+                       switch(*desc) {
+                       case '{': m = (m << 1) | 1; k = 1; break;
+                       case '}': m = m >> 1; k = m&1; break;
+                       case '[': m = m << 1; k = 0; break;
+                       case ']': m = m >> 1; k = m&1; break;
+                       case 's': printf(" s:%s", k ? va_arg(args, const char*) : *(va_arg(args, const char**)?:&value)); k ^= m&1; break;
+                       case '%': printf(" %%:%zu", *va_arg(args, size_t*)); k = m&1; break;
+                       case 'n': printf(" n"); k = m&1; break;
+                       case 'b': printf(" b:%d", *va_arg(args, int*)); k = m&1; break;
+                       case 'i': printf(" i:%d", *va_arg(args, int*)); k = m&1; break;
+                       case 'I': printf(" I:%lld", *va_arg(args, int64_t*)); k = m&1; break;
+                       case 'f': printf(" f:%f", *va_arg(args, double*)); k = m&1; break;
+                       case 'F': printf(" F:%f", *va_arg(args, double*)); k = m&1; break;
+                       case 'o': printf(" o:%s", json_object_to_json_string(*va_arg(args, struct json_object**))); k = m&1; break;
+                       case 'O': o = *va_arg(args, struct json_object**); printf(" O:%s", json_object_to_json_string(o)); json_object_put(o); k = m&1; break;
+                       default: break;
+                       }
+                       desc++;
+               }
+               va_end(args);
+               printf("\n\n");
+       }
+       json_object_put(obj);
+}
+
+#define P(...) do{ printf("pack(%s)\n",#__VA_ARGS__); p(__VA_ARGS__); } while(0)
+#define U(...) do{ printf("unpack(%s)\n",#__VA_ARGS__); u(__VA_ARGS__); } while(0)
+
+int main()
+{
+       char buffer[4] = {'t', 'e', 's', 't'};
+
+       P("n");
+       P("b", 1);
+       P("b", 0);
+       P("i", 1);
+       P("I", (uint64_t)0x123456789abcdef);
+       P("f", 3.14);
+       P("s", "test");
+       P("s?", "test");
+       P("s?", NULL);
+       P("s#", "test asdf", 4);
+       P("s%", "test asdf", (size_t)4);
+       P("s#", buffer, 4);
+       P("s%", buffer, (size_t)4);
+       P("s++", "te", "st", "ing");
+       P("s#+#+", "test", 1, "test", 2, "test");
+       P("s%+%+", "test", (size_t)1, "test", (size_t)2, "test");
+       P("{}", 1.0);
+       P("[]", 1.0);
+       P("o", json_object_new_int(1));
+       P("o?", json_object_new_int(1));
+       P("o?", NULL);
+       P("O", json_object_new_int(1));
+       P("O?", json_object_new_int(1));
+       P("O?", NULL);
+       P("{s:[]}", "foo");
+       P("{s+#+: []}", "foo", "barbar", 3, "baz");
+       P("{s:s,s:o,s:O}", "a", NULL, "b", NULL, "c", NULL);
+       P("{s:**}", "a", NULL);
+       P("{s:s*,s:o*,s:O*}", "a", NULL, "b", NULL, "c", NULL);
+       P("[i,i,i]", 0, 1, 2);
+       P("[s,o,O]", NULL, NULL, NULL);
+       P("[**]", NULL);
+       P("[s*,o*,O*]", NULL, NULL, NULL);
+       P(" s ", "test");
+       P("[ ]");
+       P("[ i , i,  i ] ", 1, 2, 3);
+       P("{\n\n1");
+       P("[}");
+       P("{]");
+       P("[");
+       P("{");
+       P("[i]a", 42);
+       P("ia", 42);
+       P("s", NULL);
+       P("+", NULL);
+       P(NULL);
+       P("{s:i}", NULL, 1);
+       P("{ {}: s }", "foo");
+       P("{ s: {},  s:[ii{} }", "foo", "bar", 12, 13);
+       P("[[[[[   [[[[[  [[[[ }]]]] ]]]] ]]]]]");
+
+       U("true", "b", &xi[0]);
+       U("false", "b", &xi[0]);
+       U("null", "n");
+       U("42", "i", &xi[0]);
+       U("123456789", "I", &xI[0]);
+       U("3.14", "f", &xf[0]);
+       U("12345", "F", &xf[0]);
+       U("3.14", "F", &xf[0]);
+       U("\"foo\"", "s", &xs[0]);
+       U("\"foo\"", "s%", &xs[0], &xz[0]);
+       U("{}", "{}");
+       U("[]", "[]");
+       U("{}", "o", &xo[0]);
+       U("{}", "O", &xo[0]);
+       U("{\"foo\":42}", "{si}", "foo", &xi[0]);
+       U("[1,2,3]", "[i,i,i]", &xi[0], &xi[1], &xi[2]);
+       U("{\"a\":1,\"b\":2,\"c\":3}", "{s:i, s:i, s:i}", "a", &xi[0], "b", &xi[1], "c", &xi[2]);
+       U("42", "z");
+       U("null", "[i]");
+       U("[]", "[}");
+       U("{}", "{]");
+       U("[]", "[");
+       U("{}", "{");
+       U("[42]", "[i]a", &xi[0]);
+       U("42", "ia", &xi[0]);
+       U("[]", NULL);
+       U("\"foo\"", "s", NULL);
+       U("42", "s", NULL);
+       U("42", "n");
+       U("42", "b", NULL);
+       U("42", "f", NULL);
+       U("42", "[i]", NULL);
+       U("42", "{si}", "foo", NULL);
+       U("\"foo\"", "n");
+       U("\"foo\"", "b", NULL);
+       U("\"foo\"", "i", NULL);
+       U("\"foo\"", "I", NULL);
+       U("\"foo\"", "f", NULL);
+       U("\"foo\"", "F", NULL);
+       U("true", "s", NULL);
+       U("true", "n");
+       U("true", "i", NULL);
+       U("true", "I", NULL);
+       U("true", "f", NULL);
+       U("true", "F", NULL);
+       U("[42]", "[ii]", &xi[0], &xi[1]);
+       U("{\"foo\":42}", "{si}", NULL, &xi[0]);
+       U("{\"foo\":42}", "{si}", "baz", &xi[0]);
+       U("[1,2,3]", "[iii!]", &xi[0], &xi[1], &xi[2]);
+       U("[1,2,3]", "[ii!]", &xi[0], &xi[1]);
+       U("[1,2,3]", "[ii]", &xi[0], &xi[1]);
+       U("[1,2,3]", "[ii*]", &xi[0], &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{sisi}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{sisi*}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{sisi!}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{si}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{si*}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42,\"baz\":45}", "{si!}", "baz", &xi[0], "foo", &xi[1]);
+       U("[1,{\"foo\":2,\"bar\":null},[3,4]]", "[i{sisn}[ii]]", &xi[0], "foo", &xi[1], "bar", &xi[2], &xi[3]);
+       U("[1,2,3]", "[ii!i]", &xi[0], &xi[1], &xi[2]);
+       U("[1,2,3]", "[ii*i]", &xi[0], &xi[1], &xi[2]);
+       U("{\"foo\":1,\"bar\":2}", "{si!si}", "foo", &xi[1], "bar", &xi[2]);
+       U("{\"foo\":1,\"bar\":2}", "{si*si}", "foo", &xi[1], "bar", &xi[2]);
+       U("{\"foo\":{\"baz\":null,\"bar\":null}}", "{s{sn!}}", "foo", "bar");
+       U("[[1,2,3]]", "[[ii!]]", &xi[0], &xi[1]);
+       U("{}", "{s?i}", "foo", &xi[0]);
+       U("{\"foo\":1}", "{s?i}", "foo", &xi[0]);
+       U("{}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]);
+       U("{\"foo\":[1,2]}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]);
+       U("{\"bar\":{\"baz\":{\"quux\":15}}}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]);
+       U("{\"foo\":{\"bar\":4}}", "{s?{s?i}}", "foo", "bar", &xi[0]);
+       U("{\"foo\":{}}", "{s?{s?i}}", "foo", "bar", &xi[0]);
+       U("{}", "{s?{s?i}}", "foo", "bar", &xi[0]);
+       U("{\"foo\":42,\"baz\":45}", "{s?isi!}", "baz", &xi[0], "foo", &xi[1]);
+       U("{\"foo\":42}", "{s?isi!}", "baz", &xi[0], "foo", &xi[1]);
+       return 0;
+}
+
+#endif
+
+#if 0
+
+
+    /* Unpack the same item twice */
+    j = json_pack("{s:s, s:i, s:b}", "foo", "bar", "baz", 42, "quux", 1);
+    if(!json_unpack_ex(j, &error, 0, "{s:s,s:s!}", "foo", &s, "foo", &s))
+        fail("json_unpack object with strict validation failed");
+    {
+        const char *possible_errors[] = {
+            "2 object item(s) left unpacked: baz, quux",
+            "2 object item(s) left unpacked: quux, baz"
+        };
+        check_errors(possible_errors, 2, "<validation>", 1, 10, 10);
+    }
+    json_decref(j);
+
+#endif
diff --git a/src/wrap-json.h b/src/wrap-json.h
new file mode 100644 (file)
index 0000000..cb2d0bf
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2016, 2017 "IoT.bzh"
+
+ author: José Bollo <jose.bollo@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.
+*/
+
+#pragma once
+
+#include <stdarg.h>
+#include <json-c/json.h>
+
+extern int wrap_json_get_error_position(int rc);
+extern int wrap_json_get_error_code(int rc);
+extern const char *wrap_json_get_error_string(int rc);
+
+extern int wrap_json_vpack(struct json_object **result, const char *desc, va_list args);
+extern int wrap_json_pack(struct json_object **result, const char *desc, ...);
+
+extern int wrap_json_vunpack(struct json_object *object, const char *desc, va_list args);
+extern int wrap_json_unpack(struct json_object *object, const char *desc, ...);
+extern int wrap_json_vcheck(struct json_object *object, const char *desc, va_list args);
+extern int wrap_json_check(struct json_object *object, const char *desc, ...);
+extern int wrap_json_vmatch(struct json_object *object, const char *desc, va_list args);
+extern int wrap_json_match(struct json_object *object, const char *desc, ...);
+
+extern void wrap_json_optarray_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure);
+extern void wrap_json_array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure);
+
+extern void wrap_json_optarray_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure);
+extern void wrap_json_array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure);
+extern void wrap_json_object_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure);
+extern void wrap_json_optobject_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure);
+extern void wrap_json_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure);
+
diff --git a/src/wrap-json.md b/src/wrap-json.md
new file mode 100644 (file)
index 0000000..92940f1
--- /dev/null
@@ -0,0 +1,305 @@
+WRAP-JSON facility
+==================
+
+The facility wrap-json is based on the pack/unpack API on the
+libray jansson. The two chapters below are copied from the
+documentation of jansson library copyrighted by Petri Lehtinen
+(see at end).
+
+Building Values
+---------------
+
+This section describes functions that help to create, or *pack*, complex
+JSON values, especially nested objects and arrays. Value building is
+based on a *format string* that is used to tell the functions about the
+expected arguments.
+
+For example, the format string `"i"` specifies a single integer value,
+while the format string `"[ssb]"` or the equivalent `"[s, s, b]"`
+specifies an array value with two strings and a boolean as its items:
+
+    /* Create the JSON integer 42 */
+    wrap_json_pack(&result, "i", 42);
+
+    /* Create the JSON array ["foo", "bar", true] */
+    wrap_json_pack(&result, "[ssb]", "foo", "bar", 1);
+
+Here's the full list of format specifiers. The type in parentheses
+denotes the resulting JSON type, and the type in brackets (if any)
+denotes the C type that is expected as the corresponding argument or
+arguments.
+
+`s` (string) \[const char \*\]
+
+:   Convert a null terminated UTF-8 string to a JSON string.
+
+`s?` (string) \[const char \*\]
+
+:   Like `s`, but if the argument is *NULL*, output a JSON null value.
+
+`s*` (string) \[const char \*\]
+
+:   Like `s`, but if the argument is *NULL*, do not output any value.
+    This format can only be used inside an object or an array. If used
+    inside an object, the corresponding key is additionally suppressed
+    when the value is omitted. See below for an example.
+
+`s#` (string) \[const char \*, int\]
+
+:   Convert a UTF-8 buffer of a given length to a JSON string.
+
+`s%` (string) \[const char \*, size\_t\]
+
+:   Like `s#` but the length argument is of type size\_t.
+
+`+` \[const char \*\]
+
+:   Like `s`, but concatenate to the previous string. Only valid after
+    `s`, `s#`, `+` or `+#`.
+
+`+#` \[const char \*, int\]
+
+:   Like `s#`, but concatenate to the previous string. Only valid after
+    `s`, `s#`, `+` or `+#`.
+
+`+%` (string) \[const char \*, size\_t\]
+
+:   Like `+#` but the length argument is of type size\_t.
+
+`n` (null)
+
+:   Output a JSON null value. No argument is consumed.
+
+`b` (boolean) \[int\]
+
+:   Convert a C int to JSON boolean value. Zero is converted to `false`
+    and non-zero to `true`.
+
+`i` (integer) \[int\]
+
+:   Convert a C int to JSON integer.
+
+`I` (integer) \[json\_int\_t\]
+
+:   Convert a C json\_int\_t to JSON integer.
+
+`f` (real) \[double\]
+
+:   Convert a C double to JSON real.
+
+`o` (any value) \[json\_t \*\]
+
+:   Output any given JSON value as-is. If the value is added to an array
+    or object, the reference to the value passed to `o` is stolen by the
+    container.
+
+`O` (any value) \[json\_t \*\]
+
+:   Like `o`, but the argument's reference count is incremented. This is
+    useful if you pack into an array or object and want to keep the
+    reference for the JSON value consumed by `O` to yourself.
+
+`o?`, `O?` (any value) \[json\_t \*\]
+
+:   Like `o` and `O`, respectively, but if the argument is *NULL*,
+    output a JSON null value.
+
+`o*`, `O*` (any value) \[json\_t \*\]
+
+:   Like `o` and `O`, respectively, but if the argument is *NULL*, do
+    not output any value. This format can only be used inside an object
+    or an array. If used inside an object, the corresponding key is
+    additionally suppressed. See below for an example.
+
+`[fmt]` (array)
+
+:   Build an array with contents from the inner format string. `fmt` may
+    contain objects and arrays, i.e. recursive value building is
+    supported.
+
+`{fmt}` (object)
+
+:   Build an object with contents from the inner format string `fmt`.
+    The first, third, etc. format specifier represent a key, and must be
+    a string (see `s`, `s#`, `+` and `+#` above), as object keys are
+    always strings. The second, fourth, etc. format specifier represent
+    a value. Any value may be an object or array, i.e. recursive value
+    building is supported.
+
+Whitespace, `:` and `,` are ignored.
+
+More examples:
+
+    /* Build an empty JSON object */
+    wrap_json_pack(&result, "{}");
+
+    /* Build the JSON object {"foo": 42, "bar": 7} */
+    wrap_json_pack(&result, "{sisi}", "foo", 42, "bar", 7);
+
+    /* Like above, ':', ',' and whitespace are ignored */
+    wrap_json_pack(&result, "{s:i, s:i}", "foo", 42, "bar", 7);
+
+    /* Build the JSON array [[1, 2], {"cool": true}] */
+    wrap_json_pack(&result, "[[i,i],{s:b}]", 1, 2, "cool", 1);
+
+    /* Build a string from a non-null terminated buffer */
+    char buffer[4] = {'t', 'e', 's', 't'};
+    wrap_json_pack(&result, "s#", buffer, 4);
+
+    /* Concatenate strings together to build the JSON string "foobarbaz" */
+    wrap_json_pack(&result, "s++", "foo", "bar", "baz");
+
+    /* Create an empty object or array when optional members are missing */
+    wrap_json_pack(&result, "{s:s*,s:o*,s:O*}", "foo", NULL, "bar", NULL, "baz", NULL);
+    wrap_json_pack(&result, "[s*,o*,O*]", NULL, NULL, NULL);
+
+Parsing and Validating Values
+-----------------------------
+
+This section describes functions that help to validate complex values
+and extract, or *unpack*, data from them. Like building values
+&lt;apiref-pack&gt;, this is also based on format strings.
+
+While a JSON value is unpacked, the type specified in the format string
+is checked to match that of the JSON value. This is the validation part
+of the process. In addition to this, the unpacking functions can also
+check that all items of arrays and objects are unpacked. This check be
+enabled with the format specifier `!` or by using the flag
+`JSON_STRICT`. See below for details.
+
+Here's the full list of format specifiers. The type in parentheses
+denotes the JSON type, and the type in brackets (if any) denotes the C
+type whose address should be passed.
+
+`s` (string) \[const char \*\]
+
+:   Convert a JSON string to a pointer to a null terminated UTF-8
+    string. The resulting string is extracted by using
+    json\_string\_value() internally, so it exists as long as there are
+    still references to the corresponding JSON string.
+
+`s%` (string) \[const char \*, size\_t \*\]
+
+:   Convert a JSON string to a pointer to a null terminated UTF-8 string
+    and its length.
+
+`n` (null)
+
+:   Expect a JSON null value. Nothing is extracted.
+
+`b` (boolean) \[int\]
+
+:   Convert a JSON boolean value to a C int, so that `true` is converted
+    to 1 and `false` to 0.
+
+`i` (integer) \[int\]
+
+:   Convert a JSON integer to C int.
+
+`I` (integer) \[json\_int\_t\]
+
+:   Convert a JSON integer to C json\_int\_t.
+
+`f` (real) \[double\]
+
+:   Convert a JSON real to C double.
+
+`F` (integer or real) \[double\]
+
+:   Convert a JSON number (integer or real) to C double.
+
+`o` (any value) \[json\_t \*\]
+
+:   Store a JSON value with no conversion to a json\_t pointer.
+
+`O` (any value) \[json\_t \*\]
+
+:   Like `O`, but the JSON value's reference count is incremented.
+
+`[fmt]` (array)
+
+:   Convert each item in the JSON array according to the inner format
+    string. `fmt` may contain objects and arrays, i.e. recursive value
+    extraction is supported.
+
+`{fmt}` (object)
+
+:   Convert each item in the JSON object according to the inner format
+    string `fmt`. The first, third, etc. format specifier represent a
+    key, and must be `s`. The corresponding argument to unpack functions
+    is read as the object key. The second fourth, etc. format specifier
+    represent a value and is written to the address given as the
+    corresponding argument. **Note** that every other argument is read
+    from and every other is written to.
+
+    `fmt` may contain objects and arrays as values, i.e. recursive value
+    extraction is supported.
+
+`!`
+
+:   This special format specifier is used to enable the check that all
+    object and array items are accessed, on a per-value basis. It must
+    appear inside an array or object as the last format specifier before
+    the closing bracket or brace.
+
+`*`
+
+:   This special format specifier is the opposite of `!`. This is the default.
+    It must appear inside an array or object as the last format specifier
+    before the closing bracket or brace.
+
+Whitespace, `:` and `,` are ignored.
+
+Examples:
+
+    /* root is the JSON integer 42 */
+    int myint;
+    wrap_json_unpack(root, "i", &myint);
+    assert(myint == 42);
+
+    /* root is the JSON object {"foo": "bar", "quux": true} */
+    const char *str;
+    int boolean;
+    wrap_json_unpack(root, "{s:s, s:b}", "foo", &str, "quux", &boolean);
+    assert(strcmp(str, "bar") == 0 && boolean == 1);
+
+    /* root is the JSON array [[1, 2], {"baz": null} */
+    wrap_json_check(root, "[[i,i], {s:n}]", "baz");
+    /* returns 0 for validation success, nothing is extracted */
+
+    /* root is the JSON array [1, 2, 3, 4, 5] */
+    int myint1, myint2;
+    wrap_json_unpack(root, "[ii!]", &myint1, &myint2);
+    /* returns -1 for failed validation */
+
+    /* root is an empty JSON object */
+    int myint = 0, myint2 = 0, myint3 = 0;
+    wrap_json_unpack(root, "{s?i, s?[ii]}",
+                "foo", &myint1,
+                "bar", &myint2, &myint3);
+    /* myint1, myint2 or myint3 is no touched as "foo" and "bar" don't exist */
+
+
+Copyright
+---------
+
+Copyright (c) 2009-2016 Petri Lehtinen <petri@digip.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+