1/*
2 * lws-minimal-dbus-client
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 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
13#include <stdbool.h>
14#include <string.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <unistd.h>
18#include <signal.h>
19
20#include <libwebsockets.h>
21#include <libwebsockets/lws-dbus.h>
22
23static struct lws_dbus_ctx *dbus_ctx;
24static struct lws_context *context;
25static int interrupted;
26
27#define THIS_INTERFACE	 "org.libwebsockets.test"
28#define THIS_OBJECT	 "/org/libwebsockets/test"
29#define THIS_BUSNAME	 "org.libwebsockets.test"
30
31#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test"
32
33
34static DBusHandlerResult
35client_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
36{
37	const char *str;
38
39	lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
40		  dbus_message_get_interface(message),
41		  dbus_message_get_member(message),
42		  dbus_message_get_path(message));
43
44	if (!dbus_message_get_args(message, NULL,
45				   DBUS_TYPE_STRING, &str,
46				   DBUS_TYPE_INVALID))
47		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
48
49	lwsl_notice("%s: '%s'\n", __func__, str);
50
51	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
52}
53
54static void
55destroy_dbus_client_conn(struct lws_dbus_ctx *ctx)
56{
57	if (!ctx || !ctx->conn)
58		return;
59
60	lwsl_notice("%s\n", __func__);
61
62	dbus_connection_remove_filter(ctx->conn, client_message_handler, ctx);
63	dbus_connection_close(ctx->conn);
64	dbus_connection_unref(ctx->conn);
65
66	free(ctx);
67}
68
69/*
70 * This callback is coming when lws has noticed the fd took a POLLHUP.  The
71 * ctx has effectively gone out of scope before this, and the connection can
72 * be cleaned up and the ctx freed.
73 */
74
75static void
76cb_closing(struct lws_dbus_ctx *ctx)
77{
78	lwsl_err("%s: closing\n", __func__);
79
80	if (ctx == dbus_ctx)
81		dbus_ctx = NULL;
82
83	destroy_dbus_client_conn(ctx);
84}
85
86static struct lws_dbus_ctx *
87create_dbus_client_conn(struct lws_vhost *vh, int tsi, const char *ads)
88{
89	struct lws_dbus_ctx *ctx;
90	DBusError err;
91
92	ctx = malloc(sizeof(*ctx));
93	if (!ctx)
94		return NULL;
95
96	memset(ctx, 0, sizeof(*ctx));
97
98	ctx->vh = vh;
99	ctx->tsi = tsi;
100
101        dbus_error_init(&err);
102
103	/* connect to the daemon bus */
104	ctx->conn = dbus_connection_open_private(ads, &err);
105	if (!ctx->conn) {
106		lwsl_err("%s: Failed to connect: %s\n",
107			 __func__, err.message);
108		goto fail;
109	}
110
111	dbus_connection_set_exit_on_disconnect(ctx->conn, 0);
112
113	if (!dbus_connection_add_filter(ctx->conn, client_message_handler,
114					ctx, NULL)) {
115		lwsl_err("%s: Failed to add filter\n", __func__);
116		goto fail;
117	}
118
119	/*
120	 * This is the part that binds the connection to lws watcher and
121	 * timeout handling provided by lws
122	 */
123
124	if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) {
125		lwsl_err("%s: connection bind to lws failed\n", __func__);
126		goto fail;
127	}
128
129	lwsl_notice("%s: created OK\n", __func__);
130
131	return ctx;
132
133fail:
134	dbus_error_free(&err);
135
136	free(ctx);
137
138	return NULL;
139}
140
141
142void sigint_handler(int sig)
143{
144	interrupted = 1;
145}
146
147/*
148 * This gets called if we timed out waiting for the server reply, or the
149 * reply arrived.
150 */
151
152static void
153pending_call_notify(DBusPendingCall *pending, void *data)
154{
155	// struct lws_dbus_ctx *ctx = (struct lws_dbus_ctx *)data;
156	const char *payload;
157	DBusMessage *msg;
158
159	if (!dbus_pending_call_get_completed(pending)) {
160		lwsl_err("%s: timed out waiting for reply\n", __func__);
161
162		goto bail;
163	}
164
165	msg = dbus_pending_call_steal_reply(pending);
166	if (!msg)
167		goto bail;
168
169	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload,
170				   DBUS_TYPE_INVALID)) {
171		goto bail1;
172	}
173
174	lwsl_user("%s: received '%s'\n", __func__, payload);
175
176bail1:
177	dbus_message_unref(msg);
178bail:
179	dbus_pending_call_unref(pending);
180}
181
182static int
183remote_method_call(struct lws_dbus_ctx *ctx)
184{
185	DBusMessage *msg;
186	const char *payload = "Hello!";
187	int ret = 1;
188
189	msg = dbus_message_new_method_call(
190			/* dest */	  THIS_BUSNAME,
191			/* object-path */ THIS_OBJECT,
192			/* interface */   THIS_INTERFACE,
193			/* method */	  "Echo");
194	if (!msg)
195		return 1;
196
197	if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload,
198				      DBUS_TYPE_INVALID))
199		goto bail;
200
201	if (!dbus_connection_send_with_reply(ctx->conn, msg,
202					     &ctx->pc,
203					     DBUS_TIMEOUT_USE_DEFAULT)) {
204		lwsl_err("%s: unable to send\n", __func__);
205
206		goto bail;
207	}
208
209	dbus_pending_call_set_notify(ctx->pc, pending_call_notify, ctx, NULL);
210
211	ret = 0;
212
213bail:
214	dbus_message_unref(msg);
215
216	return ret;
217}
218
219int main(int argc, const char **argv)
220{
221	struct lws_vhost *vh;
222	struct lws_context_creation_info info;
223	const char *p;
224	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
225			/* for LLL_ verbosity above NOTICE to be built into lws,
226			 * lws must have been configured and built with
227			 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
228			/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
229			/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
230			/* | LLL_DEBUG */ /* | LLL_THREAD */;
231
232	signal(SIGINT, sigint_handler);
233
234	if ((p = lws_cmdline_option(argc, argv, "-d")))
235		logs = atoi(p);
236
237	lws_set_log_level(logs, NULL);
238	lwsl_user("LWS minimal DBUS client\n");
239
240	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
241	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
242	context = lws_create_context(&info);
243	if (!context) {
244		lwsl_err("lws init failed\n");
245		return 1;
246	}
247
248	vh = lws_create_vhost(context, &info);
249	if (!vh)
250		goto bail;
251
252	dbus_ctx = create_dbus_client_conn(vh, 0, THIS_LISTEN_PATH);
253	if (!dbus_ctx)
254		goto bail1;
255
256	if (remote_method_call(dbus_ctx))
257		goto bail2;
258
259	/* lws event loop (default poll one) */
260
261	while (n >= 0 && !interrupted)
262		n = lws_service(context, 0);
263
264bail2:
265	destroy_dbus_client_conn(dbus_ctx);
266
267bail1:
268	/* this is required for valgrind-cleanliness */
269	dbus_shutdown();
270	lws_context_destroy(context);
271
272	lwsl_notice("Exiting cleanly\n");
273
274	return 0;
275
276bail:
277	lwsl_err("%s: failed to start\n", __func__);
278	lws_context_destroy(context);
279
280	return 1;
281}
282