mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-12-25 23:00:12 +00:00
796 lines
21 KiB
C
796 lines
21 KiB
C
/*
|
|
* ws protocol handler plugin for dbus ws proxy
|
|
*
|
|
* Written in 2010-2019 by Andy Green <andy@warmcat.com>
|
|
*
|
|
* This file is made available under the Creative Commons CC0 1.0
|
|
* Universal Public Domain Dedication.
|
|
*
|
|
* This proxies outgoing ws client connections on DBUS. So a DBUS client can
|
|
* reach out and get remote WS payloads in both directions.
|
|
*
|
|
* DEVELOPER NOTE
|
|
*
|
|
* Two worlds, dbus and ws, collide in this file.
|
|
*
|
|
* There main thing keeping it sane is both worlds are running in the same
|
|
* thread and on the same event loop. Although things may happen completely
|
|
* asynchronously in both worlds, the logical reaction to those events are
|
|
* serialized in a single event loop doing one thing at a time.
|
|
*
|
|
* So while you are servicing an event in the ws world, you can be certain the
|
|
* logical state of any related dbus thing cannot change underneath you, until
|
|
* you return back to the event loop, and vice versa. So other-world objects
|
|
* can't be freed, other-world handles can't close etc while you are servicing
|
|
* in your world.
|
|
*
|
|
* Since all bets are off what happens next, and in which world, after you
|
|
* return back to the event loop though, an additional rule is needed: worlds
|
|
* must not allocate in objects owned by the other world. They must generate
|
|
* their own objects in their world and use those for allocations and state.
|
|
*
|
|
* For example in the dbus-world there is a struct lws_dbus_ctx_wsproxy with
|
|
* various state, but he is subject to deletion by events in dbus-world. If
|
|
* the ws-world stored things there, they are subject to going out of scope
|
|
* at the whim of the dbus connection without the ws world hearing about it and
|
|
* cleanly deallocaing them. So the ws world must keep his own pss that remains
|
|
* in scope until the ws link closes for allocations from ws-world.
|
|
*
|
|
* In this application there's a point of contact between the worlds, a ring
|
|
* buffer allocated in ws world when the ws connection is established, and
|
|
* deallocated when the ws connection is closed. The DBUS world needs to put
|
|
* things in this ringbuffer. But the way lws_ring works, when the message
|
|
* allocated in DBUS world is queued on the ringbuffer, the ringbuffer itself
|
|
* takes responsibility for deallocation. So there is no problem.
|
|
*/
|
|
|
|
#if !defined (LWS_PLUGIN_STATIC)
|
|
#define LWS_DLL
|
|
#define LWS_INTERNAL
|
|
#include <libwebsockets.h>
|
|
#include <libwebsockets/lws-dbus.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <signal.h>
|
|
|
|
/*
|
|
* dbus accepted connections create these larger context structs that start
|
|
* with the lws dbus context
|
|
*/
|
|
|
|
struct vhd_dbus_proxy;
|
|
|
|
struct msg {
|
|
void *payload; /* is malloc'd */
|
|
size_t len;
|
|
char binary;
|
|
char first;
|
|
char final;
|
|
};
|
|
|
|
struct pss_dbus_proxy {
|
|
struct lws_ring *ring_out;
|
|
uint32_t ring_out_tail;
|
|
};
|
|
|
|
struct lws_dbus_ctx_wsproxy {
|
|
struct lws_dbus_ctx ctx;
|
|
|
|
struct lws *cwsi;
|
|
struct vhd_dbus_proxy *vhd;
|
|
struct pss_dbus_proxy *pss;
|
|
};
|
|
|
|
struct vhd_dbus_proxy {
|
|
struct lws_context *context;
|
|
struct lws_vhost *vhost;
|
|
|
|
/*
|
|
* Because the listener ctx is composed in the vhd, we can always get a
|
|
* pointer to the outer vhd from a pointer to ctx_listener inside.
|
|
*/
|
|
struct lws_dbus_ctx ctx_listener;
|
|
struct lws_dbus_ctx_wsproxy dctx;
|
|
|
|
const char *dbus_listen_ads;
|
|
};
|
|
|
|
#define THIS_INTERFACE "org.libwebsockets.wsclientproxy"
|
|
#define THIS_OBJECT "/org/libwebsockets/wsclientproxy"
|
|
#define THIS_BUSNAME "org.libwebsockets.wsclientproxy"
|
|
static const char *version = "0.1";
|
|
|
|
static const char *server_introspection_xml =
|
|
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
|
|
"<node>\n"
|
|
" <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
|
|
" <method name='Introspect'>\n"
|
|
" <arg name='data' type='s' direction='out' />\n"
|
|
" </method>\n"
|
|
" </interface>\n"
|
|
|
|
" <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n"
|
|
" <method name='Get'>\n"
|
|
" <arg name='interface' type='s' direction='in' />\n"
|
|
" <arg name='property' type='s' direction='in' />\n"
|
|
" <arg name='value' type='s' direction='out' />\n"
|
|
" </method>\n"
|
|
" <method name='GetAll'>\n"
|
|
" <arg name='interface' type='s' direction='in'/>\n"
|
|
" <arg name='properties' type='a{sv}' direction='out'/>\n"
|
|
" </method>\n"
|
|
" </interface>\n"
|
|
|
|
" <interface name='"THIS_INTERFACE"'>\n"
|
|
" <property name='Version' type='s' access='read' />\n"
|
|
" <method name='Connect' >\n"
|
|
" <arg name='url' type='s' direction='in' />\n"
|
|
" <arg name='subprotocol' type='s' direction='in' />\n"
|
|
" </method>\n"
|
|
" <method name='Send'>\n"
|
|
" <arg name='payload' type='s' direction='in' />\n"
|
|
" </method>\n"
|
|
" <signal name='Receive'>\n"
|
|
" </signal>"
|
|
" <signal name='Status'>\n"
|
|
" </signal>"
|
|
" </interface>\n"
|
|
|
|
"</node>\n";
|
|
|
|
static void
|
|
destroy_message(void *_msg)
|
|
{
|
|
struct msg *msg = _msg;
|
|
|
|
free(msg->payload);
|
|
msg->payload = NULL;
|
|
msg->len = 0;
|
|
}
|
|
|
|
/*
|
|
* DBUS WORLD
|
|
*/
|
|
|
|
static DBusHandlerResult
|
|
dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
|
|
{
|
|
dbus_message_append_args(*reply,
|
|
DBUS_TYPE_STRING, &server_introspection_xml,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
|
|
{
|
|
const char *interface, *property;
|
|
DBusError err;
|
|
|
|
dbus_error_init(&err);
|
|
|
|
if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface,
|
|
DBUS_TYPE_STRING, &property,
|
|
DBUS_TYPE_INVALID)) {
|
|
dbus_message_unref(*reply);
|
|
*reply = dbus_message_new_error(m, err.name, err.message);
|
|
dbus_error_free(&err);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
if (strcmp(property, "Version")) /* Unknown property */
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
|
|
{
|
|
DBusMessageIter arr, di, iter, va;
|
|
const char *property = "Version";
|
|
|
|
dbus_message_iter_init_append(*reply, &iter);
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr);
|
|
|
|
/* Append all properties name/value pairs */
|
|
dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di);
|
|
dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property);
|
|
dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va);
|
|
dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version);
|
|
dbus_message_iter_close_container(&di, &va);
|
|
dbus_message_iter_close_container(&arr, &di);
|
|
|
|
dbus_message_iter_close_container(&iter, &arr);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
dmh_connect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
|
|
{
|
|
struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d;
|
|
const char *prot = "", *ads = "", *path = "", *baduri = "Bad Uri",
|
|
*connecting = "Connecting", *failed = "Failed", **pp;
|
|
struct lws_client_connect_info i;
|
|
char host[128], uri_copy[512];
|
|
const char *uri, *subprotocol;
|
|
DBusError err;
|
|
int port = 0;
|
|
|
|
dbus_error_init(&err);
|
|
|
|
if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &uri,
|
|
DBUS_TYPE_STRING, &subprotocol,
|
|
DBUS_TYPE_INVALID)) {
|
|
dbus_message_unref(*reply);
|
|
*reply = dbus_message_new_error(m, err.name, err.message);
|
|
dbus_error_free(&err);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
strncpy(uri_copy, uri, sizeof(uri_copy) - 1);
|
|
uri_copy[sizeof(uri_copy) - 1] = '\0';
|
|
|
|
if (lws_parse_uri(uri_copy, &prot, &ads, &port, &path)) {
|
|
pp = &baduri;
|
|
goto send_reply;
|
|
}
|
|
|
|
lws_snprintf(host, sizeof(host), "%s:%u", ads, port);
|
|
|
|
memset(&i, 0, sizeof(i));
|
|
|
|
assert(wspctx);
|
|
assert(wspctx->vhd);
|
|
|
|
i.context = wspctx->vhd->context;
|
|
i.port = port;
|
|
i.address = ads;
|
|
i.path = path;
|
|
i.host = host;
|
|
i.origin = host;
|
|
i.ssl_connection = !strcmp(prot, "https") || !strcmp(prot, "wss");
|
|
i.vhost = wspctx->ctx.vh;
|
|
i.protocol = subprotocol;
|
|
i.local_protocol_name = "lws-minimal-dbus-wsproxy";
|
|
i.pwsi = &wspctx->cwsi;
|
|
|
|
lwsl_user("%s: connecting to %s://%s:%d%s\n", __func__, prot,
|
|
i.address, i.port, i.path);
|
|
|
|
if (!lws_client_connect_via_info(&i)) {
|
|
lwsl_notice("%s: client connect failed\n", __func__);
|
|
pp = &failed;
|
|
goto send_reply;
|
|
}
|
|
|
|
lws_set_opaque_parent_data(wspctx->cwsi, wspctx);
|
|
lwsl_notice("%s: client connecting...\n", __func__);
|
|
pp = &connecting;
|
|
|
|
send_reply:
|
|
dbus_message_append_args(*reply, DBUS_TYPE_STRING, pp,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static int
|
|
issue_dbus_signal(struct lws *wsi, const char *signame, const char *string)
|
|
{
|
|
struct lws_dbus_ctx_wsproxy *wspctx =
|
|
lws_get_opaque_parent_data(wsi);
|
|
DBusMessage *m;
|
|
|
|
if (!wspctx)
|
|
return 1;
|
|
|
|
m = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE, signame);
|
|
if (!m) {
|
|
lwsl_err("%s: new signal failed\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
dbus_message_append_args(m, DBUS_TYPE_STRING, &string,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
if (!dbus_connection_send(wspctx->ctx.conn, m, NULL))
|
|
lwsl_err("%s: unable to send\n", __func__);
|
|
|
|
dbus_message_unref(m);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
dmh_send(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
|
|
{
|
|
struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d;
|
|
const char *payload;
|
|
struct msg amsg;
|
|
DBusError err;
|
|
|
|
dbus_error_init(&err);
|
|
|
|
if (!wspctx->cwsi || !wspctx->pss) {
|
|
dbus_message_unref(*reply);
|
|
*reply = dbus_message_new_error(m, "Send Fail", "No ws conn");
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &payload,
|
|
DBUS_TYPE_INVALID)) {
|
|
dbus_message_unref(*reply);
|
|
*reply = dbus_message_new_error(m, err.name, err.message);
|
|
dbus_error_free(&err);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* we allocate on the ringbuffer in ws world, but responsibility for
|
|
* freeing it is understood by lws_ring.
|
|
*/
|
|
|
|
amsg.len = strlen(payload);
|
|
/* notice we over-allocate by LWS_PRE */
|
|
amsg.payload = malloc(LWS_PRE + amsg.len);
|
|
if (!amsg.payload) {
|
|
lwsl_user("OOM: dropping\n");
|
|
dbus_message_unref(*reply);
|
|
*reply = dbus_message_new_error(m, "Send Fail", "OOM");
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
amsg.binary = 0;
|
|
amsg.first = 1;
|
|
amsg.final = 1;
|
|
|
|
memcpy((char *)amsg.payload + LWS_PRE, payload, amsg.len);
|
|
if (!lws_ring_insert(wspctx->pss->ring_out, &amsg, 1)) {
|
|
destroy_message(&amsg);
|
|
lwsl_user("Ring Full!\n");
|
|
dbus_message_unref(*reply);
|
|
*reply = dbus_message_new_error(m, "Send Fail", "Ring full");
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
if (wspctx->cwsi)
|
|
lws_callback_on_writable(wspctx->cwsi);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
struct lws_dbus_methods {
|
|
const char *inter;
|
|
const char *call;
|
|
lws_dbus_message_handler handler;
|
|
} meths[] = {
|
|
{ DBUS_INTERFACE_INTROSPECTABLE, "Introspect", dmh_introspect },
|
|
{ DBUS_INTERFACE_PROPERTIES, "Get", dmh_get },
|
|
{ DBUS_INTERFACE_PROPERTIES, "GetAll", dmh_getall },
|
|
{ THIS_INTERFACE, "Connect", dmh_connect },
|
|
{ THIS_INTERFACE, "Send", dmh_send },
|
|
};
|
|
|
|
static DBusHandlerResult
|
|
server_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
|
|
{
|
|
struct lws_dbus_methods *mp = meths;
|
|
DBusMessage *reply = NULL;
|
|
DBusHandlerResult result;
|
|
size_t n;
|
|
|
|
assert(data);
|
|
|
|
lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
|
|
dbus_message_get_interface(message),
|
|
dbus_message_get_member(message),
|
|
dbus_message_get_path(message));
|
|
|
|
for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) {
|
|
if (dbus_message_is_method_call(message, mp->inter, mp->call)) {
|
|
reply = dbus_message_new_method_return(message);
|
|
if (!reply)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
result = mp->handler(conn, message, &reply, data);
|
|
|
|
if (result == DBUS_HANDLER_RESULT_HANDLED &&
|
|
!dbus_connection_send(conn, reply, NULL))
|
|
result = DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
dbus_message_unref(reply);
|
|
|
|
return result;
|
|
}
|
|
|
|
mp++;
|
|
}
|
|
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
static const DBusObjectPathVTable vtable = {
|
|
.message_function = server_message_handler
|
|
};
|
|
|
|
static void
|
|
destroy_dbus_server_conn(struct lws_dbus_ctx_wsproxy *wsctx)
|
|
{
|
|
if (!wsctx->ctx.conn)
|
|
return;
|
|
|
|
lwsl_notice("%s\n", __func__);
|
|
|
|
dbus_connection_unregister_object_path(wsctx->ctx.conn, THIS_OBJECT);
|
|
lws_dll2_remove(&wsctx->ctx.next);
|
|
dbus_connection_unref(wsctx->ctx.conn);
|
|
}
|
|
|
|
/*
|
|
* This is the client dbus side going away. We need to stop the associated
|
|
* client ws part and make sure it can't dereference us now we are gone.
|
|
*/
|
|
|
|
static void
|
|
cb_closing(struct lws_dbus_ctx *ctx)
|
|
{
|
|
struct lws_dbus_ctx_wsproxy *wspctx =
|
|
(struct lws_dbus_ctx_wsproxy *)ctx;
|
|
lwsl_err("%s: closing\n", __func__);
|
|
|
|
/*
|
|
* We have to take care that the associated proxy wsi knows our
|
|
* dbus ctx is going out of scope after we return from here.
|
|
*
|
|
* We do it by setting its pointer to our dbus ctx to NULL.
|
|
*/
|
|
|
|
if (wspctx->cwsi) {
|
|
lws_set_opaque_parent_data(wspctx->cwsi, NULL);
|
|
lws_set_timeout(wspctx->cwsi,
|
|
PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE,
|
|
LWS_TO_KILL_ASYNC);
|
|
}
|
|
|
|
destroy_dbus_server_conn(wspctx);
|
|
|
|
free(wspctx);
|
|
}
|
|
|
|
static void
|
|
new_conn(DBusServer *server, DBusConnection *conn, void *d)
|
|
{
|
|
struct lws_dbus_ctx_wsproxy *conn_wspctx, /* the new conn context */
|
|
/* the listener context */
|
|
*wspctx = (struct lws_dbus_ctx_wsproxy *)d;
|
|
struct vhd_dbus_proxy *vhd = lws_container_of(d,
|
|
struct vhd_dbus_proxy, ctx_listener);
|
|
|
|
assert(vhd->vhost == wspctx->ctx.vh);
|
|
|
|
lwsl_notice("%s\n", __func__);
|
|
|
|
conn_wspctx = malloc(sizeof(*conn_wspctx));
|
|
if (!conn_wspctx)
|
|
return;
|
|
|
|
memset(conn_wspctx, 0, sizeof(*conn_wspctx));
|
|
|
|
conn_wspctx->ctx.tsi = wspctx->ctx.tsi;
|
|
conn_wspctx->ctx.vh = wspctx->ctx.vh;
|
|
conn_wspctx->ctx.conn = conn;
|
|
conn_wspctx->vhd = vhd; /* let accepted connections also know the vhd */
|
|
|
|
assert(conn_wspctx->vhd);
|
|
|
|
if (lws_dbus_connection_setup(&conn_wspctx->ctx, conn, cb_closing)) {
|
|
lwsl_err("%s: connection bind to lws failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
if (!dbus_connection_register_object_path(conn, THIS_OBJECT, &vtable,
|
|
conn_wspctx)) {
|
|
lwsl_err("%s: Failed to register object path\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
lws_dll2_add_head(&conn_wspctx->ctx.next, &wspctx->ctx.owner);
|
|
|
|
/* we take on responsibility for explicit close / unref with this... */
|
|
dbus_connection_ref(conn);
|
|
|
|
return;
|
|
|
|
bail:
|
|
free(conn_wspctx);
|
|
}
|
|
|
|
static int
|
|
create_dbus_listener(struct vhd_dbus_proxy *vhd, int tsi)
|
|
{
|
|
DBusError e;
|
|
|
|
dbus_error_init(&e);
|
|
#if 0
|
|
vhd->dctx.ctx.tsi = tsi;
|
|
vhd->dctx.ctx.vh = vhd->vhost;
|
|
vhd->dctx.ctx.next.prev = NULL;
|
|
vhd->dctx.ctx.next.next = NULL;
|
|
vhd->dctx.vhd = vhd;
|
|
vhd->dctx.cwsi = NULL;
|
|
|
|
/* connect to the SYSTEM bus */
|
|
|
|
vhd->dctx.ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e);
|
|
if (!vhd->dctx.ctx.conn) {
|
|
lwsl_notice("%s: Failed to get a session DBus connection: '%s'"
|
|
", continuing with daemon listener only\n",
|
|
__func__, e.message);
|
|
dbus_error_free(&e);
|
|
dbus_error_init(&e);
|
|
goto daemon;
|
|
}
|
|
|
|
/*
|
|
* by default dbus will call exit() when this connection closes...
|
|
* we have to shut down other things cleanly, so disable that
|
|
*/
|
|
dbus_connection_set_exit_on_disconnect(vhd->dctx.ctx.conn, 0);
|
|
|
|
if (dbus_bus_request_name(vhd->dctx.ctx.conn, THIS_BUSNAME,
|
|
DBUS_NAME_FLAG_REPLACE_EXISTING, &e) !=
|
|
DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
|
|
lwsl_notice("%s: Failed to request name on bus: '%s',"
|
|
" continuing with daemon listener only\n",
|
|
__func__, e.message);
|
|
dbus_connection_unref(vhd->dctx.ctx.conn);
|
|
vhd->dctx.ctx.conn = NULL;
|
|
dbus_error_free(&e);
|
|
dbus_error_init(&e);
|
|
goto daemon;
|
|
}
|
|
|
|
if (!dbus_connection_register_object_path(vhd->dctx.ctx.conn,
|
|
THIS_OBJECT, &vtable,
|
|
&vhd->dctx)) {
|
|
lwsl_err("%s: Failed to register object path\n", __func__);
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* This is the part that binds the connection to lws watcher and
|
|
* timeout handling provided by lws
|
|
*/
|
|
|
|
if (lws_dbus_connection_setup(&vhd->dctx.ctx, vhd->dctx.ctx.conn,
|
|
cb_closing)) {
|
|
lwsl_err("%s: connection bind to lws failed\n", __func__);
|
|
goto fail;
|
|
}
|
|
|
|
daemon:
|
|
#endif
|
|
vhd->ctx_listener.vh = vhd->vhost;
|
|
vhd->ctx_listener.tsi = tsi;
|
|
|
|
if (!lws_dbus_server_listen(&vhd->ctx_listener, vhd->dbus_listen_ads,
|
|
&e, new_conn)) {
|
|
lwsl_err("%s: failed\n", __func__);
|
|
dbus_error_free(&e);
|
|
|
|
return 1;
|
|
}
|
|
|
|
lwsl_notice("%s: created DBUS listener on %s\n", __func__,
|
|
vhd->dbus_listen_ads);
|
|
|
|
return 0;
|
|
#if 0
|
|
fail:
|
|
dbus_error_free(&e);
|
|
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
destroy_dbus_server_listener(struct vhd_dbus_proxy *vhd)
|
|
{
|
|
dbus_server_disconnect(vhd->ctx_listener.dbs);
|
|
|
|
lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
|
|
vhd->ctx_listener.owner.head) {
|
|
struct lws_dbus_ctx *r = lws_container_of(rdt,
|
|
struct lws_dbus_ctx, next);
|
|
|
|
dbus_connection_close(r->conn);
|
|
dbus_connection_unref(r->conn);
|
|
free(r);
|
|
} lws_end_foreach_dll_safe(rdt, nx);
|
|
|
|
if (vhd->dctx.ctx.conn)
|
|
dbus_connection_unref(vhd->dctx.ctx.conn);
|
|
dbus_server_unref(vhd->ctx_listener.dbs);
|
|
}
|
|
|
|
/*
|
|
* WS WORLD
|
|
*/
|
|
|
|
static int
|
|
callback_minimal_dbus_wsproxy(struct lws *wsi, enum lws_callback_reasons reason,
|
|
void *user, void *in, size_t len)
|
|
{
|
|
struct pss_dbus_proxy *pss = (struct pss_dbus_proxy *)user;
|
|
struct vhd_dbus_proxy *vhd = (struct vhd_dbus_proxy *)
|
|
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
|
lws_get_protocol(wsi));
|
|
struct lws_dbus_ctx_wsproxy *wspctx;
|
|
const struct msg *pmsg;
|
|
int flags, m;
|
|
|
|
switch (reason) {
|
|
|
|
case LWS_CALLBACK_PROTOCOL_INIT:
|
|
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
|
lws_get_protocol(wsi), sizeof(*vhd));
|
|
if (!vhd)
|
|
return -1;
|
|
|
|
vhd->context = lws_get_context(wsi);
|
|
vhd->vhost = lws_get_vhost(wsi);
|
|
|
|
if (lws_pvo_get_str(in, "ads", &vhd->dbus_listen_ads)) {
|
|
lwsl_err("%s: pvo 'ads' must be set\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
if (create_dbus_listener(vhd, 0)) {
|
|
lwsl_err("%s: create_dbus_listener failed\n", __func__);
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
|
destroy_dbus_server_listener(vhd);
|
|
/* this is required for valgrind-cleanliness */
|
|
dbus_shutdown();
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_ESTABLISHED:
|
|
lwsl_user("LWS_CALLBACK_CLIENT_ESTABLISHED\n");
|
|
|
|
/*
|
|
* create the send ringbuffer now the ws connection is
|
|
* established.
|
|
*/
|
|
|
|
wspctx = lws_get_opaque_parent_data(wsi);
|
|
if (!wspctx)
|
|
break;
|
|
|
|
wspctx->pss = pss;
|
|
pss->ring_out_tail = 0;
|
|
pss->ring_out = lws_ring_create(sizeof(struct msg), 8,
|
|
destroy_message);
|
|
if (!pss->ring_out) {
|
|
lwsl_err("OOM\n");
|
|
return -1;
|
|
}
|
|
|
|
issue_dbus_signal(wsi, "Status",
|
|
"ws client connection established");
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_WRITEABLE:
|
|
lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE:\n");
|
|
|
|
pmsg = lws_ring_get_element(pss->ring_out, &pss->ring_out_tail);
|
|
if (!pmsg) {
|
|
lwsl_user(" (nothing in ring)\n");
|
|
break;
|
|
}
|
|
|
|
flags = lws_write_ws_flags(
|
|
pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
|
|
pmsg->first, pmsg->final);
|
|
|
|
/* notice we allowed for LWS_PRE in the payload already */
|
|
m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
|
|
pmsg->len, flags);
|
|
if (m < (int)pmsg->len) {
|
|
lwsl_err("ERROR %d writing to ws socket\n", m);
|
|
return -1;
|
|
}
|
|
|
|
lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n",
|
|
m, flags, pmsg->first, pmsg->final);
|
|
|
|
lws_ring_consume_single_tail(pss->ring_out,
|
|
&pss->ring_out_tail, 1);
|
|
|
|
/* more to do for us? */
|
|
if (lws_ring_get_element(pss->ring_out, &pss->ring_out_tail))
|
|
/* come back as soon as we can write more */
|
|
lws_callback_on_writable(wsi);
|
|
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_RECEIVE:
|
|
|
|
lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d "
|
|
"(rpp %5d, first %d, last %d, bin %d)\n",
|
|
(int)len, (int)lws_remaining_packet_payload(wsi),
|
|
lws_is_first_fragment(wsi),
|
|
lws_is_final_fragment(wsi),
|
|
lws_frame_is_binary(wsi));
|
|
|
|
{
|
|
char strbuf[256];
|
|
size_t l = len;
|
|
|
|
if (l > sizeof(strbuf) - 1u)
|
|
l = sizeof(strbuf) - 1u;
|
|
|
|
memcpy(strbuf, in, l);
|
|
strbuf[l] = '\0';
|
|
|
|
issue_dbus_signal(wsi, "Receive", strbuf);
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
|
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
|
|
in ? (char *)in : "(null)");
|
|
issue_dbus_signal(wsi, "Status", "ws client connection error");
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_CLOSED:
|
|
lwsl_err("LWS_CALLBACK_CLIENT_CLOSED ()\n");
|
|
issue_dbus_signal(wsi, "Status", "ws client connection closed");
|
|
|
|
/* destroy any ringbuffer and pending messages */
|
|
|
|
lws_ring_destroy(pss->ring_out);
|
|
|
|
wspctx = lws_get_opaque_parent_data(wsi);
|
|
if (!wspctx)
|
|
break;
|
|
|
|
/*
|
|
* the wspctx cannot refer to its child wsi any longer, it is
|
|
* about to go out of scope.
|
|
*/
|
|
|
|
wspctx->cwsi = NULL;
|
|
wspctx->pss = NULL;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY \
|
|
{ \
|
|
"lws-minimal-dbus-wsproxy", \
|
|
callback_minimal_dbus_wsproxy, \
|
|
sizeof(struct pss_dbus_proxy), \
|
|
1024, \
|
|
0, NULL, 0 \
|
|
}
|