xref: /aosp_15_r20/external/libwebsockets/minimal-examples/dbus-server/minimal-dbus-server/main.c (revision 1c60b9aca93fdbc9b5f19b2d2194c91294b22281)
1 /*
2  * lws-minimal-dbus-server
3  *
4  * Written in 2010-2019 by Andy Green <[email protected]>
5  *
6  * This file is made available under the Creative Commons CC0 1.0
7  * Universal Public Domain Dedication.
8  *
9  * This demonstrates a minimal session dbus server that uses the lws event loop,
10  * making it possible to integrate it with other lws features.
11  *
12  * The dbus server parts are based on "Sample code illustrating basic use of
13  * D-BUS" (presumed Public Domain) here:
14  *
15  * https://github.com/fbuihuu/samples-dbus/blob/master/dbus-server.c
16  */
17 
18 #include <stdbool.h>
19 #include <string.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <signal.h>
24 
25 #include <libwebsockets.h>
26 #include <libwebsockets/lws-dbus.h>
27 
28 static struct lws_context *context;
29 static const char *version = "0.1";
30 static int interrupted;
31 static struct lws_dbus_ctx dbus_ctx, ctx_listener;
32 static char session;
33 
34 #define THIS_INTERFACE	 "org.libwebsockets.test"
35 #define THIS_OBJECT	 "/org/libwebsockets/test"
36 #define THIS_BUSNAME	 "org.libwebsockets.test"
37 
38 #define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test"
39 
40 static const char *
41 server_introspection_xml =
42 	DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
43 	"<node>\n"
44 	"  <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
45 	"    <method name='Introspect'>\n"
46 	"      <arg name='data' type='s' direction='out' />\n"
47 	"    </method>\n"
48 	"  </interface>\n"
49 
50 	"  <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n"
51 	"    <method name='Get'>\n"
52 	"      <arg name='interface' type='s' direction='in' />\n"
53 	"      <arg name='property'  type='s' direction='in' />\n"
54 	"      <arg name='value'     type='s' direction='out' />\n"
55 	"    </method>\n"
56 	"    <method name='GetAll'>\n"
57 	"      <arg name='interface'  type='s'     direction='in'/>\n"
58 	"      <arg name='properties' type='a{sv}' direction='out'/>\n"
59 	"    </method>\n"
60 	"  </interface>\n"
61 
62 	"  <interface name='"THIS_INTERFACE"'>\n"
63 	"    <property name='Version' type='s' access='read' />\n"
64 	"    <method name='Ping' >\n"
65 	"      <arg type='s' direction='out' />\n"
66 	"    </method>\n"
67 	"    <method name='Echo'>\n"
68 	"      <arg name='string' direction='in' type='s'/>\n"
69 	"      <arg type='s' direction='out' />\n"
70 	"    </method>\n"
71 	"    <method name='EmitSignal'>\n"
72 	"    </method>\n"
73 	"    <method name='Quit'>\n"
74 	"    </method>\n"
75 	"    <signal name='OnEmitSignal'>\n"
76 	"    </signal>"
77 	"  </interface>\n"
78 
79 	"</node>\n";
80 
81 static DBusHandlerResult
dmh_introspect(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)82 dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
83 {
84 	dbus_message_append_args(*reply, DBUS_TYPE_STRING,
85 				 &server_introspection_xml, DBUS_TYPE_INVALID);
86 
87 	return DBUS_HANDLER_RESULT_HANDLED;
88 }
89 
90 static DBusHandlerResult
dmh_get(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)91 dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
92 {
93 	const char *interface, *property;
94 	DBusError err;
95 
96 	dbus_error_init(&err);
97 
98 	if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface,
99 					    DBUS_TYPE_STRING, &property,
100 					    DBUS_TYPE_INVALID)) {
101 		dbus_message_unref(*reply);
102 		*reply = dbus_message_new_error(m, err.name, err.message);
103 		dbus_error_free(&err);
104 
105 		return DBUS_HANDLER_RESULT_HANDLED;
106 	}
107 
108 	if (strcmp(property, "Version")) /* Unknown property */
109 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
110 
111 	dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version,
112 				 DBUS_TYPE_INVALID);
113 
114 	return DBUS_HANDLER_RESULT_HANDLED;
115 }
116 
117 static DBusHandlerResult
dmh_getall(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)118 dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
119 {
120 	DBusMessageIter arr, di, iter, va;
121 	const char *property = "Version";
122 
123 	dbus_message_iter_init_append(*reply, &iter);
124 	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr);
125 
126 	/* Append all properties name/value pairs */
127 	dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di);
128 	dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property);
129 	dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va);
130 	dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version);
131 	dbus_message_iter_close_container(&di, &va);
132 	dbus_message_iter_close_container(&arr, &di);
133 
134 	dbus_message_iter_close_container(&iter, &arr);
135 
136 	return DBUS_HANDLER_RESULT_HANDLED;
137 }
138 
139 static DBusHandlerResult
dmh_ping(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)140 dmh_ping(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
141 {
142 	const char *pong = "Pong";
143 
144 	dbus_message_append_args(*reply, DBUS_TYPE_STRING, &pong,
145 					 DBUS_TYPE_INVALID);
146 
147 	return DBUS_HANDLER_RESULT_HANDLED;
148 }
149 
150 static DBusHandlerResult
dmh_echo(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)151 dmh_echo(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
152 {
153 	const char *msg;
154 	DBusError err;
155 
156 	dbus_error_init(&err);
157 
158 	if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING,
159 				   &msg, DBUS_TYPE_INVALID)) {
160 		dbus_message_unref(*reply);
161 		*reply = dbus_message_new_error(m, err.name, err.message);
162 		dbus_error_free(&err);
163 
164 		return DBUS_HANDLER_RESULT_HANDLED;
165 	}
166 
167 	dbus_message_append_args(*reply, DBUS_TYPE_STRING, &msg,
168 					 DBUS_TYPE_INVALID);
169 
170 	return DBUS_HANDLER_RESULT_HANDLED;
171 }
172 
173 static DBusHandlerResult
dmh_emit_signal(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)174 dmh_emit_signal(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
175 {
176 	DBusMessage *r = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE,
177 					         "OnEmitSignal");
178 
179 	if (!r)
180 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
181 
182 	if (!dbus_connection_send(c, r, NULL))
183 		return DBUS_HANDLER_RESULT_NEED_MEMORY;
184 
185 	/* and send the original empty reply after */
186 
187 	return DBUS_HANDLER_RESULT_HANDLED;
188 }
189 
190 static DBusHandlerResult
dmh_emit_quit(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)191 dmh_emit_quit(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
192 {
193 	interrupted = 1;
194 
195 	return DBUS_HANDLER_RESULT_HANDLED;
196 }
197 
198 struct lws_dbus_methods {
199 	const char *inter;
200 	const char *call;
201 	lws_dbus_message_handler handler;
202 } meths[] = {
203 	{ DBUS_INTERFACE_INTROSPECTABLE, "Introspect",	dmh_introspect	},
204 	{ DBUS_INTERFACE_PROPERTIES,	 "Get",		dmh_get		},
205 	{ DBUS_INTERFACE_PROPERTIES,	 "GetAll",	dmh_getall	},
206 	{ THIS_INTERFACE,		 "Ping",	dmh_ping	},
207 	{ THIS_INTERFACE,		 "Echo",	dmh_echo	},
208 	{ THIS_INTERFACE,		 "EmitSignal",	dmh_emit_signal },
209 	{ THIS_INTERFACE,		 "Quit",	dmh_emit_quit	},
210 };
211 
212 static DBusHandlerResult
server_message_handler(DBusConnection * conn,DBusMessage * message,void * data)213 server_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
214 {
215 	struct lws_dbus_methods *mp = meths;
216 	DBusHandlerResult result;
217         DBusMessage *reply = NULL;
218 	size_t n;
219 
220 	lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
221 		  dbus_message_get_interface(message),
222 		  dbus_message_get_member(message),
223 		  dbus_message_get_path(message));
224 
225 	for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) {
226 		if (dbus_message_is_method_call(message, mp->inter, mp->call)) {
227 			reply = dbus_message_new_method_return(message);
228 			if (!reply)
229 				return DBUS_HANDLER_RESULT_NEED_MEMORY;
230 
231 			result = mp->handler(conn, message, &reply, data);
232 
233 			if (result == DBUS_HANDLER_RESULT_HANDLED &&
234 			    !dbus_connection_send(conn, reply, NULL))
235 				result = DBUS_HANDLER_RESULT_NEED_MEMORY;
236 
237 			dbus_message_unref(reply);
238 
239 			return result;
240 		}
241 
242 		mp++;
243 	}
244 
245 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
246 }
247 
248 static const DBusObjectPathVTable server_vtable = {
249 	.message_function = server_message_handler
250 };
251 
252 static void
destroy_dbus_server_conn(struct lws_dbus_ctx * ctx)253 destroy_dbus_server_conn(struct lws_dbus_ctx *ctx)
254 {
255 	if (!ctx->conn)
256 		return;
257 
258 	lwsl_notice("%s\n", __func__);
259 
260 	dbus_connection_unregister_object_path(ctx->conn, THIS_OBJECT);
261 	lws_dll2_remove(&ctx->next);
262 	dbus_connection_unref(ctx->conn);
263 }
264 
265 static void
cb_closing(struct lws_dbus_ctx * ctx)266 cb_closing(struct lws_dbus_ctx *ctx)
267 {
268 	lwsl_err("%s: closing\n", __func__);
269 	destroy_dbus_server_conn(ctx);
270 
271 	free(ctx);
272 }
273 
274 
275 static void
new_conn(DBusServer * server,DBusConnection * conn,void * data)276 new_conn(DBusServer *server, DBusConnection *conn, void *data)
277 {
278 	struct lws_dbus_ctx *conn_ctx, *ctx = (struct lws_dbus_ctx *)data;
279 
280 	lwsl_notice("%s: vh %s\n", __func__, lws_get_vhost_name(ctx->vh));
281 
282 	conn_ctx = malloc(sizeof(*conn_ctx));
283 	if (!conn_ctx)
284 		return;
285 
286 	memset(conn_ctx, 0, sizeof(*conn_ctx));
287 
288 	conn_ctx->tsi = ctx->tsi;
289 	conn_ctx->vh = ctx->vh;
290 	conn_ctx->conn = conn;
291 
292 	if (lws_dbus_connection_setup(conn_ctx, conn, cb_closing)) {
293 		lwsl_err("%s: connection bind to lws failed\n", __func__);
294 		goto bail;
295 	}
296 
297 	if (!dbus_connection_register_object_path(conn, THIS_OBJECT,
298 						  &server_vtable, conn_ctx)) {
299 		lwsl_err("%s: Failed to register object path\n", __func__);
300 		goto bail;
301 	}
302 
303 	lws_dll2_add_head(&conn_ctx->next, &ctx->owner);
304 
305 	/* we take on responsibility for explicit close / unref with this... */
306 	dbus_connection_ref(conn);
307 
308 	return;
309 
310 bail:
311 	free(conn_ctx);
312 }
313 
314 static int
create_dbus_listener(const char * ads)315 create_dbus_listener(const char *ads)
316 {
317 	DBusError e;
318 
319         dbus_error_init(&e);
320 
321 	if (!lws_dbus_server_listen(&ctx_listener, ads, &e, new_conn)) {
322 		lwsl_err("%s: failed\n", __func__);
323 		dbus_error_free(&e);
324 
325 		return 1;
326 	}
327 
328 	return 0;
329 }
330 
331 static int
create_dbus_server_conn(struct lws_dbus_ctx * ctx,DBusBusType type)332 create_dbus_server_conn(struct lws_dbus_ctx *ctx, DBusBusType type)
333 {
334 	DBusError err;
335 	int rv;
336 
337         dbus_error_init(&err);
338 
339 	/* connect to the daemon bus */
340 	ctx->conn = dbus_bus_get(type, &err);
341 	if (!ctx->conn) {
342 		lwsl_err("%s: Failed to get a session DBus connection: %s\n",
343 			 __func__, err.message);
344 		goto fail;
345 	}
346 
347 	/*
348 	 * by default dbus will call exit() when this connection closes...
349 	 * we have to shut down other things cleanly, so disable that
350 	 */
351 	dbus_connection_set_exit_on_disconnect(ctx->conn, 0);
352 
353 	rv = dbus_bus_request_name(ctx->conn, THIS_BUSNAME,
354 				   DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
355 	if (rv != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
356 		lwsl_err("%s: Failed to request name on bus: %s\n",
357 			 __func__, err.message);
358 		goto fail;
359 	}
360 
361 	if (!dbus_connection_register_object_path(ctx->conn, THIS_OBJECT,
362 						  &server_vtable, NULL)) {
363 		lwsl_err("%s: Failed to register object path for TestObject\n",
364 			 __func__);
365 		dbus_bus_release_name(ctx->conn, THIS_BUSNAME, &err);
366 		goto fail;
367 	}
368 
369 	/*
370 	 * This is the part that binds the connection to lws watcher and
371 	 * timeout handling provided by lws
372 	 */
373 
374 	if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) {
375 		lwsl_err("%s: connection bind to lws failed\n", __func__);
376 		goto fail;
377 	}
378 
379 	lwsl_notice("%s: created OK\n", __func__);
380 
381 	return 0;
382 
383 fail:
384 	dbus_error_free(&err);
385 
386 	return 1;
387 }
388 
389 /*
390  * Cleanly release the connection
391  */
392 
393 static void
destroy_dbus_server_listener(struct lws_dbus_ctx * ctx)394 destroy_dbus_server_listener(struct lws_dbus_ctx *ctx)
395 {
396 	dbus_server_disconnect(ctx->dbs);
397 
398 	lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
399 				   ctx->owner.head) {
400 		struct lws_dbus_ctx *r =
401 			lws_container_of(rdt, struct lws_dbus_ctx, next);
402 
403 		dbus_connection_close(r->conn);
404 		dbus_connection_unref(r->conn);
405 		free(r);
406 	} lws_end_foreach_dll_safe(rdt, nx);
407 
408 	dbus_server_unref(ctx->dbs);
409 }
410 
411 /*
412  * DBUS can send messages outside the usual client-initiated RPC concept.
413  *
414  * You can receive them using a message filter.
415  */
416 
417 static void
spam_connected_clients(struct lws_dbus_ctx * ctx)418 spam_connected_clients(struct lws_dbus_ctx *ctx)
419 {
420 
421 	/* send connected clients an unsolicited message */
422 
423 	lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
424 				   ctx->owner.head) {
425 		struct lws_dbus_ctx *r =
426 			lws_container_of(rdt, struct lws_dbus_ctx, next);
427 
428 
429 		DBusMessage *msg;
430 		const char *payload = "Unsolicited message";
431 
432 		msg = dbus_message_new(DBUS_NUM_MESSAGE_TYPES + 1);
433 		if (!msg) {
434 			lwsl_err("%s: new message failed\n", __func__);
435 		}
436 
437 		dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload,
438 						 DBUS_TYPE_INVALID);
439 		if (!dbus_connection_send(r->conn, msg, NULL)) {
440 			lwsl_err("%s: unable to send\n", __func__);
441 		}
442 
443 		lwsl_notice("%s\n", __func__);
444 
445 		dbus_message_unref(msg);
446 
447 	} lws_end_foreach_dll_safe(rdt, nx);
448 
449 }
450 
451 
sigint_handler(int sig)452 void sigint_handler(int sig)
453 {
454 	interrupted = 1;
455 }
456 
main(int argc,const char ** argv)457 int main(int argc, const char **argv)
458 {
459 	struct lws_context_creation_info info;
460 	const char *p;
461 	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
462 			/* for LLL_ verbosity above NOTICE to be built into lws,
463 			 * lws must have been configured and built with
464 			 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
465 			/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
466 			/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
467 			/* | LLL_DEBUG */ /* | LLL_THREAD */;
468 
469 	signal(SIGINT, sigint_handler);
470 
471 	if ((p = lws_cmdline_option(argc, argv, "-d")))
472 		logs = atoi(p);
473 
474 	lws_set_log_level(logs, NULL);
475 	lwsl_user("LWS minimal DBUS server\n");
476 
477 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
478 	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
479 	context = lws_create_context(&info);
480 	if (!context) {
481 		lwsl_err("lws init failed\n");
482 		return 1;
483 	}
484 
485 	info.options |=
486 		LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
487 
488 	dbus_ctx.tsi = 0;
489 	ctx_listener.tsi = 0;
490 	ctx_listener.vh = dbus_ctx.vh = lws_create_vhost(context, &info);
491 	if (!dbus_ctx.vh)
492 		goto bail;
493 
494 	session = !!lws_cmdline_option(argc, argv, "--session");
495 
496 	if (session) {
497 		/* create the dbus connection, loosely bound to our lws vhost */
498 
499 		if (create_dbus_server_conn(&dbus_ctx, DBUS_BUS_SESSION))
500 			goto bail;
501 	} else {
502 		if (create_dbus_listener(THIS_LISTEN_PATH)) {
503 			lwsl_err("%s: create_dbus_listener failed\n", __func__);
504 			goto bail;
505 		}
506 	}
507 
508 	/* lws event loop (default poll one) */
509 
510 	while (n >= 0 && !interrupted) {
511 		if (!session)
512 			spam_connected_clients(&ctx_listener);
513 		n = lws_service(context, 0);
514 	}
515 
516 	if (session)
517 		destroy_dbus_server_conn(&dbus_ctx);
518 	else
519 		destroy_dbus_server_listener(&ctx_listener);
520 
521 	/* this is required for valgrind-cleanliness */
522 	dbus_shutdown();
523 	lws_context_destroy(context);
524 
525 	lwsl_notice("Exiting cleanly\n");
526 
527 	return 0;
528 
529 bail:
530 	lwsl_err("%s: failed to start\n", __func__);
531 
532 	lws_context_destroy(context);
533 
534 	return 1;
535 }
536