diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a11c15aa..6ffb6b4a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,10 @@ find_package(RDMACM) find_package(spdlog) find_package(Etherlab) find_package(Lua) +find_package(LibDataChannel) + +# For compat between libfmt 8 and 9 +add_compile_definitions(FMT_DEPRECATED_OSTREAM) # For compat between libfmt 8 and 9 add_compile_definitions(FMT_DEPRECATED_OSTREAM) @@ -89,17 +93,10 @@ endif() # Check programs find_program(PROTOBUFC_COMPILER NAMES protoc-c) find_program(PROTOBUF_COMPILER NAMES protoc) -find_program(GO NAMES go PATHS /usr/local/go/bin) # Build without any GPL-code option(WITHOUT_GPL "Build VILLASnode without any GPL code" OFF) -# Optionally download Go toolchain -option(DOWNLOAD_GO "Download Go toolchain" ON) -if(NOT GO AND DOWNLOAD_GO) - include("${CMAKE_CURRENT_LIST_DIR}/cmake/Go.cmake") -endif() - set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig:/usr/local/lib64/pkgconfig:/usr/local/share/pkgconfig:/usr/lib64/pkgconfig") pkg_check_modules(JANSSON IMPORTED_TARGET REQUIRED jansson>=2.13) @@ -161,7 +158,6 @@ cmake_dependent_option(WITH_CLIENTS "Build client applications" cmake_dependent_option(WITH_CONFIG "Build with support for libconfig configuration syntax" ON "LIBCONFIG_FOUND" OFF) cmake_dependent_option(WITH_DOC "Build documentation" ON "TOPLEVEL_PROJECT" OFF) cmake_dependent_option(WITH_FPGA "Build with support for VILLASfpga" ON "FOUND_SUBMODULE_FPGA" OFF) -cmake_dependent_option(WITH_GO "Build with Go" ON "GO" OFF) cmake_dependent_option(WITH_GRAPHVIZ "Build with Graphviz" ON "CGRAPH_FOUND; GVC_FOUND" OFF) cmake_dependent_option(WITH_HOOKS "Build with support for processing hook plugins" ON "" OFF) cmake_dependent_option(WITH_LUA "Build with Lua" ON "LUA_FOUND" OFF) @@ -180,7 +176,6 @@ cmake_dependent_option(WITH_NODE_EXAMPLE "Build with example node-type" cmake_dependent_option(WITH_NODE_EXEC "Build with exec node-type" ON "" OFF) cmake_dependent_option(WITH_NODE_FILE "Build with file node-type" ON "" OFF) cmake_dependent_option(WITH_NODE_FPGA "Build with fpga node-type" ON "WITH_FPGA" OFF) -cmake_dependent_option(WITH_NODE_GO "Build with Go nodes-types" ON "WITH_GO" OFF) cmake_dependent_option(WITH_NODE_IEC61850 "Build with iec61850 node-types" ON "LIBIEC61850_FOUND;NOT WITHOUT_GPL" OFF) cmake_dependent_option(WITH_NODE_IEC60870 "Build with iec60870 node-types" ON "LIB60870_FOUND;NOT WITHOUT_GPL" OFF) cmake_dependent_option(WITH_NODE_INFINIBAND "Build with infiniband node-type" ON "IBVerbs_FOUND; RDMACM_FOUND" OFF) # Infiniband node-type is currenly broken @@ -200,6 +195,7 @@ cmake_dependent_option(WITH_NODE_STATS "Build with stats node-type" cmake_dependent_option(WITH_NODE_TEMPER "Build with temper node-type" ON "LIBUSB_FOUND" OFF) cmake_dependent_option(WITH_NODE_TEST_RTT "Build with test_rtt node-type" ON "" OFF) cmake_dependent_option(WITH_NODE_ULDAQ "Build with uldaq node-type" ON "LIBULDAQ_FOUND" OFF) +cmake_dependent_option(WITH_NODE_WEBRTC "Build with webrtc node-type" ON "LibDataChannel_FOUND" OFF) cmake_dependent_option(WITH_NODE_WEBSOCKET "Build with websocket node-type" ON "WITH_WEB; LIBWEBSOCKETS_FOUND" OFF) cmake_dependent_option(WITH_NODE_ZEROMQ "Build with zeromq node-type" ON "LIBZMQ_FOUND;NOT WITHOUT_GPL" OFF) @@ -216,10 +212,6 @@ if(WITH_FPGA) add_subdirectory(fpga) endif() -if(WITH_GO) - add_subdirectory(go) -endif() - add_subdirectory(common) add_subdirectory(etc) add_subdirectory(lib) @@ -261,7 +253,6 @@ add_feature_info(CLIENTS WITH_CLIENTS "Build clien add_feature_info(CONFIG WITH_CONFIG "Build with support for libconfig configuration syntax") add_feature_info(DOC WITH_DOC "Build documentation") add_feature_info(FPGA WITH_FPGA "Build with FPGA support") -add_feature_info(GO WITH_GO "Build with Go code") add_feature_info(GRAPHVIZ WITH_GRAPHVIZ "Build with Graphviz support") add_feature_info(HOOKS WITH_HOOKS "Build with support for processing hook plugins") add_feature_info(LUA WITH_LUA "Build with Lua support") @@ -280,7 +271,6 @@ add_feature_info(NODE_EXAMPLE WITH_NODE_EXAMPLE "Build with add_feature_info(NODE_EXEC WITH_NODE_EXEC "Build with exec node-type") add_feature_info(NODE_FILE WITH_NODE_FILE "Build with file node-type") add_feature_info(NODE_FPGA WITH_NODE_FPGA "Build with fpga node-type") -add_feature_info(NODE_GO WITH_NODE_GO "Build with Go node-types") add_feature_info(NODE_IEC61850 WITH_NODE_IEC61850 "Build with iec61850 node-types") add_feature_info(NODE_IEC60870 WITH_NODE_IEC60870 "Build with iec60870 node-types") add_feature_info(NODE_INFINIBAND WITH_NODE_INFINIBAND "Build with infiniband node-type") @@ -300,6 +290,7 @@ add_feature_info(NODE_STATS WITH_NODE_STATS "Build with add_feature_info(NODE_TEMPER WITH_NODE_TEMPER "Build with temper node-type") add_feature_info(NODE_TEST_RTT WITH_NODE_TEST_RTT "Build with test_rtt node-type") add_feature_info(NODE_ULDAQ WITH_NODE_ULDAQ "Build with uldaq node-type") +add_feature_info(NODE_WEBRTC WITH_NODE_WEBRTC "Build with webrtc node-type") add_feature_info(NODE_WEBSOCKET WITH_NODE_WEBSOCKET "Build with websocket node-type") add_feature_info(NODE_ZEROMQ WITH_NODE_ZEROMQ "Build with zeromq node-type") diff --git a/common b/common index b18d2aca5..d9d4ac76a 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit b18d2aca5b9a158aaba3a0f82cffe253fc427b15 +Subproject commit d9d4ac76a5403e14f7899dae480781e9cdcf0572 diff --git a/etc/examples/nodes/go-example.conf b/etc/examples/nodes/go-example.conf deleted file mode 100644 index 412c84586..000000000 --- a/etc/examples/nodes/go-example.conf +++ /dev/null @@ -1,15 +0,0 @@ -nodes = { - example_node = { - type = "go.example" - value = 555 - - format = "json" - - in = { - # signals ={ - # count = 5, - # type = "float" - # } - } - } -} diff --git a/etc/examples/nodes/go-loopback.conf b/etc/examples/nodes/go-loopback.conf deleted file mode 100644 index 3be4ab541..000000000 --- a/etc/examples/nodes/go-loopback.conf +++ /dev/null @@ -1,38 +0,0 @@ -nodes = { - loopback_node = { - type = "go.loopback" - value = 555 - - format = "opal.asyncip" - - in = { - signals ={ - count = 5, - type = "float" - } - } - } - - signal = { - type = "signal" - signal = "counter" - rate = 1 - values = 5 - } -} - -paths = ( - { - in = "loopback_node" - - hooks = ( - { - type = "dump" - } - ) - }, - { - in = "signal", - out = "loopback_node" - } -) diff --git a/etc/examples/nodes/webrtc.conf b/etc/examples/nodes/webrtc.conf index 69b130eae..f587093ed 100644 --- a/etc/examples/nodes/webrtc.conf +++ b/etc/examples/nodes/webrtc.conf @@ -1,31 +1,15 @@ + nodes = { - webrtc_node = { - type = "webrtc", - + webrtc = { + type = "webrtc" + # required session key. + session = "" + # optional signaling server. + server = "
" + # optional format. format = "json" - - # A unique session identifier which must be shared between two nodes - session = "my-session-name" - - # Address to the websocket signaling server - server = "wss://villas.k8s.eonerc.rwth-aachen.de/ws/signaling" - - # Setting for Interactive Connectivity Establishment - ice = { - # List of STUN/TURN servers - servers = ( - { - urls = [ - "stun:stun.0l.de:3478", - "turn:turn.0l.de:3478?transport=udp", - "turn:turn.0l.de:3478?transport=tcp" - ], - - # Credentials for TURN servers - username = "villas" - password = "villas" - } - ) - } + # optional initial connect timeout. + wait_seconds = 120 } } + diff --git a/go/CMakeLists.txt b/go/CMakeLists.txt deleted file mode 100644 index 3ea7a4199..000000000 --- a/go/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(lib) diff --git a/go/cmd/example.go b/go/cmd/example.go deleted file mode 100644 index 716fcd11a..000000000 --- a/go/cmd/example.go +++ /dev/null @@ -1,108 +0,0 @@ -/** Example of using libvillas node-types in Go code - * - * This example demonstrate how you can use VILLASnode's - * node-types from a Go application. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package main - -import ( - "log" - "os" - "time" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/config" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/node" - - "github.com/google/uuid" -) - -func main() { - cfg := &config.LoopbackNode{ - Node: config.Node{ - Name: "lo1", - Type: "loopback", - }, - In: config.NodeLoopbackIn{ - Hooks: []interface{}{ - &config.PrintHook{ - Hook: config.Hook{ - Type: "print", - }, - }, - }, - Signals: []config.Signal{ - { - Name: "sig1", - }, - { - Name: "sig2", - }, - { - Name: "sig3", - }, - }, - }, - } - - n, err := node.NewNode(cfg, uuid.New()) - if err != nil { - log.Fatalf("Failed to create node: %s", err) - } - defer n.Close() - - if err := n.Check(); err != nil { - log.Fatalf("Failed to check node: %s", err) - } - - if err := n.Prepare(); err != nil { - log.Fatalf("Failed to prepare node: %s", err) - } - - if err := n.Start(); err != nil { - log.Fatalf("Failed to start node: %s", err) - } - defer n.Stop() - - log.Printf("%s\n", n.NameFull()) - - now := time.Now() - - smps_send := []node.Sample{ - { - Sequence: 1234, - Timestamps: pkg.Timestamps{ - Origin: pkg.Timestamp{now.Unix(), now.UnixNano()}, - }, - Data: []float64{1.1, 2.2, 3.3}, - }, - { - Sequence: 1235, - Timestamps: pkg.Timestamps{ - Origin: pkg.Timestamp{now.Unix(), now.UnixNano()}, - }, Data: []float64{4.4, 5.5, 6.6}, - }, - } - - log.Printf("Sent: %+#v\n", smps_send) - - cnt_written := n.Write(smps_send) - smps_received := n.Read(cnt_written) - - log.Printf("Received: %+#v\n", smps_received) - - if len(smps_send) != len(smps_received) { - log.Printf("Different length") - os.Exit(-1) - } - - if smps_send[0].Data[0] != smps_received[0].Data[0] { - log.Printf("Different data") - os.Exit(-1) - } -} diff --git a/go/go.mod b/go/go.mod deleted file mode 100644 index ef2e67aea..000000000 --- a/go/go.mod +++ /dev/null @@ -1,32 +0,0 @@ -module git.rwth-aachen.de/acs/public/villas/node/go - -go 1.17 - -require ( - github.com/google/uuid v1.3.0 - github.com/gorilla/websocket v1.5.0 - github.com/pion/webrtc/v3 v3.1.24 -) - -require ( - github.com/pion/datachannel v1.5.2 // indirect - github.com/pion/dtls/v2 v2.1.3 // indirect - github.com/pion/ice/v2 v2.2.1 // indirect - github.com/pion/interceptor v0.1.7 // indirect - github.com/pion/logging v0.2.2 // indirect - github.com/pion/mdns v0.0.5 // indirect - github.com/pion/randutil v0.1.0 // indirect - github.com/pion/rtcp v1.2.9 // indirect - github.com/pion/rtp v1.7.4 // indirect - github.com/pion/sctp v1.8.2 // indirect - github.com/pion/sdp/v3 v3.0.4 // indirect - github.com/pion/srtp/v2 v2.0.5 // indirect - github.com/pion/stun v0.3.5 // indirect - github.com/pion/transport v0.13.0 // indirect - github.com/pion/turn/v2 v2.0.8 // indirect - github.com/pion/udp v0.1.1 // indirect - golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect -) diff --git a/go/go.sum b/go/go.sum deleted file mode 100644 index 5aa07fa97..000000000 --- a/go/go.sum +++ /dev/null @@ -1,154 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= -github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= -github.com/pion/dtls/v2 v2.1.2/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= -github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio= -github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= -github.com/pion/ice/v2 v2.2.1 h1:R3MeuJZpU1ty3diPqpD5OxaxcZ15eprAc+EtUiSoFxg= -github.com/pion/ice/v2 v2.2.1/go.mod h1:Op8jlPtjeiycsXh93Cs4jK82C9j/kh7vef6ztIOvtIQ= -github.com/pion/interceptor v0.1.7 h1:HThW0tIIKT9RRoDWGURe8rlZVOx0fJHxBHpA0ej0+bo= -github.com/pion/interceptor v0.1.7/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U= -github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= -github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= -github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= -github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= -github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= -github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= -github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= -github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= -github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA= -github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= -github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= -github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA= -github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= -github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8= -github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= -github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ= -github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A= -github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= -github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= -github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= -github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= -github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY= -github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= -github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= -github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= -github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o= -github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= -github.com/pion/webrtc/v3 v3.1.24 h1:s9PuwisrgHe1FTqfwK4p3T7rXtAHaUNhycbdMjADT28= -github.com/pion/webrtc/v3 v3.1.24/go.mod h1:mO/yv7fBN3Lp7YNlnYcTj1jtpvNvssJG+7eh6itZ4xM= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE= -golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/lib/CMakeLists.txt b/go/lib/CMakeLists.txt deleted file mode 100644 index b866a7c70..000000000 --- a/go/lib/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -set(LIB libvillas-go.a) -set(HEADER libvillas-go.h) - -file(GLOB_RECURSE SRCS *.go) -file(GLOB_RECURSE NODE_SRCS ../pkg/nodes/*) - -list(FILTER SRCS EXCLUDE REGEX /_obj/) -list(FILTER NODE_SRCS EXCLUDE REGEX /_obj/) - -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${HEADER} - DEPENDS ${SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND env "GOPATH=${GOPATH}" "CGO_ENABLED=1" "GOARM=${GOARM}" "GOARCH=${GOARCH}" ${GO} tool cgo -exportheader "${CMAKE_CURRENT_BINARY_DIR}/${HEADER}" -- -I "${CMAKE_CURRENT_SOURCE_DIR}/../../include" ${SRCS} - COMMENT "Generating CGo header ${HEADER}" - COMMAND_EXPAND_LISTS -) - -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LIB} - DEPENDS ${SRCS} ${NODE_SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND env "PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig" "GOPATH=${GOPATH}" "CGO_ENABLED=1" "GOARM=${GOARM}" "GOARCH=${GOARCH}" "CGO_CFLAGS=-I${CMAKE_CURRENT_SOURCE_DIR}/../../include" ${GO} build -buildmode=c-archive -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB}" ${CMAKE_GO_FLAGS} . - COMMENT "Building CGo library ${LIB}" - COMMAND_EXPAND_LISTS) - -add_custom_target(villas-go-lib DEPENDS ${LIB}) -add_custom_target(villas-go-header DEPENDS ${HEADER}) - -add_library(villas-go STATIC IMPORTED GLOBAL) -add_dependencies(villas-go villas-go-lib) -set_target_properties(villas-go - PROPERTIES - IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${LIB}" - INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR} -) diff --git a/go/lib/README.md b/go/lib/README.md deleted file mode 100644 index f9e857ef5..000000000 --- a/go/lib/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Writing custom node-type in Go - -This directory contains the required glue code for implementing custom node-types in the Go programming language. - -Contents of this directory will be compiled into a static library and linked into 'libvillas.so'. - -Have a look at the 'go.example' node-type implemented in '/go/pkg/nodes/example' for an example on how to implement your own node-type in Go. diff --git a/go/lib/bridge.c b/go/lib/bridge.c deleted file mode 100644 index 719c09582..000000000 --- a/go/lib/bridge.c +++ /dev/null @@ -1,21 +0,0 @@ -/** Bridge code for call C-function pointers from Go code - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -#include - -#include - -void bridge_go_register_node_factory(_go_register_node_factory_cb cb, _go_plugin_list pl, char *name, char *desc, int flags) -{ - cb(pl, name, desc, flags); -} - -void bridge_go_logger_log(_go_logger_log_cb cb, _go_logger l, int level, char *msg) -{ - cb(l, level, msg); - free(msg); -} diff --git a/go/lib/lib.go b/go/lib/lib.go deleted file mode 100644 index 95cc39a6d..000000000 --- a/go/lib/lib.go +++ /dev/null @@ -1,190 +0,0 @@ -/** CGo interface for writing node-types in Go - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package main - -// #include -// #include -// void bridge_go_register_node_factory(_go_register_node_factory_cb cb, _go_plugin_list pl, char *name, char *desc, int flags); -import "C" - -import ( - "runtime/cgo" - "unsafe" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes" - - _ "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes/example" - _ "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes/loopback" - _ "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes/webrtc" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/errors" -) - -func main() {} - -type NodeType struct { - nodes.NodeType -} - -//export RegisterGoNodeTypes -func RegisterGoNodeTypes(cb C._go_register_node_factory_cb, pl C._go_plugin_list) { - ntm := nodes.NodeTypes() - - for n, t := range ntm { - C.bridge_go_register_node_factory(cb, pl, C.CString(n), C.CString(t.Description), C.int(t.Flags)) - } -} - -//export NewGoNode -func NewGoNode(t *C.char) C.uintptr_t { - nodeTypes := nodes.NodeTypes() - - typ, ok := nodeTypes[C.GoString(t)] - if !ok { - return 0 - } - - node := typ.Constructor() - return C.uintptr_t(cgo.NewHandle(node)) -} - -//export GoNodeSetLogger -func GoNodeSetLogger(p C.uintptr_t, cb C._go_logger_log_cb, l C._go_logger) { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - n.SetLogger(NewVillasLogger(cb, l)) -} - -//export GoNodeClose -func GoNodeClose(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Close())) -} - -//export GoNodePrepare -func GoNodePrepare(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Prepare())) -} - -//export GoNodeParse -func GoNodeParse(p C.uintptr_t, c *C.char) C.int { - scfg := C.GoString(c) - bcfg := []byte(scfg) - - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Parse(bcfg))) -} - -//export GoNodeCheck -func GoNodeCheck(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Check())) -} - -//export GoNodeStart -func GoNodeStart(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Start())) -} - -//export GoNodeStop -func GoNodeStop(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Stop())) -} - -//export GoNodePause -func GoNodePause(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Pause())) -} - -//export GoNodeResume -func GoNodeResume(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Resume())) -} - -//export GoNodeRestart -func GoNodeRestart(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Restart())) -} - -//export GoNodeRead -func GoNodeRead(p C.uintptr_t, buf *C.char, sz C.int) (C.int, C.int) { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - - src, err := n.Read() - if err != nil { - return -1, C.int(errors.ErrorToInt(err)) - } - - // Create slice which is backed by buf - dst := (*[1 << 30]byte)(unsafe.Pointer(buf))[:sz] - lsz := copy(dst, src) - - return C.int(lsz), 0 -} - -//export GoNodeWrite -func GoNodeWrite(p C.uintptr_t, data []byte) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Write(data))) -} - -//export GoNodeReverse -func GoNodeReverse(p C.uintptr_t) C.int { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - return C.int(errors.ErrorToInt(n.Reverse())) -} - -//export GoNodeGetPollFDs -func GoNodeGetPollFDs(p C.uintptr_t) ([]int, C.int) { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - f, err := n.PollFDs() - if err != nil { - return nil, C.int(errors.ErrorToInt(err)) - } - - return f, 0 -} - -//export GoNodeGetNetemFDs -func GoNodeGetNetemFDs(p C.uintptr_t) ([]int, C.int) { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - f, err := n.NetemFDs() - if err != nil { - return nil, C.int(errors.ErrorToInt(err)) - } - - return f, 0 -} - -//export GoNodeDetails -func GoNodeDetails(p C.uintptr_t) *C.char { - h := cgo.Handle(p) - n := h.Value().(nodes.Node) - d := n.Details() - return C.CString(d) -} diff --git a/go/lib/logger.go b/go/lib/logger.go deleted file mode 100644 index fd613fc9c..000000000 --- a/go/lib/logger.go +++ /dev/null @@ -1,113 +0,0 @@ -/** CGo interface for VILLASnode logger - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package main - -// #include -// #include -// void bridge_go_logger_log(_go_logger_log_cb cb, _go_logger l, int level, char *msg); -import "C" - -import ( - "fmt" - "os" -) - -type LogLevel int - -const ( - // https://github.com/gabime/spdlog/blob/a51b4856377a71f81b6d74b9af459305c4c644f8/include/spdlog/common.h#L76 - LogLevelTrace LogLevel = iota - LogLevelDebug LogLevel = iota - LogLevelInfo LogLevel = iota - LogLevelWarn LogLevel = iota - LogLevelError LogLevel = iota - LogLevelCritical LogLevel = iota - LogLevelOff LogLevel = iota -) - -type VillasLogger struct { - inst C._go_logger - cb C._go_logger_log_cb -} - -func NewVillasLogger(cb C._go_logger_log_cb, l C._go_logger) *VillasLogger { - return &VillasLogger{ - cb: cb, - inst: l, - } -} - -func (l *VillasLogger) log(lvl LogLevel, msg string) { - C.bridge_go_logger_log(l.cb, l.inst, C.int(lvl), C.CString(msg)) -} - -func (l *VillasLogger) Trace(msg string) { - l.log(LogLevelTrace, msg) -} - -func (l *VillasLogger) Debug(msg string) { - l.log(LogLevelDebug, msg) -} - -func (l *VillasLogger) Info(msg string) { - l.log(LogLevelInfo, msg) -} - -func (l *VillasLogger) Warn(msg string) { - l.log(LogLevelWarn, msg) -} - -func (l *VillasLogger) Error(msg string) { - l.log(LogLevelError, msg) -} - -func (l *VillasLogger) Critical(msg string) { - l.log(LogLevelCritical, msg) -} - -func (l *VillasLogger) Tracef(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - l.log(LogLevelTrace, msg) -} - -func (l *VillasLogger) Debugf(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - l.log(LogLevelDebug, msg) -} - -func (l *VillasLogger) Infof(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - l.log(LogLevelInfo, msg) -} - -func (l *VillasLogger) Warnf(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - l.log(LogLevelWarn, msg) -} - -func (l *VillasLogger) Errorf(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - l.log(LogLevelError, msg) -} - -func (l *VillasLogger) Criticalf(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - l.log(LogLevelCritical, msg) -} - -func (l *VillasLogger) Panic(msg string) { - l.Critical(msg) - fmt.Println("Paniced") - os.Exit(-1) -} - -func (l *VillasLogger) Panicf(format string, args ...interface{}) { - l.Criticalf(format, args...) - fmt.Println("Paniced") - os.Exit(-1) -} diff --git a/go/pkg/config/hook.go b/go/pkg/config/hook.go deleted file mode 100644 index 243b08e79..000000000 --- a/go/pkg/config/hook.go +++ /dev/null @@ -1,17 +0,0 @@ -/** Go types for hook configuration. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package config - -type Hook struct { - Type string `json:"type"` - Priority int `json:"priority,omitempty"` -} - -type PrintHook struct { - Hook -} diff --git a/go/pkg/config/node.go b/go/pkg/config/node.go deleted file mode 100644 index d1cd3b774..000000000 --- a/go/pkg/config/node.go +++ /dev/null @@ -1,28 +0,0 @@ -/** Go types for node configuration. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package config - -type Node struct { - Name string `json:"name"` - Type string `json:"type"` -} - -type NodeDir struct{} - -type NodeLoopbackIn struct { - NodeDir - - Signals []Signal `json:"signals"` - Hooks []interface{} `json:"hooks"` -} - -type LoopbackNode struct { - Node - - In NodeLoopbackIn `json:"in"` -} diff --git a/go/pkg/config/signal.go b/go/pkg/config/signal.go deleted file mode 100644 index 2935bfd3b..000000000 --- a/go/pkg/config/signal.go +++ /dev/null @@ -1,22 +0,0 @@ -/** Go types for signal configuration. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package config - -const ( - SignalTypeFloat = "float" - SignalTypeInteger = "integer" - SignalTypeBoolean = "boolean" - SignalTypeComplex = "complex" -) - -type Signal struct { - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Unit string `json:"unit,omitempty"` - Init float64 `json:"init,omitempty"` -} diff --git a/go/pkg/errors/errors.go b/go/pkg/errors/errors.go deleted file mode 100644 index 75a120e00..000000000 --- a/go/pkg/errors/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -/** Common error types - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package errors - -import ( - "C" -) -import "fmt" - -var ErrEndOfFile = fmt.Errorf("end-of-file") - -func ErrorToInt(e error) int { - if e == nil { - return 0 - } else { - return -1 - } -} - -func IntToError(ret int) error { - if ret == 0 { - return nil - } else { - return fmt.Errorf("ret=%d", ret) - } -} diff --git a/go/pkg/node/node.go b/go/pkg/node/node.go deleted file mode 100644 index e15823f88..000000000 --- a/go/pkg/node/node.go +++ /dev/null @@ -1,212 +0,0 @@ -/** Wrapper for using libvillas in Go applications. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package node - -// #cgo LDFLAGS: -lvillas -// #include -import "C" - -import ( - "encoding/json" - "fmt" - "log" - "unsafe" - - "github.com/google/uuid" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/errors" -) - -const ( - MAX_SIGNALS = 64 - NUM_HUGEPAGES = 100 -) - -type Node struct { - inst *C.vnode -} - -func IsValidNodeNname(name string) bool { - return bool(C.node_is_valid_name(C.CString(name))) -} - -func NewNode(cfg interface{}, sn_uuid uuid.UUID) (*Node, error) { - if js, err := json.Marshal(cfg); err != nil { - return nil, fmt.Errorf("failed to serialize config: %w", err) - } else { - log.Printf("Config = %s\n", js) - n := C.node_new(C.CString(string(js)), C.CString(sn_uuid.String())) - return &Node{n}, nil - } -} - -func (n *Node) Prepare() error { - return errors.IntToError(int(C.node_prepare(n.inst))) -} - -func (n *Node) Check() error { - return errors.IntToError(int(C.node_check(n.inst))) -} - -func (n *Node) Start() error { - return errors.IntToError(int(C.node_start(n.inst))) -} - -func (n *Node) Stop() error { - return errors.IntToError(int(C.node_stop(n.inst))) -} - -func (n *Node) Pause() error { - return errors.IntToError(int(C.node_pause(n.inst))) -} - -func (n *Node) Resume() error { - return errors.IntToError(int(C.node_resume(n.inst))) -} - -func (n *Node) Restart() error { - return errors.IntToError(int(C.node_restart(n.inst))) -} - -func (n *Node) Close() error { - return errors.IntToError(int(C.node_destroy(n.inst))) -} - -func (n *Node) Reverse() error { - return errors.IntToError(int(C.node_reverse(n.inst))) -} - -func (n *Node) Read(cnt int) []Sample { - csmps := make([]*C.vsample, cnt) - - for i := 0; i < cnt; i++ { - csmps[i] = C.sample_alloc(MAX_SIGNALS) - } - - read := int(C.node_read(n.inst, (**C.vsample)(unsafe.Pointer(&csmps[0])), C.uint(cnt))) - - smps := make([]Sample, read) - for i := 0; i < read; i++ { - smps[i].FromC(csmps[i]) - C.sample_decref(csmps[i]) - } - - return smps -} - -func (n *Node) Write(smps []Sample) int { - cnt := len(smps) - csmps := make([]*C.vsample, cnt) - - for i := 0; i < cnt; i++ { - csmps[i] = smps[i].ToC() - } - - return int(C.node_write(n.inst, (**C.vsample)(unsafe.Pointer(&csmps[0])), C.uint(cnt))) -} - -func (n *Node) PollFDs() []int { - var cfds [16]C.int - cnt := int(C.node_poll_fds(n.inst, (*C.int)(unsafe.Pointer(&cfds)))) - - fds := make([]int, cnt) - for i := 0; i < cnt; i++ { - fds[i] = int(cfds[i]) - } - - return fds -} - -func (n *Node) NetemFDs() []int { - var cfds [16]C.int - cnt := int(C.node_netem_fds(n.inst, (*C.int)(unsafe.Pointer(&cfds)))) - - fds := make([]int, cnt) - for i := 0; i < cnt; i++ { - fds[i] = int(cfds[i]) - } - - return fds -} - -func (n *Node) IsEnabled() bool { - return bool(C.node_is_enabled(n.inst)) -} - -func (n *Node) Name() string { - return C.GoString(C.node_name(n.inst)) -} - -func (n *Node) NameShort() string { - return C.GoString(C.node_name_short(n.inst)) -} - -func (n *Node) NameFull() string { - return C.GoString(C.node_name_full(n.inst)) -} - -func (n *Node) Details() string { - return C.GoString(C.node_details(n.inst)) -} - -func (n *Node) InputSignalsMaxCount() uint { - return uint(C.node_input_signals_max_cnt(n.inst)) -} - -func (n *Node) OutputSignalsMaxCount() uint { - return uint(C.node_output_signals_max_cnt(n.inst)) -} - -func (n *Node) ToJSON() string { - json_str := C.node_to_json_str(n.inst) - return C.GoString(json_str) -} - -func init() { - C.memory_init(NUM_HUGEPAGES) -} - -type Sample pkg.Sample - -func (s *Sample) ToC() *C.vsample { - return C.sample_pack( - C.uint(s.Sequence), - &C.struct_timespec{ - tv_sec: C.long(s.Timestamps.Origin[0]), - tv_nsec: C.long(s.Timestamps.Origin[1]), - }, - &C.struct_timespec{ - tv_sec: C.long(s.Timestamps.Received[0]), - tv_nsec: C.long(s.Timestamps.Received[1]), - }, - C.uint(len(s.Data)), - (*C.double)(unsafe.Pointer(&s.Data[0])), - ) -} - -func (s *Sample) FromC(c *C.vsample) { - var tsOrigin C.struct_timespec - var tsReceived C.struct_timespec - - len := C.sample_length(c) - - s.Data = make([]float64, uint(len)) - - C.sample_unpack(c, - (*C.uint)(unsafe.Pointer(&s.Sequence)), - &tsOrigin, - &tsReceived, - (*C.int)(unsafe.Pointer(&s.Flags)), - &len, - (*C.double)(unsafe.Pointer(&s.Data[0])), - ) - - s.Timestamps.Origin = pkg.Timestamp{int64(tsOrigin.tv_sec), int64(tsOrigin.tv_nsec)} - s.Timestamps.Received = pkg.Timestamp{int64(tsReceived.tv_sec), int64(tsReceived.tv_nsec)} -} diff --git a/go/pkg/node/node_test.go b/go/pkg/node/node_test.go deleted file mode 100644 index c5e1e4057..000000000 --- a/go/pkg/node/node_test.go +++ /dev/null @@ -1,95 +0,0 @@ -/** Unit tests for using libvillas in Go code. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package node_test - -import ( - "testing" - "time" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/config" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/node" - - "github.com/google/uuid" -) - -func TestNode(t *testing.T) { - cfg := &config.LoopbackNode{ - Node: config.Node{ - Name: "lo1", - Type: "loopback", - }, - In: config.NodeLoopbackIn{ - Hooks: []interface{}{ - &config.PrintHook{ - Hook: config.Hook{ - Type: "print", - }, - }, - }, - Signals: []config.Signal{ - { - Name: "sig1", - }, - { - Name: "sig2", - }, - { - Name: "sig3", - }, - }, - }, - } - - n, err := node.NewNode(cfg, uuid.New()) - if err != nil { - t.Fatalf("Failed to create node: %s", err) - } - defer n.Close() - - if err := n.Check(); err != nil { - t.Fatalf("Failed to check node: %s", err) - } - - if err := n.Prepare(); err != nil { - t.Fatalf("Failed to prepare node: %s", err) - } - - if err := n.Start(); err != nil { - t.Fatalf("Failed to start node: %s", err) - } - defer n.Stop() - - t.Logf("%s", n.NameFull()) - - smps_send := []node.Sample{ - { - Sequence: 1234, - TimestampOrigin: time.Now(), - Data: []float64{1.1, 2.2, 3.3}, - }, - { - Sequence: 1235, - TimestampOrigin: time.Now(), - Data: []float64{4.4, 5.5, 6.6}, - }, - } - - t.Logf("Sent: %+#v", smps_send) - - cnt_written := n.Write(smps_send) - if cnt_written != len(smps_send) { - t.Fatalf("Failed to send all samples") - } - - smps_received := n.Read(cnt_written) - if len(smps_received) != cnt_written { - t.Fatalf("Failed to receive samples back") - } - - t.Logf("Received: %+#v", smps_received) -} diff --git a/go/pkg/nodes/base.go b/go/pkg/nodes/base.go deleted file mode 100644 index 16fe3de75..000000000 --- a/go/pkg/nodes/base.go +++ /dev/null @@ -1,63 +0,0 @@ -/** Common code for implementing node-types in Go code. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package nodes - -type BaseNode struct { - Node - - Logger Logger - - Stopped chan struct{} -} - -func NewBaseNode() BaseNode { - return BaseNode{} -} - -func (n *BaseNode) Start() error { - n.Stopped = make(chan struct{}) - return nil -} - -func (n *BaseNode) Stop() error { - close(n.Stopped) - - return nil -} - -func (n *BaseNode) Parse(c []byte) error { - return nil -} - -func (n *BaseNode) Check() error { - return nil -} - -func (n *BaseNode) Restart() error { - return nil -} - -func (n *BaseNode) Pause() error { - return nil -} - -func (n *BaseNode) Resume() error { - return nil -} - -func (n *BaseNode) Reverse() error { - return nil -} - -func (n *BaseNode) Close() error { - return nil -} - -func (n *BaseNode) SetLogger(l Logger) { - n.Logger = l -} diff --git a/go/pkg/nodes/example/node.go b/go/pkg/nodes/example/node.go deleted file mode 100644 index 6e14fc296..000000000 --- a/go/pkg/nodes/example/node.go +++ /dev/null @@ -1,102 +0,0 @@ -/** Little example node-type written in Go code. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package nodes - -import ( - "encoding/json" - "fmt" - "time" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/errors" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes" -) - -type Node struct { - nodes.BaseNode - - ticker time.Ticker - - lastSequence uint64 - - Config Config -} - -type Config struct { - nodes.NodeConfig - - Value int `json:"value"` -} - -func NewNode() nodes.Node { - return &Node{ - BaseNode: nodes.NewBaseNode(), - ticker: *time.NewTicker(1 * time.Second), - lastSequence: 0, - } -} - -func (n *Node) Parse(c []byte) error { - return json.Unmarshal(c, &n.Config) -} - -func (n *Node) Check() error { - return nil -} - -func (n *Node) Prepare() error { - return nil -} - -func (n *Node) Start() error { - n.Logger.Infof("hello from node") - n.Logger.Warnf("hello from node") - n.Logger.Errorf("hello from node") - n.Logger.Tracef("hello from node") - n.Logger.Criticalf("hello from node") - - return n.BaseNode.Start() -} - -func (n *Node) Read() ([]byte, error) { - select { - case <-n.Stopped: - return nil, errors.ErrEndOfFile - - case <-n.ticker.C: - n.lastSequence++ - smp := pkg.GenerateRandomSample() - smp.Sequence = n.lastSequence - smps := []pkg.Sample{smp} - - return json.Marshal(smps) - } -} - -func (n *Node) Write(data []byte) error { - n.Logger.Infof("Data: %s", string(data)) - - return nil -} - -func (n *Node) PollFDs() ([]int, error) { - return []int{}, nil -} - -func (n *Node) NetemFDs() ([]int, error) { - return []int{}, nil -} - -func (n *Node) Details() string { - return fmt.Sprintf("value=%d", n.Config.Value) -} - -func init() { - // Do not forget to import the package in go/lib/main.go! - nodes.RegisterNodeType("go.example", "A example node implemented in Go", NewNode, nodes.NodeSupportsRead|nodes.NodeSupportsWrite|nodes.NodeHidden) -} diff --git a/go/pkg/nodes/logger.go b/go/pkg/nodes/logger.go deleted file mode 100644 index 293628616..000000000 --- a/go/pkg/nodes/logger.go +++ /dev/null @@ -1,25 +0,0 @@ -/** Logger interface. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package nodes - -type Logger interface { - Trace(msg string) - Debug(msg string) - Info(msg string) - Warn(msg string) - Error(msg string) - Critical(msg string) - Tracef(format string, args ...interface{}) - Debugf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Warnf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) - Criticalf(format string, args ...interface{}) - Panic(msg string) - Panicf(format string, args ...interface{}) -} diff --git a/go/pkg/nodes/loopback/node.go b/go/pkg/nodes/loopback/node.go deleted file mode 100644 index 6c196fe12..000000000 --- a/go/pkg/nodes/loopback/node.go +++ /dev/null @@ -1,85 +0,0 @@ -/** Simple loopback node-type written in Go code. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package nodes - -import ( - "encoding/json" - "fmt" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/errors" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes" -) - -type Node struct { - nodes.BaseNode - - channel chan []byte - - Config LoopbackConfig -} - -type LoopbackConfig struct { - nodes.NodeConfig - - Value int `json:"value"` -} - -func NewNode() nodes.Node { - return &Node{ - BaseNode: nodes.NewBaseNode(), - channel: make(chan []byte, 1024), - } -} - -func (n *Node) Parse(c []byte) error { - return json.Unmarshal(c, &n.Config) -} - -func (n *Node) Check() error { - return nil -} - -func (n *Node) Prepare() error { - return nil -} - -func (n *Node) Start() error { - return n.BaseNode.Start() -} - -func (n *Node) Read() ([]byte, error) { - select { - case <-n.Stopped: - return nil, errors.ErrEndOfFile - - case buf := <-n.channel: - return buf, nil - } -} - -func (n *Node) Write(data []byte) error { - n.channel <- data - - return nil -} - -func (n *Node) PollFDs() ([]int, error) { - return []int{}, nil -} - -func (n *Node) NetemFDs() ([]int, error) { - return []int{}, nil -} - -func (n *Node) Details() string { - return fmt.Sprintf("value=%d", n.Config.Value) -} - -func init() { - nodes.RegisterNodeType("go.loopback", "A loopback node implmented in Go", NewNode, nodes.NodeSupportsRead|nodes.NodeSupportsWrite|nodes.NodeHidden) -} diff --git a/go/pkg/nodes/node.go b/go/pkg/nodes/node.go deleted file mode 100644 index 8ca3279ff..000000000 --- a/go/pkg/nodes/node.go +++ /dev/null @@ -1,57 +0,0 @@ -/** Node interface. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package nodes - -const ( - NodeSupportsPoll = (1 << iota) - NodeSupportsRead = (1 << iota) - NodeSupportsWrite = (1 << iota) - NodeRequiresWeb = (1 << iota) - NodeProvidesSignals = (1 << iota) - NodeInternal = (1 << iota) - NodeHidden = (1 << iota) -) - -type NodeConstructor func() Node - -type Node interface { - Close() error - - Prepare() error - - Parse(cfg []byte) error - - Check() error - - Start() error - Stop() error - - Pause() error - Resume() error - Restart() error - - Read() ([]byte, error) - Write(data []byte) error - - Reverse() error - - PollFDs() ([]int, error) - NetemFDs() ([]int, error) - - Details() string - - SetLogger(l Logger) -} - -type NodeConfig struct { - Type string `json:"type"` - - In struct{} `json:"in"` - - Out struct{} `json:"out"` -} diff --git a/go/pkg/nodes/registry.go b/go/pkg/nodes/registry.go deleted file mode 100644 index f3afcba9d..000000000 --- a/go/pkg/nodes/registry.go +++ /dev/null @@ -1,28 +0,0 @@ -/** Node-type registry. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package nodes - -var goNodeTypes = map[string]NodeType{} - -type NodeType struct { - Constructor NodeConstructor - Flags int - Description string -} - -func RegisterNodeType(name string, desc string, ctor NodeConstructor, flags int) { - goNodeTypes[name] = NodeType{ - Constructor: ctor, - Flags: flags, - Description: desc, - } -} - -func NodeTypes() map[string]NodeType { - return goNodeTypes -} diff --git a/go/pkg/nodes/webrtc/backoff.go b/go/pkg/nodes/webrtc/backoff.go deleted file mode 100644 index c8b4b8f2f..000000000 --- a/go/pkg/nodes/webrtc/backoff.go +++ /dev/null @@ -1,38 +0,0 @@ -/** Exponential backoffs for reconnect timing. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package webrtc - -import "time" - -var DefaultExponentialBackoff = ExponentialBackoff{ - Factor: 1.5, - Maximum: 1 * time.Minute, - Initial: 1 * time.Second, - Duration: 1 * time.Second, -} - -type ExponentialBackoff struct { - Factor float32 - Maximum time.Duration - Initial time.Duration - - Duration time.Duration -} - -func (e *ExponentialBackoff) Next() time.Duration { - e.Duration = time.Duration(1.5 * float32(e.Duration)).Round(time.Second) - if e.Duration > e.Maximum { - e.Duration = e.Maximum - } - - return e.Duration -} - -func (e *ExponentialBackoff) Reset() { - e.Duration = e.Initial -} diff --git a/go/pkg/nodes/webrtc/client.go b/go/pkg/nodes/webrtc/client.go deleted file mode 100644 index 4f25f84f7..000000000 --- a/go/pkg/nodes/webrtc/client.go +++ /dev/null @@ -1,173 +0,0 @@ -/** Websocket signaling channel for WebRTC. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package webrtc - -import ( - "fmt" - "net/url" - "time" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes" - "github.com/gorilla/websocket" -) - -type SignalingClient struct { - *websocket.Conn - - logger nodes.Logger - - URL *url.URL - - done chan struct{} - - isClosing bool - backoff ExponentialBackoff - - messageCallbacks []func(msg *SignalingMessage) - connectCallbacks []func() - disconnectCallbacks []func() -} - -func NewSignalingClient(u *url.URL, logger nodes.Logger) (*SignalingClient, error) { - c := &SignalingClient{ - messageCallbacks: []func(msg *SignalingMessage){}, - connectCallbacks: []func(){}, - disconnectCallbacks: []func(){}, - isClosing: false, - backoff: DefaultExponentialBackoff, - URL: u, - logger: logger, - } - - return c, nil -} - -func (c *SignalingClient) OnConnect(cb func()) { - c.connectCallbacks = append(c.connectCallbacks, cb) -} - -func (c *SignalingClient) OnDisconnect(cb func()) { - c.disconnectCallbacks = append(c.connectCallbacks, cb) -} - -func (c *SignalingClient) OnMessage(cb func(msg *SignalingMessage)) { - c.messageCallbacks = append(c.messageCallbacks, cb) -} - -func (c *SignalingClient) SendSignalingMessage(msg *SignalingMessage) error { - c.logger.Infof("Sending signaling message: %s", msg) - return c.Conn.WriteJSON(msg) -} - -func (c *SignalingClient) Close() error { - // Return immediatly if there is no open connection - if c.Conn == nil { - return nil - } - - c.isClosing = true - - // Cleanly close the connection by sending a close message and then - // waiting (with timeout) for the server to close the connection. - err := c.Conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(5*time.Second)) - if err != nil { - return fmt.Errorf("failed to send close message: %s", err) - } - - select { - case <-c.done: - c.logger.Infof("Connection closed") - case <-time.After(3 * time.Second): - c.logger.Warn("Timed-out waiting for connection close") - } - - return nil -} - -func (c *SignalingClient) Connect() error { - var err error - - dialer := websocket.Dialer{ - HandshakeTimeout: 1 * time.Second, - } - - c.Conn, _, err = dialer.Dial(c.URL.String(), nil) - if err != nil { - return fmt.Errorf("failed to dial %s: %w", c.URL, err) - } - - for _, cb := range c.connectCallbacks { - cb() - } - - go c.read() - - // Reset reconnect timer - c.backoff.Reset() - - c.done = make(chan struct{}) - - return nil -} - -func (c *SignalingClient) ConnectWithBackoff() error { - t := time.NewTimer(c.backoff.Duration) - for range t.C { - if err := c.Connect(); err != nil { - t.Reset(c.backoff.Next()) - - c.logger.Errorf("Failed to connect: %s. Reconnecting in %s", err, c.backoff.Duration) - } else { - break - } - } - - return nil -} - -func (c *SignalingClient) read() { - for { - msg := &SignalingMessage{} - if err := c.Conn.ReadJSON(msg); err != nil { - if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) { - } else { - c.logger.Errorf("Failed to read: %s", err) - } - break - } - - c.logger.Infof("Received signaling message: %s", msg) - - for _, cb := range c.messageCallbacks { - cb(msg) - } - } - - c.closed() -} - -func (c *SignalingClient) closed() { - if err := c.Conn.Close(); err != nil { - c.logger.Errorf("Failed to close connection: %s", err) - } - - c.Conn = nil - - for _, cb := range c.disconnectCallbacks { - cb() - } - - close(c.done) - - if c.isClosing { - c.logger.Infof("Connection closed") - } else { - c.logger.Warnf("Connection lost. Reconnecting in %s", c.backoff.Duration) - go c.ConnectWithBackoff() - } -} diff --git a/go/pkg/nodes/webrtc/node.go b/go/pkg/nodes/webrtc/node.go deleted file mode 100644 index 02d7ae761..000000000 --- a/go/pkg/nodes/webrtc/node.go +++ /dev/null @@ -1,220 +0,0 @@ -/** WebRTC node-type. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package webrtc - -import ( - "encoding/json" - "errors" - "fmt" - "net/url" - "strings" - - verrors "git.rwth-aachen.de/acs/public/villas/node/go/pkg/errors" - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes" - "github.com/pion/webrtc/v3" -) - -var DefaultConfig = Config{ - Server: &url.URL{ - Scheme: "wss", - Host: "villas.k8s.eonerc.rwth-aachen.de", - Path: "/ws/signaling", - }, - Wait: true, - MaxRetransmits: 0, - Ordered: false, - WebRTC: webrtc.Configuration{ - ICEServers: []webrtc.ICEServer{ - { - URLs: []string{"stun:stun.l.google.com:19302"}, - }, - { - URLs: []string{ - "stun:stun.0l.de", - }, - CredentialType: webrtc.ICECredentialTypePassword, - Username: "villas", - Credential: "villas", - }, - { - URLs: []string{ - "turn:turn.0l.de?transport=udp", - "turn:turn.0l.de?transport=tcp", - }, - CredentialType: webrtc.ICECredentialTypePassword, - Username: "villas", - Credential: "villas", - }, - }, - }, -} - -type Node struct { - nodes.BaseNode - *PeerConnection - - Config Config -} - -type Config struct { - Server *url.URL - Session string - - Wait bool - MaxRetransmits uint16 - Ordered bool - - WebRTC webrtc.Configuration -} - -func NewNode() nodes.Node { - return &Node{ - Config: DefaultConfig, - } -} - -func (n *Node) Parse(c []byte) error { - var err error - var cfg struct { - Session *string `json:"session"` - Server *string `json:"server,omitempty"` - Wait *bool `json:"wait,omitemty"` - MaxRetransmits *uint16 `json:"max_retransmits,omitempty"` - Ordered *bool `json:"ordered,omitempty"` - Ice *struct { - Servers []struct { - URLs []string `json:"urls,omitempty"` - Username *string `json:"username,omitempty"` - Password *string `json:"password,omitempty"` - } `json:"servers,omitempty"` - } `json:"ice,omitempty"` - } - - if err := json.Unmarshal(c, &cfg); err != nil { - return fmt.Errorf("failed to unmarshal config: %w", err) - } - - if cfg.Wait != nil { - n.Config.Wait = *cfg.Wait - } - - if cfg.Ordered != nil { - n.Config.Ordered = *cfg.Ordered - } - - if cfg.MaxRetransmits != nil { - n.Config.MaxRetransmits = *cfg.MaxRetransmits - } - - if cfg.Session == nil || *cfg.Session == "" { - return errors.New("missing or invalid session name") - } else { - n.Config.Session = *cfg.Session - } - - if cfg.Server != nil { - n.Config.Server, err = url.Parse(*cfg.Server) - if err != nil { - return fmt.Errorf("failed to parse server address: %w", err) - } - } - - if cfg.Ice != nil { - for _, server := range cfg.Ice.Servers { - iceServer := webrtc.ICEServer{ - URLs: server.URLs, - } - - if server.Username != nil && server.Password != nil { - iceServer.Username = *server.Username - iceServer.CredentialType = webrtc.ICECredentialTypePassword - iceServer.Credential = *server.Password - } - - n.Config.WebRTC.ICEServers = append(n.Config.WebRTC.ICEServers, iceServer) - } - } - - return nil -} - -func (n *Node) Prepare() error { - var err error - - n.PeerConnection, err = NewPeerConnection(&n.Config, n.Logger) - if err != nil { - return fmt.Errorf("failed to create peer connection: %w", err) - } - - return nil -} - -func (n *Node) Start() error { - n.DataChannelLock.Lock() - defer n.DataChannelLock.Unlock() - - if n.Config.Wait { - n.Logger.Info("Waiting until datachannel is connected...") - for n.DataChannel == nil { - n.DataChannelConnected.Wait() - } - } - - return n.BaseNode.Start() -} - -func (n *Node) Read() ([]byte, error) { - select { - case <-n.Stopped: - return nil, verrors.ErrEndOfFile - case b := <-n.PeerConnection.ReceivedMessages: - return b, nil - } -} - -func (n *Node) Write(data []byte) error { - n.DataChannelLock.Lock() - defer n.DataChannelLock.Unlock() - - if n.DataChannel == nil { - n.logger.Infof("No datachannel open. Skipping sample...") - return nil - } - - return n.DataChannel.Send(data) -} - -func (n *Node) PollFDs() ([]int, error) { - return []int{}, nil -} - -func (n *Node) NetemFDs() ([]int, error) { - return []int{}, nil -} - -func (n *Node) Details() string { - details := map[string]string{ - "server": n.Config.Server.String(), - "session": n.Config.Session, - } - - kv := []string{} - for k, v := range details { - kv = append(kv, fmt.Sprintf("%s=%s", k, v)) - } - - return strings.Join(kv, ", ") -} - -func (n *Node) Close() error { - return nil -} - -func init() { - nodes.RegisterNodeType("webrtc", "Web Real-time Communication", NewNode, nodes.NodeSupportsRead|nodes.NodeSupportsWrite) -} diff --git a/go/pkg/nodes/webrtc/types.go b/go/pkg/nodes/webrtc/types.go deleted file mode 100644 index d67cdc289..000000000 --- a/go/pkg/nodes/webrtc/types.go +++ /dev/null @@ -1,44 +0,0 @@ -/** Types for WebRTC node-type. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package webrtc - -import ( - "encoding/json" - "time" - - "github.com/pion/webrtc/v3" -) - -type Connection struct { - ID int `json:"id"` - - Remote string `json:"remote"` - UserAgent string `json:"user_agent"` - Created time.Time `json:"created"` -} - -type ControlMessage struct { - ConnectionID int `json:"connection_id"` - Connections []Connection `json:"connections"` -} - -type Role struct { - Polite bool `json:"polite"` - First bool `json:"first"` -} - -type SignalingMessage struct { - Description *webrtc.SessionDescription `json:"description,omitempty"` - Candidate *webrtc.ICECandidateInit `json:"candidate,omitempty"` - Control *ControlMessage `json:"control,omitempty"` -} - -func (msg SignalingMessage) String() string { - b, _ := json.Marshal(msg) - return string(b) -} diff --git a/go/pkg/nodes/webrtc/webrtc.go b/go/pkg/nodes/webrtc/webrtc.go deleted file mode 100644 index bd613abe2..000000000 --- a/go/pkg/nodes/webrtc/webrtc.go +++ /dev/null @@ -1,341 +0,0 @@ -/** WebRTC peer connection handling. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package webrtc - -import ( - "fmt" - "path" - "sync" - - "git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes" - "github.com/pion/webrtc/v3" -) - -type PeerConnection struct { - *webrtc.PeerConnection - *SignalingClient - - config *Config - - logger nodes.Logger - - makingOffer bool - ignoreOffer bool - - first bool - polite bool - - rollback bool - - DataChannel *webrtc.DataChannel - DataChannelLock sync.Mutex - DataChannelConnected *sync.Cond - - ReceivedMessages chan []byte -} - -func NewPeerConnection(config *Config, logger nodes.Logger) (*PeerConnection, error) { - u := *config.Server - u.Path = path.Join(u.Path, config.Session) - sc, err := NewSignalingClient(&u, logger) - if err != nil { - return nil, fmt.Errorf("failed to create signaling client: %w", err) - } - - ppc := &PeerConnection{ - SignalingClient: sc, - config: config, - logger: logger, - - DataChannel: nil, - ReceivedMessages: make(chan []byte, 1024), - } - - ppc.DataChannelConnected = sync.NewCond(&ppc.DataChannelLock) - - ppc.SignalingClient.OnMessage(ppc.OnSignalingMessageHandler) - ppc.SignalingClient.OnConnect(ppc.OnSignalingConnectedHandler) - - if err := ppc.SignalingClient.ConnectWithBackoff(); err != nil { - return nil, fmt.Errorf("failed to connect signaling client: %w", err) - } - - return ppc, nil -} - -func (pc *PeerConnection) createPeerConnection() (*webrtc.PeerConnection, error) { - pc.logger.Info("Created new peer connection") - - // Create a new RTCPeerConnection - ppc, err := webrtc.NewPeerConnection(pc.config.WebRTC) - if err != nil { - return nil, fmt.Errorf("failed to create peer connection: %w", err) - } - - // Set the handler for ICE connection state - // This will notify you when the peer has connected/disconnected - ppc.OnConnectionStateChange(pc.OnConnectionStateChangeHandler) - ppc.OnSignalingStateChange(pc.OnSignalingStateChangeHandler) - ppc.OnICECandidate(pc.OnICECandidateHandler) - ppc.OnNegotiationNeeded(pc.OnNegotiationNeededHandler) - ppc.OnDataChannel(pc.OnDataChannelHandler) - - return ppc, nil -} - -func (pc *PeerConnection) createDataChannel() (*webrtc.DataChannel, error) { - dc, err := pc.CreateDataChannel("villas", &webrtc.DataChannelInit{ - Ordered: &pc.config.Ordered, - MaxRetransmits: &pc.config.MaxRetransmits, - }) - if err != nil { - return nil, fmt.Errorf("failed to create datachannel: %w", err) - } - - close := make(chan struct{}) - - dc.OnClose(func() { pc.OnDataChannelCloseHandler(dc, close) }) - dc.OnOpen(func() { pc.OnDataChannelOpenHandler(dc, close) }) - dc.OnMessage(func(msg webrtc.DataChannelMessage) { pc.OnDataChannelMessageHandler(dc, &msg, close) }) - - return dc, nil -} - -func (pc *PeerConnection) rollbackPeerConnection() (*webrtc.PeerConnection, error) { - pc.rollback = true - defer func() { pc.rollback = false }() - - // Close previous peer connection in before creating a new one - // We need to do this as pion/webrtc currently does not support rollbacks - if err := pc.PeerConnection.Close(); err != nil { - return nil, fmt.Errorf("failed to close peer connection: %w", err) - } - - if ppc, err := pc.createPeerConnection(); err != nil { - return nil, err - } else { - return ppc, nil - } -} - -func (pc *PeerConnection) OnConnectionCreated() error { - if !pc.first { - if _, err := pc.createDataChannel(); err != nil { - return err - } - } - - return nil -} - -func (pc *PeerConnection) OnDataChannelOpenHandler(dc *webrtc.DataChannel, close chan struct{}) { - pc.logger.Infof("DataChannel opened: %s", dc.Label()) - - pc.DataChannelLock.Lock() - defer pc.DataChannelLock.Unlock() - - pc.DataChannel = dc - - pc.DataChannelConnected.Broadcast() -} - -func (pc *PeerConnection) OnDataChannelCloseHandler(dc *webrtc.DataChannel, cl chan struct{}) { - pc.logger.Infof("DataChannel closed: %s", dc.Label()) - - pc.DataChannel = nil - - close(cl) - - // We close the connection here to avoid waiting for the disconnected event - if err := pc.PeerConnection.Close(); err != nil { - pc.logger.Errorf("Failed to close peer connection: %s", err) - } -} - -func (pc *PeerConnection) OnDataChannelMessageHandler(dc *webrtc.DataChannel, msg *webrtc.DataChannelMessage, close chan struct{}) { - pc.logger.Debugf("Received: %s", string(msg.Data)) - - pc.ReceivedMessages <- msg.Data -} - -func (pc *PeerConnection) OnDataChannelHandler(dc *webrtc.DataChannel) { - pc.logger.Infof("New DataChannel opened: %s", dc.Label()) - - close := make(chan struct{}) - - dc.OnOpen(func() { pc.OnDataChannelOpenHandler(dc, close) }) - dc.OnClose(func() { pc.OnDataChannelCloseHandler(dc, close) }) - dc.OnMessage(func(msg webrtc.DataChannelMessage) { pc.OnDataChannelMessageHandler(dc, &msg, close) }) -} - -func (pc *PeerConnection) OnICECandidateHandler(c *webrtc.ICECandidate) { - if c == nil { - pc.logger.Info("Candidate gathering concluded") - return - } - - pc.logger.Infof("Found new candidate: %s", c) - - ci := c.ToJSON() - if err := pc.SendSignalingMessage(&SignalingMessage{ - Candidate: &ci, - }); err != nil { - pc.logger.Errorf("Failed to send candidate: %s", err) - } -} - -func (pc *PeerConnection) OnNegotiationNeededHandler() { - pc.logger.Info("Negotation needed!") - - pc.makingOffer = true - - offer, err := pc.CreateOffer(nil) - if err != nil { - pc.logger.Panicf("Failed to create offer: %s", err) - } - - if err := pc.SetLocalDescription(offer); err != nil { - pc.logger.Panicf("Failed to set local description: %s", err) - } - - if err := pc.SendSignalingMessage(&SignalingMessage{ - Description: &offer, - }); err != nil { - pc.logger.Panicf("Failed to send offer: %s", err) - } - - pc.makingOffer = false -} - -func (pc *PeerConnection) OnSignalingStateChangeHandler(ss webrtc.SignalingState) { - pc.logger.Infof("Signaling State has changed: %s", ss.String()) -} - -func (pc *PeerConnection) OnConnectionStateChangeHandler(pcs webrtc.PeerConnectionState) { - pc.logger.Infof("Connection State has changed: %s", pcs.String()) - - switch pcs { - case webrtc.PeerConnectionStateFailed: - fallthrough - case webrtc.PeerConnectionStateDisconnected: - pc.logger.Info("Closing peer connection") - - if err := pc.PeerConnection.Close(); err != nil { - pc.logger.Panicf("Failed to close peer connection: %s", err) - } - - case webrtc.PeerConnectionStateClosed: - if pc.rollback { - return - } - - pc.logger.Info("Closed peer connection") - - var err error - pc.PeerConnection, err = pc.createPeerConnection() - if err != nil { - pc.logger.Panicf("Failed to set re-create peer connection: %s", err) - } - - if err := pc.OnConnectionCreated(); err != nil { - pc.logger.Panicf("Failed to create connection: %w", err) - } - } -} - -func (pc *PeerConnection) OnSignalingConnectedHandler() { - var err error - - pc.logger.Info("Signaling connected") - - // Create initial peer connection - if pc.PeerConnection == nil { - if pc.PeerConnection, err = pc.createPeerConnection(); err != nil { - pc.logger.Panicf("Failed to create peer connection: %s", err) - } - - if err := pc.OnConnectionCreated(); err != nil { - pc.logger.Panicf("Failed to create connection: %s", err) - } - } -} - -func (pc *PeerConnection) OnSignalingMessageHandler(msg *SignalingMessage) { - var err error - - if msg.Control != nil { - if len(msg.Control.Connections) > 2 { - pc.logger.Panicf("There are already two peers connected to this session.") - } - - // The peer with the smallest connection ID connected first - pc.first = true - for _, c := range msg.Control.Connections { - if c.ID < msg.Control.ConnectionID { - pc.first = false - } - } - - pc.polite = pc.first - - pc.logger.Infof("New role: polite=%v, first=%v", pc.polite, pc.first) - } else if msg.Description != nil { - readyForOffer := !pc.makingOffer && pc.SignalingState() == webrtc.SignalingStateStable - offerCollision := msg.Description.Type == webrtc.SDPTypeOffer && !readyForOffer - - pc.ignoreOffer = !pc.polite && offerCollision - if pc.ignoreOffer { - return - } - - if msg.Description.Type == webrtc.SDPTypeOffer && pc.PeerConnection.SignalingState() != webrtc.SignalingStateStable { - if pc.PeerConnection, err = pc.rollbackPeerConnection(); err != nil { - pc.logger.Panicf("Failed to rollback peer connection: %s", err) - } - - if err := pc.OnConnectionCreated(); err != nil { - pc.logger.Panicf("Failed to create connection: %s", err) - } - } - - if err := pc.PeerConnection.SetRemoteDescription(*msg.Description); err != nil { - pc.logger.Panicf("Failed to set remote description: %s", err) - } - - if msg.Description.Type == webrtc.SDPTypeOffer { - answer, err := pc.PeerConnection.CreateAnswer(nil) - if err != nil { - pc.logger.Panicf("Failed to create answer: %s", err) - } - - if err := pc.SetLocalDescription(answer); err != nil { - pc.logger.Panicf("Failed to rollback signaling state: %s", err) - } - - pc.SendSignalingMessage(&SignalingMessage{ - Description: pc.LocalDescription(), - }) - } - } else if msg.Candidate != nil { - if err := pc.AddICECandidate(*msg.Candidate); err != nil && !pc.ignoreOffer { - pc.logger.Panicf("Failed to add new ICE candidate: %s", err) - } - } -} - -func (pc *PeerConnection) Close() error { - if err := pc.SignalingClient.Close(); err != nil { - return fmt.Errorf("failed to close signaling client: %w", err) - } - - if err := pc.PeerConnection.Close(); err != nil { - return fmt.Errorf("failed to close peer connection: %w", err) - } - - return nil -} diff --git a/go/pkg/sample.go b/go/pkg/sample.go deleted file mode 100644 index a6efcb811..000000000 --- a/go/pkg/sample.go +++ /dev/null @@ -1,79 +0,0 @@ -/** Sample datastructure. - * - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -package pkg - -import ( - "encoding/json" - "math/rand" - "time" -) - -type Timestamp [2]int64 - -func TimestampFromTime(t time.Time) Timestamp { - return Timestamp{t.Unix(), int64(t.Nanosecond())} -} - -type Timestamps struct { - Origin Timestamp `json:"origin"` - Received Timestamp `json:"received"` -} - -type Sample struct { - Flags int `json:"-"` - Timestamps Timestamps `json:"ts"` - Sequence uint64 `json:"sequence"` - Data []float64 `json:"data"` -} - -func GenerateRandomSample() Sample { - now := time.Now() - - return Sample{ - Timestamps: Timestamps{ - Origin: TimestampFromTime(now), - }, - Sequence: 1, - Data: []float64{ - 1 * rand.Float64(), - 10 * rand.Float64(), - 100 * rand.Float64(), - }, - } -} - -func (s Sample) Bytes() []byte { - b, _ := json.Marshal(s) - return b -} - -func (s Sample) Equal(t Sample) bool { - if s.Flags != t.Flags { - return false - } - - if s.Sequence != t.Sequence { - return false - } - - if s.Timestamps != t.Timestamps { - return false - } - - if len(s.Data) != len(t.Data) { - return false - } - - for i, va := range s.Data { - if vb := t.Data[i]; va != vb { - return false - } - } - - return true -} diff --git a/include/villas/node/config.hpp.in b/include/villas/node/config.hpp.in index f1d3d30d3..68e9a245a 100644 --- a/include/villas/node/config.hpp.in +++ b/include/villas/node/config.hpp.in @@ -37,6 +37,7 @@ /* Available Features */ #cmakedefine WITH_WEB #cmakedefine WITH_NODE_WEBSOCKET +#cmakedefine WITH_NODE_WEBRTC #cmakedefine WITH_NODE_OPAL #cmakedefine WITH_API #cmakedefine WITH_HOOKS diff --git a/include/villas/nodes/webrtc.hpp b/include/villas/nodes/webrtc.hpp new file mode 100644 index 000000000..c9bede10e --- /dev/null +++ b/include/villas/nodes/webrtc.hpp @@ -0,0 +1,120 @@ +/** Node-type webrtc. + * + * @file + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +namespace villas { +namespace node { + +/* Forward declarations */ +struct Sample; + +class WebRTCNode : public Node { + +protected: + std::string server; + std::string session; + + int wait_seconds; + Format *format; + struct CQueueSignalled queue; + struct Pool pool; + + std::shared_ptr conn; + rtc::Configuration rtcConf; + rtc::DataChannelInit dci; + + virtual + int _read(struct Sample *smps[], unsigned cnt); + + virtual + int _write(struct Sample *smps[], unsigned cnt); + +public: + WebRTCNode(const std::string &name = ""); + + virtual + ~WebRTCNode(); + + virtual + int prepare(); + + virtual + int parse(json_t *json, const uuid_t sn_uuid); + + virtual + int check(); + + virtual + int start(); + + virtual + int stop(); + + virtual + std::vector getPollFDs(); + + virtual + const std::string & getDetails(); +}; + + +class WebRTCNodeFactory : public NodeFactory { + +public: + using NodeFactory::NodeFactory; + + virtual + Node * make() + { + auto *n = new WebRTCNode(); + + init(n); + + return n; + } + + virtual + int getFlags() const + { + return (int) NodeFactory::Flags::SUPPORTS_READ | + (int) NodeFactory::Flags::SUPPORTS_WRITE | + (int) NodeFactory::Flags::SUPPORTS_POLL | + (int) NodeFactory::Flags::REQUIRES_WEB; + } + + virtual + std::string getName() const + { + return "webrtc"; + } + + virtual + std::string getDescription() const + { + return "Web Real-time Communication"; + } + + virtual + int start(SuperNode *sn); +}; + + +} /* namespace node */ +} /* namespace villas */ diff --git a/include/villas/nodes/webrtc/peer_connection.hpp b/include/villas/nodes/webrtc/peer_connection.hpp new file mode 100644 index 000000000..6dd8f94cb --- /dev/null +++ b/include/villas/nodes/webrtc/peer_connection.hpp @@ -0,0 +1,91 @@ +/** WebRTC peer connection + * + * @file + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#pragma once + +#include + +#include +#include +#include + +namespace villas { +namespace node { +namespace webrtc { + +class PeerConnection { + +public: + PeerConnection(const std::string &server, const std::string &session, rtc::Configuration config, Web *w, rtc::DataChannelInit d); + ~PeerConnection(); + + bool waitForDataChannel(std::chrono::seconds timeout); + void onMessage(std::function callback); + void sendMessage(rtc::binary msg); + + void connect(); + void disconnect(); + +protected: + Web *web; + std::vector extraServers; + rtc::DataChannelInit dataChannelInit; + rtc::Configuration defaultConfig; + + std::shared_ptr conn; + std::shared_ptr chan; + std::shared_ptr client; + + Logger logger; + + std::mutex mutex; + + std::condition_variable_any startupCondition; + bool stopStartup; + + bool warnNotConnected; + bool standby; + bool first; + int firstID; + int secondID; + + std::function onMessageCallback; + + void resetConnection(std::unique_lock &lock); + void resetConnectionAndStandby(std::unique_lock &lock); + void notifyStartup(); + + void setupPeerConnection(std::shared_ptr = nullptr); + void setupDataChannel(std::shared_ptr = nullptr); + + void onLocalDescription(rtc::Description sdp); + void onLocalCandidate(rtc::Candidate cand); + + void onConnectionStateChange(rtc::PeerConnection::State state); + void onSignalingStateChange(rtc::PeerConnection::SignalingState state); + void onGatheringStateChange(rtc::PeerConnection::GatheringState state); + + void onSignalingConnected(); + void onSignalingDisconnected(); + void onSignalingError(std::string err); + void onSignalingMessage(SignalingMessage msg); + + void onDataChannel(std::shared_ptr dc); + void onDataChannelOpen(); + void onDataChannelClosed(); + void onDataChannelError(std::string err); + void onDataChannelMessage(rtc::string msg); + void onDataChannelMessage(rtc::binary msg); +}; + +} /* namespace webrtc */ +} /* namespace node */ +} /* namespace villas */ + diff --git a/include/villas/nodes/webrtc/signaling_client.hpp b/include/villas/nodes/webrtc/signaling_client.hpp new file mode 100644 index 000000000..6e54df87e --- /dev/null +++ b/include/villas/nodes/webrtc/signaling_client.hpp @@ -0,0 +1,117 @@ +/** WebRTC signaling client + * + * @file + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include + +namespace villas { +namespace node { + +/* Forward declarations */ +class Web; + +namespace webrtc { + +class SignalingClient { + +protected: + struct sul_offsetof_helper { + lws_sorted_usec_list_t sul; /**> Schedule connection retry */ + SignalingClient *self; + } sul_helper; + + uint16_t retry_count; /**> Count of consecutive retries */ + + struct lws *wsi; + struct lws_client_connect_info info; + + /* The retry and backoff policy we want to use for our client connections */ + static constexpr uint32_t backoff_ms[] = { 1<<4, 1<<6, 1<<8, 1<<10, 1<<12, 1<<14, 1<<16 }; + static constexpr lws_retry_bo_t retry = { + .retry_ms_table = backoff_ms, + .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms), + .conceal_count = LWS_ARRAY_SIZE(backoff_ms) + 1, + + .secs_since_valid_ping = 3, /* force PINGs after secs idle */ + .secs_since_valid_hangup = 10, /* hangup after secs idle */ + + .jitter_percent = 20, + }; + + std::function cbMessage; + std::function cbConnected; + std::function cbDisconnected; + std::function cbError; + + Queue outgoingMessages; + + Web *web; + + char *uri; + char *path; + + std::atomic running; + + Logger logger; + + int protocolCallback(struct lws *wsi, enum lws_callback_reasons reason, void *in, size_t len); + + static + void connectStatic(struct lws_sorted_usec_list *sul); + + int receive(void *in, size_t len); + int writable(); + +public: + SignalingClient(const std::string &server, const std::string &session, Web *w); + ~SignalingClient(); + + static + int protocolCallbackStatic(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); + + void connect(); + void disconnect(); + + void sendMessage(SignalingMessage); + + void onMessage(std::function callback) + { + cbMessage = callback; + } + + void onConnected(std::function callback) + { + cbConnected = callback; + } + + void onDisconnected(std::function callback) + { + cbDisconnected = callback; + } + + void onError(std::function callback) + { + cbError = callback; + } +}; + +} /* namespace webrtc */ +} /* namespace node */ +} /* namespace villas */ + diff --git a/include/villas/nodes/webrtc/signaling_message.hpp b/include/villas/nodes/webrtc/signaling_message.hpp new file mode 100644 index 000000000..711bdac56 --- /dev/null +++ b/include/villas/nodes/webrtc/signaling_message.hpp @@ -0,0 +1,59 @@ +/** WebRTC signaling messages. + * + * @file + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace villas { +namespace node { +namespace webrtc { + +struct Connection { + int id; + std::string remote; + std::string userAgent; + std::chrono::time_point created; + + Connection(json_t *j); + json_t * toJSON() const; +}; + +struct RelayMessage { + std::vector servers; + + RelayMessage(json_t *j); +}; + +struct ControlMessage { + int connectionID; + std::vector connections; + + ControlMessage(json_t *j); + json_t * toJSON() const; +}; + +struct SignalingMessage { + std::variant message; + + static SignalingMessage fromJSON(json_t *j); + json_t * toJSON() const; + std::string toString() const; +}; + +} /* namespace webrtc */ +} /* namespace node */ +} /* namespace villas */ diff --git a/lib/nodes/CMakeLists.txt b/lib/nodes/CMakeLists.txt index c0c526853..a0b38ecba 100644 --- a/lib/nodes/CMakeLists.txt +++ b/lib/nodes/CMakeLists.txt @@ -13,12 +13,6 @@ if(WITH_WEB) list(APPEND NODE_SRC api.cpp) endif() -# Enable Golang support -if(WITH_NODE_GO) - list(APPEND NODE_SRC go.cpp) - list(APPEND LIBRARIES villas-go) -endif() - if(LIBNL3_ROUTE_FOUND) list(APPEND LIBRARIES PkgConfig::LIBNL3_ROUTE) endif() @@ -186,12 +180,12 @@ if(WITH_NODE_REDIS) list(APPEND LIBRARIES PkgConfig::HIREDIS PkgConfig::REDISPP) endif() +# Enable WebRTC support +if(WITH_NODE_WEBRTC) + list(APPEND NODE_SRC webrtc.cpp webrtc/signaling_client.cpp webrtc/signaling_message.cpp webrtc/peer_connection.cpp) + list(APPEND LIBRARIES LibDataChannel::LibDataChannel) +endif() + add_library(nodes STATIC ${NODE_SRC}) target_include_directories(nodes PUBLIC ${INCLUDE_DIRS}) target_link_libraries(nodes PUBLIC ${LIBRARIES}) - -if(WITH_NODE_GO) - add_dependencies(nodes villas-go-header) - target_include_directories(nodes PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/../../go/lib) - target_link_libraries(nodes PUBLIC villas-go) -endif() diff --git a/lib/nodes/go.cpp b/lib/nodes/go.cpp deleted file mode 100644 index 2790dc2eb..000000000 --- a/lib/nodes/go.cpp +++ /dev/null @@ -1,288 +0,0 @@ -/** Node-type implemeted in Go language - * - * @file - * @author Steffen Vogel - * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC - * @license Apache 2.0 - *********************************************************************************/ - -#include -#include -#include - -extern "C" { - #include - #include -} - -using namespace villas; -using namespace villas::node; - -void _go_register_node_factory(_go_plugin_list pl, char *name, char *desc, int flags) -{ - auto *plugins = (villas::plugin::List<> *) pl; - plugins->push_back(new villas::node::GoNodeFactory(name, desc, flags)); -} - -_go_logger * _go_logger_get(char *name) -{ - return (_go_logger *) villas::logging.get(name).get(); -} - -void _go_logger_log(_go_logger l, int level, char *msg) -{ - auto *log = (spdlog::logger *) l; - log->log((spdlog::level::level_enum) level, "{}", msg); -} - -GoNode::GoNode(uintptr_t n) : - Node(), - node(n), - formatter(nullptr) -{ } - -GoNode::~GoNode() -{ - GoNodeClose(node); -} - -int GoNode::parse(json_t *json, const uuid_t sn_uuid) -{ - int ret = Node::parse(json, sn_uuid); - if (ret) - return ret; - - GoNodeSetLogger(node, _go_logger_log, logger.get()); - - json_t *json_format = nullptr; - json_error_t err; - - ret = json_unpack_ex(json, &err, 0, "{ s?: o }", - "format", &json_format - ); - if (ret) - throw ConfigError(json, err, "node-config-node-format", "Failed to parse node configuration"); - - formatter = json_format - ? FormatFactory::make(json_format) - : FormatFactory::make("json"); - if (!formatter) - throw ConfigError(json_format, "node-config-node-format", "Invalid format configuration"); - - auto *cfg = json_dumps(json, JSON_COMPACT); - ret = GoNodeParse(node, cfg); - free(cfg); - return ret; -} - -int GoNode::check() -{ - int ret = Node::check(); - if (ret) - return ret; - - return GoNodeCheck(node); -} - -int GoNode::prepare() -{ - int ret = GoNodePrepare(node); - if (ret) - return ret; - - return Node::prepare(); -} - -int GoNode::start() -{ - int ret; - - assert(state == State::PREPARED || - state == State::PAUSED); - - /* Initialize IO */ - formatter->start(getInputSignals(false)); - - ret = GoNodeStart(node); - if (ret) - return ret; - - ret = Node::start(); - if (!ret) - state = State::STARTED; - - return ret; -} - -int GoNode::stop() { - int ret; - - assert(state == State::STARTED || - state == State::PAUSED || - state == State::STOPPING); - - ret = Node::stop(); - if (ret) - return ret; - - ret = GoNodeStop(node); - if (ret) - return ret; - - return 0; -} - -int GoNode::pause() -{ - int ret; - - ret = Node::pause(); - if (ret) - return ret; - - ret = GoNodePause(node); - if (!ret) - state = State::PAUSED; - - return ret; -} - -int GoNode::resume() -{ - int ret; - - ret = Node::resume(); - if (ret) - return ret; - - ret = GoNodeResume(node); - if (!ret) - state = State::STARTED; - - return ret; -} - -const std::string & GoNode::getDetails() -{ - if (_details.empty()) { - auto *d = GoNodeDetails(node); - _details = std::string(d); - free(d); - } - - return _details; -} - -std::vector GoNode::getPollFDs() -{ - auto ret = GoNodeGetPollFDs(node); - if (ret.r1) - return {}; - - auto begin = (int *) ret.r0.data; - auto end = begin + ret.r0.len; - - return std::vector(begin, end); -} - -std::vector GoNode::getNetemFDs() -{ - auto ret = GoNodeGetNetemFDs(node); - if (ret.r1) - return {}; - - auto begin = (int *) ret.r0.data; - auto end = begin + ret.r0.len; - - return std::vector(begin, end); -} - -int GoNode::_read(struct Sample * smps[], unsigned cnt) -{ - int ret; - char data[DEFAULT_FORMAT_BUFFER_LENGTH]; - size_t rbytes; - - auto d = GoNodeRead(node, data, sizeof(data)); - if (d.r1) - return d.r1; - - ret = formatter->sscan(data, d.r0, &rbytes, smps, cnt); - if (ret < 0 || (size_t) d.r0 != rbytes) { - logger->warn("Received invalid packet: ret={}, bytes={}, rbytes={}", ret, d.r0, rbytes); - return ret; - } - - return ret; -} - -int GoNode::_write(struct Sample * smps[], unsigned cnt) -{ - int ret; - char buf[DEFAULT_FORMAT_BUFFER_LENGTH]; - size_t wbytes; - - ret = formatter->sprint(buf, DEFAULT_FORMAT_BUFFER_LENGTH, &wbytes, smps, cnt); - if (ret < 0) - return ret; - - GoSlice slice = { - data: buf, - len: GoInt(wbytes), - cap: DEFAULT_FORMAT_BUFFER_LENGTH - }; - - ret = GoNodeWrite(node, slice); - if (ret) - return ret; - - return cnt; -} - -int GoNode::restart() -{ - assert(state == State::STARTED); - - logger->info("Restarting node"); - - return GoNodeRestart(node); -} - -int GoNode::reverse() -{ - return GoNodeReverse(node); -} - - -Node * GoNodeFactory::make() -{ - auto nt = NewGoNode((char *) name.c_str()); - if (!nt) - return nullptr; - - auto *n = new GoNode(nt); - - init(n); - - GoNodeSetLogger(n->node, _go_logger_log, n->logger.get()); - - return n; -} - -GoPluginRegistry::GoPluginRegistry() { - if (plugin::registry == nullptr) - plugin::registry = new plugin::Registry(); - - plugin::registry->addSubRegistry(this); -} - -villas::plugin::List<> GoPluginRegistry::lookup() -{ - plugin::List<> plugins; - - RegisterGoNodeTypes(_go_register_node_factory, &plugins); - - return plugins; -} - -static GoPluginRegistry pr; diff --git a/lib/nodes/webrtc.cpp b/lib/nodes/webrtc.cpp new file mode 100644 index 000000000..28ced7c30 --- /dev/null +++ b/lib/nodes/webrtc.cpp @@ -0,0 +1,272 @@ +/** Node-type: webrtc + * + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace villas; +using namespace villas::node; +using namespace villas::utils; + +static villas::node::Web *web; + +WebRTCNode::WebRTCNode(const std::string &name) : + Node(name), + server("wss://villas.k8s.eonerc.rwth-aachen.de/ws/signaling"), + wait_seconds(0), + format(nullptr), + queue({}), + pool({}), + dci({}) +{ + dci.reliability.type = rtc::Reliability::Type::Rexmit; +} + +WebRTCNode::~WebRTCNode() +{ + int ret = pool_destroy(&pool); + if (ret) // TODO log + ; +} + +int WebRTCNode::parse(json_t *json, const uuid_t sn_uuid) +{ + int ret = Node::parse(json, sn_uuid); + if (ret) + return ret; + + const char *sess; + const char *svr = nullptr; + int ord = -1; + int &rexmit = dci.reliability.rexmit.emplace(0); + json_t *ice_json = nullptr; + json_t *fmt_json = nullptr; + + json_error_t err; + ret = json_unpack_ex(json, &err, 0, "{ s:s, s?s, s?i, s?i, s?b, s?o }", + "session", &sess, + "server", &svr, + "wait_seconds", &wait_seconds, + "max_retransmits", &rexmit, + "ordered", &ord, + "ice", &ice_json, + "format", &fmt_json + ); + if (ret) + throw ConfigError(json, err, "node-config-node-webrtc"); + + session = sess; + + if (svr) + server = svr; + + if (ord) + dci.reliability.unordered = !ord; + + if (ice_json) { + json_t *json_servers = nullptr; + + ret = json_unpack_ex(ice_json, &err, 0, "{ s?: o }", + "servers", &json_servers + ); + if (ret) + throw ConfigError(json, err, "node-config-node-webrtc-ice"); + + if (json_servers) { + rtcConf.iceServers.clear(); + + if (!json_is_array(json_servers)) + throw ConfigError(json_servers, "node-config-node-webrtc-ice-servers", "ICE Servers must be a an array of server configurations."); + + size_t i; + json_t *json_server; + json_array_foreach(json_servers, i, json_server) { + if (!json_is_string(json_server)) + throw ConfigError(json_server, "node-config-node-webrtc-ice-server", "ICE servers must be provided as STUN/TURN url."); + + std::string uri = json_string_value(json_server); + + rtcConf.iceServers.emplace_back(uri); + } + } + } + + format = fmt_json + ? FormatFactory::make(fmt_json) + : FormatFactory::make("villas.binary"); + + assert(format); + + return 0; +} + +int WebRTCNode::check() +{ + return Node::check(); +} + +int WebRTCNode::prepare() +{ + int ret = Node::prepare(); + if (ret) + return ret; + + format->start(getInputSignals(false), ~(int) SampleFlags::HAS_OFFSET); + + conn = std::make_shared(server, session, rtcConf, web, dci); + + ret = pool_init(&pool, 1024, SAMPLE_LENGTH(getInputSignals(false)->size())); + if (ret) // TODO log + return ret; + + ret = queue_signalled_init(&queue, 1024); + if (ret) // TODO log + return ret; + + conn->onMessage([this](rtc::binary msg){ + int ret; + std::vector smps; + smps.resize(this->in.vectorize); + + ret = sample_alloc_many(&this->pool, smps.data(), smps.size()); + if (ret < 0) // TODO log + return; + + ret = format->sscan((const char *)msg.data(), msg.size(), nullptr, smps.data(), ret); + if (ret < 0) // TODO log + return; + + ret = queue_signalled_push_many(&this->queue, (void **) smps.data(), ret); + if (ret < 0) // TODO log + return; + + this->logger->debug("onMessage(rtc::binary) callback finished pushing {} samples", ret); + }); + + return 0; +} + +int WebRTCNode::start() +{ + int ret = Node::start(); + if (!ret) + state = State::STARTED; + + conn->connect(); + + if (wait_seconds > 0) { + logger->info("Waiting for datachannel..."); + + if (!conn->waitForDataChannel(std::chrono::seconds { wait_seconds })) { + throw RuntimeError { "Waiting for datachannel timed out after {} seconds", wait_seconds }; + } + } + + return 0; +} + +int WebRTCNode::stop() +{ + conn->disconnect(); + return Node::stop(); +} + +// int WebRTCNode::pause() +// { +// // TODO add implementation here +// return 0; +// } + +// int WebRTCNode::resume() +// { +// // TODO add implementation here +// return 0; +// } + +// int WebRTCNode::restart() +// { +// // TODO add implementation here +// return 0; +// } + +// int WebRTCNode::reverse() +// { +// // TODO add implementation here +// return 0; +// } + +std::vector WebRTCNode::getPollFDs() +{ + return { queue_signalled_fd(&queue) }; +} + +// std::vector WebRTCNode::getNetemFDs() +// { +// // TODO add implementation here +// return {}; +// } + +// struct villas::node::memory::Type * WebRTCNode::getMemoryType() +// { +// // TODO add implementation here +// } + +const std::string & WebRTCNode::getDetails() +{ + details = fmt::format(""); + return details; +} + +int WebRTCNode::_read(struct Sample *smps[], unsigned cnt) +{ + std::vector smpt; + smpt.resize(cnt); + + int pulled = queue_signalled_pull_many(&queue, (void **) smpt.data(), smpt.size()); + + sample_copy_many(smps, smpt.data(), pulled); + sample_decref_many(smpt.data(), pulled); + + return pulled; +} + +int WebRTCNode::_write(struct Sample *smps[], unsigned cnt) +{ + rtc::binary buf; + size_t wbytes; + + buf.resize(4 * 1024); + int ret = format->sprint((char *) buf.data(), buf.size(), &wbytes, smps, cnt); + if (ret < 0) // TODO log + return ret; + + buf.resize(wbytes); + conn->sendMessage(buf); + + return ret; +} + +int WebRTCNodeFactory::start(SuperNode *sn) +{ + web = sn->getWeb(); + if (!web->isEnabled()) + return -1; + + return 0; +} + + +static WebRTCNodeFactory p; diff --git a/lib/nodes/webrtc/peer_connection.cpp b/lib/nodes/webrtc/peer_connection.cpp new file mode 100644 index 000000000..1c91ce955 --- /dev/null +++ b/lib/nodes/webrtc/peer_connection.cpp @@ -0,0 +1,411 @@ +/** WebRTC peer connection + * + * @file + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::placeholders; + +using namespace villas; +using namespace villas::node; +using namespace villas::node::webrtc; + +/* + * libdatachannel defines the operator<< overloads required to format + * rtc::PeerConnection::State and similar in the global namespace. + * But C++ ADL based overload set construction does not find these operators, + * if these are invoked in the spdlog/fmt libraries. + * + * See this issue for a short explaination of ADL errors: + * https://github.com/gabime/spdlog/issues/1227#issuecomment-532009129 + * + * Adding the global ::operator<< overload set to the namespace rtc where + * the data structures are defined, allows ADL to pick these up in spdlog/fmt. + */ +namespace rtc { + using ::operator<<; +} + +PeerConnection::PeerConnection(const std::string &server, const std::string &session, rtc::Configuration cfg, Web *w, rtc::DataChannelInit d) : + web(w), + dataChannelInit(d), + defaultConfig(cfg), + logger(logging.get("webrtc:pc")) +{ + client = std::make_shared(server, session, web); + client->onConnected([this](){ this->onSignalingConnected(); }); + client->onDisconnected([this](){ this->onSignalingDisconnected(); }); + client->onError([this](auto err){ this->onSignalingError(std::move(err)); }); + client->onMessage([this](auto msg){ this->onSignalingMessage(std::move(msg)); }); + + auto lock = std::unique_lock { mutex }; + resetConnectionAndStandby(lock); +} + +PeerConnection::~PeerConnection() +{ +} + +bool PeerConnection::waitForDataChannel(std::chrono::seconds timeout) +{ + auto lock = std::unique_lock { mutex }; + + auto deadline = std::chrono::steady_clock::now() + timeout; + + return startupCondition.wait_until(lock, deadline, [this](){ return this->stopStartup; }); +} + +void PeerConnection::notifyStartup() +{ + stopStartup = true; + startupCondition.notify_all(); +} + +void PeerConnection::onMessage(std::function callback) +{ + auto lock = std::unique_lock { mutex }; + + onMessageCallback = callback; +} + +void PeerConnection::sendMessage(rtc::binary msg) +{ + auto lock = std::unique_lock { mutex }; + + if (chan && chan->isOpen()) { + chan->send(msg); + warnNotConnected = true; + } else if (warnNotConnected) { + logger->warn("Dropping messages. No peer connected."); + warnNotConnected = false; + } +} + +void PeerConnection::connect() +{ + client->connect(); +} + +void PeerConnection::disconnect() +{ + client->disconnect(); +} + +void PeerConnection::resetConnection(std::unique_lock &lock) +{ + lock.unlock(); + chan.reset(); + conn.reset(); + lock.lock(); +} + +void PeerConnection::resetConnectionAndStandby(std::unique_lock &lock) +{ + if (!standby) + logger->info("Going to standby"); + + standby = true; + first = false; + firstID = INT_MAX; + secondID = INT_MAX; + warnNotConnected = false; + + resetConnection(lock); +} + +void PeerConnection::setupPeerConnection(std::shared_ptr pc) +{ + logger->debug("Setup {} peer connection", pc ? "existing" : "new"); + + auto config = defaultConfig; + config.iceServers.insert(std::end(config.iceServers), std::begin(extraServers), std::end(extraServers)); + + conn = pc ? std::move(pc) : std::make_shared(config); + conn->onLocalDescription([this](auto desc){ this->onLocalDescription(std::move(desc)); }); + conn->onLocalCandidate([this](auto cand){ this->onLocalCandidate(std::move(cand)); }); + conn->onDataChannel([this](auto channel){ this->onDataChannel(std::move(channel)); }); + conn->onGatheringStateChange([this](auto state){ this->onGatheringStateChange(std::move(state)); }); + conn->onSignalingStateChange([this](auto state){ this->onSignalingStateChange(std::move(state)); }); + conn->onStateChange([this](auto state){ this->onConnectionStateChange(std::move(state)); }); +} + +void PeerConnection::setupDataChannel(std::shared_ptr dc) +{ + logger->debug("Setup {} data channel", dc ? "existing" : "new"); + + assert(conn); + chan = dc ? std::move(dc) : conn->createDataChannel("villas", dataChannelInit); + chan->onMessage( + [this](rtc::binary msg){ this->onDataChannelMessage(std::move(msg)); }, + [this](rtc::string msg){ this->onDataChannelMessage(std::move(msg)); } + ); + chan->onOpen([this](){ this->onDataChannelOpen(); }); + chan->onClosed([this](){ this->onDataChannelClosed(); }); + chan->onError([this](auto err){ this->onDataChannelError(std::move(err)); }); + + // If this node has it's data channel set up, don't accept any new ones + conn->onDataChannel(nullptr); +} + +void PeerConnection::onLocalDescription(rtc::Description desc) +{ + logger->debug("New local description: type={} sdp=\n{}", desc.typeString(), desc.generateSdp()); + + auto lock = std::unique_lock { mutex }; + + client->sendMessage({ desc }); +} + +void PeerConnection::onLocalCandidate(rtc::Candidate cand) +{ + logger->debug("New local candidate: {}", std::string { cand }); + + auto lock = std::unique_lock { mutex }; + + client->sendMessage({ cand }); +} + +void PeerConnection::onConnectionStateChange(rtc::PeerConnection::State state) +{ + logger->debug("Connection State changed: {}", state); + + auto lock = std::unique_lock { mutex }; + + switch (state) { + case rtc::PeerConnection::State::New: { + logger->debug("New peer connection"); + break; + } + + case rtc::PeerConnection::State::Connecting: { + logger->debug("Peer connection connecting."); + break; + } + + case rtc::PeerConnection::State::Connected: { + rtc::Candidate local, remote; + std::optional rtt = conn->rtt(); + if (conn->getSelectedCandidatePair(&local, &remote)) { + std::stringstream l, r; + l << local, r << remote; + logger->debug( + "Peer connection connected:\n" + "local: {}\n" + "remote: {}\n" + "bytes sent: {} / bytes received: {} / rtt: {}\n", + l.str(), + r.str(), + conn->bytesSent(), + conn->bytesReceived(), + rtt.value_or(decltype(rtt)::value_type { 0 }) + ); + } else { + logger->debug( + "Peer connection connected.\n" + "Could not get candidate pair info.\n" + ); + } + break; + } + + case rtc::PeerConnection::State::Disconnected: + case rtc::PeerConnection::State::Failed: { + logger->debug("Closing peer connection"); + break; + } + + case rtc::PeerConnection::State::Closed: { + logger->debug("Closed peer connection"); + resetConnectionAndStandby(lock); + break; + } + } +} + +void PeerConnection::onSignalingStateChange(rtc::PeerConnection::SignalingState state) +{ + std::stringstream s; + s << state; + logger->debug("Signaling state changed: {}", s.str()); +} + +void PeerConnection::onGatheringStateChange(rtc::PeerConnection::GatheringState state) +{ + std::stringstream s; + s << state; + logger->debug("Gathering state changed: {}", s.str()); +} + +void PeerConnection::onSignalingConnected() +{ + logger->debug("Signaling connection established"); +} + +void PeerConnection::onSignalingDisconnected() +{ + logger->debug("Signaling connection closed"); + + auto lock = std::unique_lock { mutex }; + + resetConnectionAndStandby(lock); +} + +void PeerConnection::onSignalingError(std::string err) +{ + logger->debug("Signaling connection error: {}", err); + + auto lock = std::unique_lock { mutex }; + + resetConnectionAndStandby(lock); +} + +void PeerConnection::onSignalingMessage(SignalingMessage msg) +{ + logger->debug("Signaling message received: {}", msg.toString()); + + auto lock = std::unique_lock { mutex }; + + std::visit(villas::utils::overloaded { + [&](RelayMessage &c){ + extraServers = std::move(c.servers); + }, + + [&](ControlMessage &c){ + auto const &id = c.connectionID; + + if (c.connections.size() < 2) { + resetConnectionAndStandby(lock); + return; + } + + auto fst = INT_MAX, snd = INT_MAX; + for (auto &c : c.connections) { + if (c.id < fst) { + snd = fst; + fst = c.id; + } else if (c.id < snd) { + snd = c.id; + } + } + + standby = (id != fst && id != snd); + + if (standby) { + logger->error("There are already two peers connected to this session. Waiting in standby."); + return; + } + + if (fst == firstID && snd == secondID) { + logger->debug("Ignoring control message. This connection is already being established."); + return; + } + + resetConnection(lock); + + first = (id == fst); + firstID = fst; + secondID = snd; + + setupPeerConnection(); + + if (!first) { + setupDataChannel(); + conn->setLocalDescription(rtc::Description::Type::Offer); + } + + logger->trace("New connection pair: first={}, second={}, I am {}", firstID, secondID, first ? "first" : "second"); + }, + + [&](rtc::Description d){ + if (standby || !conn || (!first && d.type() == rtc::Description::Type::Offer)) + return; + + conn->setRemoteDescription(d); + }, + + [&](rtc::Candidate c){ + if (standby || !conn) + return; + + conn->addRemoteCandidate(c); + }, + + [&](auto other){ + logger->warn("unknown signaling message"); + } + }, msg.message); +} + +void PeerConnection::onDataChannel(std::shared_ptr dc) +{ + logger->debug("New data channel: {}protocol={}, max_msg_size={}, label={}", + dc->id() && dc->stream() ? fmt::format("id={}, stream={}, ", + *(dc->id()), + *(dc->stream()) + ) : "", + dc->protocol(), + dc->maxMessageSize(), + dc->label() + ); + + auto lock = std::unique_lock { mutex }; + + setupDataChannel(std::move(dc)); +} + +void PeerConnection::onDataChannelOpen() +{ + logger->debug("Datachannel opened"); + + auto lock = std::unique_lock { mutex }; + + chan->send("Hello from VILLASnode"); + + notifyStartup(); +} + +void PeerConnection::onDataChannelClosed() +{ + logger->debug("Datachannel closed"); + + auto lock = std::unique_lock { mutex }; + + resetConnectionAndStandby(lock); +} + +void PeerConnection::onDataChannelError(std::string err) +{ + logger->error("Datachannel error: {}", err); + + auto lock = std::unique_lock { mutex }; + + resetConnectionAndStandby(lock); +} + +void PeerConnection::onDataChannelMessage(rtc::string msg) +{ + logger->info("Received: {}", msg); +} + +void PeerConnection::onDataChannelMessage(rtc::binary msg) +{ + logger->trace("Received binary data"); + + auto lock = std::unique_lock { mutex }; + + if (onMessageCallback) + onMessageCallback(msg); +} diff --git a/lib/nodes/webrtc/signaling_client.cpp b/lib/nodes/webrtc/signaling_client.cpp new file mode 100644 index 000000000..4bb8ba49e --- /dev/null +++ b/lib/nodes/webrtc/signaling_client.cpp @@ -0,0 +1,223 @@ +/** WebRTC signaling client + * + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#include +#include +#include +#include + +using namespace villas; +using namespace villas::node; +using namespace villas::node::webrtc; + +SignalingClient::SignalingClient(const std::string &srv, const std::string &sess, Web *w) : + retry_count(0), + web(w), + running(false), + logger(logging.get("webrtc:signal")) +{ + int ret; + const char *prot, *a, *p; + + memset(&info, 0, sizeof(info)); + + ret = asprintf(&uri, "%s/%s", srv.c_str(), sess.c_str()); + if (ret < 0) + throw RuntimeError { "Could not format signaling server uri" }; + + ret = lws_parse_uri(uri, &prot, &a, &info.port, &p); + if (ret) + throw RuntimeError("Failed to parse WebSocket URI: '{}'", uri); + + ret = asprintf(&path, "/%s", p); + if (ret < 0) + throw RuntimeError { "Could not format signaling client path" }; + + info.ssl_connection = !strcmp(prot, "https"); + info.address = a; + info.path = path; + info.host = a; + info.origin = a; + info.protocol = "webrtc-signaling"; + info.local_protocol_name = "webrtc-signaling"; + info.pwsi = &wsi; + info.retry_and_idle_policy = &retry; + info.ietf_version_or_minus_one = -1; + info.userdata = this; + + sul_helper.self = this; + sul_helper.sul = {}; +} + +SignalingClient::~SignalingClient() +{ + disconnect(); + + free(path); + free(uri); +} + +void SignalingClient::connect() +{ + running = true; + + info.context = web->getContext(); + + lws_sul_schedule(info.context, 0, &sul_helper.sul, connectStatic, 1 * LWS_US_PER_SEC); +} + +void SignalingClient::disconnect() +{ + running = false; + // TODO: + // - wait for connectStatic to exit + // - close LWS connection + if (wsi) + lws_callback_on_writable(wsi); +} + +void SignalingClient::connectStatic(struct lws_sorted_usec_list *sul) +{ + auto *sh = lws_container_of(sul, struct sul_offsetof_helper, sul); + auto *c = sh->self; + + if (!lws_client_connect_via_info(&c->info)) { + /* Failed... schedule a retry... we can't use the _retry_wsi() + * convenience wrapper api here because no valid wsi at this + * point. + */ + if (lws_retry_sul_schedule(c->info.context, 0, sul, nullptr, connectStatic, &c->retry_count)) + c->logger->error("Signaling connection attempts exhausted"); + } +} + +int SignalingClient::protocolCallbackStatic(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) +{ + auto *c = reinterpret_cast(user); + + return c->protocolCallback(wsi, reason, in, len); +} + +int SignalingClient::protocolCallback(struct lws *wsi, enum lws_callback_reasons reason, void *in, size_t len) +{ + int ret; + + switch (reason) { + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + cbError(in ? (char *) in : "unknown error"); + goto do_retry; + + case LWS_CALLBACK_CLIENT_RECEIVE: + ret = receive(in, len); + if (ret) + goto do_retry; + + break; + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + retry_count = 0; + cbConnected(); + break; + + case LWS_CALLBACK_CLIENT_CLOSED: + cbDisconnected(); + goto do_retry; + + case LWS_CALLBACK_CLIENT_WRITEABLE: { + ret = writable(); + if (ret) + goto do_retry; + + break; + } + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, this, in, len); + +do_retry: + logger->info("Attempting to reconnect..."); + + /* Retry the connection to keep it nailed up + * + * For this example, we try to conceal any problem for one set of + * backoff retries and then exit the app. + * + * If you set retry.conceal_count to be larger than the number of + * elements in the backoff table, it will never give up and keep + * retrying at the last backoff delay plus the random jitter amount. + */ + if (lws_retry_sul_schedule_retry_wsi(wsi, &sul_helper.sul, connectStatic, &retry_count)) + logger->error("Signaling connection attempts exhaused"); + + return 0; +} + +int SignalingClient::writable() +{ + if (!running) { + auto reason = "Signaling Client Closing"; + lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, (unsigned char *) reason, strlen(reason)); + return 0; + } + + // Skip if we have nothing to send + if (outgoingMessages.empty()) { + return 0; + } + + auto msg = outgoingMessages.pop(); + auto *jsonMsg = msg.toJSON(); + + if (!jsonMsg) { + return 0; + } + + char buf[LWS_PRE + 1024]; + auto len = json_dumpb(jsonMsg, buf + LWS_PRE, 1024, JSON_INDENT(2)); + + auto ret = lws_write(wsi, (unsigned char *) buf + LWS_PRE, len, LWS_WRITE_TEXT); + if (ret < 0) + return ret; + + logger->debug("Signaling message sent: {:.{}}", buf + LWS_PRE, len); + + // Reschedule callback if there are more messages to be send + if (!outgoingMessages.empty()) + lws_callback_on_writable(wsi); + + return 0; +} + +int SignalingClient::receive(void *in, size_t len) +{ + json_error_t err; + json_t *json = json_loadb((char *) in, len, 0, &err); + if (!json) { + logger->error("Failed to decode json: {} at ({}:{})", err.text, err.line, err.column); + return -1; + } + + logger->debug("Signaling message received: {:.{}}", (char *)in, len); + + cbMessage(SignalingMessage::fromJSON(json)); + + json_decref(json); + + return 0; +} + +void SignalingClient::sendMessage(SignalingMessage msg) +{ + outgoingMessages.push(msg); + + web->callbackOnWritable(wsi); +} diff --git a/lib/nodes/webrtc/signaling_message.cpp b/lib/nodes/webrtc/signaling_message.cpp new file mode 100644 index 000000000..ddbee7e67 --- /dev/null +++ b/lib/nodes/webrtc/signaling_message.cpp @@ -0,0 +1,212 @@ +/** WebRTC signaling messages. + * + * @author Steffen Vogel + * @author Philipp Jungkamp + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @copyright 2023, OPAL-RT Germany GmbH + * @license Apache 2.0 + *********************************************************************************/ + +#include +#include +#include +#include +#include + +using namespace villas; +using namespace villas::node; +using namespace villas::node::webrtc; + +json_t * Connection::toJSON() const +{ + return json_pack("{ s:i, s:s, s:s, s:s }", + "id", id, + "remote", remote.c_str(), + "user_agent", userAgent.c_str(), + "created", "" // TODO: create json timestamp + ); +} + +Connection::Connection(json_t *json) +{ + const char *rem, *ua, *ts; + + int ret = json_unpack(json, "{ s:i, s:s, s:s, s:s }", + "id", &id, + "remote", &rem, + "user_agent", &ua, + "created", &ts + ); + if (ret) + throw RuntimeError("Failed to decode signaling message"); + + remote = rem; + userAgent = ua; + + // TODO: created +} + +RelayMessage::RelayMessage(json_t *json) +{ + + if (!json_is_array(json)) + throw RuntimeError("Failed to decode signaling message"); + + int ret; + char *url; + char *user; + char *pass; + char *realm; + char *expires; + json_t *server_json; + size_t i; + json_array_foreach(json, i, server_json) { + ret = json_unpack(server_json, "{ s:s, s:s, s:s, s:s, s:s }", + "url", &url, + "user", &user, + "pass", &pass, + "realm", &realm, + "expires", &expires + ); + if (ret) + throw RuntimeError("Failed to decode signaling message"); + + auto &server = servers.emplace_back(url); + server.username = user; + server.password = pass; + + // TODO: warn about unsupported realm + // TODO: log info about expires time + } +} + +json_t * ControlMessage::toJSON() const +{ + json_t *json_connections = json_array(); + + for (auto &c : connections) { + json_t *json_connection = c.toJSON(); + + json_array_append_new(json_connections, json_connection); + } + + return json_pack("{ s:i, s:o }", + "connection_id", connectionID, + "connections", json_connections + ); +} + +ControlMessage::ControlMessage(json_t *j) +{ + int ret; + + json_t *json_connections; + + ret = json_unpack(j, "{ s:i, s:o }", + "connection_id", &connectionID, + "connections", &json_connections + ); + if (ret) + throw RuntimeError("Failed to decode signaling message"); + + if (!json_is_array(json_connections)) + throw RuntimeError("Failed to decode signaling message"); + + json_t *json_connection; + size_t i; + // cppcheck-suppress unknownMacro + json_array_foreach(json_connections, i, json_connection) + connections.emplace_back(json_connection); +} + +json_t * SignalingMessage::toJSON() const +{ + return std::visit(villas::utils::overloaded { + [](ControlMessage const &c){ + return json_pack("{ s:o }", "control", c.toJSON()); + }, + [](rtc::Description const &d){ + return json_pack("{ s:{ s:s, s:s } }", "description", + "spd", d.generateSdp().c_str(), + "type", d.typeString().c_str() + ); + }, + [](rtc::Candidate const &c){ + return json_pack("{ s:{ s:s, s:s } }", "candidate", + "spd", c.candidate().c_str(), + "mid", c.mid().c_str() + ); + }, + [](auto &other){ + return (json_t *) { nullptr }; + } + }, message); +} + +std::string SignalingMessage::toString() const +{ + return std::visit(villas::utils::overloaded { + [](RelayMessage const &r){ + return fmt::format("type=relay"); + }, + [](ControlMessage const &c){ + return fmt::format("type=control, control={}", json_dumps(c.toJSON(), 0)); + }, + [](rtc::Description const &d){ + return fmt::format("type=description, type={}, spd=\n{}", d.typeString(), d.generateSdp()); + }, + [](rtc::Candidate const &c){ + return fmt::format("type=candidate, mid={}, spd=\n{}", c.candidate(), c.mid()); + }, + [](auto other){ + return fmt::format("invalid signaling message"); + } + }, message); +} + +SignalingMessage SignalingMessage::fromJSON(json_t *json) +{ + auto self = SignalingMessage { std::monostate() }; + + // Relay message + json_t *rlys = nullptr; + // Control message + json_t *ctrl = nullptr; + // Candidate message + const char *cand = nullptr; + const char *mid = nullptr; + // Description message + const char *desc = nullptr; + const char *typ = nullptr; + + int ret = json_unpack(json, "{ s?o, s?o, s?{ s:s, s:s }, s?{ s:s, s:s } }", + "servers", &rlys, + "control", &ctrl, + "candidate", + "spd", &cand, + "mid", &mid, + "description", + "spd", &desc, + "type", &typ + ); + + // Exactly 1 field may be specified + const void *fields[] = { ctrl, cand, desc }; + if (ret || std::count(std::begin(fields), std::end(fields), nullptr) < std::make_signed_t(std::size(fields)) - 1) + throw RuntimeError("Failed to decode signaling message"); + + if (rlys) { + self.message.emplace(rlys); + } + else if (ctrl) { + self.message.emplace(ctrl); + } + else if (cand) { + self.message.emplace(cand, mid); + } + else if (desc) { + self.message.emplace(desc, typ); + } + + return self; +} diff --git a/lib/web.cpp b/lib/web.cpp index 85ef147c1..c4bc4db67 100644 --- a/lib/web.cpp +++ b/lib/web.cpp @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -15,6 +16,10 @@ #include #include +#ifdef WITH_NODE_WEBRTC + #include +#endif + using namespace villas; using namespace villas::node; @@ -45,8 +50,16 @@ lws_protocols protocols[] = { .rx_buffer_size = 0 }, #endif /* WITH_NODE_WEBSOCKET */ +#ifdef WITH_NODE_WEBRTC { - .name = nullptr /* terminator */ + .name = "webrtc-signaling", + .callback = webrtc::SignalingClient::protocolCallbackStatic, + .per_session_data_size = sizeof(webrtc::SignalingClient), + .rx_buffer_size = 0 + }, +#endif + { + .name = nullptr, } }; diff --git a/packaging/deps.sh b/packaging/deps.sh index e4dfbecdf..b8a792e0b 100644 --- a/packaging/deps.sh +++ b/packaging/deps.sh @@ -245,7 +245,7 @@ if ! pkg-config "libxil >= 1.0.0" && \ fi # Build & Install hiredis -if ! pkg-config "hiredis>1.0.0" && \ +if ! pkg-config "hiredis >= 1.0.0" && \ [ -z "${SKIP_HIREDIS}" -a -z "${SKIP_REDIS}" ]; then git clone ${GIT_OPTS} --branch v1.0.0 https://github.com/redis/hiredis.git mkdir -p hiredis/build @@ -316,6 +316,26 @@ if ! pkg-config "libwebsockets >= 4.3.0" && \ popd fi +# Build & Install libdatachannel +if ! cmake --find-package -DNAME=LibDataChannel -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST && \ + [ -z "${SKIP_LIBDATACHANNEL}" ]; then + git clone ${GIT_OPTS} --branch v0.18.4 https://github.com/paullouisageneau/libdatachannel && pushd libdatachannel + git submodule update --init --recursive --depth 1 + mkdir build && pushd build + + if pkg-config "nice >= 0.1.16"; then + CMAKE_DATACHANNEL_USE_NICE=-DUSE_NICE=ON + fi + + cmake -DNO_MEDIA=ON \ + -DNO_WEBSOCKET=ON \ + ${CMAKE_DATACHANNEL_USE_NICE} \ + ${CMAKE_OPTS} .. + + make ${MAKE_OPTS} install + popd; popd +fi + popd rm -rf ${DIR} diff --git a/packaging/docker/Dockerfile.debian b/packaging/docker/Dockerfile.debian index f2aa59c7f..f6ad41769 100644 --- a/packaging/docker/Dockerfile.debian +++ b/packaging/docker/Dockerfile.debian @@ -49,7 +49,8 @@ RUN apt-get update && \ libfmt-dev \ libspdlog-dev \ liblua5.3-dev \ - libhiredis-dev + libhiredis-dev \ + libnice-dev # Add local and 64-bit locations to linker paths ENV echo /usr/local/lib >> /etc/ld.so.conf && \ diff --git a/packaging/docker/Dockerfile.fedora b/packaging/docker/Dockerfile.fedora index 03c15f742..e48548e91 100644 --- a/packaging/docker/Dockerfile.fedora +++ b/packaging/docker/Dockerfile.fedora @@ -24,13 +24,11 @@ RUN dnf -y install \ # Several tools only needed for developement and testing RUN dnf -y install \ - doxygen dia graphviz \ openssh-clients \ - rpmdevtools rpm-build \ jq nmap-ncat \ iproute iproute-tc \ python-pip \ - valgrind gdb gdb-gdbserver \ + gdb gdb-gdbserver \ cppcheck \ xmlto dblatex rubygem-asciidoctor \ psmisc procps-ng \ @@ -65,7 +63,8 @@ RUN dnf -y install \ librdmacm-devel \ libusb-devel \ lua-devel \ - hiredis-devel + hiredis-devel \ + libnice-devel # Add local and 64-bit locations to linker paths RUN echo /usr/local/lib >> /etc/ld.so.conf && \ diff --git a/packaging/docker/Dockerfile.rocky b/packaging/docker/Dockerfile.rocky index 30bea298e..ea0fd1212 100644 --- a/packaging/docker/Dockerfile.rocky +++ b/packaging/docker/Dockerfile.rocky @@ -46,7 +46,8 @@ RUN dnf -y install \ librdmacm-devel \ libusb1-devel \ lua-devel \ - hiredis-devel + hiredis-devel \ + libnice-devel # Add local and 64-bit locations to linker paths ENV echo /usr/local/lib >> /etc/ld.so.conf && \ diff --git a/packaging/docker/Dockerfile.ubuntu b/packaging/docker/Dockerfile.ubuntu index 50136383a..870e76a0c 100644 --- a/packaging/docker/Dockerfile.ubuntu +++ b/packaging/docker/Dockerfile.ubuntu @@ -51,7 +51,8 @@ RUN apt-get update && \ libfmt-dev \ libspdlog-dev \ liblua5.3-dev \ - libhiredis-dev + libhiredis-dev \ + libnice-dev # Add local and 64-bit locations to linker paths ENV echo /usr/local/lib >> /etc/ld.so.conf && \