diff options
-rw-r--r-- | src/CMakeLists.txt | 83 | ||||
-rw-r--r-- | src/afm-binding.c | 487 | ||||
-rw-r--r-- | src/afm-binding.export-map | 1 | ||||
-rw-r--r-- | src/wrap-json.c | 939 | ||||
-rw-r--r-- | src/wrap-json.h | 46 | ||||
-rw-r--r-- | src/wrap-json.md | 305 |
6 files changed, 1815 insertions, 46 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cd3b9aa..782e154 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 index 0000000..b111f09 --- /dev/null +++ b/src/afm-binding.c @@ -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 index 0000000..ee2f413 --- /dev/null +++ b/src/afm-binding.export-map @@ -0,0 +1 @@ +{ global: afbBindingV*; local: *; }; diff --git a/src/wrap-json.c b/src/wrap-json.c new file mode 100644 index 0000000..9bb8d19 --- /dev/null +++ b/src/wrap-json.c @@ -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 index 0000000..cb2d0bf --- /dev/null +++ b/src/wrap-json.h @@ -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 index 0000000..92940f1 --- /dev/null +++ b/src/wrap-json.md @@ -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 +<apiref-pack>, 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. + |