From 3c3ef6676a62fac65e7b8b6498281f6b3224bb79 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 5 Jun 2018 09:08:07 +0200 Subject: [PATCH 01/13] added simple config for websocket client --- etc/websocket-client.conf | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 etc/websocket-client.conf diff --git a/etc/websocket-client.conf b/etc/websocket-client.conf new file mode 100644 index 000000000..68079eee5 --- /dev/null +++ b/etc/websocket-client.conf @@ -0,0 +1,43 @@ +/** Example configuration file for VILLASnode. + * + * The syntax of this file is similar to JSON. + * A detailed description of the format can be found here: + * http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files + * + * @author Steffen Vogel + * @copyright 2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +http = { + enabled = false +} + +nodes = { + ws = { + type = "websocket", + + hooks = ( + { type = "stats" } + ) + + destinations = [ + "http://localhost:8088/test_session" + ] + } +}; From 1da2d9732d58ee13a387293c736541ec2169782b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 5 Jun 2018 09:08:44 +0200 Subject: [PATCH 02/13] added some extern "C" declarations to header files --- include/villas/utils.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/villas/utils.h b/include/villas/utils.h index 942087fbb..ba10966c8 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -23,6 +23,10 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + #include #include #include @@ -281,7 +285,6 @@ pid_t spawn(const char *name, char *const argv[]); /** Determines the string length as printed on the screen (ignores escable sequences). */ size_t strlenp(const char *str); - #ifdef __cplusplus } -#endif \ No newline at end of file +#endif From 280c20de943a6434dbcc588ebb32107dd83d5931 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 5 Jun 2018 09:09:12 +0200 Subject: [PATCH 03/13] websocket: fix format selection in client connections --- lib/nodes/websocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nodes/websocket.c b/lib/nodes/websocket.c index 2f2faf8cf..54c47c0b0 100644 --- a/lib/nodes/websocket.c +++ b/lib/nodes/websocket.c @@ -415,7 +415,7 @@ int websocket_start(struct node *n) c->state = WEBSOCKET_CONNECTION_STATE_CONNECTING; c->node = n; - c->format = format_type_lookup("villas.web"); + c->format = format_type_lookup("villas.web"); /** @todo We could parse the format from the URI */ c->destination = d; d->info.context = web->context; From c58a127ca5ae110a0c2d74fd9729c6f352370b62 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 5 Jun 2018 09:13:35 +0200 Subject: [PATCH 04/13] whitespace cleanup --- lib/web.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web.c b/lib/web.c index 381e147a4..37047a3d7 100644 --- a/lib/web.c +++ b/lib/web.c @@ -37,7 +37,7 @@ lws_callback_function api_http_protocol_cb; lws_callback_function websocket_protocol_cb; /** List of libwebsockets protocols. */ - struct lws_protocols protocols[] = { +struct lws_protocols protocols[] = { { .name = "http", .callback = lws_callback_http_dummy, From 077d782990562d088786b91764c9027b3848a66e Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 5 Jun 2018 09:14:38 +0200 Subject: [PATCH 05/13] relay: added a simple websocket relay for facilitating client-to-client connections --- src/relay.cpp | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 src/relay.cpp diff --git a/src/relay.cpp b/src/relay.cpp new file mode 100644 index 000000000..3daf1012c --- /dev/null +++ b/src/relay.cpp @@ -0,0 +1,243 @@ +/** Simple WebSocket relay facilitating client-to-client connections. + * + * @author Steffen Vogel + * @copyright 2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +/** The libwebsockets server context. */ +static lws_context *context; + +/** The libwebsockets vhost. */ +static lws_vhost *vhost; + +/* Forward declarations */ +lws_callback_function protocol_cb; +struct Session; +struct Connection; + +static std::map> sessions; + +class Frame : public std::vector { }; + +class Session { +public: + typedef std::string Identifier; + + + static std::shared_ptr get(Identifier sid) + { + auto it = sessions.find(sid); + if (it == sessions.end()) { + auto s = std::make_shared(sid); + + sessions[sid] = s; + + return s; + } + else + return it->second; + } + + Session(Identifier sid) : + identifier(sid) + { } + + ~Session() + { } + + Identifier identifier; + + std::vector> connections; +}; + +class Connection { +public: + lws *wsi; + + std::shared_ptr currentFrame; + + std::deque> outgoingFrames; + + std::shared_ptr session; + + Connection() : + currentFrame(nullptr) + { } +}; + +/** List of libwebsockets protocols. */ +lws_protocols protocols[] = { + { + .name = "live", + .callback = protocol_cb, + .per_session_data_size = sizeof(Connection), + .rx_buffer_size = 0 + }, + { NULL /* terminator */ } +}; + +/** List of libwebsockets extensions. */ +static const lws_extension extensions[] = { + { + "permessage-deflate", + lws_extension_callback_pm_deflate, + "permessage-deflate" + }, + { + "deflate-frame", + lws_extension_callback_pm_deflate, + "deflate_frame" + }, + { NULL /* terminator */ } +}; + +extern log *global_log; + +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: log_print(global_log, CLR_RED("Web "), "%.*s", len, msg); break; + case LLL_WARN: log_print(global_log, CLR_YEL("Web "), "%.*s", len, msg); break; + case LLL_INFO: log_print(global_log, CLR_WHT("Web "), "%.*s", len, msg); break; + default: log_print(global_log, "Web ", "%.*s", len, msg); break; + } +} + +int protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) +{ + Connection *c = reinterpret_cast(user); + + switch (reason) { + + case LWS_CALLBACK_ESTABLISHED: { + char uri[64]; + + new (c) Connection(); + + c->wsi = wsi; + + /* We use the URI to associate this connection to a session + * Example: ws://example.com/node_1 + * Will select the session with the name 'node_1' + */ + + /* Get path of incoming request */ + lws_hdr_copy(wsi, uri, sizeof(uri), WSI_TOKEN_GET_URI); + if (strlen(uri) <= 0) { + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (unsigned char *) "Invalid URL", strlen("Invalid URL")); + return -1; + } + + Session::Identifier sid = &uri[1]; + + auto s = Session::get(sid); + + c->session = s; + s->connections.push_back(std::shared_ptr(c)); + + break; + } + + case LWS_CALLBACK_CLOSED: + + c->~Connection(); + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + while (c->outgoingFrames.size() > 0) { + std::shared_ptr fr = c->outgoingFrames.front(); + + lws_write(wsi, fr->data(), fr->size(), LWS_WRITE_BINARY); + + c->outgoingFrames.pop_front(); + } + break; + + case LWS_CALLBACK_RECEIVE: + if (!c->currentFrame.get() || lws_is_first_fragment(wsi)) + c->currentFrame = std::make_shared(); + + c->currentFrame->insert(c->currentFrame->end(), (uint8_t *) in, (uint8_t *) in + len); + + if (lws_is_final_fragment(wsi)) { + for (auto cc : c->session->connections) { + if (cc.get() == c) + continue; + + cc->outgoingFrames.push_back(c->currentFrame); + + lws_callback_on_writable(cc->wsi); + } + } + + break; + + default: + break; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + lws_set_log_level((1 << LLL_COUNT) - 1, logger); + + /* Start server */ + lws_context_creation_info ctx_info = { 0 }; + + ctx_info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + ctx_info.gid = -1; + ctx_info.uid = -1; + ctx_info.protocols = protocols; + ctx_info.extensions = extensions; + ctx_info.port = 8088; + + context = lws_create_context(&ctx_info); + if (context == NULL) + error("WebSocket: failed to initialize server context"); + + vhost = lws_create_vhost(context, &ctx_info); + if (vhost == NULL) + error("WebSocket: failed to initialize virtual host"); + + for (;;) + lws_service(context, 100); + + return 0; +} From b802ef6435591ad1d9d7e3c4675cefe8ade22ab6 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 5 Jun 2018 09:16:09 +0200 Subject: [PATCH 06/13] updated changelog --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94118ef16..81f985b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [0.6.5] - Unrelease +## [0.6.5] - Unreleased -### Changed +### Added -- The configuration of many node-types is now splitted into seperate `in` and `out` sections. Please update your configuration files accordingly. +- A new sub-command `villas-relay` implements a client-to-client WebSocket relay. + It can be used as a proxy for nodes which sit behind a NAT firewall. ## [0.6.4] - 2018-07-18 @@ -52,6 +53,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - The IO format names have changed. They now use dots (`raw.flt32`) instead of hyphens (`raw-flt32`) in their name. Please update your configuration files accordingly. +- The configuration of many node-types is now splitted into seperate `in` and `out` sections. Please update your configuration files accordingly. ## [0.6.1] - 2018-02-17 From b8ff7d03d300bbda8d8f69b9317bbb288abcbc90 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 20 Jun 2018 20:55:17 +0200 Subject: [PATCH 07/13] relay: move much of the protocol callback code into the Session, Connection classes --- src/relay.cpp | 211 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 153 insertions(+), 58 deletions(-) diff --git a/src/relay.cpp b/src/relay.cpp index 3daf1012c..c73fe274c 100644 --- a/src/relay.cpp +++ b/src/relay.cpp @@ -20,10 +20,12 @@ * along with this program. If not, see . *********************************************************************************/ +#include #include #include -#include +#include #include +#include #include #include @@ -45,25 +47,63 @@ struct Connection; static std::map> sessions; -class Frame : public std::vector { }; +class InvalidUrlException { }; + +struct Options { + bool loopback; +} opts; + +class Frame : public std::vector { +public: + Frame() { + // lws_write() requires LWS_PRE bytes in front of the payload + insert(end(), LWS_PRE, 0); + } + + uint8_t * data() { + return std::vector::data() + LWS_PRE; + } + + size_type size() { + return std::vector::size() - LWS_PRE; + } +}; class Session { public: typedef std::string Identifier; - - static std::shared_ptr get(Identifier sid) + static std::shared_ptr get(lws *wsi) { + char uri[64]; + + /* We use the URI to associate this connection to a session + * Example: ws://example.com/node_1 + * Will select the session with the name 'node_1' + */ + + /* Get path of incoming request */ + lws_hdr_copy(wsi, uri, sizeof(uri), WSI_TOKEN_GET_URI); + if (strlen(uri) <= 0) + throw InvalidUrlException(); + + Identifier sid = uri;//&uri[1]; + auto it = sessions.find(sid); if (it == sessions.end()) { auto s = std::make_shared(sid); sessions[sid] = s; + info("Creating new session: %s", sid.c_str()); + return s; } - else + else { + info("Reusing existing session: %s", sid.c_str()); + return it->second; + } } Session(Identifier sid) : @@ -75,22 +115,70 @@ public: Identifier identifier; - std::vector> connections; + std::map> connections; }; class Connection { -public: + +protected: lws *wsi; std::shared_ptr currentFrame; - std::deque> outgoingFrames; + std::queue> outgoingFrames; std::shared_ptr session; - Connection() : - currentFrame(nullptr) - { } +public: + Connection(lws *w) : + wsi(w), + currentFrame(std::make_shared()), + outgoingFrames() + { + session = Session::get(sid); + session->connections[wsi] = std::shared_ptr(this); + + info("New connection established to session: %s", session->identifier.c_str()); + } + + ~Connection() { + info("Connection closed"); + + session->connections.erase(wsi); + } + + void write() { + while (!outgoingFrames.empty()) { + std::shared_ptr fr = outgoingFrames.front(); + + lws_write(wsi, fr->data(), fr->size(), LWS_WRITE_BINARY); + + outgoingFrames.pop(); + } + } + + void read(void *in, size_t len) { + currentFrame->insert(currentFrame->end(), (uint8_t *) in, (uint8_t *) in + len); + + if (lws_is_final_fragment(wsi)) { + debug(5, "Received frame, relaying to %zu connections", session->connections.size() - (opts.loopback ? 0 : 1)); + + for (auto p : session->connections) { + auto c = p.second; + + /* We skip the current connection in order + * to avoid receiving our own data */ + if (opts.loopback == false && c.get() == this) + continue; + + c->outgoingFrames.push(currentFrame); + + lws_callback_on_writable(c->wsi); + } + + currentFrame = std::make_shared(); + } + } }; /** List of libwebsockets protocols. */ @@ -144,67 +232,28 @@ int protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in switch (reason) { - case LWS_CALLBACK_ESTABLISHED: { - char uri[64]; + case LWS_CALLBACK_ESTABLISHED: + auto s = Session::get(wsi); - new (c) Connection(); - - c->wsi = wsi; - - /* We use the URI to associate this connection to a session - * Example: ws://example.com/node_1 - * Will select the session with the name 'node_1' - */ - - /* Get path of incoming request */ - lws_hdr_copy(wsi, uri, sizeof(uri), WSI_TOKEN_GET_URI); - if (strlen(uri) <= 0) { + try { + new (c) Connection(wsi); + } + catch (InvalidUrlException e) { lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (unsigned char *) "Invalid URL", strlen("Invalid URL")); return -1; } - - Session::Identifier sid = &uri[1]; - - auto s = Session::get(sid); - - c->session = s; - s->connections.push_back(std::shared_ptr(c)); - break; - } case LWS_CALLBACK_CLOSED: - c->~Connection(); break; case LWS_CALLBACK_SERVER_WRITEABLE: - while (c->outgoingFrames.size() > 0) { - std::shared_ptr fr = c->outgoingFrames.front(); - - lws_write(wsi, fr->data(), fr->size(), LWS_WRITE_BINARY); - - c->outgoingFrames.pop_front(); - } + c->write(); break; case LWS_CALLBACK_RECEIVE: - if (!c->currentFrame.get() || lws_is_first_fragment(wsi)) - c->currentFrame = std::make_shared(); - - c->currentFrame->insert(c->currentFrame->end(), (uint8_t *) in, (uint8_t *) in + len); - - if (lws_is_final_fragment(wsi)) { - for (auto cc : c->session->connections) { - if (cc.get() == c) - continue; - - cc->outgoingFrames.push_back(c->currentFrame); - - lws_callback_on_writable(cc->wsi); - } - } - + c->read(in, len); break; default: @@ -214,6 +263,20 @@ int protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in return 0; } +void usage() +{ + std::cout << "Usage: villas-relay [OPTIONS]" << std::endl; + std::cout << " OPTIONS is one or more of the following options:" << std::endl; + std::cout << " -d LVL set debug level" << std::endl; + std::cout << " -p PORT the port number to listen on" << std::endl; + std::cout << " -p PROT the websocket protocol" << std::endl; + std::cout << " -V show version and exit" << std::endl; + std::cout << " -h show usage and exit" << std::endl; + std::cout << std::endl; + + print_copyright(); +} + int main(int argc, char *argv[]) { lws_set_log_level((1 << LLL_COUNT) - 1, logger); @@ -228,6 +291,38 @@ int main(int argc, char *argv[]) ctx_info.extensions = extensions; ctx_info.port = 8088; + char c, *endptr; + while ((c = getopt (argc, argv, "hVp:P:l")) != -1) { + switch (c) { + case 'p': + ctx_info.port = strtoul(optarg, &endptr, 10); + goto check; + case 'P': + protocols[0].name = optarg; + break; + case 'l': + opts.loopback = true; + break; + case 'V': + print_version(); + exit(EXIT_SUCCESS); + case 'h': + case '?': + usage(); + exit(c == '?' ? EXIT_FAILURE : EXIT_SUCCESS); + } + + continue; + +check: if (optarg == endptr) + error("Failed to parse parse option argument '-%c %s'", c, optarg); + } + + if (argc - optind < 0) { + usage(); + exit(EXIT_FAILURE); + } + context = lws_create_context(&ctx_info); if (context == NULL) error("WebSocket: failed to initialize server context"); From 750f0374bc53a11d9c04e050e140423ca23abe51 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 16 Jul 2018 22:19:35 +0200 Subject: [PATCH 08/13] fix duplicate extern "C" keyword --- include/villas/utils.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/include/villas/utils.h b/include/villas/utils.h index ba10966c8..d47d42f75 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -37,10 +37,6 @@ extern "C" { #include #include -#ifdef __cplusplus -extern "C"{ -#endif - #ifdef __GNUC__ #define LIKELY(x) __builtin_expect((x),1) @@ -264,7 +260,8 @@ __attribute__((always_inline)) static inline uint64_t rdtsc() } /** Get log2 of long long integers */ -static inline int log2i(long long x) { +static inline int log2i(long long x) +{ if (x == 0) return 1; From 704c4cd59fcbb0151ee60225f727877845a2b180 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 16 Jul 2018 22:19:55 +0200 Subject: [PATCH 09/13] relay: add villas-relay to build system --- src/CMakeLists.txt | 1 + src/{relay.cpp => villas-relay.cpp} | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) rename src/{relay.cpp => villas-relay.cpp} (97%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a6d49d441..6685431b8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,7 @@ # All executables link against libvillas link_libraries(villas) +add_executable(villas-relay villas-relay.cpp) add_executable(villas-node villas-node.cpp) add_executable(villas-test-rtt villas-test-rtt.cpp) add_executable(villas-test-shmem villas-test-shmem.cpp) diff --git a/src/relay.cpp b/src/villas-relay.cpp similarity index 97% rename from src/relay.cpp rename to src/villas-relay.cpp index c73fe274c..463392fa6 100644 --- a/src/relay.cpp +++ b/src/villas-relay.cpp @@ -42,8 +42,8 @@ static lws_vhost *vhost; /* Forward declarations */ lws_callback_function protocol_cb; -struct Session; -struct Connection; +class Session; +class Connection; static std::map> sessions; @@ -101,7 +101,7 @@ public: } else { info("Reusing existing session: %s", sid.c_str()); - + return it->second; } } @@ -125,7 +125,7 @@ protected: std::shared_ptr currentFrame; - std::queue> outgoingFrames; + std::queue> outgoingFrames; std::shared_ptr session; @@ -135,7 +135,7 @@ public: currentFrame(std::make_shared()), outgoingFrames() { - session = Session::get(sid); + session = Session::get(wsi); session->connections[wsi] = std::shared_ptr(this); info("New connection established to session: %s", session->identifier.c_str()); @@ -162,7 +162,7 @@ public: if (lws_is_final_fragment(wsi)) { debug(5, "Received frame, relaying to %zu connections", session->connections.size() - (opts.loopback ? 0 : 1)); - + for (auto p : session->connections) { auto c = p.second; @@ -232,7 +232,7 @@ int protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in switch (reason) { - case LWS_CALLBACK_ESTABLISHED: + case LWS_CALLBACK_ESTABLISHED: { auto s = Session::get(wsi); try { @@ -243,6 +243,7 @@ int protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in return -1; } break; + } case LWS_CALLBACK_CLOSED: c->~Connection(); From eaa25885f7f53a08e0f8733ebc05e22c594b2a6f Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 17 Jul 2018 17:56:23 +0200 Subject: [PATCH 10/13] cmake: individually link libraries --- src/CMakeLists.txt | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6685431b8..b8ac4bed2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,13 +20,14 @@ # along with this program. If not, see . ################################################################################### -# All executables link against libvillas -link_libraries(villas) - -add_executable(villas-relay villas-relay.cpp) add_executable(villas-node villas-node.cpp) +target_link_libraries(villas-node PUBLIC villas) + add_executable(villas-test-rtt villas-test-rtt.cpp) +target_link_libraries(villas-test-rtt PUBLIC villas) + add_executable(villas-test-shmem villas-test-shmem.cpp) +target_link_libraries(villas-test-shmem PUBLIC villas) install( TARGETS villas-node villas-test-rtt villas-test-shmem @@ -34,13 +35,30 @@ install( RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) +if(WITH_WEB) + add_executable(villas-relay villas-relay.cpp) + target_include_directories(villas-relay PRIVATE ${LIBWEBSOCKETS_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${SPDLOG_INCLUDE_DIRS}) + target_link_libraries(villas-relay PRIVATE websockets_shared) + + install( + TARGETS villas-relay + COMPONENT bin + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) +endif() + if(WITH_IO) add_executable(villas-test-cmp villas-test-cmp.cpp) - add_executable(villas-convert villas-convert.cpp) - add_executable(villas-pipe villas-pipe.cpp) - add_executable(villas-signal villas-signal.cpp) + target_link_libraries(villas-test-cmp PUBLIC villas) - target_link_libraries(villas-pipe PUBLIC Threads::Threads) + add_executable(villas-convert villas-convert.cpp) + target_link_libraries(villas-convert PUBLIC villas) + + add_executable(villas-pipe villas-pipe.cpp) + target_link_libraries(villas-pipe PUBLIC villas) + + add_executable(villas-signal villas-signal.cpp) + target_link_libraries(villas-signal PUBLIC villas) install( TARGETS villas-convert villas-pipe villas-signal villas-test-cmp @@ -51,6 +69,7 @@ endif() if(WITH_IO AND WITH_HOOKS) add_executable(villas-hook villas-hook.cpp) + target_link_libraries(villas-hook PUBLIC villas) install( TARGETS villas-hook From fc80bf809d8a8e0abf65eff4d57edb9a1961814c Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 17 Jul 2018 17:56:56 +0200 Subject: [PATCH 11/13] start using spdlog for C++ code --- CMakeLists.txt | 1 + src/villas-relay.cpp | 49 ++++++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de537469c..d6a9478a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ find_package(Mosquitto) find_package(Opal) find_package(IBVerbs) find_package(RDMACM) +find_package(spdlog) # Check programs find_program(PROTOBUFC_COMPILER NAMES protoc-c) diff --git a/src/villas-relay.cpp b/src/villas-relay.cpp index 463392fa6..b5fa15743 100644 --- a/src/villas-relay.cpp +++ b/src/villas-relay.cpp @@ -30,9 +30,12 @@ #include +#include +#include + #include -#include +auto console = spdlog::stdout_color_mt("console"); /** The libwebsockets server context. */ static lws_context *context; @@ -95,12 +98,12 @@ public: sessions[sid] = s; - info("Creating new session: %s", sid.c_str()); + console->info("Creating new session: {}", sid); return s; } else { - info("Reusing existing session: %s", sid.c_str()); + console->info("Reusing existing session: ", sid); return it->second; } @@ -138,11 +141,11 @@ public: session = Session::get(wsi); session->connections[wsi] = std::shared_ptr(this); - info("New connection established to session: %s", session->identifier.c_str()); + console->info("New connection established to session: {}", session->identifier); } ~Connection() { - info("Connection closed"); + console->info("Connection closed"); session->connections.erase(wsi); } @@ -161,7 +164,7 @@ public: currentFrame->insert(currentFrame->end(), (uint8_t *) in, (uint8_t *) in + len); if (lws_is_final_fragment(wsi)) { - debug(5, "Received frame, relaying to %zu connections", session->connections.size() - (opts.loopback ? 0 : 1)); + console->debug("Received frame, relaying to {} connections", session->connections.size() - (opts.loopback ? 0 : 1)); for (auto p : session->connections) { auto c = p.second; @@ -207,9 +210,9 @@ static const lws_extension extensions[] = { { NULL /* terminator */ } }; -extern log *global_log; - static void logger(int level, const char *msg) { + auto log = spdlog::get("lws"); + int len = strlen(msg); if (strchr(msg, '\n')) len -= 1; @@ -219,10 +222,10 @@ static void logger(int level, const char *msg) { level = LLL_WARN; switch (level) { - case LLL_ERR: log_print(global_log, CLR_RED("Web "), "%.*s", len, msg); break; - case LLL_WARN: log_print(global_log, CLR_YEL("Web "), "%.*s", len, msg); break; - case LLL_INFO: log_print(global_log, CLR_WHT("Web "), "%.*s", len, msg); break; - default: log_print(global_log, "Web ", "%.*s", len, msg); break; + case LLL_ERR: log->error("{}", msg); break; + case LLL_WARN: log->warn( "{}", msg); break; + case LLL_INFO: log->info( "{}", msg); break; + default: log->debug("{}", msg); break; } } @@ -270,16 +273,17 @@ void usage() std::cout << " OPTIONS is one or more of the following options:" << std::endl; std::cout << " -d LVL set debug level" << std::endl; std::cout << " -p PORT the port number to listen on" << std::endl; - std::cout << " -p PROT the websocket protocol" << std::endl; + std::cout << " -P PROT the websocket protocol" << std::endl; std::cout << " -V show version and exit" << std::endl; std::cout << " -h show usage and exit" << std::endl; std::cout << std::endl; - print_copyright(); + //print_copyright(); } int main(int argc, char *argv[]) { + spdlog::stdout_color_mt("lws"); lws_set_log_level((1 << LLL_COUNT) - 1, logger); /* Start server */ @@ -305,7 +309,7 @@ int main(int argc, char *argv[]) opts.loopback = true; break; case 'V': - print_version(); + //print_version(); exit(EXIT_SUCCESS); case 'h': case '?': @@ -316,7 +320,8 @@ int main(int argc, char *argv[]) continue; check: if (optarg == endptr) - error("Failed to parse parse option argument '-%c %s'", c, optarg); + console->error("Failed to parse parse option argument '-{} {}'", c, optarg); + exit(EXIT_FAILURE); } if (argc - optind < 0) { @@ -325,12 +330,16 @@ check: if (optarg == endptr) } context = lws_create_context(&ctx_info); - if (context == NULL) - error("WebSocket: failed to initialize server context"); + if (context == NULL) { + console->error("WebSocket: failed to initialize server context"); + exit(EXIT_FAILURE); + } vhost = lws_create_vhost(context, &ctx_info); - if (vhost == NULL) - error("WebSocket: failed to initialize virtual host"); + if (vhost == NULL) { + console->error("WebSocket: failed to initialize virtual host"); + exit(EXIT_FAILURE); + } for (;;) lws_service(context, 100); From 583e3b730f198a239b5a39705432386af132ed2d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 18 Jul 2018 08:13:13 +0200 Subject: [PATCH 12/13] utils: seperated print_{copyright,version}(), box and color parts of utils --- include/villas/boxes.h | 47 ++++++++++++++++++++++++++++++++++++ include/villas/colors.h | 40 +++++++++++++++++++++++++++++++ include/villas/copyright.h | 38 +++++++++++++++++++++++++++++ include/villas/utils.h | 49 +++----------------------------------- lib/CMakeLists.txt | 1 + lib/copyright.c | 40 +++++++++++++++++++++++++++++++ lib/utils.c | 13 ---------- 7 files changed, 169 insertions(+), 59 deletions(-) create mode 100644 include/villas/boxes.h create mode 100644 include/villas/colors.h create mode 100644 include/villas/copyright.h create mode 100644 lib/copyright.c diff --git a/include/villas/boxes.h b/include/villas/boxes.h new file mode 100644 index 000000000..2ad23ad0e --- /dev/null +++ b/include/villas/boxes.h @@ -0,0 +1,47 @@ +/** Various helper functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +/* Alternate character set + * + * The suffixed of the BOX_ macro a constructed by + * combining the following letters in the written order: + * - U for a line facing upwards + * - D for a line facing downwards + * - L for a line facing leftwards + * - R for a line facing rightwards + * + * E.g. a cross can be constructed by combining all line fragments: + * BOX_UDLR + */ +#define BOX(chr) "\e(0" chr "\e(B" +#define BOX_LR BOX("\x71") /**< Boxdrawing: ─ */ +#define BOX_UD BOX("\x78") /**< Boxdrawing: │ */ +#define BOX_UDR BOX("\x74") /**< Boxdrawing: ├ */ +#define BOX_UDLR BOX("\x6E") /**< Boxdrawing: ┼ */ +#define BOX_UDL BOX("\x75") /**< Boxdrawing: ┤ */ +#define BOX_ULR BOX("\x76") /**< Boxdrawing: ┴ */ +#define BOX_UL BOX("\x6A") /**< Boxdrawing: ┘ */ +#define BOX_DLR BOX("\x77") /**< Boxdrawing: ┘ */ +#define BOX_DL BOX("\x6B") /**< Boxdrawing: ┘ */ diff --git a/include/villas/colors.h b/include/villas/colors.h new file mode 100644 index 000000000..916d3a22f --- /dev/null +++ b/include/villas/colors.h @@ -0,0 +1,40 @@ +/** Various helper functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +/* CPP stringification */ +#define XSTR(x) STR(x) +#define STR(x) #x + +/* Some color escape codes for pretty log messages */ +#define CLR(clr, str) "\e[" XSTR(clr) "m" str "\e[0m" +#define CLR_GRY(str) CLR(30, str) /**< Print str in gray */ +#define CLR_RED(str) CLR(31, str) /**< Print str in red */ +#define CLR_GRN(str) CLR(32, str) /**< Print str in green */ +#define CLR_YEL(str) CLR(33, str) /**< Print str in yellow */ +#define CLR_BLU(str) CLR(34, str) /**< Print str in blue */ +#define CLR_MAG(str) CLR(35, str) /**< Print str in magenta */ +#define CLR_CYN(str) CLR(36, str) /**< Print str in cyan */ +#define CLR_WHT(str) CLR(37, str) /**< Print str in white */ +#define CLR_BLD(str) CLR( 1, str) /**< Print str in bold */ diff --git a/include/villas/copyright.h b/include/villas/copyright.h new file mode 100644 index 000000000..a0603c8cd --- /dev/null +++ b/include/villas/copyright.h @@ -0,0 +1,38 @@ +/** Various helper functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** Print copyright message to stdout. */ +void print_copyright(); + +/** Print version to stdout. */ +void print_version(); + +#ifdef __cplusplus +} +#endif diff --git a/include/villas/utils.h b/include/villas/utils.h index d47d42f75..8d6079f42 100644 --- a/include/villas/utils.h +++ b/include/villas/utils.h @@ -36,7 +36,9 @@ extern "C" { #include #include - +#include +#include +#include #ifdef __GNUC__ #define LIKELY(x) __builtin_expect((x),1) @@ -46,45 +48,6 @@ extern "C" { #define UNLIKELY(x) (x) #endif -/* Some color escape codes for pretty log messages */ -#define CLR(clr, str) "\e[" XSTR(clr) "m" str "\e[0m" -#define CLR_GRY(str) CLR(30, str) /**< Print str in gray */ -#define CLR_RED(str) CLR(31, str) /**< Print str in red */ -#define CLR_GRN(str) CLR(32, str) /**< Print str in green */ -#define CLR_YEL(str) CLR(33, str) /**< Print str in yellow */ -#define CLR_BLU(str) CLR(34, str) /**< Print str in blue */ -#define CLR_MAG(str) CLR(35, str) /**< Print str in magenta */ -#define CLR_CYN(str) CLR(36, str) /**< Print str in cyan */ -#define CLR_WHT(str) CLR(37, str) /**< Print str in white */ -#define CLR_BLD(str) CLR( 1, str) /**< Print str in bold */ - -/* Alternate character set - * - * The suffixed of the BOX_ macro a constructed by - * combining the following letters in the written order: - * - U for a line facing upwards - * - D for a line facing downwards - * - L for a line facing leftwards - * - R for a line facing rightwards - * - * E.g. a cross can be constructed by combining all line fragments: - * BOX_UDLR - */ -#define BOX(chr) "\e(0" chr "\e(B" -#define BOX_LR BOX("\x71") /**< Boxdrawing: ─ */ -#define BOX_UD BOX("\x78") /**< Boxdrawing: │ */ -#define BOX_UDR BOX("\x74") /**< Boxdrawing: ├ */ -#define BOX_UDLR BOX("\x6E") /**< Boxdrawing: ┼ */ -#define BOX_UDL BOX("\x75") /**< Boxdrawing: ┤ */ -#define BOX_ULR BOX("\x76") /**< Boxdrawing: ┴ */ -#define BOX_UL BOX("\x6A") /**< Boxdrawing: ┘ */ -#define BOX_DLR BOX("\x77") /**< Boxdrawing: ┘ */ -#define BOX_DL BOX("\x6B") /**< Boxdrawing: ┘ */ - -/* CPP stringification */ -#define XSTR(x) STR(x) -#define STR(x) #x - #define CONCAT_DETAIL(x, y) x##y #define CONCAT(x, y) CONCAT_DETAIL(x, y) #define UNIQUE(x) CONCAT(x, __COUNTER__) @@ -141,12 +104,6 @@ extern "C" { /* Forward declarations */ struct timespec; -/** Print copyright message to stdout. */ -void print_copyright(); - -/** Print version to stdout. */ -void print_version(); - /** Normal random variate generator using the Box-Muller method * * @param m Mean diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index dd8753a5a..336790ebf 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -72,6 +72,7 @@ set(LIB_SRC table.c bitset.c signal.c + copyright.c ) if(IBVERBS_FOUND AND RDMACM_FOUND) diff --git a/lib/copyright.c b/lib/copyright.c new file mode 100644 index 000000000..d68e6b387 --- /dev/null +++ b/lib/copyright.c @@ -0,0 +1,40 @@ +/** General purpose helper functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include +#include + +void print_copyright() +{ + printf("VILLASnode %s (built on %s %s)\n", + CLR_BLU(BUILDID), CLR_MAG(__DATE__), CLR_MAG(__TIME__)); + printf(" Copyright 2014-2017, Institute for Automation of Complex Power Systems, EONERC\n"); + printf(" Steffen Vogel \n"); +} + +void print_version() +{ + printf("%s\n", BUILDID); +} diff --git a/lib/utils.c b/lib/utils.c index b30ad0027..cd43e7a48 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -36,19 +36,6 @@ pthread_t main_thread; -void print_copyright() -{ - printf("VILLASnode %s (built on %s %s)\n", - CLR_BLU(BUILDID), CLR_MAG(__DATE__), CLR_MAG(__TIME__)); - printf(" Copyright 2014-2017, Institute for Automation of Complex Power Systems, EONERC\n"); - printf(" Steffen Vogel \n"); -} - -void print_version() -{ - printf("%s\n", BUILDID); -} - int version_parse(const char *s, struct version *v) { return sscanf(s, "%u.%u", &v->major, &v->minor) != 2; From 1e46313d49e7fecdbf8ee6d0b64e594300c9b16a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 18 Jul 2018 08:13:34 +0200 Subject: [PATCH 13/13] fix defines for websocket support --- include/villas/config.h.in | 2 +- lib/web.c | 4 ++-- src/villas-pipe.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/villas/config.h.in b/include/villas/config.h.in index 282818f4d..2612bb2f1 100644 --- a/include/villas/config.h.in +++ b/include/villas/config.h.in @@ -76,7 +76,7 @@ extern "C"{ #cmakedefine HAS_SEMAPHORE -#cmakedefine LIBWEBSOCKETS_FOUND +#cmakedefine Libwebsockets_FOUND #cmakedefine HDF5_FOUND #cmakedefine PROTOBUF_FOUND #cmakedefine LIBNL3_ROUTE_FOUND diff --git a/lib/web.c b/lib/web.c index 37047a3d7..30b15cd27 100644 --- a/lib/web.c +++ b/lib/web.c @@ -58,14 +58,14 @@ struct lws_protocols protocols[] = { .rx_buffer_size = 0 }, #endif /* WITH_API */ -#ifdef LIBWEBSOCKETS_FOUND +#ifdef Libwebsockets_FOUND { .name = "live", .callback = websocket_protocol_cb, .per_session_data_size = sizeof(struct websocket_connection), .rx_buffer_size = 0 }, -#endif /* LIBWEBSOCKETS_FOUND */ +#endif /* Libwebsockets_FOUND */ #if 0 /* not supported yet */ { .name = "log", diff --git a/src/villas-pipe.cpp b/src/villas-pipe.cpp index 99f3c65d3..097f56f77 100644 --- a/src/villas-pipe.cpp +++ b/src/villas-pipe.cpp @@ -351,20 +351,20 @@ check: if (optarg == endptr) if (!node) error("Node '%s' does not exist!", nodestr); -#ifdef LIBWEBSOCKETS_FOUND +#ifdef Libwebsockets_FOUND /* Only start web subsystem if villas-pipe is used with a websocket node */ - if (node->_vt->start == websocket_start) { + if (node_type(node)->start == websocket_start) { web_start(&sn.web); api_start(&sn.api); } -#endif /* LIBWEBSOCKETS_FOUND */ +#endif /* Libwebsockets_FOUND */ if (reverse) node_reverse(node); - ret = node_type_start(node->_vt, &sn); + ret = node_type_start(node_type(node), &sn); if (ret) - error("Failed to intialize node type: %s", node_type_name(node->_vt)); + error("Failed to intialize node type: %s", node_type_name(node_type(node))); ret = node_check(node); if (ret)