diff --git a/include/villas/api.h b/include/villas/api.h new file mode 100644 index 000000000..f75bc3426 --- /dev/null +++ b/include/villas/api.h @@ -0,0 +1,106 @@ +/** REST-API-releated functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#pragma once + +#include "list.h" + +#include + +/* Forward declarations */ +enum lws_callback_reasons; +struct lws; +struct cfg; + +struct api; +struct api_ressource; +struct api_buffer; +struct api_session; + +extern struct list apis; + +/** Callback type of command function + * + * @param[inout] c Command handle + * @param[in] args JSON command arguments. + * @param[out] resp JSON command response. + * @param[in] i Execution context. + */ +typedef int (*api_cb_t)(struct api_ressource *c, json_t *args, json_t **resp, struct api_session *s); + +enum api_version { + API_VERSION_UNKOWN = 0, + API_VERSION_1 = 1 +}; + +enum api_mode { + API_MODE_WS, /**< This API session was established over a WebSocket connection. */ + API_MODE_HTTP /**< This API session was established via a HTTP REST request. */ +}; + +struct api { + struct list sessions; /**< List of currently active connections */ + + struct cfg *cfg; +}; + +struct api_buffer { + char *buf; /**< A pointer to the buffer. Usually resized via realloc() */ + size_t size; /**< The allocated size of the buffer. */ + size_t len; /**< The used length of the buffer. */ + size_t sent; /**< Pointer to api_buffer::buf to indicate how much has been sent. */ +}; + +/** A connection via HTTP REST or WebSockets to issue API actions. */ +struct api_session { + enum api_mode mode; + enum api_version version; + + int runs; + + struct { + struct api_buffer body; /**< HTTP body / WS payload */ + } request; + + struct { + struct api_buffer body; /**< HTTP body / WS payload */ + struct api_buffer headers; /**< HTTP headers */ + } response; + + struct api *api; +}; + +/** Command descriptor + * + * Every command is described by a descriptor. + */ +struct api_ressource { + char *name; + char *description; + api_cb_t cb; +}; + +/** Initalize the API. + * + * Save references to list of paths / nodes for command execution. + */ +int api_init(struct api *a, struct cfg *cfg); + +int api_destroy(struct api *a); + +int api_deinit(struct api *a); + +int api_session_init(struct api_session *s, enum api_mode m); + +int api_session_destroy(struct api_session *s); + +int api_session_run_command(struct api_session *s, json_t *req, json_t **resp); + +/** Libwebsockets callback for "api" endpoint */ +int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); \ No newline at end of file diff --git a/include/villas/web.h b/include/villas/web.h new file mode 100644 index 000000000..81826b25b --- /dev/null +++ b/include/villas/web.h @@ -0,0 +1,42 @@ +/** LWS-releated functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#pragma once + +/* Forward declarations */ +struct api; + +struct web { + struct api *api; + + struct lws_context *context; /**< The libwebsockets server context. */ + struct lws_vhost *vhost; /**< The libwebsockets vhost. */ + + int port; /**< Port of the build in HTTP / WebSocket server. */ + const char *htdocs; /**< The root directory for files served via HTTP. */ + const char *ssl_cert; /**< Path to the SSL certitifcate for HTTPS / WSS. */ + const char *ssl_private_key; /**< Path to the SSL private key for HTTPS / WSS. */ +}; + +/** Initialize the web interface. + * + * The web interface is based on the libwebsockets library. + */ +int web_init(struct web *w, struct api *a); + +int web_destroy(struct web *w); + +/** Parse HTTPd and WebSocket related options */ +int web_parse(struct web *w, config_setting_t *lcs); + +/** De-initializes the web interface. */ +int web_deinit(struct web *w); + +/** libwebsockets service routine. Call periodically */ +int web_service(struct web *w); \ No newline at end of file diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 728653992..70b415678 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -6,8 +6,8 @@ LIB_SRCS = $(addprefix lib/nodes/, file.c cbuilder.c) \ $(addprefix lib/kernel/, kernel.c rt.c) \ $(addprefix lib/, sample.c path.c node.c hooks.c \ log.c utils.c cfg.c hist.c timing.c pool.c list.c \ - queue.c memory.c advio.c \ - ) \ + queue.c memory.c advio.c web.c api.c \ + ) LIB_CFLAGS = $(CFLAGS) -fPIC LIB_LDFLAGS = -shared @@ -47,12 +47,12 @@ endif endif # Enable WebSocket support -ifndef WITHOUT_WEBSOCKETS +#ifndef WITHOUT_WEBSOCKETS ifeq ($(shell pkg-config libwebsockets jansson; echo $$?),0) - LIB_SRCS += lib/nodes/websocket.c +# LIB_SRCS += lib/nodes/websocket.c LIB_PKGS += libwebsockets jansson endif -endif +#endif # Enable OPAL-RT Asynchronous Process support (will result in 32bit binary!!!) ifdef WITH_OPAL diff --git a/lib/api.c b/lib/api.c new file mode 100644 index 000000000..dc9efca0d --- /dev/null +++ b/lib/api.c @@ -0,0 +1,252 @@ +/** REST-API-releated functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include + +#include "api.h" +#include "log.h" +#include "config.h" + +static int parse_request(struct api_buffer *b, json_t **req) +{ + json_error_t e; + + if (b->len <= 0) + return -1; + + *req = json_loadb(b->buf, b->len, JSON_DISABLE_EOF_CHECK, &e); + if (!*req) + return -1; + + if (e.position < b->len) { + void *dst = (void *) b->buf; + void *src = (void *) (b->buf + e.position); + + memmove(dst, src, b->len - e.position); + + b->len -= e.position; + + return 1; + } + + return 0; +} + +#if JANSSON_VERSION_HEX < 0x020A00 +size_t json_dumpb(const json_t *json, char *buffer, size_t size, size_t flags) +{ + char *str = json_dumps(json, flags); + size_t len = strlen(str) - 1; // not \0 terminated + + if (!buffer || len > size) + return len; + else + memcpy(buffer, str, len); + + return len; +} +#endif + +static int unparse_response(struct api_buffer *b, json_t *res) +{ + size_t len; + + retry: len = json_dumpb(res, b->buf + b->len, b->size - b->len, 0); + if (len > b->size - b->len) { + b->buf = realloc(b->buf, b->len + len); + goto retry; + } + + return 0; +} + +int api_session_run_command(struct api_session *s, json_t *req, json_t **resp) +{ + int ret; + const char *rstr; + struct api_ressource *res; + + json_t *args; + + ret = json_unpack(req, "{ s: s, s: o }", + "command", &rstr, + "arguments", &args); + if (ret) + *resp = json_pack("{ s: s, s: d }", + "error", "invalid request", + "code", -1); + + res = list_lookup(&apis, rstr); + if (!res) + *resp = json_pack("{ s: s, s: d, s: s }", + "error", "command not found", + "code", -2, + "command", rstr); + + ret = res->cb(res, args, resp, s); + if (ret) + *resp = json_pack("{ s: s, s: d }", + "error", "command failed", + "code", ret); + + return 0; +} + +int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) +{ + int ret; + + struct api_session *s = (struct api_session *) user; + + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: + api_session_init(s, API_MODE_WS); + break; + + case LWS_CALLBACK_HTTP: + if (len < 1) { + lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); + return -1; + } + + char *uri = (char *) in; + + /* Parse request URI */ + ret = sscanf(uri, "/api/v%d", (int *) &s->version); + if (ret != 1) + return -1; + + debug(LOG_API, "New REST API session initiated: version = %d", s->version); + + api_session_init(s, API_MODE_HTTP); + + /* Prepare HTTP response header */ + s->response.headers.buf = alloc(512); + + unsigned char *p = (unsigned char *) s->response.headers.buf; + unsigned char *e = (unsigned char *) s->response.headers.buf + 512; + + ret = lws_add_http_header_status(wsi, 200, &p, e); + if (ret) + return 1; + ret = lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *) USER_AGENT, strlen(USER_AGENT), &p, e); + if (ret) + return 1; + ret = lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *) "application/json", strlen("application/json"), &p, e); + if (ret) + return 1; + //ret = lws_add_http_header_content_length(wsi, file_len, &p, e); + //if (ret) + // return 1; + ret = lws_finalize_http_header(wsi, &p, e); + if (ret) + return 1; + + *p = '\0'; + + s->response.headers.len = p - (unsigned char *) s->response.headers.buf + 1; + s->response.headers.sent = 0; + + /* book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ + lws_callback_on_writable(wsi); + + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + case LWS_CALLBACK_RECEIVE: + case LWS_CALLBACK_HTTP_BODY: { + char *newbuf; + + newbuf = realloc(s->request.body.buf, s->request.body.len + len); + + s->request.body.buf = memcpy(newbuf + s->request.body.len, in, len); + s->request.body.len += len; + + json_t *req, *resp; + while (parse_request(&s->request.body, &req) == 1) { + + api_session_run_command(s, req, &resp); + + unparse_response(&s->response.body, resp); + } + + break; + } + + case LWS_CALLBACK_HTTP_BODY_COMPLETION: + return -1; /* close connection */ + + case LWS_CALLBACK_SERVER_WRITEABLE: + case LWS_CALLBACK_HTTP_WRITEABLE: { + enum lws_write_protocol prot; + struct api_buffer *protbuf; + + if (s->mode == API_MODE_HTTP && s->response.headers.sent < s->response.headers.len) { + prot = LWS_WRITE_HTTP_HEADERS; + protbuf = &s->response.headers; + } + else if (s->response.body.sent < s->response.body.len) { + prot = LWS_WRITE_HTTP; + protbuf = &s->response.body; + } + else + break; + + int sent; + + void *buf = (void *) (protbuf->buf + protbuf->sent); + size_t len = protbuf->len - protbuf->sent; + + sent = lws_write(wsi, buf, len, prot); + if (sent > 0) + protbuf->sent += sent; + else + return -1; + + break; + } + + default: + return 0; + } + + return 0; + +} + +int api_init(struct api *a, struct cfg *cfg) +{ + list_init(&a->sessions); + + a->cfg = cfg; + + return 0; +} + +int api_deinit(struct api *a) +{ + list_destroy(&a->sessions, (dtor_cb_t) api_session_destroy, false); + + return 0; +} + +int api_session_init(struct api_session *s, enum api_mode m) +{ + s->mode = m; + + s->request.body = + s->response.body = + s->response.headers = (struct api_buffer) { 0 }; + + return 0; +} + +int api_session_destroy(struct api_session *s) +{ + return 0; +} diff --git a/lib/apis/Makefile.inc b/lib/apis/Makefile.inc new file mode 100644 index 000000000..f249c5bbf --- /dev/null +++ b/lib/apis/Makefile.inc @@ -0,0 +1 @@ +LIB_SRCS += $(wildcard lib/cmds/*.c) \ No newline at end of file diff --git a/lib/apis/config.c b/lib/apis/config.c new file mode 100644 index 000000000..a0db95c8d --- /dev/null +++ b/lib/apis/config.c @@ -0,0 +1,30 @@ +/** The "config" command + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include "api.h" +#include "utils.h" +#include "plugin.h" + +static int api_config(struct api_ressource *h, json_t *args, json_t **resp, struct api_info *i) +{ + if (!i->settings.config) + return -1; + + *resp = config_to_json(config_setting_root(i->settings.config)); + + return 0; +} + +static struct plugin p = { + .name = "config", + .description = "retrieve current VILLASnode configuration", + .type = PLUGIN_TYPE_API, + .api.cb = api_config +}; + +REGISTER_PLUGIN(&p) \ No newline at end of file diff --git a/lib/apis/nodes.c b/lib/apis/nodes.c new file mode 100644 index 000000000..106185586 --- /dev/null +++ b/lib/apis/nodes.c @@ -0,0 +1,48 @@ +/** The "nodes" command + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include + +#include "api.h" +#include "node.h" +#include "utils.h" + +extern struct list nodes; + +static int api_nodes(struct api_ressource *r, json_t *args, json_t **resp, struct api_info *i) +{ + json_t *json_nodes = json_array(); + + list_foreach(struct node *n, i->nodes) { + json_t *json_node = json_pack("{ s: s, s: i, s: i, s: i, s: i }", + "name", node_name_short(n), + "state", n->state, + "vectorize", n->vectorize, + "affinity", n->affinity + ); + + /* Add all additional fields of node here. + * This can be used for metadata */ + json_object_update(json_node, config_to_json(n->cfg)); + + json_array_append_new(json_nodes, json_node); + } + + *resp = json_nodes; + + return 0; +} + +static struct plugin p = { + .name = "nodes", + .description = "retrieve list of all known nodes", + .type = PLUGIN_TYPE_API, + .api.cb = api_nodes +}; + +REGISTER_PLUGIN(&p) \ No newline at end of file diff --git a/lib/apis/reload.c b/lib/apis/reload.c new file mode 100644 index 000000000..796ba724a --- /dev/null +++ b/lib/apis/reload.c @@ -0,0 +1,23 @@ +/** The "reload" command + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include "api.h" + +static int api_reload(struct api_ressource *h, json_t *args, json_t **resp, struct api_info *i) +{ + return -1; +} + +static struct plugin p = { + .name = "reload", + .description = "restart VILLASnode with new configuration", + .type = PLUGIN_TYPE_API, + .api.cb = api_reload +}; + +REGISTER_PLUGIN(&p) \ No newline at end of file diff --git a/lib/nodes/websocket.c b/lib/nodes/websocket.c index 4550d21d6..ed3eb37ea 100644 --- a/lib/nodes/websocket.c +++ b/lib/nodes/websocket.c @@ -22,225 +22,10 @@ #include "cfg.h" #include "config.h" -/* Private static storage */ -static config_setting_t *cfg_root; /**< Root config */ -static pthread_t thread; /**< All nodes are served by a single websocket server. This server is running in a dedicated thread. */ -static struct lws_context *context; /**< The libwebsockets server context. */ - -static int port; /**< Port of the build in HTTP / WebSocket server */ - -static const char *ssl_cert; /**< Path to the SSL certitifcate for HTTPS / WSS */ -static const char *ssl_private_key; /**< Path to the SSL private key for HTTPS / WSS */ -static const char *htdocs; /**< Path to the directory which should be served by build in HTTP server */ - /* Forward declarations */ static struct node_type vt; -static int protocol_cb_http(struct lws *, enum lws_callback_reasons, void *, void *, size_t); -static int protocol_cb_live(struct lws *, enum lws_callback_reasons, void *, void *, size_t); -static struct lws_protocols protocols[] = { - { - "http-only", - protocol_cb_http, - 0, - 0 - }, - { - "live", - protocol_cb_live, - sizeof(struct websocket_connection), - 0 - }, - { 0 /* terminator */ } -}; - -#if 0 -static const struct lws_extension exts[] = { - { - "permessage-deflate", - lws_extension_callback_pm_deflate, - "permessage-deflate" - }, - { - "deflate-frame", - lws_extension_callback_pm_deflate, - "deflate_frame" - }, - { NULL, NULL, NULL /* terminator */ } -}; -#endif - -static void logger(int level, const char *msg) { - int len = strlen(msg); - if (strchr(msg, '\n')) - len -= 1; - - /* Decrease severity for some errors. */ - if (strstr(msg, "Unable to open") == msg) - level = LLL_WARN; - - switch (level) { - case LLL_ERR: error("LWS: %.*s", len, msg); break; - case LLL_WARN: warn("LWS: %.*s", len, msg); break; - case LLL_INFO: info("LWS: %.*s", len, msg); break; - default: debug(DBG_WEBSOCKET | 1, "LWS: %.*s", len, msg); break; - } -} - -static void * server_thread(void *ctx) -{ - debug(DBG_WEBSOCKET | 3, "WebSocket: Started server thread"); - - while (lws_service(context, 10) >= 0); - - debug(DBG_WEBSOCKET | 3, "WebSocket: shutdown voluntarily"); - - return NULL; -} - -/* Choose mime type based on the file extension */ -static char * get_mimetype(const char *resource_path) -{ - char *extension = strrchr(resource_path, '.'); - - if (extension == NULL) - return "text/plain"; - else if (!strcmp(extension, ".png")) - return "image/png"; - else if (!strcmp(extension, ".svg")) - return "image/svg+xml"; - else if (!strcmp(extension, ".jpg")) - return "image/jpg"; - else if (!strcmp(extension, ".gif")) - return "image/gif"; - else if (!strcmp(extension, ".html")) - return "text/html"; - else if (!strcmp(extension, ".css")) - return "text/css"; - else if (!strcmp(extension, ".js")) - return "application/javascript"; - else - return "text/plain"; -} - -int protocol_cb_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) -{ - switch (reason) { - case LWS_CALLBACK_HTTP: - if (!htdocs) { - lws_return_http_status(wsi, HTTP_STATUS_SERVICE_UNAVAILABLE, NULL); - goto try_to_reuse; - } - - if (len < 1) { - lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); - goto try_to_reuse; - } - - char *requested_uri = (char *) in; - - debug(DBG_WEBSOCKET | 3, "LWS: New HTTP request: %s", requested_uri); - - /* Handle default path */ - if (!strcmp(requested_uri, "/")) { - char *response = "HTTP/1.1 302 Found\r\n" - "Content-Length: 0\r\n" - "Location: /index.html\r\n" - "\r\n"; - - lws_write(wsi, (void *) response, strlen(response), LWS_WRITE_HTTP); - - goto try_to_reuse; - } -#ifdef WITH_JANSSON - /* Return list of websocket nodes */ - else if (!strcmp(requested_uri, "/nodes.json")) { - json_t *json_body = json_array(); - - list_foreach(struct node *n, &vt.instances) { - struct websocket *w = n->_vd; - - json_t *json_node = json_pack("{ s: s, s: i, s: i, s: i, s: i }", - "name", node_name_short(n), - "connections", list_length(&w->connections), - "state", n->state, - "vectorize", n->vectorize, - "affinity", n->affinity - ); - - /* Add all additional fields of node here. - * This can be used for metadata */ - json_object_update(json_node, config_to_json(n->cfg)); - - json_array_append_new(json_body, json_node); - } - - char *body = json_dumps(json_body, JSON_INDENT(4)); - - char *header = "HTTP/1.1 200 OK\r\n" - "Connection: close\r\n" - "Content-Type: application/json\r\n" - "\r\n"; - - lws_write(wsi, (void *) header, strlen(header), LWS_WRITE_HTTP); - lws_write(wsi, (void *) body, strlen(body), LWS_WRITE_HTTP); - - free(body); - json_decref(json_body); - - return -1; - } - else if (!strcmp(requested_uri, "/config.json")) { - char *body = json_dumps(config_to_json(cfg_root), JSON_INDENT(4)); - - char *header = "HTTP/1.1 200 OK\r\n" - "Connection: close\r\n" - "Content-Type: application/json\r\n" - "\r\n"; - - lws_write(wsi, (void *) header, strlen(header), LWS_WRITE_HTTP); - lws_write(wsi, (void *) body, strlen(body), LWS_WRITE_HTTP); - - free(body); - - return -1; - } -#endif - else { - char path[4069]; - snprintf(path, sizeof(path), "%s%s", htdocs, requested_uri); - - /* refuse to serve files we don't understand */ - char *mimetype = get_mimetype(path); - if (!mimetype) { - warn("HTTP: Unknown mimetype for %s", path); - lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); - return -1; - } - - int n = lws_serve_http_file(wsi, path, mimetype, NULL, 0); - if (n < 0) - return -1; - else if (n == 0) - break; - else - goto try_to_reuse; - } - - default: - break; - } - - return 0; - -try_to_reuse: - if (lws_http_transaction_completed(wsi)) - return -1; - - return 0; -} - -int protocol_cb_live(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) +int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct websocket_connection *c = user; struct websocket *w; @@ -364,61 +149,6 @@ int protocol_cb_live(struct lws *wsi, enum lws_callback_reasons reason, void *us } } -int websocket_init(int argc, char * argv[], config_setting_t *cfg) -{ - config_setting_t *cfg_http; - - lws_set_log_level((1 << LLL_COUNT) - 1, logger); - - /* Parse global config */ - cfg_http = config_setting_lookup(cfg, "http"); - if (cfg_http) { - config_setting_lookup_string(cfg_http, "ssl_cert", &ssl_cert); - config_setting_lookup_string(cfg_http, "ssl_private_key", &ssl_private_key); - config_setting_lookup_string(cfg_http, "htdocs", &htdocs); - config_setting_lookup_int(cfg_http, "port", &port); - } - - /* Default settings */ - if (!port) - port = 80; - if (!htdocs) - htdocs = "/villas/contrib/websocket"; - - /* Start server */ - struct lws_context_creation_info info = { - .port = port, - .protocols = protocols, - .extensions = NULL, //exts, - .ssl_cert_filepath = ssl_cert, - .ssl_private_key_filepath = ssl_private_key, - .gid = -1, - .uid = -1 - }; - - context = lws_create_context(&info); - if (context == NULL) - error("WebSocket: failed to initialize server"); - - /* Save root config for GET /config.json request */ - cfg_root = cfg; - - pthread_create(&thread, NULL, server_thread, NULL); - - return 0; -} - -int websocket_deinit() -{ - lws_cancel_service(context); - lws_context_destroy(context); - - pthread_cancel(thread); - pthread_join(thread, NULL); - - return 0; -} - int websocket_open(struct node *n) { struct websocket *w = n->_vd; diff --git a/lib/web.c b/lib/web.c new file mode 100644 index 000000000..a44da1072 --- /dev/null +++ b/lib/web.c @@ -0,0 +1,184 @@ +/** LWS-releated functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + *********************************************************************************/ + +#include +#include + +#include + +#if 0 + #include "nodes/websocket.h" +#endif + +#include "utils.h" +#include "log.h" +#include "web.h" +#include "api.h" + +/* Forward declarations */ +lws_callback_function api_protocol_cb; + +/** Path to the directory which should be served by build in HTTP server */ +static char htdocs[PATH_MAX] = "/usr/local/share/villas/node/htdocs"; + +/** List of libwebsockets protocols. */ +static struct lws_protocols protocols[] = { + { + .name = "http-only", + .callback = api_protocol_cb, + .per_session_data_size = sizeof(struct api_session), + .rx_buffer_size = 0 + }, +#if 0 + { + .name = "live", + .callback = websocket_protocol_cb, + .per_session_data_size = sizeof(struct websocket_connection), + .rx_buffer_size = 0 + }, +#endif + { + .name = "api", + .callback = api_protocol_cb, + .per_session_data_size = sizeof(struct api_session), + .rx_buffer_size = 0 + }, + { 0 /* terminator */ } +}; + +/** List of libwebsockets mounts. */ +static struct lws_http_mount mounts[] = { + { + .mount_next = &mounts[1], + .mountpoint = "/api/v1/", + .origin = "cmd", + .def = NULL, + .cgienv = NULL, + .cgi_timeout = 0, + .cache_max_age = 0, + .cache_reusable = 0, + .cache_revalidate = 0, + .cache_intermediaries = 0, + .origin_protocol = LWSMPRO_CALLBACK, + .mountpoint_len = 8 + }, + { + .mount_next = NULL, + .mountpoint = "/", + .origin = htdocs, + .def = "/index.html", + .cgienv = NULL, + .cgi_timeout = 0, + .cache_max_age = 0, + .cache_reusable = 0, + .cache_revalidate = 0, + .cache_intermediaries = 0, + .origin_protocol = LWSMPRO_FILE, + .mountpoint_len = 1 + } +}; + +/** List of libwebsockets extensions. */ +static const struct lws_extension extensions[] = { + { + "permessage-deflate", + lws_extension_callback_pm_deflate, + "permessage-deflate" + }, + { + "deflate-frame", + lws_extension_callback_pm_deflate, + "deflate_frame" + }, + { NULL /* terminator */ } +}; + +static void logger(int level, const char *msg) { + int len = strlen(msg); + if (strchr(msg, '\n')) + len -= 1; + + /* Decrease severity for some errors. */ + if (strstr(msg, "Unable to open") == msg) + level = LLL_WARN; + + switch (level) { + case LLL_ERR: error("LWS: %.*s", len, msg); break; + case LLL_WARN: warn("LWS: %.*s", len, msg); break; + case LLL_INFO: info("LWS: %.*s", len, msg); break; + default: debug(LOG_WEBSOCKET | 1, "LWS: %.*s", len, msg); break; + } +} + +int web_service(struct web *w) +{ + return lws_service(w->context, 10); +} + +int web_parse(struct web *w, config_setting_t *lcs) +{ + config_setting_t *lcs_http; + + /* Parse global config */ + lcs_http = config_setting_lookup(lcs, "http"); + if (lcs_http) { + const char *ht; + + config_setting_lookup_string(lcs_http, "ssl_cert", &w->ssl_cert); + config_setting_lookup_string(lcs_http, "ssl_private_key", &w->ssl_private_key); + config_setting_lookup_int(lcs_http, "port", &w->port); + + if (config_setting_lookup_string(lcs_http, "htdocs", &w->htdocs)) { + strncpy(htdocs, ht, sizeof(htdocs)); + } + } + + return 0; +} + +int web_init(struct web *w, struct api *a) +{ + w->api = a; + + lws_set_log_level((1 << LLL_COUNT) - 1, logger); + + /* Start server */ + struct lws_context_creation_info ctx_info = { + .options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS, + .gid = -1, + .uid = -1, + .user = (void *) w + }; + + struct lws_context_creation_info vhost_info = { + .protocols = protocols, + .mounts = mounts, + .extensions = extensions, + .port = w->port, + .ssl_cert_filepath = w->ssl_cert, + .ssl_private_key_filepath = w->ssl_private_key + }; + + w->context = lws_create_context(&ctx_info); + if (w->context == NULL) + error("WebSocket: failed to initialize server"); + + w->vhost = lws_create_vhost(w->context, &vhost_info); + if (w->vhost == NULL) + error("WebSocket: failed to initialize server"); + + return 0; +} + +int web_deinit(struct web *w) +{ + lws_cancel_service(w->context); + lws_context_destroy(w->context); + + return 0; +}