|
@@ -0,0 +1,309 @@
|
|
|
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
|
|
|
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
|
|
|
+ *
|
|
|
+ * relies heavily on concepts from libwebsockets minimal examples
|
|
|
+ * Copyright 2019 (c) Matthias Konnerth
|
|
|
+ * Copyright 2019 (c) Michael Derfler
|
|
|
+ */
|
|
|
+
|
|
|
+#define UA_INTERNAL
|
|
|
+
|
|
|
+#include <open62541/network_ws.h>
|
|
|
+#include <open62541/plugin/log_stdout.h>
|
|
|
+#include <open62541/util.h>
|
|
|
+#include "open62541_queue.h"
|
|
|
+#include <libwebsockets.h>
|
|
|
+#include <string.h>
|
|
|
+
|
|
|
+struct BufferEntry {
|
|
|
+ UA_ByteString msg;
|
|
|
+ SIMPLEQ_ENTRY(BufferEntry) next;
|
|
|
+};
|
|
|
+
|
|
|
+typedef struct BufferEntry BufferEntry;
|
|
|
+
|
|
|
+struct ConnectionUserData {
|
|
|
+ struct lws *wsi;
|
|
|
+ SIMPLEQ_HEAD(, BufferEntry) messages;
|
|
|
+};
|
|
|
+
|
|
|
+typedef struct ConnectionUserData ConnectionUserData;
|
|
|
+
|
|
|
+//one of these is created for each client connecting to us
|
|
|
+struct SessionData {
|
|
|
+ UA_Connection *connection;
|
|
|
+};
|
|
|
+
|
|
|
+// one of these is created for each vhost our protocol is used with
|
|
|
+struct VHostData {
|
|
|
+ struct lws_context *context;
|
|
|
+};
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ const UA_Logger *logger;
|
|
|
+ UA_UInt16 port;
|
|
|
+ struct lws_context *context;
|
|
|
+ UA_Server *server;
|
|
|
+ UA_ConnectionConfig config;
|
|
|
+} ServerNetworkLayerWS;
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+connection_getsendbuffer(UA_Connection *connection, size_t length, UA_ByteString *buf) {
|
|
|
+ if(length > connection->config.sendBufferSize)
|
|
|
+ return UA_STATUSCODE_BADCOMMUNICATIONERROR;
|
|
|
+ return UA_ByteString_allocBuffer(buf, length);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+connection_releasesendbuffer(UA_Connection *connection, UA_ByteString *buf) {
|
|
|
+ UA_ByteString_deleteMembers(buf);
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+connection_releaserecvbuffer(UA_Connection *connection, UA_ByteString *buf) {
|
|
|
+ UA_ByteString_deleteMembers(buf);
|
|
|
+}
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+connection_send(UA_Connection *connection, UA_ByteString *buf) {
|
|
|
+ ConnectionUserData *buffer = (ConnectionUserData *)connection->handle;
|
|
|
+ if(connection->state == UA_CONNECTION_CLOSED) {
|
|
|
+ UA_ByteString_deleteMembers(buf);
|
|
|
+ return UA_STATUSCODE_BADCONNECTIONCLOSED;
|
|
|
+ }
|
|
|
+
|
|
|
+ BufferEntry *entry = (BufferEntry *)malloc(sizeof(BufferEntry));
|
|
|
+ entry->msg.length = buf->length;
|
|
|
+ entry->msg.data = (UA_Byte *)malloc(LWS_PRE + buf->length);
|
|
|
+ memcpy(entry->msg.data + LWS_PRE, buf->data, buf->length);
|
|
|
+ UA_ByteString_deleteMembers(buf);
|
|
|
+ SIMPLEQ_INSERT_TAIL(&buffer->messages, entry, next);
|
|
|
+ lws_callback_on_writable(buffer->wsi);
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+ServerNetworkLayerWS_close(UA_Connection *connection) {
|
|
|
+ if(connection->state == UA_CONNECTION_CLOSED)
|
|
|
+ return;
|
|
|
+ connection->state = UA_CONNECTION_CLOSED;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+freeConnection(UA_Connection *connection) {
|
|
|
+ if(connection->handle) {
|
|
|
+ UA_free(connection->handle);
|
|
|
+ }
|
|
|
+ UA_Connection_deleteMembers(connection);
|
|
|
+ UA_free(connection);
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+callback_opcua(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
|
|
|
+ size_t len) {
|
|
|
+ struct SessionData *pss = (struct SessionData *)user;
|
|
|
+ struct VHostData *vhd =
|
|
|
+ (struct VHostData *)lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
|
|
+ lws_get_protocol(wsi));
|
|
|
+
|
|
|
+ switch(reason) {
|
|
|
+ case LWS_CALLBACK_PROTOCOL_INIT:
|
|
|
+ vhd = (struct VHostData *)lws_protocol_vh_priv_zalloc(
|
|
|
+ lws_get_vhost(wsi), lws_get_protocol(wsi),
|
|
|
+ sizeof(struct VHostData));
|
|
|
+ vhd->context = lws_get_context(wsi);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case LWS_CALLBACK_ESTABLISHED:
|
|
|
+ if(!wsi)
|
|
|
+ break;
|
|
|
+ ServerNetworkLayerWS *layer = (ServerNetworkLayerWS*)lws_context_user(vhd->context);
|
|
|
+ UA_Connection *c = (UA_Connection *)malloc(sizeof(UA_Connection));
|
|
|
+ ConnectionUserData *buffer =
|
|
|
+ (ConnectionUserData *)malloc(sizeof(ConnectionUserData));
|
|
|
+ SIMPLEQ_INIT(&buffer->messages);
|
|
|
+ buffer->wsi = wsi;
|
|
|
+ memset(c, 0, sizeof(UA_Connection));
|
|
|
+ c->sockfd = 0;
|
|
|
+ c->handle = buffer;
|
|
|
+ c->config = layer->config;
|
|
|
+ c->send = connection_send;
|
|
|
+ c->close = ServerNetworkLayerWS_close;
|
|
|
+ c->free = freeConnection;
|
|
|
+ c->getSendBuffer = connection_getsendbuffer;
|
|
|
+ c->releaseSendBuffer = connection_releasesendbuffer;
|
|
|
+ c->releaseRecvBuffer = connection_releaserecvbuffer;
|
|
|
+ // stack sets the connection to established
|
|
|
+ c->state = UA_CONNECTION_OPENING;
|
|
|
+ c->openingDate = UA_DateTime_nowMonotonic();
|
|
|
+ pss->connection = c;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case LWS_CALLBACK_CLOSED:
|
|
|
+ // notify server
|
|
|
+ if(!pss->connection->state != UA_CONNECTION_CLOSED) {
|
|
|
+ pss->connection->state = UA_CONNECTION_CLOSED;
|
|
|
+ }
|
|
|
+
|
|
|
+ layer = (ServerNetworkLayerWS*)lws_context_user(vhd->context);
|
|
|
+ if(layer && layer->server)
|
|
|
+ {
|
|
|
+ UA_Server_removeConnection(layer->server, pss->connection);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+
|
|
|
+ case LWS_CALLBACK_SERVER_WRITEABLE:
|
|
|
+ if(!pss->connection)
|
|
|
+ break;
|
|
|
+
|
|
|
+ ConnectionUserData *b = (ConnectionUserData *)pss->connection->handle;
|
|
|
+ do {
|
|
|
+
|
|
|
+ BufferEntry *entry = SIMPLEQ_FIRST(&b->messages);
|
|
|
+ if(!entry)
|
|
|
+ break;
|
|
|
+
|
|
|
+ int m = lws_write(wsi, entry->msg.data + LWS_PRE, entry->msg.length,
|
|
|
+ LWS_WRITE_BINARY);
|
|
|
+ if(m < (int)entry->msg.length) {
|
|
|
+ lwsl_err("ERROR %d writing to ws\n", m);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ UA_ByteString_deleteMembers(&entry->msg);
|
|
|
+ UA_free(entry);
|
|
|
+ SIMPLEQ_REMOVE_HEAD(&b->messages, next);
|
|
|
+ } while(!lws_send_pipe_choked(wsi));
|
|
|
+
|
|
|
+ // process remaining messages
|
|
|
+ if(SIMPLEQ_FIRST(&b->messages)) {
|
|
|
+ lws_callback_on_writable(wsi);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case LWS_CALLBACK_RECEIVE:
|
|
|
+ if(!vhd->context)
|
|
|
+ break;
|
|
|
+ layer =
|
|
|
+ (ServerNetworkLayerWS *)lws_context_user(vhd->context);
|
|
|
+ if(!layer->server)
|
|
|
+ break;
|
|
|
+
|
|
|
+ UA_ByteString message = {len, (UA_Byte *)in};
|
|
|
+ UA_Server_processBinaryMessage(layer->server, pss->connection, &message);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct lws_protocols protocols[] = {
|
|
|
+ {"http", lws_callback_http_dummy, 0, 0, 0, NULL, 0},
|
|
|
+ {"opcua", callback_opcua, sizeof(struct SessionData), 0, 0, NULL, 0},
|
|
|
+ {NULL, NULL, 0, 0, 0, NULL, 0}
|
|
|
+};
|
|
|
+
|
|
|
+// make the opcua protocol callback the default one
|
|
|
+const struct lws_protocol_vhost_options pvo_opt = {NULL, NULL, "default", "1"};
|
|
|
+const struct lws_protocol_vhost_options pvo = {NULL, &pvo_opt, "opcua", ""};
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+ServerNetworkLayerWS_start(UA_ServerNetworkLayer *nl, const UA_String *customHostname) {
|
|
|
+ UA_initialize_architecture_network();
|
|
|
+
|
|
|
+ ServerNetworkLayerWS *layer = (ServerNetworkLayerWS *)nl->handle;
|
|
|
+
|
|
|
+ /* Get the discovery url from the hostname */
|
|
|
+ UA_String du = UA_STRING_NULL;
|
|
|
+ char discoveryUrlBuffer[256];
|
|
|
+ char hostnameBuffer[256];
|
|
|
+ if(customHostname->length) {
|
|
|
+ du.length = (size_t)UA_snprintf(discoveryUrlBuffer, 255, "ws://%.*s:%d/",
|
|
|
+ (int)customHostname->length, customHostname->data,
|
|
|
+ layer->port);
|
|
|
+ du.data = (UA_Byte *)discoveryUrlBuffer;
|
|
|
+ } else {
|
|
|
+ if(UA_gethostname(hostnameBuffer, 255) == 0) {
|
|
|
+ du.length = (size_t)UA_snprintf(discoveryUrlBuffer, 255, "ws://%s:%d/",
|
|
|
+ hostnameBuffer, layer->port);
|
|
|
+ du.data = (UA_Byte *)discoveryUrlBuffer;
|
|
|
+ } else {
|
|
|
+ UA_LOG_ERROR(layer->logger, UA_LOGCATEGORY_NETWORK,
|
|
|
+ "Could not get the hostname");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ UA_String_copy(&du, &nl->discoveryUrl);
|
|
|
+
|
|
|
+ UA_LOG_INFO(layer->logger, UA_LOGCATEGORY_NETWORK,
|
|
|
+ "Websocket network layer listening on %.*s", (int)nl->discoveryUrl.length,
|
|
|
+ nl->discoveryUrl.data);
|
|
|
+
|
|
|
+ struct lws_context_creation_info info;
|
|
|
+ int logLevel = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
|
|
|
+ lws_set_log_level(logLevel, NULL);
|
|
|
+ memset(&info, 0, sizeof info);
|
|
|
+ info.port = layer->port;
|
|
|
+ info.protocols = protocols;
|
|
|
+ info.vhost_name = (char *)du.data;
|
|
|
+ info.ws_ping_pong_interval = 10;
|
|
|
+ info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
|
|
|
+ info.pvo = &pvo;
|
|
|
+ info.user = layer;
|
|
|
+
|
|
|
+ struct lws_context *context = lws_create_context(&info);
|
|
|
+ if(!context) {
|
|
|
+ UA_LOG_ERROR(layer->logger, UA_LOGCATEGORY_NETWORK, "lws init failed");
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+ }
|
|
|
+ layer->context = context;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+ServerNetworkLayerWS_listen(UA_ServerNetworkLayer *nl, UA_Server *server,
|
|
|
+ UA_UInt16 timeout) {
|
|
|
+ ServerNetworkLayerWS *layer = (ServerNetworkLayerWS *)nl->handle;
|
|
|
+ layer->server = server;
|
|
|
+ // set timeout to zero to return immediately if nothing to do
|
|
|
+ lws_service(layer->context, 0);
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+ServerNetworkLayerWS_stop(UA_ServerNetworkLayer *nl, UA_Server *server) {
|
|
|
+ ServerNetworkLayerWS *layer = (ServerNetworkLayerWS *)nl->handle;
|
|
|
+ UA_LOG_INFO(layer->logger, UA_LOGCATEGORY_NETWORK,
|
|
|
+ "Shutting down the WS network layer");
|
|
|
+ lws_context_destroy(layer->context);
|
|
|
+ UA_deinitialize_architecture_network();
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+ServerNetworkLayerWS_deleteMembers(UA_ServerNetworkLayer *nl) {
|
|
|
+ UA_free(nl->handle);
|
|
|
+ UA_String_deleteMembers(&nl->discoveryUrl);
|
|
|
+}
|
|
|
+
|
|
|
+UA_ServerNetworkLayer
|
|
|
+UA_ServerNetworkLayerWS(UA_ConnectionConfig config, UA_UInt16 port, UA_Logger *logger) {
|
|
|
+ UA_ServerNetworkLayer nl;
|
|
|
+ memset(&nl, 0, sizeof(UA_ServerNetworkLayer));
|
|
|
+ nl.deleteMembers = ServerNetworkLayerWS_deleteMembers;
|
|
|
+ nl.localConnectionConfig = config;
|
|
|
+ nl.start = ServerNetworkLayerWS_start;
|
|
|
+ nl.listen = ServerNetworkLayerWS_listen;
|
|
|
+ nl.stop = ServerNetworkLayerWS_stop;
|
|
|
+
|
|
|
+ ServerNetworkLayerWS *layer =
|
|
|
+ (ServerNetworkLayerWS *)UA_calloc(1, sizeof(ServerNetworkLayerWS));
|
|
|
+ if(!layer)
|
|
|
+ return nl;
|
|
|
+ nl.handle = layer;
|
|
|
+ layer->logger = logger;
|
|
|
+ layer->port = port;
|
|
|
+ layer->config = config;
|
|
|
+ return nl;
|
|
|
+}
|