• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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