diff options
-rw-r--r-- | plugins/CMakeLists.txt | 1 | ||||
-rw-r--r-- | plugins/intrinsics/CMakeLists.txt | 15 | ||||
-rw-r--r-- | plugins/intrinsics/afb-dbus-binding.c | 633 | ||||
-rw-r--r-- | plugins/intrinsics/export.map | 1 | ||||
-rw-r--r-- | test/test-dbus-rawcall.html | 57 |
5 files changed, 707 insertions, 0 deletions
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c8928b53..2aacf1a1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -2,3 +2,4 @@ ADD_SUBDIRECTORY(samples) ADD_SUBDIRECTORY(audio) ADD_SUBDIRECTORY(radio) ADD_SUBDIRECTORY(media) +ADD_SUBDIRECTORY(intrinsics) diff --git a/plugins/intrinsics/CMakeLists.txt b/plugins/intrinsics/CMakeLists.txt new file mode 100644 index 00000000..b9bd638a --- /dev/null +++ b/plugins/intrinsics/CMakeLists.txt @@ -0,0 +1,15 @@ + +INCLUDE_DIRECTORIES(${include_dirs}) + +################################################## +# DBus Binding +################################################## +ADD_LIBRARY(afb-dbus-binding MODULE afb-dbus-binding.c) +SET_TARGET_PROPERTIES(afb-dbus-binding PROPERTIES + PREFIX "" + LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/export.map" +) +TARGET_LINK_LIBRARIES(afb-dbus-binding ${link_libraries}) +INSTALL(TARGETS afb-dbus-binding + LIBRARY DESTINATION ${plugin_install_dir}) + diff --git a/plugins/intrinsics/afb-dbus-binding.c b/plugins/intrinsics/afb-dbus-binding.c new file mode 100644 index 00000000..37988559 --- /dev/null +++ b/plugins/intrinsics/afb-dbus-binding.c @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2016 "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. + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> +#include <json-c/json.h> + +#include <systemd/sd-bus.h> +#include <systemd/sd-bus-protocol.h> + +#include <afb/afb-plugin.h> +#include <afb/afb-plugin.h> +#include <afb/afb-plugin.h> + +/* + * the interface to afb-daemon + */ +const struct AFB_interface *afbitf; + +/* + * union of possible dbus values + */ +union any { + uint8_t u8; + int16_t i16; + uint16_t u16; + int32_t i32; + uint32_t u32; + int64_t i64; + uint64_t u64; + double dbl; + const char *cstr; + char *str; +}; + +static int unpacklist(struct sd_bus_message *msg, struct json_object **result); +static int packlist(struct sd_bus_message *msg, const char *signature, struct json_object *list); + +/* + * Get the string of 'key' from 'obj' + * Returns NULL if 'key' isn't in 'obj' + */ +static const char *strval(struct json_object *obj, const char *key) +{ + struct json_object *keyval; + return json_object_object_get_ex(obj, key, &keyval) ? json_object_get_string(keyval) : NULL; +} + +/* + * Signature of a json object + */ +static const char *signature_for_json(struct json_object *obj) +{ + switch (json_object_get_type(obj)) { + default: + case json_type_null: + return NULL; + case json_type_boolean: + return "b"; + case json_type_double: + return "d"; + case json_type_int: + return "i"; + case json_type_object: + return "a{sv}"; + case json_type_array: + return "av"; + case json_type_string: + return "s"; + } +} + +/* + * Length of a single complete type + */ +static int lentype(const char *signature, int allows_dict, int allows_not_basic) +{ + int rc, len; + switch(signature[0]) { + + case SD_BUS_TYPE_ARRAY: + if (!allows_not_basic) + break; + rc = lentype(signature + 1, 1, 1); + if (rc < 0) + break; + return 1 + rc; + + case SD_BUS_TYPE_STRUCT_BEGIN: + if (!allows_not_basic) + break; + len = 1; + rc = lentype(signature + len, 0, 1); + while (rc > 0 && signature[len] != SD_BUS_TYPE_STRUCT_END) { + len += rc; + rc = lentype(signature + len, 0, 1); + } + if (rc < 0) + break; + return 1 + len; + + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: + if (!allows_not_basic || !allows_dict) + break; + rc = lentype(signature + 1, 0, 0); + if (rc < 0) + break; + len = 1 + rc; + rc = lentype(signature + len, 0, 1); + if (rc < 0 || signature[len + rc] != SD_BUS_TYPE_DICT_ENTRY_END) + break; + return len + rc + 1; + + case '\x0': + case SD_BUS_TYPE_STRUCT: + case SD_BUS_TYPE_STRUCT_END: + case SD_BUS_TYPE_DICT_ENTRY: + case SD_BUS_TYPE_DICT_ENTRY_END: + break; + + default: + return 1; + } + return -1; +} + + +/* + * Unpack a D-Bus message to a json object + */ +static int unpacksingle(struct sd_bus_message *msg, struct json_object **result) +{ + char c; + int rc; + union any any; + const char *content; + struct json_object *item; + + *result = NULL; + rc = sd_bus_message_peek_type(msg, &c, &content); + if (rc <= 0) + return rc; + + switch (c) { + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + rc = sd_bus_message_read_basic(msg, c, &any); + if (rc < 0) + goto error; + switch (c) { + case SD_BUS_TYPE_BOOLEAN: + *result = json_object_new_boolean(any.i32); + break; + case SD_BUS_TYPE_BYTE: + *result = json_object_new_int(any.u8); + break; + case SD_BUS_TYPE_INT16: + *result = json_object_new_int(any.i16); + break; + case SD_BUS_TYPE_UINT16: + *result = json_object_new_int(any.u16); + break; + case SD_BUS_TYPE_INT32: + *result = json_object_new_int(any.i32); + break; + case SD_BUS_TYPE_UINT32: + *result = json_object_new_int64(any.u32); + break; + case SD_BUS_TYPE_INT64: + *result = json_object_new_int64(any.i64); + break; + case SD_BUS_TYPE_UINT64: + *result = json_object_new_int64((int64_t)any.u64); + break; + case SD_BUS_TYPE_DOUBLE: + *result = json_object_new_string(any.cstr); + break; + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + *result = json_object_new_string(any.cstr); + break; + } + return *result == NULL ? -1 : 1; + + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_VARIANT: + case SD_BUS_TYPE_STRUCT: + case SD_BUS_TYPE_DICT_ENTRY: + rc = sd_bus_message_enter_container(msg, c, content); + if (rc < 0) + goto error; + if (c == SD_BUS_TYPE_ARRAY && content[0] == SD_BUS_TYPE_DICT_ENTRY_BEGIN && content[1] == SD_BUS_TYPE_STRING) { + *result = json_object_new_object(); + if (*result == NULL) + return -1; + for(;;) { + rc = sd_bus_message_enter_container(msg, 0, NULL); + if (rc < 0) + goto error; + if (rc == 0) + break; + rc = sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, &any); + if (rc < 0) + goto error; + rc = unpacksingle(msg, &item); + if (rc < 0) + goto error; + json_object_object_add(*result, any.cstr, item); + rc = sd_bus_message_exit_container(msg); + if (rc < 0) + goto error; + } + } else { + rc = unpacklist(msg, result); + if (rc < 0) + goto error; + } + rc = sd_bus_message_exit_container(msg); + if (rc < 0) + goto error; + return 1; + default: + goto error; + } +error: + json_object_put(*result); + return -1; +} + +/* + * Unpack a D-Bus message to a json object + */ +static int unpacklist(struct sd_bus_message *msg, struct json_object **result) +{ + int rc; + struct json_object *item; + + /* allocates the result */ + *result = json_object_new_array(); + if (*result == NULL) + goto error; + + /* read the values */ + for (;;) { + rc = unpacksingle(msg, &item); + if (rc < 0) + goto error; + if (rc == 0) + return 0; + json_object_array_add(*result, item); + } +error: + json_object_put(*result); + *result = NULL; + return -1; +} + +static int packsingle(struct sd_bus_message *msg, const char *signature, struct json_object *item) +{ + int index, count, rc, len; + union any any; + char *subsig; + struct json_object_iterator it, end; + + len = lentype(signature, 0, 1); + if (len < 0) + goto error; + + switch (*signature) { + case SD_BUS_TYPE_BOOLEAN: + any.i32 = json_object_get_boolean(item); + break; + + case SD_BUS_TYPE_BYTE: + any.i32 = json_object_get_int(item); + if (any.i32 != (int32_t)(uint8_t)any.i32) + goto error; + any.u8 = (uint8_t)any.i32; + break; + + case SD_BUS_TYPE_INT16: + any.i32 = json_object_get_int(item); + if (any.i32 != (int32_t)(int16_t)any.i32) + goto error; + any.i16 = (int16_t)any.i32; + break; + + case SD_BUS_TYPE_UINT16: + any.i32 = json_object_get_int(item); + if (any.i32 != (int32_t)(uint16_t)any.i32) + goto error; + any.u16 = (uint16_t)any.i32; + break; + + case SD_BUS_TYPE_INT32: + any.i64 = json_object_get_int64(item); + if (any.i64 != (int64_t)(int32_t)any.i64) + goto error; + any.i32 = (int32_t)any.i64; + break; + + case SD_BUS_TYPE_UINT32: + any.i64 = json_object_get_int64(item); + if (any.i64 != (int64_t)(uint32_t)any.i64) + goto error; + any.u32 = (uint32_t)any.i64; + break; + + case SD_BUS_TYPE_INT64: + any.i64 = json_object_get_int64(item); + break; + + case SD_BUS_TYPE_UINT64: + any.u64 = (uint64_t)json_object_get_int64(item); + break; + + case SD_BUS_TYPE_DOUBLE: + any.dbl = json_object_get_double(item); + break; + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + any.cstr = json_object_get_string(item); + break; + + case SD_BUS_TYPE_VARIANT: + signature = signature_for_json(item); + if (signature == NULL) + goto error; + rc = sd_bus_message_open_container(msg, SD_BUS_TYPE_VARIANT, signature); + if (rc < 0) + goto error; + rc = packsingle(msg, signature, item); + if (rc < 0) + goto error; + rc = sd_bus_message_close_container(msg); + if (rc < 0) + goto error; + return len; + + case SD_BUS_TYPE_ARRAY: + subsig = strndupa(signature + 1, len - 1); + rc = sd_bus_message_open_container(msg, SD_BUS_TYPE_ARRAY, subsig); + if (rc < 0) + goto error; + if (json_object_is_type(item, json_type_array)) { + /* Is an array! */ + count = json_object_array_length(item); + index = 0; + while(index < count) { + rc = packsingle(msg, subsig, json_object_array_get_idx(item, index++)); + if (rc < 0) + goto error; + } + } else { + /* Not an array! Check if it matches an string dictionnary */ + if (!json_object_is_type(item, json_type_object)) + goto error; + if (*subsig++ != SD_BUS_TYPE_DICT_ENTRY_BEGIN) + goto error; + if (*subsig != SD_BUS_TYPE_STRING) + goto error; + /* iterate the object values */ + subsig[strlen(subsig) - 1] = 0; + it = json_object_iter_begin(item); + end = json_object_iter_end(item); + while (!json_object_iter_equal(&it, &end)) { + rc = sd_bus_message_open_container(msg, SD_BUS_TYPE_DICT_ENTRY, subsig); + if (rc < 0) + goto error; + any.cstr = json_object_iter_peek_name(&it); + rc = sd_bus_message_append_basic(msg, *subsig, &any); + if (rc < 0) + goto error; + rc = packsingle(msg, subsig + 1, json_object_iter_peek_value(&it)); + if (rc < 0) + goto error; + rc = sd_bus_message_close_container(msg); + if (rc < 0) + goto error; + json_object_iter_next(&it); + } + } + rc = sd_bus_message_close_container(msg); + if (rc < 0) + goto error; + return len; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: + subsig = strndupa(signature + 1, len - 2); + rc = sd_bus_message_open_container(msg, + ((*signature) == SD_BUS_TYPE_STRUCT_BEGIN) ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, + subsig); + if (rc < 0) + goto error; + rc = packlist(msg, subsig, item); + if (rc < 0) + goto error; + rc = sd_bus_message_close_container(msg); + if (rc < 0) + goto error; + return len; + + default: + goto error; + } + + rc = sd_bus_message_append_basic(msg, *signature, &any); + if (rc < 0) + goto error; + return len; + +error: + return -1; +} + +static int packlist(struct sd_bus_message *msg, const char *signature, struct json_object *list) +{ + int rc, count, index, scan; + struct json_object *item; + + scan = 0; + if (list == NULL) { + /* empty case */ + if (*signature) + goto error; + return scan; + } + + if (!json_object_is_type(list, json_type_array)) { + /* down grade gracefully to single */ + rc = packsingle(msg, signature, list); + if (rc < 0) + goto error; + scan = rc; + if (signature[scan] != 0) + goto error; + return scan; + } + + /* iterate over elements */ + count = json_object_array_length(list); + index = 0; + for (;;) { + /* check state */ + if (index == count && signature[scan] == 0) + return scan; + if (index == count || signature[scan] == 0) + goto error; + + /* get the item */ + item = json_object_array_get_idx(list, index); + if (item == NULL) + goto error; + + /* pack the item */ + rc = packsingle(msg, signature + scan, item); + if (rc < 0) + goto error; + + /* advance */ + scan += rc; + index++; + } + +error: + return -(scan + 1); +} + +/* + * handle the reply + */ +static int on_rawcall_reply(sd_bus_message *msg, struct afb_req *req, sd_bus_error *ret_error) +{ + struct json_object *obj = NULL; + int rc; + const sd_bus_error *err; + + err = sd_bus_message_get_error(msg); + if (err != NULL) + afb_req_fail_f(*req, "failed", "DBus-error-name: %s, DBus-error-message: %s", err->name, err->message); + else { + rc = unpacklist(msg, &obj); + if (rc < 0) + afb_req_fail(*req, "failed", "can't unpack"); + else + afb_req_success(*req, obj, NULL); + } + json_object_put(obj); + afb_req_unref(*req); + free(req); + return 1; +} + +/* + * Make a raw call to DBUS method + * The query should have: + * { + * "bus": "optional: 'system' or 'user' (default)" + * "destination": "destination handling the object", + * "path": "object path", + * "interface": "interface of the call", + * "member": "member of the interface of the call", + * "signature": "signature of the arguments", + * "arguments": "ARRAY of arguments" + * } + */ +static void rawcall(struct afb_req req) +{ + struct json_object *obj; + struct json_object *args; + + const char *busname; + const char *destination; + const char *path; + const char *interface; + const char *member; + const char *signature; + + struct sd_bus_message *msg = NULL; + struct sd_bus *bus; + int rc; + + /* get the query */ + obj = afb_req_json(req); + if (obj == NULL) + goto internal_error; + + /* get parameters */ + destination = strval(obj, "destination"); + path = strval(obj, "path"); + interface = strval(obj, "interface"); + member = strval(obj, "member"); + if (path == NULL || member == NULL) + goto bad_request; + + /* get arguments */ + signature = strval(obj, "signature") ? : ""; + args = NULL; + json_object_object_get_ex(obj, "arguments", &args); + + /* get bus */ + busname = strval(obj, "bus"); + if (busname != NULL && !strcmp(busname, "system")) + bus = afb_daemon_get_system_bus(afbitf->daemon); + else + bus = afb_daemon_get_user_bus(afbitf->daemon); + + /* creates the message */ + rc = sd_bus_message_new_method_call(bus, &msg, destination, path, interface, member); + if (rc != 0) + goto internal_error; + rc = packlist(msg, signature, args); + if (rc < 0) + goto bad_request; + + /* */ + rc = sd_bus_call_async(bus, NULL, msg, (void*)on_rawcall_reply, afb_req_store(req), -1); + if (rc < 0) + goto internal_error; + goto cleanup; + +internal_error: + afb_req_fail(req, "failed", "internal error"); + goto cleanup; + +bad_request: + afb_req_fail(req, "failed", "bad request"); + +cleanup: + sd_bus_message_unref(msg); +} + +/* + * array of the verbs exported to afb-daemon + */ +static const struct AFB_verb_desc_v1 plugin_verbs[] = { + /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */ + { .name= "rawcall", .session= AFB_SESSION_NONE, .callback= rawcall, .info= "raw call to dbus method" }, + { .name= NULL } /* marker for end of the array */ +}; + +/* + * description of the plugin for afb-daemon + */ +static const struct AFB_plugin plugin_description = +{ + /* description conforms to VERSION 1 */ + .type= AFB_PLUGIN_VERSION_1, + .v1= { /* fills the v1 field of the union when AFB_PLUGIN_VERSION_1 */ + .prefix= "dbus", /* the API name (or plugin name or prefix) */ + .info= "raw dbus binding", /* short description of of the plugin */ + .verbs = plugin_verbs /* the array describing the verbs of the API */ + } +}; + +/* + * activation function for registering the plugin called by afb-daemon + */ +const struct AFB_plugin *pluginAfbV1Register(const struct AFB_interface *itf) +{ + afbitf = itf; /* records the interface for accessing afb-daemon */ + return &plugin_description; /* returns the description of the plugin */ +} + diff --git a/plugins/intrinsics/export.map b/plugins/intrinsics/export.map new file mode 100644 index 00000000..e2da85ca --- /dev/null +++ b/plugins/intrinsics/export.map @@ -0,0 +1 @@ +{ global: pluginAfbV1Register; local: *; }; diff --git a/test/test-dbus-rawcall.html b/test/test-dbus-rawcall.html new file mode 100644 index 00000000..74e57624 --- /dev/null +++ b/test/test-dbus-rawcall.html @@ -0,0 +1,57 @@ +<html> +<head> + <title>Test of DBUS binding</title> + <script type="text/javascript" src="AFB.js"></script> + <script type="text/javascript"> + var afb = new AFB("api", "hello"); + var ws; + var names = [ "bus", "destination", "path", "interface", "member", "arguments", "signature" ]; + + function onopen() { + document.getElementById("main").style.visibility = "visible"; + document.getElementById("connected").innerHTML = "Connected to WebSocket server"; + ws.onevent("*", gotevent); + } + function onabort() { + document.getElementById("main").style.visibility = "hidden"; + document.getElementById("connected").innerHTML = "Connection Closed"; + } + function init() { + ws = new afb.ws(onopen, onabort); + } + function replyok(obj) { + document.getElementById("output").innerHTML = "OK: "+JSON.stringify(obj); + } + function replyerr(obj) { + document.getElementById("output").innerHTML = "ERROR: "+JSON.stringify(obj); + } + function gotevent(obj) { + document.getElementById("outevt").innerHTML = JSON.stringify(obj); + } + function send() { + var req = { }; + names.forEach(function(n){ + var v = document.getElementById(n).value; + if(v) req[n] = n == "arguments" ? JSON.parse(v) : v; + }); + ws.call("dbus/rawcall", req).then(replyok, replyerr); + } + </script> + +<body onload="init();"> + <h1>WebSocket Echo</h1> + <div id="connected">Not Connected</div> + <div id="main" style="visibility:hidden"> + bus: <input id="bus" type="text" size="80"/><br/> + destination: <input id="destination" type="text" size="80"/><br/> + path: <input id="path" type="text" size="80"/><br/> + interface: <input id="interface" type="text" size="80"/><br/> + member: <input id="member" type="text" size="80"/><br/> + signature: <input id="signature" type="text" size="80"/><br/> + arguments: <input id="arguments" type="text" size="80"/><br/> + <input type="button" onclick="send()" value="SEND..."/><br/> + Server says... <div id="output"></div> + Events: <div id="outevt"></div> + </div> + + |