| /* |
| * ws protocol handler plugin for dbus ws proxy |
| * |
| * Copyright (C) 2010-2018 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_dll_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_dll_add_front(&conn_wspctx->ctx.next, &wspctx->ctx.next); |
| |
| /* 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_dll *, rdt, nx, |
| vhd->ctx_listener.next.next) { |
| 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]; |
| int l = len; |
| |
| if (l > (int)sizeof(strbuf) - 1) |
| l = sizeof(strbuf) - 1; |
| |
| 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 \ |
| } |
| |
| #if !defined (LWS_PLUGIN_STATIC) |
| |
| /* boilerplate needed if we are built as a dynamic plugin */ |
| |
| static const struct lws_protocols protocols[] = { |
| LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY |
| }; |
| |
| LWS_EXTERN LWS_VISIBLE int |
| init_protocol_minimal_dbus_wsproxy(struct lws_context *context, |
| struct lws_plugin_capability *c) |
| { |
| if (c->api_magic != LWS_PLUGIN_API_MAGIC) { |
| lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, |
| c->api_magic); |
| return 1; |
| } |
| |
| c->protocols = protocols; |
| c->count_protocols = LWS_ARRAY_SIZE(protocols); |
| c->extensions = NULL; |
| c->count_extensions = 0; |
| |
| return 0; |
| } |
| |
| LWS_EXTERN LWS_VISIBLE int |
| destroy_protocol_minimal_dbus_wsproxy(struct lws_context *context) |
| { |
| return 0; |
| } |
| #endif |