aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjobol <jose.bollo@iot.bzh>2016-06-06 11:32:19 +0200
committerJosé Bollo <jose.bollo@iot.bzh>2016-06-08 11:48:35 +0200
commitcee4240979b3b9c4ebb877631e66157918598c3d (patch)
tree3072776b681121e48fc73c87548fb77f39f559c1
parent7c8b8a78f9029d8568a89e5f2a27c88a75b1daa2 (diff)
DBus binding: first draft
Implements the verb 'rawcall' Change-Id: Id83b065f778f2fd61ecf6e4e13bff3cc17d8ef18 Signed-off-by: José Bollo <jose.bollo@iot.bzh>
-rw-r--r--plugins/CMakeLists.txt1
-rw-r--r--plugins/intrinsics/CMakeLists.txt15
-rw-r--r--plugins/intrinsics/afb-dbus-binding.c633
-rw-r--r--plugins/intrinsics/export.map1
-rw-r--r--test/test-dbus-rawcall.html57
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>
+
+