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