diff --git a/.travis.yml b/.travis.yml index be87f71cf..c2186067f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ env: global: - secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI=" matrix: - - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG" - - LWS_METHOD=lwsws2 CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG" + - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/" + - LWS_METHOD=lwsws2 CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/" - LWS_METHOD=default CMAKE_ARGS="-DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=mbedtls CMAKE_ARGS="-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG" - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON -DLWS_WITH_MINIMAL_EXAMPLES=1" diff --git a/CMakeLists.txt b/CMakeLists.txt index ba7dac783..8150f82f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,6 +133,12 @@ if(LWS_WITH_DISTRO_RECOMMENDED) set(LWS_ROLE_DBUS 1) endif() +# do you care about this? Then send me a patch where it disables it on travis +# but allows it on APPLE +if (APPLE) + set(LWS_ROLE_DBUS 0) +endif() + if(NOT DEFINED CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type") endif() @@ -686,15 +692,15 @@ CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H) CHECK_LIBRARY_EXISTS(cap cap_set_flag "" LWS_HAVE_LIBCAP) if (LWS_ROLE_DBUS) - if (NOT LWS_HAVE_LIBDBUS) - message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc") - endif() if (NOT LWS_DBUS_LIB) set(LWS_DBUS_LIB "dbus-1") endif() CHECK_LIBRARY_EXISTS(${LWS_DBUS_LIB} dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS) + if (NOT LWS_HAVE_LIBDBUS) + message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc") + endif() if (NOT LWS_DBUS_INCLUDE1) # look in fedora and debian / ubuntu place diff --git a/minimal-examples/dbus-client/README.md b/minimal-examples/dbus-client/README.md index 7951825e7..ecde9d156 100644 --- a/minimal-examples/dbus-client/README.md +++ b/minimal-examples/dbus-client/README.md @@ -1,4 +1,4 @@ |Example|Demonstrates| ---|--- minimal-dbus-client|Shows how to connect to a DBusServer dbus server like minimal-dbus-server - +minimal-dbus-ws-proxy-testclient|A test client for use with minimal-dbus-ws-proxy diff --git a/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c b/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c index 7e30d13be..5b0f2d9c0 100644 --- a/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c +++ b/minimal-examples/dbus-client/minimal-dbus-client/minimal-dbus-client.c @@ -40,8 +40,9 @@ client_message_handler(DBusConnection *conn, DBusMessage *message, void *data) dbus_message_get_member(message), dbus_message_get_path(message)); - if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, - &str, DBUS_TYPE_INVALID)) + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; lwsl_notice("%s: '%s'\n", __func__, str); diff --git a/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/CMakeLists.txt b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/CMakeLists.txt new file mode 100644 index 000000000..dda46bf13 --- /dev/null +++ b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/CMakeLists.txt @@ -0,0 +1,120 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) +include(CheckLibraryExists) + +set(SAMP lws-minimal-dbus-ws-proxy-testclient) +set(SRCS minimal-dbus-ws-proxy-testclient.c) + +if (NOT LWS_WITH_MINIMAL_EXAMPLES) + CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS) + if (NOT LWS_HAVE_LIBDBUS) + message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc") + endif() + + if (NOT LWS_DBUS_LIB) + set(LWS_DBUS_LIB "dbus-1") + endif() + + if (NOT LWS_DBUS_INCLUDE1) + # look in fedora and debian / ubuntu place + if (EXISTS "/usr/include/dbus-1.0") + set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are") + endif() + endif() + + if (NOT LWS_DBUS_INCLUDE2) + # look in fedora... debian / ubuntu has the ARCH in the path... + if (EXISTS "/usr/lib64/dbus-1.0/include") + set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system") + endif() + endif() + + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2}) + + if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2) + message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md") + endif() + +endif() + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_DBUS 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + include_directories("${LWS_DBUS_INCLUDE1}") + include_directories("${LWS_DBUS_INCLUDE2}") + list(APPEND LIB_LIST dbus-1) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB}) + else() + target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB}) + endif() +endif() diff --git a/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/README.md b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/README.md new file mode 100644 index 000000000..1e0dbbf57 --- /dev/null +++ b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/README.md @@ -0,0 +1,52 @@ +# lws minimal dbus ws proxy testclient + +This is a test client used to test `./minimal-examples/dbus-server/minimal-dbus-ws-proxy` + +It asks the minimal dbus ws proxy application to connect to libwebsockets.org +over the mirror protocol. And it proxies back the ASCII packets used to +communicate the mirror sample drawing vectors over dbus to this test client +if you draw on the mirror example app at https://libwebsockets.org/testserver/ +in a browser. + +## build + +Using libdbus requires additional non-default include paths setting, same as +is necessary for lws build described in ./lib/roles/dbus/README.md + +CMake can guess one path and the library name usually, see the README above +for details of how to override for custom libdbus and cross build. + +Fedora example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include" +$ make +``` + +Ubuntu example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include" +$ make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 + +This connects to the minimal-dbus-ws-proxy example running in another terminal. + +``` + $ ./lws-minimal-dbus-ws-proxy-testclient +[2018/10/05 14:17:16:6286] USER: LWS minimal DBUS ws proxy testclient +[2018/10/05 14:17:16:6538] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off +[2018/10/05 14:17:16:6617] USER: create_dbus_client_conn: connecting to 'unix:abstract=org.libwebsockets.wsclientproxy' +[2018/10/05 14:17:16:7189] NOTICE: create_dbus_client_conn: created OK +[2018/10/05 14:17:16:7429] USER: remote_method_call: requesting proxy connection wss://libwebsockets.org/ lws-mirror-protocol +[2018/10/05 14:17:17:0387] USER: pending_call_notify: received 'Connecting' +[2018/10/05 14:17:18:7475] NOTICE: client_message_handler: (type 7) 'ws client connection established' +[2018/10/05 14:17:21:2028] NOTICE: client_message_handler: (type 6) 'd #000000 323 63 323 67;' +[2018/10/05 14:17:21:2197] NOTICE: client_message_handler: (type 6) 'd #000000 323 67 327 73;' +... +``` + diff --git a/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c new file mode 100644 index 000000000..294a5566d --- /dev/null +++ b/minimal-examples/dbus-client/minimal-dbus-ws-proxy-testclient/minimal-dbus-ws-proxy-testclient.c @@ -0,0 +1,447 @@ +/* + * lws-minimal-dbus-ws-proxy-testclient + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This acts as a test client over DBUS, opening a session with + * minimal-dbus-ws-proxy and sending and receiving data on the libwebsockets + * mirror demo page. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * These are the various states our connection can be in, both with regards + * to the direct connection to the proxy, and the state of the onward ws + * connection the proxy opens at our request. + */ + +enum lws_dbus_client_state { + LDCS_NOTHING, /* no connection yet */ + LDCS_CONN, /* conn to proxy */ + LDCS_CONN_WAITING_ONWARD, /* conn to proxy, awaiting proxied conn */ + LDCS_CONN_ONWARD, /* conn to proxy and onward conn OK */ + LDCS_CONN_CLOSED, /* conn to proxy but onward conn closed */ + LDCS_CLOSED, /* connection to proxy is closed */ +}; + +/* + * our expanded dbus context + */ + +struct lws_dbus_ctx_wsproxy_client { + struct lws_dbus_ctx ctx; + + enum lws_dbus_client_state state; +}; + +static struct lws_dbus_ctx_wsproxy_client *dbus_ctx; +static struct lws_context *context; +static int interrupted, autoexit_budget = -1, count_rx, count_tx; + +#define THIS_INTERFACE "org.libwebsockets.wsclientproxy" +#define THIS_OBJECT "/org/libwebsockets/wsclientproxy" +#define THIS_BUSNAME "org.libwebsockets.wsclientproxy" + +#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.wsclientproxy" + +static void +state_transition(struct lws_dbus_ctx_wsproxy_client *dcwc, + enum lws_dbus_client_state state) +{ + lwsl_notice("%s: %p: from state %d -> %d\n", __func__, + dcwc,dcwc->state, state); + dcwc->state = state; +} + +static DBusHandlerResult +filter(DBusConnection *conn, DBusMessage *message, void *data) +{ + struct lws_dbus_ctx_wsproxy_client *dcwc = + (struct lws_dbus_ctx_wsproxy_client *)data; + const char *str; + + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + /* received ws data */ + + if (dbus_message_is_signal(message, THIS_INTERFACE, "Receive")) { + lwsl_user("%s: Received '%s'\n", __func__, str); + count_rx++; + } + + /* proxy ws connection failed */ + + if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") && + !strcmp(str, "ws client connection error")) + state_transition(dcwc, LDCS_CONN_CLOSED); + + /* proxy ws connection succeeded */ + + if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") && + !strcmp(str, "ws client connection established")) + state_transition(dcwc, LDCS_CONN_ONWARD); + + /* proxy ws connection has closed */ + + if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") && + !strcmp(str, "ws client connection closed")) + state_transition(dcwc, LDCS_CONN_CLOSED); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static void +destroy_dbus_client_conn(struct lws_dbus_ctx_wsproxy_client **pdcwc) +{ + struct lws_dbus_ctx_wsproxy_client *dcwc = *pdcwc; + + if (!dcwc || !dcwc->ctx.conn) + return; + + lwsl_notice("%s\n", __func__); + + dbus_connection_remove_filter(dcwc->ctx.conn, filter, &dcwc->ctx); + dbus_connection_close(dcwc->ctx.conn); + dbus_connection_unref(dcwc->ctx.conn); + + free(dcwc); + + *pdcwc = NULL; +} + +/* + * This callback is coming when lws has noticed the fd took a POLLHUP. The + * ctx has effectively gone out of scope before this, and the connection can + * be cleaned up and the ctx freed. + */ + +static void +cb_closing(struct lws_dbus_ctx *ctx) +{ + struct lws_dbus_ctx_wsproxy_client *dcwc = + (struct lws_dbus_ctx_wsproxy_client *)ctx; + + lwsl_err("%s: closing\n", __func__); + + if (dcwc == dbus_ctx) + dbus_ctx = NULL; + + destroy_dbus_client_conn(&dcwc); + + interrupted = 1; +} + +static struct lws_dbus_ctx_wsproxy_client * +create_dbus_client_conn(struct lws_vhost *vh, int tsi, const char *ads) +{ + struct lws_dbus_ctx_wsproxy_client *dcwc; + DBusError e; + + dcwc = malloc(sizeof(*dcwc)); + if (!dcwc) + return NULL; + + memset(dcwc, 0, sizeof(*dcwc)); + + dcwc->state = LDCS_NOTHING; + dcwc->ctx.vh = vh; + dcwc->ctx.tsi = tsi; + + dbus_error_init(&e); + + lwsl_user("%s: connecting to '%s'\n", __func__, ads); +#if 1 + /* connect to our daemon bus */ + + dcwc->ctx.conn = dbus_connection_open_private(ads, &e); + if (!dcwc->ctx.conn) { + lwsl_err("%s: Failed to connect: %s\n", + __func__, e.message); + goto fail; + } +#else + /* connect to the SYSTEM bus */ + + dcwc->ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e); + if (!dcwc->ctx.conn) { + lwsl_err("%s: Failed to get a session DBus connection: %s\n", + __func__, e.message); + goto fail; + } +#endif + dbus_connection_set_exit_on_disconnect(dcwc->ctx.conn, 0); + + if (!dbus_connection_add_filter(dcwc->ctx.conn, filter, + &dcwc->ctx, NULL)) { + lwsl_err("%s: Failed to add filter\n", __func__); + goto fail; + } + + /* + * This is the part that binds the connection to lws watcher and + * timeout handling provided by lws + */ + + if (lws_dbus_connection_setup(&dcwc->ctx, dcwc->ctx.conn, cb_closing)) { + lwsl_err("%s: connection bind to lws failed\n", __func__); + goto fail; + } + + state_transition(dcwc, LDCS_CONN); + + lwsl_notice("%s: created OK\n", __func__); + + return dcwc; + +fail: + dbus_error_free(&e); + + free(dcwc); + + return NULL; +} + + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +/* + * This gets called if we timed out waiting for the dbus server reply, or the + * reply arrived. + */ + +static void +pending_call_notify(DBusPendingCall *pending, void *data) +{ + const char *payload; + DBusMessage *msg; + + if (!dbus_pending_call_get_completed(pending)) { + lwsl_err("%s: timed out waiting for reply\n", __func__); + + goto bail; + } + + msg = dbus_pending_call_steal_reply(pending); + if (!msg) + goto bail; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload, + DBUS_TYPE_INVALID)) { + goto bail1; + } + + lwsl_user("%s: received '%s'\n", __func__, payload); + +bail1: + dbus_message_unref(msg); +bail: + dbus_pending_call_unref(pending); +} + +static int +remote_method_call(struct lws_dbus_ctx_wsproxy_client *dcwc) +{ + const char *uri = "wss://libwebsockets.org/"; + const char *subprotocol = "lws-mirror-protocol"; + DBusMessage *msg; + int ret = 1; + + msg = dbus_message_new_method_call( + /* dest */ THIS_BUSNAME, + /* object-path */ THIS_OBJECT, + /* interface */ THIS_INTERFACE, + /* method */ "Connect"); + if (!msg) + return 1; + + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &uri, + DBUS_TYPE_STRING, &subprotocol, + DBUS_TYPE_INVALID)) + goto bail; + + lwsl_user("%s: requesting proxy connection %s %s\n", __func__, + uri, subprotocol); + + if (!dbus_connection_send_with_reply(dcwc->ctx.conn, msg, &dcwc->ctx.pc, + DBUS_TIMEOUT_USE_DEFAULT)) { + lwsl_err("%s: unable to send\n", __func__); + + goto bail; + } + + dbus_pending_call_set_notify(dcwc->ctx.pc, pending_call_notify, + &dcwc->ctx, NULL); + + state_transition(dcwc, LDCS_CONN_WAITING_ONWARD); + + ret = 0; + +bail: + dbus_message_unref(msg); + + return ret; +} + +/* + * Stub lws protocol, just so we can get synchronous timers conveniently. + * + * Set up a 1Hz timer and if our connection state is suitable, use that + * to write mirror protocol drawing packets to the proxied ws connection + */ + +static int +callback_just_timer(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + char payload[64]; + const char *ws_pkt = payload; + DBusMessage *msg; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + case LWS_CALLBACK_USER: + lwsl_info("%s: LWS_CALLBACK_USER\n", __func__); + + if (!dbus_ctx || dbus_ctx->state != LDCS_CONN_ONWARD) + goto again; + + if (autoexit_budget > 0) { + if (!--autoexit_budget) { + lwsl_notice("reached autoexit budget\n"); + interrupted = 1; + break; + } + } + + msg = dbus_message_new_method_call(THIS_BUSNAME, THIS_OBJECT, + THIS_INTERFACE, "Send"); + if (!msg) + break; + + lws_snprintf(payload, sizeof(payload), "d #%06X %d %d %d %d;", + rand() & 0xffffff, rand() % 480, rand() % 300, + rand() % 480, rand() % 300); + + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &ws_pkt, + DBUS_TYPE_INVALID)) { + dbus_message_unref(msg); + break; + } + + if (!dbus_connection_send_with_reply(dbus_ctx->ctx.conn, msg, + &dbus_ctx->ctx.pc, + DBUS_TIMEOUT_USE_DEFAULT)) { + lwsl_err("%s: unable to send\n", __func__); + dbus_message_unref(msg); + break; + } + + dbus_message_unref(msg); + dbus_pending_call_set_notify(dbus_ctx->ctx.pc, + pending_call_notify, + &dbus_ctx->ctx, NULL); + count_tx++; + +again: + lws_timed_callback_vh_protocol(lws_get_vhost(wsi), + lws_get_protocol(wsi), + LWS_CALLBACK_USER, 2); + break; + default: + break; + } + + return 0; +} + +static struct lws_protocols protocols[] = { + { "_just_timer", callback_just_timer, 0, 10, 0, NULL, 0 }, + { } +}; + + +int main(int argc, const char **argv) +{ + struct lws_vhost *vh; + struct lws_context_creation_info info; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */ /* | LLL_THREAD */; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + if ((p = lws_cmdline_option(argc, argv, "-x"))) + autoexit_budget = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal DBUS ws proxy testclient\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + info.protocols = protocols; + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + vh = lws_create_vhost(context, &info); + if (!vh) + goto bail; + + dbus_ctx = create_dbus_client_conn(vh, 0, THIS_LISTEN_PATH); + if (!dbus_ctx) + goto bail1; + + if (remote_method_call(dbus_ctx)) + goto bail2; + + /* lws event loop (default poll one) */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 1000); + +bail2: + destroy_dbus_client_conn(&dbus_ctx); + +bail1: + /* this is required for valgrind-cleanliness */ + dbus_shutdown(); + lws_context_destroy(context); + + lwsl_notice("Exiting cleanly, rx: %d, tx: %d\n", count_rx, count_tx); + + return 0; + +bail: + lwsl_err("%s: failed to start\n", __func__); + lws_context_destroy(context); + + return 1; +} diff --git a/minimal-examples/dbus-server/README.md b/minimal-examples/dbus-server/README.md index 95039204c..fc59bfb32 100644 --- a/minimal-examples/dbus-server/README.md +++ b/minimal-examples/dbus-server/README.md @@ -1,3 +1,4 @@ |Example|Demonstrates| ---|--- minimal-dbus-server|Shows how to run a DBUS session server using lws event loop +minimal-dbus-ws-proxy|Control ws client connections via DBUS diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/CMakeLists.txt b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/CMakeLists.txt new file mode 100644 index 000000000..bad9ec331 --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/CMakeLists.txt @@ -0,0 +1,122 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) +include(CheckLibraryExists) + +set(SAMP lws-minimal-dbus-ws-proxy) +set(SRCS main.c) + +if (NOT LWS_WITH_MINIMAL_EXAMPLES) + CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS) + if (NOT LWS_HAVE_LIBDBUS) + message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc") + endif() + + if (NOT LWS_DBUS_LIB) + set(LWS_DBUS_LIB "dbus-1") + endif() + + if (NOT LWS_DBUS_INCLUDE1) + # look in fedora and debian / ubuntu place + if (EXISTS "/usr/include/dbus-1.0") + set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are") + endif() + endif() + + if (NOT LWS_DBUS_INCLUDE2) + # look in fedora... debian / ubuntu has the ARCH in the path... + if (EXISTS "/usr/lib64/dbus-1.0/include") + set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include") + else() + message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system") + endif() + endif() + + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2}) + + if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2) + message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md") + endif() + +endif() + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + + +set(requirements 1) +require_lws_config(LWS_ROLE_DBUS 1 requirements) +require_lws_config(LWS_ROLE_WS 1 requirements) +require_lws_config(LWS_WITHOUT_SERVER 0 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + include_directories("${LWS_DBUS_INCLUDE1}") + include_directories("${LWS_DBUS_INCLUDE2}") + list(APPEND LIB_LIST dbus-1) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB}) + else() + target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB}) + endif() +endif() diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/README.md b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/README.md new file mode 100644 index 000000000..7192854c5 --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/README.md @@ -0,0 +1,115 @@ +# lws minimal dbus ws proxy + +This is an application which presents a DBUS server on one side, and a +websocket client proxy on the other. + +You connect to it over DBUS, send a Connect method on its interface giving +a URI and a ws subprotocol name. + +It replies with a string "Connecting" if all is well. + +Connection progress (including close) is then provided using type 7 messages +sent back to the dbus client. + +Payload from the ws connection is provided using type 6 messages sent back to +the dbus client. + +## build + +Using libdbus requires additional non-default include paths setting, same as +is necessary for lws build described in ./lib/roles/dbus/README.md + +CMake can guess one path and the library name usually, see the README above +for details of how to override for custom libdbus and cross build. + +Fedora example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include" +$ make +``` + +Ubuntu example: +``` +$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include" +$ make +``` + +## Configuration + +The dbus-ws-proxy server tries to register its actual bus name with the SYSTEM +bus in DBUS. If it fails, eg because of insufficient permissions on the user, +then it continues without that and starts its own daemon normally. + +The main dbus daemon must be told how to accept these registrations if that's +what you want. A config file is provided that tells dbus to allow the +well-known busname for this daemon to be registered, but only by root. + +``` +$ sudo cp org.libwebsockets.wsclientproxy.conf /etc/dbus-1/system.d +$ sudo systemctl restart dbus +``` + +## usage + +Run the dbus-ws-proxy server, then start lws-minimal-dbus-ws-proxy-testclient in +another terminal. + +This test app sends a random line drawing message to the mirror example on +https://libwebsockets.org/testserver every couple of seconds, and displays +any received messages (such as its own sends mirrored back, or anything +drawn in the canvas in a browser). + +``` + $ sudo ./lws-minimal-dbus-ws-proxy-testclient +[2018/10/07 10:05:29:2084] USER: LWS minimal DBUS ws proxy testclient +[2018/10/07 10:05:29:2345] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off +[2018/10/07 10:05:29:2424] USER: create_dbus_client_conn: connecting to 'unix:abstract=org.libwebsockets.wsclientproxy' +[2018/10/07 10:05:29:2997] NOTICE: state_transition: 0x5679720: from state 0 -> 1 +[2018/10/07 10:05:29:2999] NOTICE: create_dbus_client_conn: created OK +[2018/10/07 10:05:29:3232] USER: remote_method_call: requesting proxy connection wss://libwebsockets.org/ lws-mirror-protocol +[2018/10/07 10:05:29:3450] NOTICE: state_transition: 0x5679720: from state 1 -> 2 +[2018/10/07 10:05:29:5972] USER: pending_call_notify: received 'Connecting' +[2018/10/07 10:05:31:3387] NOTICE: state_transition: 0x5679720: from state 2 -> 3 +[2018/10/07 10:05:33:6672] USER: filter: Received 'd #B0DC51 115 177 166 283;' +[2018/10/07 10:05:35:9723] USER: filter: Received 'd #E87CCD 9 192 106 235;' +[2018/10/07 10:05:38:2784] USER: filter: Received 'd #E2A9E3 379 290 427 62;' +[2018/10/07 10:05:39:5833] USER: filter: Received 'd #B127F8 52 126 60 226;' +[2018/10/07 10:05:41:8908] USER: filter: Received 'd #0E0F76 429 267 8 11;' +... +``` + +## ws proxy DBUS details + +### Fixed details + +Item|Value +---|--- +Address|unix:abstract=org.libwebsockets.wsclientproxy +Interface|org.libwebsockets.wsclientproxy +Bus Name|org.libwebsockets.wsclientproxy +Object path|/org/libwebsockets/wsclientproxy + +### Interface Methods + +Method|Arguments|Returns +---|---|--- +Connect|s: ws URI, s: ws subprotocol name|"Bad Uri", "Connecting" or "Failed" +Send|s: payload|Empty message if no problem, or error message + +When Connecting, the actual connection happens asynchronously if the initial +connection attempt doesn't fail immediately. If it's continuing in the +background, the reply will have been "Connecting". + +### Signals + +Signal Name|Argument|Meaning +---|---|--- +Receive|s: payload|Received data from the ws link +Status|s: status|See table below + +Status String|Meaning +---|--- +"ws client connection error"|The ws connection attempt ended with a fatal error +"ws client connection established"|The ws connection attempt succeeded +"ws client connection closed"|The ws connection has closed + diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/main.c b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/main.c new file mode 100644 index 000000000..4510fd80a --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/main.c @@ -0,0 +1,101 @@ +/* + * lws-minimal-dbus-ws-proxy + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal session dbus server that uses the lws event loop, + * and allows proxying ws client connections via DBUS. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LWS_PLUGIN_STATIC +#include "protocol_lws_minimal_dbus_ws_proxy.c" + +static int interrupted; +static struct lws_protocols protocols[] = { + LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +/* + * we pass the dbus address to connect to proxy with from outside the + * protocol plugin... eg if built as a plugin for lwsws, you would instead + * set this pvo in the lwsws JSON config. + */ + +static const struct lws_protocol_vhost_options pvo_ads = { + NULL, + NULL, + "ads", /* pvo name */ + (void *)"unix:abstract=org.libwebsockets.wsclientproxy" /* pvo value */ +}; + +static const struct lws_protocol_vhost_options pvo = { + NULL, /* "next" pvo linked-list */ + &pvo_ads, /* "child" pvo linked-list */ + "lws-minimal-dbus-wsproxy", /* protocol name we belong to on this vhost */ + "" /* ignored */ +}; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + static struct lws_context *context; + struct lws_context_creation_info info; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */ /* | LLL_THREAD */; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS DBUS ws client proxy\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; + info.ws_ping_pong_interval = 30; + info.protocols = protocols; + info.pvo = &pvo; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* lws event loop (default poll one) */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 1000); + + lws_context_destroy(context); + + lwsl_notice("Exiting cleanly\n"); + + return 0; +} diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/org.libwebsockets.wsclientproxy.conf b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/org.libwebsockets.wsclientproxy.conf new file mode 100644 index 000000000..49e430b40 --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/org.libwebsockets.wsclientproxy.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/minimal-examples/dbus-server/minimal-dbus-ws-proxy/protocol_lws_minimal_dbus_ws_proxy.c b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/protocol_lws_minimal_dbus_ws_proxy.c new file mode 100644 index 000000000..5a61ed536 --- /dev/null +++ b/minimal-examples/dbus-server/minimal-dbus-ws-proxy/protocol_lws_minimal_dbus_ws_proxy.c @@ -0,0 +1,827 @@ +/* + * ws protocol handler plugin for dbus ws proxy + * + * Copyright (C) 2010-2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This proxies outgoing ws client connections on DBUS. So a DBUS client can + * reach out and get remote WS payloads in both directions. + * + * DEVELOPER NOTE + * + * Two worlds, dbus and ws, collide in this file. + * + * There main thing keeping it sane is both worlds are running in the same + * thread and on the same event loop. Although things may happen completely + * asynchronously in both worlds, the logical reaction to those events are + * serialized in a single event loop doing one thing at a time. + * + * So while you are servicing an event in the ws world, you can be certain the + * logical state of any related dbus thing cannot change underneath you, until + * you return back to the event loop, and vice versa. So other-world objects + * can't be freed, other-world handles can't close etc while you are servicing + * in your world. + * + * Since all bets are off what happens next, and in which world, after you + * return back to the event loop though, an additional rule is needed: worlds + * must not allocate in objects owned by the other world. They must generate + * their own objects in their world and use those for allocations and state. + * + * For example in the dbus-world there is a struct lws_dbus_ctx_wsproxy with + * various state, but he is subject to deletion by events in dbus-world. If + * the ws-world stored things there, they are subject to going out of scope + * at the whim of the dbus connection without the ws world hearing about it and + * cleanly deallocaing them. So the ws world must keep his own pss that remains + * in scope until the ws link closes for allocations from ws-world. + * + * In this application there's a point of contact between the worlds, a ring + * buffer allocated in ws world when the ws connection is established, and + * deallocated when the ws connection is closed. The DBUS world needs to put + * things in this ringbuffer. But the way lws_ring works, when the message + * allocated in DBUS world is queued on the ringbuffer, the ringbuffer itself + * takes responsibility for deallocation. So there is no problem. + */ + +#if !defined (LWS_PLUGIN_STATIC) +#define LWS_DLL +#define LWS_INTERNAL +#include +#include +#endif + +#include +#include +#include + +/* + * dbus accepted connections create these larger context structs that start + * with the lws dbus context + */ + +struct vhd_dbus_proxy; + +struct msg { + void *payload; /* is malloc'd */ + size_t len; + char binary; + char first; + char final; +}; + +struct pss_dbus_proxy { + struct lws_ring *ring_out; + uint32_t ring_out_tail; +}; + +struct lws_dbus_ctx_wsproxy { + struct lws_dbus_ctx ctx; + + struct lws *cwsi; + struct vhd_dbus_proxy *vhd; + struct pss_dbus_proxy *pss; +}; + +struct vhd_dbus_proxy { + struct lws_context *context; + struct lws_vhost *vhost; + + /* + * Because the listener ctx is composed in the vhd, we can always get a + * pointer to the outer vhd from a pointer to ctx_listener inside. + */ + struct lws_dbus_ctx ctx_listener; + struct lws_dbus_ctx_wsproxy dctx; + + const char *dbus_listen_ads; +}; + +#define THIS_INTERFACE "org.libwebsockets.wsclientproxy" +#define THIS_OBJECT "/org/libwebsockets/wsclientproxy" +#define THIS_BUSNAME "org.libwebsockets.wsclientproxy" +static const char *version = "0.1"; + +static const char *server_introspection_xml = + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " " + " \n" + " " + " \n" + + "\n"; + +static void +destroy_message(void *_msg) +{ + struct msg *msg = _msg; + + free(msg->payload); + msg->payload = NULL; + msg->len = 0; +} + +/* + * DBUS WORLD + */ + +static DBusHandlerResult +dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + dbus_message_append_args(*reply, + DBUS_TYPE_STRING, &server_introspection_xml, + DBUS_TYPE_INVALID); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + const char *interface, *property; + DBusError err; + + dbus_error_init(&err); + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, err.name, err.message); + dbus_error_free(&err); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (strcmp(property, "Version")) /* Unknown property */ + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version, + DBUS_TYPE_INVALID); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + DBusMessageIter arr, di, iter, va; + const char *property = "Version"; + + dbus_message_iter_init_append(*reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr); + + /* Append all properties name/value pairs */ + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di); + dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property); + dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va); + dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version); + dbus_message_iter_close_container(&di, &va); + dbus_message_iter_close_container(&arr, &di); + + dbus_message_iter_close_container(&iter, &arr); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +dmh_connect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d; + const char *prot = "", *ads = "", *path = "", *baduri = "Bad Uri", + *connecting = "Connecting", *failed = "Failed", **pp; + struct lws_client_connect_info i; + char host[128], uri_copy[512]; + const char *uri, *subprotocol; + DBusError err; + int port = 0; + + dbus_error_init(&err); + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &uri, + DBUS_TYPE_STRING, &subprotocol, + DBUS_TYPE_INVALID)) { + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, err.name, err.message); + dbus_error_free(&err); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + strncpy(uri_copy, uri, sizeof(uri_copy) - 1); + uri_copy[sizeof(uri_copy) - 1] = '\0'; + + if (lws_parse_uri(uri_copy, &prot, &ads, &port, &path)) { + pp = &baduri; + goto send_reply; + } + + lws_snprintf(host, sizeof(host), "%s:%u", ads, port); + + memset(&i, 0, sizeof(i)); + + assert(wspctx); + assert(wspctx->vhd); + + i.context = wspctx->vhd->context; + i.port = port; + i.address = ads; + i.path = path; + i.host = host; + i.origin = host; + i.ssl_connection = !strcmp(prot, "https") || !strcmp(prot, "wss"); + i.vhost = wspctx->ctx.vh; + i.protocol = subprotocol; + i.local_protocol_name = "lws-minimal-dbus-wsproxy"; + i.pwsi = &wspctx->cwsi; + + lwsl_user("%s: connecting to %s://%s:%d%s\n", __func__, prot, + i.address, i.port, i.path); + + if (!lws_client_connect_via_info(&i)) { + lwsl_notice("%s: client connect failed\n", __func__); + pp = &failed; + goto send_reply; + } + + lws_set_opaque_parent_data(wspctx->cwsi, wspctx); + lwsl_notice("%s: client connecting...\n", __func__); + pp = &connecting; + +send_reply: + dbus_message_append_args(*reply, DBUS_TYPE_STRING, pp, + DBUS_TYPE_INVALID); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static int +issue_dbus_signal(struct lws *wsi, const char *signame, const char *string) +{ + struct lws_dbus_ctx_wsproxy *wspctx = + lws_get_opaque_parent_data(wsi); + DBusMessage *m; + + if (!wspctx) + return 1; + + m = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE, signame); + if (!m) { + lwsl_err("%s: new signal failed\n", __func__); + return 1; + } + + dbus_message_append_args(m, DBUS_TYPE_STRING, &string, + DBUS_TYPE_INVALID); + + if (!dbus_connection_send(wspctx->ctx.conn, m, NULL)) + lwsl_err("%s: unable to send\n", __func__); + + dbus_message_unref(m); + + return 0; +} + +static DBusHandlerResult +dmh_send(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d) +{ + struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d; + const char *payload; + struct msg amsg; + DBusError err; + + dbus_error_init(&err); + + if (!wspctx->cwsi || !wspctx->pss) { + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, "Send Fail", "No ws conn"); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &payload, + DBUS_TYPE_INVALID)) { + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, err.name, err.message); + dbus_error_free(&err); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + /* + * we allocate on the ringbuffer in ws world, but responsibility for + * freeing it is understood by lws_ring. + */ + + amsg.len = strlen(payload); + /* notice we over-allocate by LWS_PRE */ + amsg.payload = malloc(LWS_PRE + amsg.len); + if (!amsg.payload) { + lwsl_user("OOM: dropping\n"); + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, "Send Fail", "OOM"); + + return DBUS_HANDLER_RESULT_HANDLED; + } + amsg.binary = 0; + amsg.first = 1; + amsg.final = 1; + + memcpy((char *)amsg.payload + LWS_PRE, payload, amsg.len); + if (!lws_ring_insert(wspctx->pss->ring_out, &amsg, 1)) { + destroy_message(&amsg); + lwsl_user("Ring Full!\n"); + dbus_message_unref(*reply); + *reply = dbus_message_new_error(m, "Send Fail", "Ring full"); + + return DBUS_HANDLER_RESULT_HANDLED; + } + if (wspctx->cwsi) + lws_callback_on_writable(wspctx->cwsi); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +struct lws_dbus_methods { + const char *inter; + const char *call; + lws_dbus_message_handler handler; +} meths[] = { + { DBUS_INTERFACE_INTROSPECTABLE, "Introspect", dmh_introspect }, + { DBUS_INTERFACE_PROPERTIES, "Get", dmh_get }, + { DBUS_INTERFACE_PROPERTIES, "GetAll", dmh_getall }, + { THIS_INTERFACE, "Connect", dmh_connect }, + { THIS_INTERFACE, "Send", dmh_send }, +}; + +static DBusHandlerResult +server_message_handler(DBusConnection *conn, DBusMessage *message, void *data) +{ + struct lws_dbus_methods *mp = meths; + DBusMessage *reply = NULL; + DBusHandlerResult result; + size_t n; + + assert(data); + + lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__, + dbus_message_get_interface(message), + dbus_message_get_member(message), + dbus_message_get_path(message)); + + for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) { + if (dbus_message_is_method_call(message, mp->inter, mp->call)) { + reply = dbus_message_new_method_return(message); + if (!reply) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + result = mp->handler(conn, message, &reply, data); + + if (result == DBUS_HANDLER_RESULT_HANDLED && + !dbus_connection_send(conn, reply, NULL)) + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(reply); + + return result; + } + + mp++; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable vtable = { + .message_function = server_message_handler +}; + +static void +destroy_dbus_server_conn(struct lws_dbus_ctx_wsproxy *wsctx) +{ + if (!wsctx->ctx.conn) + return; + + lwsl_notice("%s\n", __func__); + + dbus_connection_unregister_object_path(wsctx->ctx.conn, THIS_OBJECT); + lws_dll_remove(&wsctx->ctx.next); + dbus_connection_unref(wsctx->ctx.conn); +} + +/* + * This is the client dbus side going away. We need to stop the associated + * client ws part and make sure it can't dereference us now we are gone. + */ + +static void +cb_closing(struct lws_dbus_ctx *ctx) +{ + struct lws_dbus_ctx_wsproxy *wspctx = + (struct lws_dbus_ctx_wsproxy *)ctx; + lwsl_err("%s: closing\n", __func__); + + /* + * We have to take care that the associated proxy wsi knows our + * dbus ctx is going out of scope after we return from here. + * + * We do it by setting its pointer to our dbus ctx to NULL. + */ + + if (wspctx->cwsi) { + lws_set_opaque_parent_data(wspctx->cwsi, NULL); + lws_set_timeout(wspctx->cwsi, + PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE, + LWS_TO_KILL_ASYNC); + } + + destroy_dbus_server_conn(wspctx); + + free(wspctx); +} + +static void +new_conn(DBusServer *server, DBusConnection *conn, void *d) +{ + struct lws_dbus_ctx_wsproxy *conn_wspctx, /* the new conn context */ + /* the listener context */ + *wspctx = (struct lws_dbus_ctx_wsproxy *)d; + struct vhd_dbus_proxy *vhd = lws_container_of(d, + struct vhd_dbus_proxy, ctx_listener); + + assert(vhd->vhost == wspctx->ctx.vh); + + lwsl_notice("%s\n", __func__); + + conn_wspctx = malloc(sizeof(*conn_wspctx)); + if (!conn_wspctx) + return; + + memset(conn_wspctx, 0, sizeof(*conn_wspctx)); + + conn_wspctx->ctx.tsi = conn_wspctx->ctx.tsi; + conn_wspctx->ctx.vh = wspctx->ctx.vh; + conn_wspctx->ctx.conn = conn; + conn_wspctx->vhd = vhd; /* let accepted connections also know the vhd */ + + assert(conn_wspctx->vhd); + + if (lws_dbus_connection_setup(&conn_wspctx->ctx, conn, cb_closing)) { + lwsl_err("%s: connection bind to lws failed\n", __func__); + goto bail; + } + + if (!dbus_connection_register_object_path(conn, THIS_OBJECT, &vtable, + conn_wspctx)) { + lwsl_err("%s: Failed to register object path\n", __func__); + goto bail; + } + + lws_dll_add_front(&conn_wspctx->ctx.next, &wspctx->ctx.next); + + /* we take on responsibility for explicit close / unref with this... */ + dbus_connection_ref(conn); + + return; + +bail: + free(conn_wspctx); +} + +static int +create_dbus_listener(struct vhd_dbus_proxy *vhd, int tsi) +{ + DBusError e; + + dbus_error_init(&e); +#if 0 + vhd->dctx.ctx.tsi = tsi; + vhd->dctx.ctx.vh = vhd->vhost; + vhd->dctx.ctx.next.prev = NULL; + vhd->dctx.ctx.next.next = NULL; + vhd->dctx.vhd = vhd; + vhd->dctx.cwsi = NULL; + + /* connect to the SYSTEM bus */ + + vhd->dctx.ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e); + if (!vhd->dctx.ctx.conn) { + lwsl_notice("%s: Failed to get a session DBus connection: '%s'" + ", continuing with daemon listener only\n", + __func__, e.message); + dbus_error_free(&e); + dbus_error_init(&e); + goto daemon; + } + + /* + * by default dbus will call exit() when this connection closes... + * we have to shut down other things cleanly, so disable that + */ + dbus_connection_set_exit_on_disconnect(vhd->dctx.ctx.conn, 0); + + if (dbus_bus_request_name(vhd->dctx.ctx.conn, THIS_BUSNAME, + DBUS_NAME_FLAG_REPLACE_EXISTING, &e) != + DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + lwsl_notice("%s: Failed to request name on bus: '%s'," + " continuing with daemon listener only\n", + __func__, e.message); + dbus_connection_unref(vhd->dctx.ctx.conn); + vhd->dctx.ctx.conn = NULL; + dbus_error_free(&e); + dbus_error_init(&e); + goto daemon; + } + + if (!dbus_connection_register_object_path(vhd->dctx.ctx.conn, + THIS_OBJECT, &vtable, + &vhd->dctx)) { + lwsl_err("%s: Failed to register object path\n", __func__); + goto fail; + } + + /* + * This is the part that binds the connection to lws watcher and + * timeout handling provided by lws + */ + + if (lws_dbus_connection_setup(&vhd->dctx.ctx, vhd->dctx.ctx.conn, + cb_closing)) { + lwsl_err("%s: connection bind to lws failed\n", __func__); + goto fail; + } + +daemon: +#endif + vhd->ctx_listener.vh = vhd->vhost; + vhd->ctx_listener.tsi = tsi; + + if (!lws_dbus_server_listen(&vhd->ctx_listener, vhd->dbus_listen_ads, + &e, new_conn)) { + lwsl_err("%s: failed\n", __func__); + dbus_error_free(&e); + + return 1; + } + + lwsl_notice("%s: created DBUS listener on %s\n", __func__, + vhd->dbus_listen_ads); + + return 0; +#if 0 +fail: + dbus_error_free(&e); + + return 1; +#endif +} + +static void +destroy_dbus_server_listener(struct vhd_dbus_proxy *vhd) +{ + dbus_server_disconnect(vhd->ctx_listener.dbs); + + lws_start_foreach_dll_safe(struct lws_dll *, rdt, nx, + vhd->ctx_listener.next.next) { + struct lws_dbus_ctx *r = lws_container_of(rdt, + struct lws_dbus_ctx, next); + + dbus_connection_close(r->conn); + dbus_connection_unref(r->conn); + free(r); + } lws_end_foreach_dll_safe(rdt, nx); + + if (vhd->dctx.ctx.conn) + dbus_connection_unref(vhd->dctx.ctx.conn); + dbus_server_unref(vhd->ctx_listener.dbs); +} + +/* + * WS WORLD + */ + +static int +callback_minimal_dbus_wsproxy(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct pss_dbus_proxy *pss = (struct pss_dbus_proxy *)user; + struct vhd_dbus_proxy *vhd = (struct vhd_dbus_proxy *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); + struct lws_dbus_ctx_wsproxy *wspctx; + const struct msg *pmsg; + int flags, m; + + switch (reason) { + + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), sizeof(*vhd)); + if (!vhd) + return -1; + + vhd->context = lws_get_context(wsi); + vhd->vhost = lws_get_vhost(wsi); + + if (lws_pvo_get_str(in, "ads", &vhd->dbus_listen_ads)) { + lwsl_err("%s: pvo 'ads' must be set\n", __func__); + return -1; + } + + if (create_dbus_listener(vhd, 0)) { + lwsl_err("%s: create_dbus_listener failed\n", __func__); + return -1; + } + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + destroy_dbus_server_listener(vhd); + /* this is required for valgrind-cleanliness */ + dbus_shutdown(); + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + lwsl_user("LWS_CALLBACK_CLIENT_ESTABLISHED\n"); + + /* + * create the send ringbuffer now the ws connection is + * established. + */ + + wspctx = lws_get_opaque_parent_data(wsi); + if (!wspctx) + break; + + wspctx->pss = pss; + pss->ring_out_tail = 0; + pss->ring_out = lws_ring_create(sizeof(struct msg), 8, + destroy_message); + if (!pss->ring_out) { + lwsl_err("OOM\n"); + return -1; + } + + issue_dbus_signal(wsi, "Status", + "ws client connection established"); + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE:\n"); + + pmsg = lws_ring_get_element(pss->ring_out, &pss->ring_out_tail); + if (!pmsg) { + lwsl_user(" (nothing in ring)\n"); + break; + } + + flags = lws_write_ws_flags( + pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, + pmsg->first, pmsg->final); + + /* notice we allowed for LWS_PRE in the payload already */ + m = lws_write(wsi, pmsg->payload + LWS_PRE, pmsg->len, flags); + if (m < (int)pmsg->len) { + lwsl_err("ERROR %d writing to ws socket\n", m); + return -1; + } + + lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n", + m, flags, pmsg->first, pmsg->final); + + lws_ring_consume_single_tail(pss->ring_out, + &pss->ring_out_tail, 1); + + /* more to do for us? */ + if (lws_ring_get_element(pss->ring_out, &pss->ring_out_tail)) + /* come back as soon as we can write more */ + lws_callback_on_writable(wsi); + + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + + lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d " + "(rpp %5d, first %d, last %d, bin %d)\n", + (int)len, (int)lws_remaining_packet_payload(wsi), + lws_is_first_fragment(wsi), + lws_is_final_fragment(wsi), + lws_frame_is_binary(wsi)); + + { + char strbuf[256]; + int l = len; + + if (l > (int)sizeof(strbuf) - 1) + l = sizeof(strbuf) - 1; + + memcpy(strbuf, in, l); + strbuf[l] = '\0'; + + issue_dbus_signal(wsi, "Receive", strbuf); + } + break; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", + in ? (char *)in : "(null)"); + issue_dbus_signal(wsi, "Status", "ws client connection error"); + break; + + case LWS_CALLBACK_CLIENT_CLOSED: + lwsl_err("LWS_CALLBACK_CLIENT_CLOSED ()\n"); + issue_dbus_signal(wsi, "Status", "ws client connection closed"); + + /* destroy any ringbuffer and pending messages */ + + lws_ring_destroy(pss->ring_out); + + wspctx = lws_get_opaque_parent_data(wsi); + if (!wspctx) + break; + + /* + * the wspctx cannot refer to its child wsi any longer, it is + * about to go out of scope. + */ + + wspctx->cwsi = NULL; + wspctx->pss = NULL; + break; + + default: + break; + } + + return 0; +} + +#define LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY \ + { \ + "lws-minimal-dbus-wsproxy", \ + callback_minimal_dbus_wsproxy, \ + sizeof(struct pss_dbus_proxy), \ + 1024, \ + 0, NULL, 0 \ + } + +#if !defined (LWS_PLUGIN_STATIC) + +/* boilerplate needed if we are built as a dynamic plugin */ + +static const struct lws_protocols protocols[] = { + LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY +}; + +LWS_EXTERN LWS_VISIBLE int +init_protocol_minimal_dbus_wsproxy(struct lws_context *context, + struct lws_plugin_capability *c) +{ + if (c->api_magic != LWS_PLUGIN_API_MAGIC) { + lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, + c->api_magic); + return 1; + } + + c->protocols = protocols; + c->count_protocols = LWS_ARRAY_SIZE(protocols); + c->extensions = NULL; + c->count_extensions = 0; + + return 0; +} + +LWS_EXTERN LWS_VISIBLE int +destroy_protocol_minimal_dbus_wsproxy(struct lws_context *context) +{ + return 0; +} +#endif diff --git a/scripts/test-dbus-proxy.sh b/scripts/test-dbus-proxy.sh new file mode 100755 index 000000000..1aec16f73 --- /dev/null +++ b/scripts/test-dbus-proxy.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +echo "Starting $0" + +bin/lws-minimal-dbus-ws-proxy 2> /tmp/dbuss& + +echo " server starting" +sleep 1s +PID_PROX=$! + +echo " client starting" +bin/lws-minimal-dbus-ws-proxy-testclient -x 10 2> /tmp/dbusc +R=$? + +kill -2 $PID_PROX + +if [ $R -ne 0 ] ; then + echo "$0 FAILED" + cat /tmp/dbuss + cat /tmp/dbusc + exit 1 +fi + +if [ -z "`cat /tmp/dbusc | grep 'rx: 9, tx: 9'`" ] ; then + echo "$0 FAILED" + cat /tmp/dbuss + cat /tmp/dbusc + exit 1 +fi + +echo "$0 PASSED" + +exit 0 + diff --git a/scripts/travis_control.sh b/scripts/travis_control.sh index 92e086b49..41fc77575 100755 --- a/scripts/travis_control.sh +++ b/scripts/travis_control.sh @@ -15,6 +15,7 @@ else cmake --build . && sudo make install && ../minimal-examples/selftests.sh && + ../scripts/test-dbus-proxy.sh && ../scripts/h2spec.sh && ../scripts/attack.sh && ../scripts/h2load.sh && diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh index 4cdeccb7b..724ced870 100755 --- a/scripts/travis_install.sh +++ b/scripts/travis_install.sh @@ -8,7 +8,7 @@ then if [ "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ]; then - sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev + sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev libdbus-1-dev sudo apt-get remove python-six sudo pip install six>=1.9 sudo pip install Twisted==16.0.0 @@ -56,6 +56,12 @@ fi if [ "$TRAVIS_OS_NAME" == "osx" ]; then + if [ "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ]; + then + brew update; + brew install dbus; + fi + if [ "$LWS_METHOD" == "libev" ]; then brew update;