1/* 2 * lws-minimal-dbus-ws-proxy-testclient 3 * 4 * Written in 2010-2019 by Andy Green <andy@warmcat.com> 5 * 6 * This file is made available under the Creative Commons CC0 1.0 7 * Universal Public Domain Dedication. 8 * 9 * This acts as a test client over DBUS, opening a session with 10 * minimal-dbus-ws-proxy and sending and receiving data on the libwebsockets 11 * mirror demo page. 12 */ 13 14#include <stdbool.h> 15#include <string.h> 16#include <stdio.h> 17#include <stdlib.h> 18#include <unistd.h> 19#include <signal.h> 20 21#include <libwebsockets.h> 22#include <libwebsockets/lws-dbus.h> 23 24/* 25 * These are the various states our connection can be in, both with regards 26 * to the direct connection to the proxy, and the state of the onward ws 27 * connection the proxy opens at our request. 28 */ 29 30enum lws_dbus_client_state { 31 LDCS_NOTHING, /* no connection yet */ 32 LDCS_CONN, /* conn to proxy */ 33 LDCS_CONN_WAITING_ONWARD, /* conn to proxy, awaiting proxied conn */ 34 LDCS_CONN_ONWARD, /* conn to proxy and onward conn OK */ 35 LDCS_CONN_CLOSED, /* conn to proxy but onward conn closed */ 36 LDCS_CLOSED, /* connection to proxy is closed */ 37}; 38 39/* 40 * our expanded dbus context 41 */ 42 43struct lws_dbus_ctx_wsproxy_client { 44 struct lws_dbus_ctx ctx; 45 46 lws_sorted_usec_list_t sul; 47 48 enum lws_dbus_client_state state; 49}; 50 51static struct lws_dbus_ctx_wsproxy_client *dbus_ctx; 52static struct lws_context *context; 53static int interrupted, autoexit_budget = -1, count_rx, count_tx; 54 55#define THIS_INTERFACE "org.libwebsockets.wsclientproxy" 56#define THIS_OBJECT "/org/libwebsockets/wsclientproxy" 57#define THIS_BUSNAME "org.libwebsockets.wsclientproxy" 58 59#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.wsclientproxy" 60 61static void 62state_transition(struct lws_dbus_ctx_wsproxy_client *dcwc, 63 enum lws_dbus_client_state state) 64{ 65 lwsl_notice("%s: %p: from state %d -> %d\n", __func__, 66 dcwc,dcwc->state, state); 67 dcwc->state = state; 68} 69 70static DBusHandlerResult 71filter(DBusConnection *conn, DBusMessage *message, void *data) 72{ 73 struct lws_dbus_ctx_wsproxy_client *dcwc = 74 (struct lws_dbus_ctx_wsproxy_client *)data; 75 const char *str; 76 77 if (!dbus_message_get_args(message, NULL, 78 DBUS_TYPE_STRING, &str, 79 DBUS_TYPE_INVALID)) 80 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 81 82 /* received ws data */ 83 84 if (dbus_message_is_signal(message, THIS_INTERFACE, "Receive")) { 85 lwsl_user("%s: Received '%s'\n", __func__, str); 86 count_rx++; 87 } 88 89 /* proxy ws connection failed */ 90 91 if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") && 92 !strcmp(str, "ws client connection error")) 93 state_transition(dcwc, LDCS_CONN_CLOSED); 94 95 /* proxy ws connection succeeded */ 96 97 if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") && 98 !strcmp(str, "ws client connection established")) 99 state_transition(dcwc, LDCS_CONN_ONWARD); 100 101 /* proxy ws connection has closed */ 102 103 if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") && 104 !strcmp(str, "ws client connection closed")) 105 state_transition(dcwc, LDCS_CONN_CLOSED); 106 107 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 108} 109 110static void 111destroy_dbus_client_conn(struct lws_dbus_ctx_wsproxy_client **pdcwc) 112{ 113 struct lws_dbus_ctx_wsproxy_client *dcwc = *pdcwc; 114 115 if (!dcwc || !dcwc->ctx.conn) 116 return; 117 118 lwsl_notice("%s\n", __func__); 119 120 dbus_connection_remove_filter(dcwc->ctx.conn, filter, &dcwc->ctx); 121 dbus_connection_close(dcwc->ctx.conn); 122 dbus_connection_unref(dcwc->ctx.conn); 123 124 free(dcwc); 125 126 *pdcwc = NULL; 127} 128 129/* 130 * This callback is coming when lws has noticed the fd took a POLLHUP. The 131 * ctx has effectively gone out of scope before this, and the connection can 132 * be cleaned up and the ctx freed. 133 */ 134 135static void 136cb_closing(struct lws_dbus_ctx *ctx) 137{ 138 struct lws_dbus_ctx_wsproxy_client *dcwc = 139 (struct lws_dbus_ctx_wsproxy_client *)ctx; 140 141 lwsl_err("%s: closing\n", __func__); 142 143 if (dcwc == dbus_ctx) 144 dbus_ctx = NULL; 145 146 destroy_dbus_client_conn(&dcwc); 147 148 interrupted = 1; 149} 150 151static struct lws_dbus_ctx_wsproxy_client * 152create_dbus_client_conn(struct lws_vhost *vh, int tsi, const char *ads) 153{ 154 struct lws_dbus_ctx_wsproxy_client *dcwc; 155 DBusError e; 156 157 dcwc = malloc(sizeof(*dcwc)); 158 if (!dcwc) 159 return NULL; 160 161 memset(dcwc, 0, sizeof(*dcwc)); 162 163 dcwc->state = LDCS_NOTHING; 164 dcwc->ctx.vh = vh; 165 dcwc->ctx.tsi = tsi; 166 167 dbus_error_init(&e); 168 169 lwsl_user("%s: connecting to '%s'\n", __func__, ads); 170#if 1 171 /* connect to our daemon bus */ 172 173 dcwc->ctx.conn = dbus_connection_open_private(ads, &e); 174 if (!dcwc->ctx.conn) { 175 lwsl_err("%s: Failed to connect: %s\n", 176 __func__, e.message); 177 goto fail; 178 } 179#else 180 /* connect to the SYSTEM bus */ 181 182 dcwc->ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e); 183 if (!dcwc->ctx.conn) { 184 lwsl_err("%s: Failed to get a session DBus connection: %s\n", 185 __func__, e.message); 186 goto fail; 187 } 188#endif 189 dbus_connection_set_exit_on_disconnect(dcwc->ctx.conn, 0); 190 191 if (!dbus_connection_add_filter(dcwc->ctx.conn, filter, 192 &dcwc->ctx, NULL)) { 193 lwsl_err("%s: Failed to add filter\n", __func__); 194 goto fail; 195 } 196 197 /* 198 * This is the part that binds the connection to lws watcher and 199 * timeout handling provided by lws 200 */ 201 202 if (lws_dbus_connection_setup(&dcwc->ctx, dcwc->ctx.conn, cb_closing)) { 203 lwsl_err("%s: connection bind to lws failed\n", __func__); 204 goto fail; 205 } 206 207 state_transition(dcwc, LDCS_CONN); 208 209 lwsl_notice("%s: created OK\n", __func__); 210 211 return dcwc; 212 213fail: 214 dbus_error_free(&e); 215 216 free(dcwc); 217 218 return NULL; 219} 220 221 222void sigint_handler(int sig) 223{ 224 interrupted = 1; 225} 226 227/* 228 * This gets called if we timed out waiting for the dbus server reply, or the 229 * reply arrived. 230 */ 231 232static void 233pending_call_notify(DBusPendingCall *pending, void *data) 234{ 235 const char *payload; 236 DBusMessage *msg; 237 238 if (!dbus_pending_call_get_completed(pending)) { 239 lwsl_err("%s: timed out waiting for reply\n", __func__); 240 241 goto bail; 242 } 243 244 msg = dbus_pending_call_steal_reply(pending); 245 if (!msg) 246 goto bail; 247 248 if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload, 249 DBUS_TYPE_INVALID)) { 250 goto bail1; 251 } 252 253 lwsl_user("%s: received '%s'\n", __func__, payload); 254 255bail1: 256 dbus_message_unref(msg); 257bail: 258 dbus_pending_call_unref(pending); 259} 260 261static int 262remote_method_call(struct lws_dbus_ctx_wsproxy_client *dcwc) 263{ 264 char _uri[96]; 265 const char *subprotocol = "lws-mirror-protocol", *uri = _uri; 266 DBusMessage *msg; 267 int ret = 1; 268 269 /* 270 * make our own private mirror session... because others may run this 271 * at the same time against libwebsockets.org... as happened 2019-03-14 272 * and broke travis tests :-) 273 */ 274 275 lws_snprintf(_uri, sizeof(_uri), "wss://libwebsockets.org/?mirror=dbt-%d", 276 (int)getpid()); 277 278 msg = dbus_message_new_method_call( 279 /* dest */ THIS_BUSNAME, 280 /* object-path */ THIS_OBJECT, 281 /* interface */ THIS_INTERFACE, 282 /* method */ "Connect"); 283 if (!msg) 284 return 1; 285 286 if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &uri, 287 DBUS_TYPE_STRING, &subprotocol, 288 DBUS_TYPE_INVALID)) 289 goto bail; 290 291 lwsl_user("%s: requesting proxy connection %s %s\n", __func__, 292 uri, subprotocol); 293 294 if (!dbus_connection_send_with_reply(dcwc->ctx.conn, msg, &dcwc->ctx.pc, 295 DBUS_TIMEOUT_USE_DEFAULT)) { 296 lwsl_err("%s: unable to send\n", __func__); 297 298 goto bail; 299 } 300 301 dbus_pending_call_set_notify(dcwc->ctx.pc, pending_call_notify, 302 &dcwc->ctx, NULL); 303 304 state_transition(dcwc, LDCS_CONN_WAITING_ONWARD); 305 306 ret = 0; 307 308bail: 309 dbus_message_unref(msg); 310 311 return ret; 312} 313 314static void 315sul_timer(struct lws_sorted_usec_list *sul) 316{ 317 char payload[64]; 318 const char *ws_pkt = payload; 319 DBusMessage *msg; 320 321 if (!dbus_ctx || dbus_ctx->state != LDCS_CONN_ONWARD) 322 goto again; 323 324 if (autoexit_budget > 0) { 325 if (!--autoexit_budget) { 326 lwsl_notice("reached autoexit budget\n"); 327 interrupted = 1; 328 return; 329 } 330 } 331 332 msg = dbus_message_new_method_call(THIS_BUSNAME, THIS_OBJECT, 333 THIS_INTERFACE, "Send"); 334 if (!msg) 335 goto again; 336 337 lws_snprintf(payload, sizeof(payload), "d #%06X %d %d %d %d;", 338 rand() & 0xffffff, rand() % 480, rand() % 300, 339 rand() % 480, rand() % 300); 340 341 if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &ws_pkt, 342 DBUS_TYPE_INVALID)) { 343 dbus_message_unref(msg); 344 goto again; 345 } 346 347 if (!dbus_connection_send_with_reply(dbus_ctx->ctx.conn, msg, 348 &dbus_ctx->ctx.pc, 349 DBUS_TIMEOUT_USE_DEFAULT)) { 350 lwsl_err("%s: unable to send\n", __func__); 351 dbus_message_unref(msg); 352 goto again; 353 } 354 355 dbus_message_unref(msg); 356 dbus_pending_call_set_notify(dbus_ctx->ctx.pc, 357 pending_call_notify, 358 &dbus_ctx->ctx, NULL); 359 count_tx++; 360 361again: 362 lws_sul_schedule(context, 0, &dbus_ctx->sul, sul_timer, 2 * LWS_US_PER_SEC); 363} 364 365int main(int argc, const char **argv) 366{ 367 struct lws_vhost *vh; 368 struct lws_context_creation_info info; 369 const char *p; 370 int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE 371 /* for LLL_ verbosity above NOTICE to be built into lws, 372 * lws must have been configured and built with 373 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ 374 /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ 375 /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ 376 /* | LLL_DEBUG */ /* | LLL_THREAD */; 377 378 signal(SIGINT, sigint_handler); 379 380 if ((p = lws_cmdline_option(argc, argv, "-d"))) 381 logs = atoi(p); 382 383 if ((p = lws_cmdline_option(argc, argv, "-x"))) 384 autoexit_budget = atoi(p); 385 386 lws_set_log_level(logs, NULL); 387 lwsl_user("LWS minimal DBUS ws proxy testclient\n"); 388 389 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ 390 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS; 391 context = lws_create_context(&info); 392 if (!context) { 393 lwsl_err("lws init failed\n"); 394 return 1; 395 } 396 397 info.options |= 398 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; 399 400 vh = lws_create_vhost(context, &info); 401 if (!vh) 402 goto bail; 403 404 dbus_ctx = create_dbus_client_conn(vh, 0, THIS_LISTEN_PATH); 405 if (!dbus_ctx) 406 goto bail1; 407 408 lws_sul_schedule(context, 0, &dbus_ctx->sul, sul_timer, LWS_US_PER_SEC); 409 410 411 if (remote_method_call(dbus_ctx)) 412 goto bail2; 413 414 /* lws event loop (default poll one) */ 415 416 while (n >= 0 && !interrupted) 417 n = lws_service(context, 0); 418 419bail2: 420 destroy_dbus_client_conn(&dbus_ctx); 421 422bail1: 423 /* this is required for valgrind-cleanliness */ 424 dbus_shutdown(); 425 lws_context_destroy(context); 426 427 lwsl_notice("Exiting cleanly, rx: %d, tx: %d\n", count_rx, count_tx); 428 429 return 0; 430 431bail: 432 lwsl_err("%s: failed to start\n", __func__); 433 lws_context_destroy(context); 434 435 return 1; 436} 437