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
30 enum 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
43 struct 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
51 static struct lws_dbus_ctx_wsproxy_client *dbus_ctx;
52 static struct lws_context *context;
53 static 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
61 static void
state_transition(struct lws_dbus_ctx_wsproxy_client * dcwc,enum lws_dbus_client_state state)62 state_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
70 static DBusHandlerResult
filter(DBusConnection * conn,DBusMessage * message,void * data)71 filter(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
110 static void
destroy_dbus_client_conn(struct lws_dbus_ctx_wsproxy_client ** pdcwc)111 destroy_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
135 static void
cb_closing(struct lws_dbus_ctx * ctx)136 cb_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
151 static struct lws_dbus_ctx_wsproxy_client *
create_dbus_client_conn(struct lws_vhost * vh,int tsi,const char * ads)152 create_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
213 fail:
214 dbus_error_free(&e);
215
216 free(dcwc);
217
218 return NULL;
219 }
220
221
sigint_handler(int sig)222 void 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
232 static void
pending_call_notify(DBusPendingCall * pending,void * data)233 pending_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
255 bail1:
256 dbus_message_unref(msg);
257 bail:
258 dbus_pending_call_unref(pending);
259 }
260
261 static int
remote_method_call(struct lws_dbus_ctx_wsproxy_client * dcwc)262 remote_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
308 bail:
309 dbus_message_unref(msg);
310
311 return ret;
312 }
313
314 static void
sul_timer(struct lws_sorted_usec_list * sul)315 sul_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
361 again:
362 lws_sul_schedule(context, 0, &dbus_ctx->sul, sul_timer, 2 * LWS_US_PER_SEC);
363 }
364
main(int argc,const char ** argv)365 int 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
419 bail2:
420 destroy_dbus_client_conn(&dbus_ctx);
421
422 bail1:
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
431 bail:
432 lwsl_err("%s: failed to start\n", __func__);
433 lws_context_destroy(context);
434
435 return 1;
436 }
437