From 35713550fb57b8dea527b3084cddc69275b9dbd4 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 3 Feb 2021 18:50:04 +0100 Subject: [PATCH 01/68] - updated windows socket code (should fix #301) --- hal/socket/win32/socket_win32.c | 59 ++++++++++++++++++++++++--------- hal/time/win32/time.c | 4 +-- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/hal/socket/win32/socket_win32.c b/hal/socket/win32/socket_win32.c index 232ba17f..411265d8 100644 --- a/hal/socket/win32/socket_win32.c +++ b/hal/socket/win32/socket_win32.c @@ -106,7 +106,7 @@ Handleset_waitReady(HandleSet self, unsigned int timeoutMs) { int result; - if ((self != NULL) && (self->maxHandle >= 0)) { + if ((self != NULL) && (self->maxHandle != INVALID_SOCKET)) { struct timeval timeout; timeout.tv_sec = timeoutMs / 1000; @@ -136,12 +136,14 @@ static int socketCount = 0; void Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count) { + (void)count; /* not supported in windows socket API */ + struct tcp_keepalive keepalive; DWORD retVal=0; keepalive.onoff = 1; - keepalive.keepalivetime = CONFIG_TCP_KEEPALIVE_IDLE * 1000; - keepalive.keepaliveinterval = CONFIG_TCP_KEEPALIVE_INTERVAL * 1000; + keepalive.keepalivetime = idleTime * 1000; + keepalive.keepaliveinterval = interval * 1000; if (WSAIoctl(self->fd, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &retVal, NULL, NULL) == SOCKET_ERROR) @@ -192,7 +194,8 @@ prepareServerAddress(const char* address, int port, struct sockaddr_in* sockaddr return true; } -static bool wsaStartUp() +static bool +wsaStartUp(void) { if (wsaStartupCalled == false) { int ec; @@ -213,7 +216,8 @@ static bool wsaStartUp() return true; } -static void wsaShutdown() +static void +wsaShutdown(void) { if (wsaStartupCalled) { if (socketCount == 0) { @@ -286,13 +290,11 @@ ServerSocket_listen(ServerSocket self) Socket ServerSocket_accept(ServerSocket self) { - int fd; - Socket conSocket = NULL; - fd = accept(self->fd, NULL, NULL); + SOCKET fd = accept(self->fd, NULL, NULL); - if (fd >= 0) { + if (fd != INVALID_SOCKET) { conSocket = (Socket) GLOBAL_CALLOC(1, sizeof(struct sSocket)); conSocket->fd = fd; @@ -339,7 +341,7 @@ TcpSocket_create() if (wsaStartUp() == false) return NULL; - int sock = socket(AF_INET, SOCK_STREAM, 0); + SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock != INVALID_SOCKET) { self = (Socket) GLOBAL_MALLOC(sizeof(struct sSocket)); @@ -623,9 +625,9 @@ UdpSocket_create() { UdpSocket self = NULL; - int sock = socket(AF_INET, SOCK_DGRAM, 0); + SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock != -1) { + if (sock != INVALID_SOCKET) { self = (UdpSocket) GLOBAL_MALLOC(sizeof(struct sSocket)); self->fd = sock; @@ -697,11 +699,12 @@ UdpSocket_sendTo(UdpSocket self, const char* address, int port, uint8_t* msg, in } int -UdpSocket_receiveFrom(UdpSocket self, char** address, int maxAddrSize, uint8_t* msg, int msgSize) +UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize) { - struct sockaddr_in remoteAddress; + struct sockaddr_storage remoteAddress; + socklen_t structSize = sizeof(struct sockaddr_storage); - int result = recvfrom(self->fd, (char*) msg, msgSize, 0, NULL, NULL); + int result = recvfrom(self->fd, (char*) msg, msgSize, 0, (struct sockaddr*)&remoteAddress, &structSize); if (result == 0) /* peer has closed socket */ return -1; @@ -713,5 +716,31 @@ UdpSocket_receiveFrom(UdpSocket self, char** address, int maxAddrSize, uint8_t* return -1; } + if (address) { + bool isIPv6; + char addrString[INET6_ADDRSTRLEN + 7]; + int port; + + if (remoteAddress.ss_family == AF_INET) { + struct sockaddr_in* ipv4Addr = (struct sockaddr_in*) &remoteAddress; + port = ntohs(ipv4Addr->sin_port); + inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN); + isIPv6 = false; + } + else if (remoteAddress.ss_family == AF_INET6) { + struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*) &remoteAddress; + port = ntohs(ipv6Addr->sin6_port); + inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN); + isIPv6 = true; + } + else + return result ; + + if (isIPv6) + snprintf(address, maxAddrSize, "[%s]:%i", addrString, port); + else + snprintf(address, maxAddrSize, "%s:%i", addrString, port); + } + return result; } diff --git a/hal/time/win32/time.c b/hal/time/win32/time.c index 0b182d20..17eaf65d 100644 --- a/hal/time/win32/time.c +++ b/hal/time/win32/time.c @@ -33,7 +33,7 @@ Hal_getTimeInMs() FILETIME ft; uint64_t now; - static const uint64_t DIFF_TO_UNIXTIME = 11644473600000LL; + static const uint64_t DIFF_TO_UNIXTIME = 11644473600000ULL; GetSystemTimeAsFileTime(&ft); @@ -47,7 +47,7 @@ Hal_getTimeInNs() { FILETIME ft; - static const uint64_t DIFF_TO_UNIXTIME = 11644473600000000000LL; + static const uint64_t DIFF_TO_UNIXTIME = 11644473600000000000ULL; GetSystemTimeAsFileTime(&ft); From da9b77af7a1c790238e348ae5376729154ad8431 Mon Sep 17 00:00:00 2001 From: Mikael Bourhis Date: Tue, 2 Feb 2021 10:59:28 +0100 Subject: [PATCH 02/68] Python wrapper: add handler class for the reception of CommandTermination events --- .../eventHandlers/commandTermHandler.hpp | 68 +++++++++++++++++++ pyiec61850/iec61850.i | 3 + 2 files changed, 71 insertions(+) create mode 100644 pyiec61850/eventHandlers/commandTermHandler.hpp diff --git a/pyiec61850/eventHandlers/commandTermHandler.hpp b/pyiec61850/eventHandlers/commandTermHandler.hpp new file mode 100644 index 00000000..2595f15e --- /dev/null +++ b/pyiec61850/eventHandlers/commandTermHandler.hpp @@ -0,0 +1,68 @@ +#ifndef PYIEC61850_COMMANDTERMHANDLER_HPP +#define PYIEC61850_COMMANDTERMHANDLER_HPP + +#include "eventHandler.hpp" + +/* + * Abstract class for processing the received 'Command Termination' events. + */ +class CommandTermHandler: public EventHandler { + public: + virtual ~CommandTermHandler() {} + + virtual void setReceivedData(void *i_data_p) + { + // copy the received data + ControlObjectClient *l_my_data_p = static_cast(i_data_p); + _libiec61850_control_object_client = *l_my_data_p; + } + + ControlObjectClient _libiec61850_control_object_client; +}; + + +/* + * Class for the subscription to the 'Command Termination' events + */ +class CommandTermSubscriber: public EventSubscriber { + public: + + virtual void subscribe() { + // install the libiec61850 callback: + // the 'function pointer' is the 'static' method of this class + ControlObjectClient_setCommandTerminationHandler( + m_libiec61850_control_object_client, + CommandTermSubscriber::triggerCommandTermHandler, + NULL); + } + + // Static method: it is the 'callback' for libiec61850 in C + static void triggerCommandTermHandler(void *parameter, ControlObjectClient connection) + { + PyThreadStateLock PyThreadLock; + + // TODO: search the appropriate 'EventSubscriber' object + if (m_last_created_event_subscriber) { + EventHandler *l_event_handler_p = m_last_created_event_subscriber->getEventHandler(); + if (l_event_handler_p) { + l_event_handler_p->setReceivedData(&connection); + l_event_handler_p->trigger(); + } + else { + printf("The EventHandler is undefined\n"); + } + } + } + + // Setters + void setLibiec61850ControlObjectClient(const ControlObjectClient &i_libiec61850_control_object_client) + { + m_libiec61850_control_object_client = i_libiec61850_control_object_client; + } + + protected: + // Parameters + ControlObjectClient m_libiec61850_control_object_client; +}; + +#endif diff --git a/pyiec61850/iec61850.i b/pyiec61850/iec61850.i index 71aceaef..64cd3298 100644 --- a/pyiec61850/iec61850.i +++ b/pyiec61850/iec61850.i @@ -98,15 +98,18 @@ void GooseSubscriber_setDstMac(GooseSubscriber subscriber, /* Event Handler section */ %feature("director") RCBHandler; %feature("director") GooseHandler; +%feature("director") CommandTermHandler; %{ #include "eventHandlers/eventHandler.hpp" #include "eventHandlers/reportControlBlockHandler.hpp" #include "eventHandlers/gooseHandler.hpp" +#include "eventHandlers/commandTermHandler.hpp" EventSubscriber* EventSubscriber::m_last_created_event_subscriber = nullptr; %} %include "eventHandlers/eventHandler.hpp" %include "eventHandlers/reportControlBlockHandler.hpp" %include "eventHandlers/gooseHandler.hpp" +%include "eventHandlers/commandTermHandler.hpp" /* Goose Publisher section */ %{ From b1fc481ab88e0c771e93e42cb112ee680079891c Mon Sep 17 00:00:00 2001 From: Mikael Bourhis Date: Wed, 3 Feb 2021 14:37:47 +0100 Subject: [PATCH 03/68] Python wrapper: add some quality improvements --- .../eventHandlers/commandTermHandler.hpp | 26 ++++++++++++++++-- pyiec61850/eventHandlers/gooseHandler.hpp | 27 +++++++++++++++++-- .../reportControlBlockHandler.hpp | 24 ++++++++++++++++- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/pyiec61850/eventHandlers/commandTermHandler.hpp b/pyiec61850/eventHandlers/commandTermHandler.hpp index 2595f15e..2c91e338 100644 --- a/pyiec61850/eventHandlers/commandTermHandler.hpp +++ b/pyiec61850/eventHandlers/commandTermHandler.hpp @@ -26,8 +26,21 @@ class CommandTermHandler: public EventHandler { */ class CommandTermSubscriber: public EventSubscriber { public: + CommandTermSubscriber(): EventSubscriber() + { + m_libiec61850_control_object_client = nullptr; + } + + virtual ~CommandTermSubscriber() {} + + virtual void subscribe() + { + // preconditions + if (nullptr == m_libiec61850_control_object_client) { + fprintf(stderr, "CommandTermSubscriber::subscribe() failed: 'control object client' is null\n"); + return; + } - virtual void subscribe() { // install the libiec61850 callback: // the 'function pointer' is the 'static' method of this class ControlObjectClient_setCommandTerminationHandler( @@ -41,6 +54,12 @@ class CommandTermSubscriber: public EventSubscriber { { PyThreadStateLock PyThreadLock; + // Preconditions + if (nullptr == connection) { + fprintf(stderr, "CommandTermSubscriber::triggerCommandTermHandler() failed: input object is null\n"); + return; + } + // TODO: search the appropriate 'EventSubscriber' object if (m_last_created_event_subscriber) { EventHandler *l_event_handler_p = m_last_created_event_subscriber->getEventHandler(); @@ -49,9 +68,12 @@ class CommandTermSubscriber: public EventSubscriber { l_event_handler_p->trigger(); } else { - printf("The EventHandler is undefined\n"); + fprintf(stderr, "CommandTermSubscriber::triggerCommandTermHandler() failed: EventHandler is undefined\n"); } } + else { + fprintf(stderr, "CommandTermSubscriber::triggerCommandTermHandler() failed: subscriber is not registered\n"); + } } // Setters diff --git a/pyiec61850/eventHandlers/gooseHandler.hpp b/pyiec61850/eventHandlers/gooseHandler.hpp index a01d3a6a..f0b4f1d2 100644 --- a/pyiec61850/eventHandlers/gooseHandler.hpp +++ b/pyiec61850/eventHandlers/gooseHandler.hpp @@ -22,8 +22,22 @@ class GooseHandler: public EventHandler { class GooseSubscriberForPython: public EventSubscriber { public: + GooseSubscriberForPython(): EventSubscriber() + { + m_libiec61850_goose_subscriber = nullptr; + } + + virtual ~GooseSubscriberForPython() {} + + + virtual void subscribe() + { + // preconditions + if (nullptr == m_libiec61850_goose_subscriber) { + fprintf(stderr, "GooseSubscriberForPython::subscribe() failed: 'GOOSE subscriber' is null\n"); + return; + } - virtual void subscribe() { // install the libiec61850 callback: // the 'function pointer' is the 'static' method of this class GooseSubscriber_setListener(m_libiec61850_goose_subscriber, @@ -36,6 +50,12 @@ class GooseSubscriberForPython: public EventSubscriber { { PyThreadStateLock PyThreadLock; + // Preconditions + if (nullptr == subscriber) { + fprintf(stderr, "GooseSubscriberForPython::triggerGooseHandler() failed: input object is null\n"); + return; + } + // TODO: search the appropriate 'EventSubscriber' object if (m_last_created_event_subscriber) { EventHandler *l_event_handler_p = m_last_created_event_subscriber->getEventHandler(); @@ -44,9 +64,12 @@ class GooseSubscriberForPython: public EventSubscriber { l_event_handler_p->trigger(); } else { - printf("The EventHandler is undefined\n"); + fprintf(stderr, "GooseSubscriberForPython::triggerGooseHandler() failed: EventHandler is undefined\n"); } } + else { + fprintf(stderr, "GooseSubscriberForPython::triggerGooseHandler() failed: subscriber is not registered\n"); + } } // Setters diff --git a/pyiec61850/eventHandlers/reportControlBlockHandler.hpp b/pyiec61850/eventHandlers/reportControlBlockHandler.hpp index ad798c1d..13d71e9d 100644 --- a/pyiec61850/eventHandlers/reportControlBlockHandler.hpp +++ b/pyiec61850/eventHandlers/reportControlBlockHandler.hpp @@ -22,8 +22,21 @@ class RCBHandler: public EventHandler { class RCBSubscriber: public EventSubscriber { public: + RCBSubscriber(): EventSubscriber() + { + m_ied_connection = nullptr; + } + + virtual ~RCBSubscriber() {} + virtual void subscribe() { + // preconditions + if (nullptr == m_ied_connection) { + fprintf(stderr, "RCBSubscriber::subscribe() failed: 'IedConnection' is null\n"); + return; + } + // install the libiec61850 callback: // the 'function pointer' is the 'static' method of this class IedConnection_installReportHandler(m_ied_connection, @@ -38,6 +51,12 @@ class RCBSubscriber: public EventSubscriber { { PyThreadStateLock PyThreadLock; + // Preconditions + if (nullptr == report) { + fprintf(stderr, "RCBSubscriber::triggerRCBHandler() failed: input object is null\n"); + return; + } + // TODO: search the appropriate 'EventSubscriber' object if (m_last_created_event_subscriber) { EventHandler *l_event_handler_p = m_last_created_event_subscriber->getEventHandler(); @@ -46,9 +65,12 @@ class RCBSubscriber: public EventSubscriber { l_event_handler_p->trigger(); } else { - printf("The EventHandler is undefined\n"); + fprintf(stderr, "RCBSubscriber::triggerRCBHandler() failed: EventHandler is undefined\n"); } } + else { + fprintf(stderr, "RCBSubscriber::triggerRCBHandler() failed: subscriber is not registered\n"); + } } // Setters From e47601a81e5cb0a2fa0a1c49f6800a2579bd96ef Mon Sep 17 00:00:00 2001 From: Mikael Bourhis Date: Wed, 3 Feb 2021 14:46:50 +0100 Subject: [PATCH 04/68] Python wrapper: about the wrapped callbacks, maintain a 'map' of subscribers The 'callback' function is a part of the 'Subscriber' class. Once the 'event' (or asynchronous message) is received, the 'Subscriber' object forwards the data to the 'Handler' object. With this approach, the 'event' processing algorithm can be defined in a Python subclass by the user. Each 'subscriber' class has a function that matches the C function pointer. But this function is a static method, shared with all instances of the class. In order to dispatch the message/data to the right instance, we maintain a dictionary of instantiated objects. Therfore, we have added the following internal services : * bool registerNewSubscriber(EventSubscriber*, id); * EventSubscriber* findSubscriber(id); * void unregisterSubscriber(id); --- .../eventHandlers/commandTermHandler.hpp | 17 ++++-- pyiec61850/eventHandlers/eventHandler.hpp | 60 +++++++++++++++++-- pyiec61850/eventHandlers/gooseHandler.hpp | 18 ++++-- .../reportControlBlockHandler.hpp | 16 +++-- pyiec61850/iec61850.i | 2 +- 5 files changed, 89 insertions(+), 24 deletions(-) diff --git a/pyiec61850/eventHandlers/commandTermHandler.hpp b/pyiec61850/eventHandlers/commandTermHandler.hpp index 2c91e338..64fb6e38 100644 --- a/pyiec61850/eventHandlers/commandTermHandler.hpp +++ b/pyiec61850/eventHandlers/commandTermHandler.hpp @@ -33,12 +33,12 @@ class CommandTermSubscriber: public EventSubscriber { virtual ~CommandTermSubscriber() {} - virtual void subscribe() + virtual bool subscribe() { // preconditions if (nullptr == m_libiec61850_control_object_client) { fprintf(stderr, "CommandTermSubscriber::subscribe() failed: 'control object client' is null\n"); - return; + return false; } // install the libiec61850 callback: @@ -47,6 +47,10 @@ class CommandTermSubscriber: public EventSubscriber { m_libiec61850_control_object_client, CommandTermSubscriber::triggerCommandTermHandler, NULL); + + std::string l_object_ref = ControlObjectClient_getObjectReference(m_libiec61850_control_object_client); + + return (EventSubscriber::registerNewSubscriber(this, l_object_ref)); } // Static method: it is the 'callback' for libiec61850 in C @@ -60,9 +64,12 @@ class CommandTermSubscriber: public EventSubscriber { return; } - // TODO: search the appropriate 'EventSubscriber' object - if (m_last_created_event_subscriber) { - EventHandler *l_event_handler_p = m_last_created_event_subscriber->getEventHandler(); + // Search the appropriate 'EventSubscriber' object + std::string l_subscriber_id = ControlObjectClient_getObjectReference(connection); + EventSubscriber *l_registered_subscriber = EventSubscriber::findSubscriber(l_subscriber_id); + + if (l_registered_subscriber) { + EventHandler *l_event_handler_p = l_registered_subscriber->getEventHandler(); if (l_event_handler_p) { l_event_handler_p->setReceivedData(&connection); l_event_handler_p->trigger(); diff --git a/pyiec61850/eventHandlers/eventHandler.hpp b/pyiec61850/eventHandlers/eventHandler.hpp index 1ef20f7b..e69bf476 100644 --- a/pyiec61850/eventHandlers/eventHandler.hpp +++ b/pyiec61850/eventHandlers/eventHandler.hpp @@ -28,6 +28,7 @@ private: class EventHandler { public: + EventHandler() {} virtual ~EventHandler() {} virtual void setReceivedData(void *i_data_p) = 0; virtual void trigger() = 0; @@ -36,13 +37,9 @@ class EventHandler { class EventSubscriber { public: - // TODO: use a map to store and find the instantiated EventSubscriber - static EventSubscriber* m_last_created_event_subscriber; EventSubscriber(): _event_handler_p(nullptr) { - m_last_created_event_subscriber = this; - // add python thread support Py_Initialize(); PyEval_InitThreads(); @@ -50,11 +47,11 @@ class EventSubscriber { virtual ~EventSubscriber() { + EventSubscriber::unregisterSubscriber(m_subscriber_id); deleteEventHandler(); - m_last_created_event_subscriber = nullptr; } - virtual void subscribe() = 0; + virtual bool subscribe() = 0; void deleteEventHandler() { @@ -75,8 +72,59 @@ class EventSubscriber { return _event_handler_p; } + void setSubscriberId(const std::string &i_id) + { + m_subscriber_id = i_id; + } + + protected: + static std::map m_subscriber_map; + + static bool registerNewSubscriber(EventSubscriber *i_new_subscriber, const std::string &i_id) + { + // Preconditions + if (i_id.empty()) { + fprintf(stderr, "EventSubscriber::subscribe() failed: the subscriber id is empty\n"); + return false; + } + if (m_subscriber_map.end() != m_subscriber_map.find(i_id)) { + fprintf(stderr, "EventSubscriber::subscribe() failed: the subscriber is already registered\n"); + return false; + } + + m_subscriber_map[i_id] = i_new_subscriber; + i_new_subscriber->setSubscriberId(i_id); + + return true; + } + + static EventSubscriber* findSubscriber(const std::string &i_id) + { + EventSubscriber *o_found_event_subscriber_p = nullptr; + std::map::iterator l_it = m_subscriber_map.find(i_id); + + if (m_subscriber_map.end() != l_it) { + o_found_event_subscriber_p = l_it->second; + } + + return o_found_event_subscriber_p; + } + + static void unregisterSubscriber(const std::string &i_subscriber_id) + { + std::map::iterator l_it = m_subscriber_map.find(i_subscriber_id); + + if (m_subscriber_map.end() != l_it) { + m_subscriber_map.erase(l_it); + } + else { + fprintf(stderr, "EventSubscriber::unregisterSubscriber() failed: '%s' is not registered\n"); + } + } + private: EventHandler *_event_handler_p; + std::string m_subscriber_id; }; #endif diff --git a/pyiec61850/eventHandlers/gooseHandler.hpp b/pyiec61850/eventHandlers/gooseHandler.hpp index f0b4f1d2..5984d478 100644 --- a/pyiec61850/eventHandlers/gooseHandler.hpp +++ b/pyiec61850/eventHandlers/gooseHandler.hpp @@ -29,13 +29,12 @@ class GooseSubscriberForPython: public EventSubscriber { virtual ~GooseSubscriberForPython() {} - - virtual void subscribe() + virtual bool subscribe() { // preconditions if (nullptr == m_libiec61850_goose_subscriber) { fprintf(stderr, "GooseSubscriberForPython::subscribe() failed: 'GOOSE subscriber' is null\n"); - return; + return false; } // install the libiec61850 callback: @@ -43,6 +42,10 @@ class GooseSubscriberForPython: public EventSubscriber { GooseSubscriber_setListener(m_libiec61850_goose_subscriber, GooseSubscriberForPython::triggerGooseHandler, NULL); + + std::string l_go_cb_ref = GooseSubscriber_getGoCbRef(m_libiec61850_goose_subscriber); + + return (EventSubscriber::registerNewSubscriber(this, l_go_cb_ref)); } // Static method: it is the 'callback' for libiec61850 in C @@ -56,9 +59,12 @@ class GooseSubscriberForPython: public EventSubscriber { return; } - // TODO: search the appropriate 'EventSubscriber' object - if (m_last_created_event_subscriber) { - EventHandler *l_event_handler_p = m_last_created_event_subscriber->getEventHandler(); + // Search the appropriate 'EventSubscriber' object + std::string l_subscriber_id = GooseSubscriber_getGoCbRef(subscriber); + EventSubscriber *l_registered_subscriber = EventSubscriber::findSubscriber(l_subscriber_id); + + if (l_registered_subscriber) { + EventHandler *l_event_handler_p = l_registered_subscriber->getEventHandler(); if (l_event_handler_p) { l_event_handler_p->setReceivedData(&subscriber); l_event_handler_p->trigger(); diff --git a/pyiec61850/eventHandlers/reportControlBlockHandler.hpp b/pyiec61850/eventHandlers/reportControlBlockHandler.hpp index 13d71e9d..25411ae3 100644 --- a/pyiec61850/eventHandlers/reportControlBlockHandler.hpp +++ b/pyiec61850/eventHandlers/reportControlBlockHandler.hpp @@ -29,12 +29,11 @@ class RCBSubscriber: public EventSubscriber { virtual ~RCBSubscriber() {} - - virtual void subscribe() { + virtual bool subscribe() { // preconditions if (nullptr == m_ied_connection) { fprintf(stderr, "RCBSubscriber::subscribe() failed: 'IedConnection' is null\n"); - return; + return false; } // install the libiec61850 callback: @@ -44,6 +43,8 @@ class RCBSubscriber: public EventSubscriber { m_rcb_rpt_id.c_str(), RCBSubscriber::triggerRCBHandler, NULL); + + return (EventSubscriber::registerNewSubscriber(this, m_rcb_reference)); } // Static method: it is the 'callback' for libiec61850 in C @@ -57,9 +58,12 @@ class RCBSubscriber: public EventSubscriber { return; } - // TODO: search the appropriate 'EventSubscriber' object - if (m_last_created_event_subscriber) { - EventHandler *l_event_handler_p = m_last_created_event_subscriber->getEventHandler(); + // Search the appropriate 'EventSubscriber' object + std::string l_subscriber_id = ClientReport_getRcbReference(report); + EventSubscriber *l_registered_subscriber = EventSubscriber::findSubscriber(l_subscriber_id); + + if (l_registered_subscriber) { + EventHandler *l_event_handler_p = l_registered_subscriber->getEventHandler(); if (l_event_handler_p) { l_event_handler_p->setReceivedData(&report); l_event_handler_p->trigger(); diff --git a/pyiec61850/iec61850.i b/pyiec61850/iec61850.i index 64cd3298..26f6655f 100644 --- a/pyiec61850/iec61850.i +++ b/pyiec61850/iec61850.i @@ -104,7 +104,7 @@ void GooseSubscriber_setDstMac(GooseSubscriber subscriber, #include "eventHandlers/reportControlBlockHandler.hpp" #include "eventHandlers/gooseHandler.hpp" #include "eventHandlers/commandTermHandler.hpp" -EventSubscriber* EventSubscriber::m_last_created_event_subscriber = nullptr; +std::map< std::string, EventSubscriber*> EventSubscriber::m_subscriber_map = {}; %} %include "eventHandlers/eventHandler.hpp" %include "eventHandlers/reportControlBlockHandler.hpp" From cdd00da05749f626ef79efb887eaca789a4a9994 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 17 Feb 2021 19:03:58 +0100 Subject: [PATCH 05/68] - .NET API: Added IDisposable interface to IedServer and IedModel classes - .NET API: Added method IedModel.SetIedName --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 130 ++++++++++++++---- dotnet/server1/Program.cs | 115 ++++++++-------- src/iec61850/inc/iec61850_dynamic_model.h | 2 +- 3 files changed, 165 insertions(+), 82 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 8a0914a2..e1b0c813 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -54,7 +54,7 @@ namespace IEC61850 /// /// Representation of the IED server data model /// - public class IedModel + public class IedModel : IDisposable { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedModel_create(string name); @@ -71,7 +71,10 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int ModelNode_getType(IntPtr self); - internal IntPtr self = IntPtr.Zero; + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedModel_setIedNameForDynamicModel(IntPtr self, string iedName); + + internal IntPtr self = IntPtr.Zero; internal IedModel(IntPtr self) { @@ -87,24 +90,46 @@ namespace IEC61850 self = IedModel_create(name); } - // causes undefined behavior - //~IedModel() - //{ - // if (self != IntPtr.Zero) - // { - // IedModel_destroy(self); - // } - //} + ~IedModel() + { + Dispose(); + } - public void Destroy() + /// + /// Releases all resource used by the object. + /// + public void Destroy() { - IedModel_destroy(self); - self = IntPtr.Zero; + Dispose(); } - public static IedModel CreateFromFile(string filePath) + /// + /// Releases all resource used by the object. + /// + /// Call when you are done using the . The + /// method leaves the in an unusable state. After + /// calling , you must release all references to the so + /// the garbage collector can reclaim the memory that the was occupying. + public void Dispose() + { + lock (this) + { + if (self != IntPtr.Zero) + { + IedModel_destroy(self); + self = IntPtr.Zero; + } + } + } + + /// + /// Creates a new instance with the data model of config file. + /// + /// new instance + /// fila name of the configuration (.cfg) file + public static IedModel CreateFromFile(string filename) { - return ConfigFileParser.CreateModelFromConfigFile(filePath); + return ConfigFileParser.CreateModelFromConfigFile(filename); } private ModelNode getModelNodeFromNodeRef(IntPtr nodeRef) @@ -129,6 +154,20 @@ namespace IEC61850 } } + /// + /// Change the IED name of the data model + /// + /// the new IED name + public void SetIedName(string iedName) + { + IedModel_setIedNameForDynamicModel(self, iedName); + } + + /// + /// Gets the model node by full object reference. + /// + /// The model node + /// Full object reference including the IED name public ModelNode GetModelNodeByObjectReference(string objectReference) { IntPtr nodeRef = IedModel_getModelNodeByObjectReference(self, objectReference); @@ -139,6 +178,11 @@ namespace IEC61850 return getModelNodeFromNodeRef (nodeRef); } + /// + /// Gets the model node by short object reference (without IED name) + /// + /// The model node + /// Object reference without IED name (e.g. LD0/GGIO1.Ind1.stVal) public ModelNode GetModelNodeByShortObjectReference(string objectReference) { IntPtr nodeRef = IedModel_getModelNodeByShortObjectReference(self, objectReference); @@ -148,9 +192,10 @@ namespace IEC61850 return getModelNodeFromNodeRef (nodeRef); } - } - public class LogicalDevice : ModelNode + } + + public class LogicalDevice : ModelNode { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LogicalDevice_create(string name, IntPtr parent); @@ -1123,7 +1168,7 @@ namespace IEC61850 /// This class acts as the entry point for the IEC 61850 client API. It represents a single /// (MMS) connection to a server. /// - public class IedServer + public class IedServer : IDisposable { [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr IedServer_createWithConfig(IntPtr modelRef, IntPtr tlsConfiguration, IntPtr serverConfiguratio); @@ -1387,8 +1432,13 @@ namespace IEC61850 internal Dictionary clientConnections = new Dictionary (); + /* store IedModel instance to prevent garbage collector */ + private IedModel iedModel = null; + public IedServer(IedModel iedModel, IedServerConfig config = null) { + this.iedModel = iedModel; + IntPtr nativeConfig = IntPtr.Zero; if (config != null) @@ -1399,7 +1449,9 @@ namespace IEC61850 public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null) { - IntPtr nativeConfig = IntPtr.Zero; + this.iedModel = iedModel; + + IntPtr nativeConfig = IntPtr.Zero; IntPtr nativeTLSConfig = IntPtr.Zero; if (config != null) @@ -1461,17 +1513,41 @@ namespace IEC61850 internalConnectionHandler = null; } - /// - /// Release all server resources. - /// - /// This function releases all MMS server resources. - public void Destroy() + /// + /// Release all server resources (same as ) + /// + /// This function releases all MMS server resources. + public void Destroy() { - IedServer_destroy(self); - self = IntPtr.Zero; - internalConnectionHandler = null; + Dispose(); } + /// + /// Releases all resource used by the object. + /// + /// Call when you are done using the . The + /// method leaves the in an unusable state. After + /// calling , you must release all references to the so + /// the garbage collector can reclaim the memory that the was occupying. + public void Dispose() + { + lock (this) + { + if (self != IntPtr.Zero) + { + IedServer_destroy(self); + self = IntPtr.Zero; + internalConnectionHandler = null; + this.iedModel = null; + } + } + } + + ~IedServer() + { + Dispose(); + } + /// /// Set the identify for the MMS identify service /// diff --git a/dotnet/server1/Program.cs b/dotnet/server1/Program.cs index bef0da90..569ba74d 100644 --- a/dotnet/server1/Program.cs +++ b/dotnet/server1/Program.cs @@ -5,33 +5,38 @@ using System.Threading; namespace server1 { - class MainClass - { - public static void Main (string[] args) - { - bool running = true; + class MainClass + { + public static void Main(string[] args) + { + bool running = true; - /* run until Ctrl-C is pressed */ - Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { - e.Cancel = true; - running = false; - }; + /* run until Ctrl-C is pressed */ + Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e) + { + e.Cancel = true; + running = false; + }; - IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("model.cfg"); + IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("model.cfg"); - if (iedModel == null) { - Console.WriteLine ("No valid data model found!"); - return; - } + if (iedModel == null) + { + Console.WriteLine("No valid data model found!"); + return; + } - DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.SPCSO1"); + iedModel.SetIedName("TestIED"); - IedServerConfig config = new IedServerConfig (); - config.ReportBufferSize = 100000; + DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO1"); - IedServer iedServer = new IedServer (iedModel, config); + IedServerConfig config = new IedServerConfig(); + config.ReportBufferSize = 100000; - iedServer.SetCheckHandler(spcso1, delegate(ControlAction action, object parameter, MmsValue ctlVal, bool test, bool interlockCheck) { + IedServer iedServer = new IedServer(iedModel, config); + + iedServer.SetCheckHandler(spcso1, delegate (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool interlockCheck) + { Console.WriteLine("Received binary control command:"); Console.WriteLine(" ctlNum: " + action.GetCtlNum()); @@ -40,58 +45,60 @@ namespace server1 return CheckHandlerResult.ACCEPTED; }, null); - iedServer.SetControlHandler (spcso1, delegate(ControlAction action, object parameter, MmsValue ctlVal, bool test) { + iedServer.SetControlHandler(spcso1, delegate (ControlAction action, object parameter, MmsValue ctlVal, bool test) + { bool val = ctlVal.GetBoolean(); if (val) Console.WriteLine("execute binary control command: on"); else Console.WriteLine("execute binary control command: off"); - + return ControlHandlerResult.OK; - }, null); + }, null); - DataObject spcso2 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO2"); + DataObject spcso2 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO2"); - iedServer.SetSelectStateChangedHandler (spcso2, delegate (ControlAction action, object parameter, bool isSelected, SelectStateChangedReason reason) { - DataObject cObj = action.GetControlObject(); + iedServer.SetSelectStateChangedHandler(spcso2, delegate (ControlAction action, object parameter, bool isSelected, SelectStateChangedReason reason) + { + DataObject cObj = action.GetControlObject(); - Console.WriteLine("Control object " + cObj.GetObjectReference() + (isSelected ? " selected" : " unselected") + " reason: " + reason.ToString()); + Console.WriteLine("Control object " + cObj.GetObjectReference() + (isSelected ? " selected" : " unselected") + " reason: " + reason.ToString()); - }, null); + }, null); - iedServer.Start (102); + iedServer.Start(102); - if (iedServer.IsRunning()) - { - Console.WriteLine("Server started"); + if (iedServer.IsRunning()) + { + Console.WriteLine("Server started"); - GC.Collect(); + GC.Collect(); - DataObject ggio1AnIn1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.AnIn1"); + DataObject ggio1AnIn1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.AnIn1"); - DataAttribute ggio1AnIn1magF = (DataAttribute)ggio1AnIn1.GetChild("mag.f"); - DataAttribute ggio1AnIn1T = (DataAttribute)ggio1AnIn1.GetChild("t"); + DataAttribute ggio1AnIn1magF = (DataAttribute)ggio1AnIn1.GetChild("mag.f"); + DataAttribute ggio1AnIn1T = (DataAttribute)ggio1AnIn1.GetChild("t"); - float floatVal = 1.0f; + float floatVal = 1.0f; - while (running) - { - floatVal += 1f; - iedServer.UpdateTimestampAttributeValue(ggio1AnIn1T, new Timestamp(DateTime.Now)); - iedServer.UpdateFloatAttributeValue(ggio1AnIn1magF, floatVal); - Thread.Sleep(100); - } + while (running) + { + floatVal += 1f; + iedServer.UpdateTimestampAttributeValue(ggio1AnIn1T, new Timestamp(DateTime.Now)); + iedServer.UpdateFloatAttributeValue(ggio1AnIn1magF, floatVal); + Thread.Sleep(100); + } - iedServer.Stop(); - Console.WriteLine("Server stopped"); - } - else - { - Console.WriteLine("Failed to start server"); - } + iedServer.Stop(); + Console.WriteLine("Server stopped"); + } + else + { + Console.WriteLine("Failed to start server"); + } - iedServer.Destroy (); - } - } + iedServer.Destroy(); + } + } } diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index 13599698..c7da23c0 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -58,7 +58,7 @@ IedModel_create(const char* name/*, MemoryAllocator allocator*/); /** * \brief Set the name of the IED (use only for dynamic model!) * - * This will change the default name (usualy "TEMPLATE") to a user configured values. + * This will change the default name (usually "TEMPLATE") to a user configured values. * NOTE: This function has to be called before IedServer_create ! * NOTE: For dynamic model (and configuration file date model) this function has to be * used instead of IedModel_setIedName. From 5e39c94cf3f6fc02f028e9998500f9d2a439d9d2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 18 Feb 2021 15:47:51 +0100 Subject: [PATCH 06/68] - IED server: allow IedServer_setServerIdentity with some parameters set to NULL --- src/iec61850/server/impl/ied_server.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index ccf6e3d0..d8d0100c 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -1642,6 +1642,10 @@ IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorag { #if (CONFIG_IEC61850_LOG_SERVICE == 1) MmsMapping_setLogStorage(self->mmsMapping, logRef, logStorage); +#else + (void)self; + (void)logRef; + (void)logStorage; #endif } @@ -1659,9 +1663,14 @@ IedServer_setServerIdentity(IedServer self, const char* vendor, const char* mode if (self->revision) GLOBAL_FREEMEM(self->revision); - self->vendorName = StringUtils_copyString(vendor); - self->modelName = StringUtils_copyString(model); - self->revision = StringUtils_copyString(revision); + if (vendor) + self->vendorName = StringUtils_copyString(vendor); + + if (model) + self->modelName = StringUtils_copyString(model); + + if (revision) + self->revision = StringUtils_copyString(revision); MmsServer_setServerIdentity(self->mmsServer, self->vendorName, self->modelName, self->revision); #endif From 5afa1310f3c7f96f46c9b2beed548101bae411b6 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 18 Feb 2021 15:56:23 +0100 Subject: [PATCH 07/68] - IED server: Add function ModelNode_getObjectReferenceEx --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 14 ++++++-- src/iec61850/inc/iec61850_model.h | 13 ++++++++ src/iec61850/server/model/model.c | 33 +++++++++++++++---- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index e1b0c813..55903ec8 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -797,7 +797,10 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ModelNode_getObjectReference(IntPtr self, IntPtr objectReference); - public IntPtr self; + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModelNode_getObjectReferenceEx(IntPtr self, IntPtr objectReference, [MarshalAs(UnmanagedType.I1)] bool withoutIedName); + + public IntPtr self; internal ModelNode() { @@ -836,9 +839,14 @@ namespace IEC61850 } - public string GetObjectReference() + /// + /// Gets the object reference of the model node + /// + /// the object reference + /// If set to true the object reference is created without IED name. + public string GetObjectReference(bool withoutIedName = false) { - IntPtr objRefPtr = ModelNode_getObjectReference(self, IntPtr.Zero); + IntPtr objRefPtr = ModelNode_getObjectReferenceEx(self, IntPtr.Zero, withoutIedName); if (objRefPtr != IntPtr.Zero) { string objRef = Marshal.PtrToStringAnsi(objRefPtr); diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index dc5fa918..9c23f105 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -397,6 +397,19 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint LIB61850_API char* ModelNode_getObjectReference(ModelNode* self, char* objectReference); +/** + * \brief Return the IEC 61850 object reference of a model node + * + * \param self the model node instance + * \param objectReference pointer to a buffer where to write the object reference string. If NULL + * is given the buffer is allocated by the function. + * \param withoutIedName create object reference without IED name part + * + * \return the object reference string + */ +LIB61850_API char* +ModelNode_getObjectReferenceEx(ModelNode* node, char* objectReference, bool withoutIedName); + /** * \brief Get the type of the ModelNode * diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 0c6e4d62..5d98c7f5 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -513,12 +513,12 @@ LogicalDevice_getChildByMmsVariableName(LogicalDevice* logicalDevice, const char } static int -createObjectReference(ModelNode* node, char* objectReference) +createObjectReference(ModelNode* node, char* objectReference, bool withoutIedName) { int bufPos; if (node->modelType != LogicalNodeModelType) { - bufPos = createObjectReference(node->parent, objectReference); + bufPos = createObjectReference(node->parent, objectReference, withoutIedName); objectReference[bufPos++] = '.'; } @@ -531,10 +531,18 @@ createObjectReference(ModelNode* node, char* objectReference) bufPos = 0; - int nameLength = strlen (iedModel->name) + strlen(lDevice->name); + int nameLength = 0; - strncpy(objectReference, iedModel->name, 64); - strncat(objectReference, lDevice->name, 64); + if (withoutIedName) { + nameLength = strlen(lDevice->name); + strncpy(objectReference, lDevice->name, 64); + } + else { + nameLength = strlen (iedModel->name) + strlen(lDevice->name); + + strncpy(objectReference, iedModel->name, 64); + strncat(objectReference, lDevice->name, 64); + } bufPos += nameLength; @@ -558,7 +566,20 @@ ModelNode_getObjectReference(ModelNode* node, char* objectReference) if (objectReference == NULL) objectReference = (char*) GLOBAL_MALLOC(130); - int bufPos = createObjectReference(node, objectReference); + int bufPos = createObjectReference(node, objectReference, false); + + objectReference[bufPos] = 0; + + return objectReference; +} + +char* +ModelNode_getObjectReferenceEx(ModelNode* node, char* objectReference, bool withoutIedName) +{ + if (objectReference == NULL) + objectReference = (char*) GLOBAL_MALLOC(130); + + int bufPos = createObjectReference(node, objectReference, withoutIedName); objectReference[bufPos] = 0; From 3984a473244fa7640ca2a8b804646a1ca22d4943 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 18 Feb 2021 15:59:34 +0100 Subject: [PATCH 08/68] - Linux - Ethernet: replace IFF_PROMISC by IFF_ALLMULTI --- hal/ethernet/linux/ethernet_linux.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index 728b5d10..3555c66a 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -142,7 +142,8 @@ getInterfaceIndex(int sock, const char* deviceName) return -1; } - ifr.ifr_flags |= IFF_PROMISC; + /* replace IFF_ALLMULTI by IFF_PROMISC to also receive unicast messages for other nodes */ + ifr.ifr_flags |= IFF_ALLMULTI; if (ioctl (sock, SIOCSIFFLAGS, &ifr) == -1) { if (DEBUG_SOCKET) From 60d66e5ba4617d6a11b9b749e8e072112a1692d2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 18 Feb 2021 16:16:35 +0100 Subject: [PATCH 09/68] - some code beautification --- src/goose/goose_publisher.c | 16 ++++++++++++++-- src/iec61850/server/mms_mapping/control.c | 7 ++++++- src/iec61850/server/mms_mapping/mms_mapping.c | 10 +++++----- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/goose/goose_publisher.c b/src/goose/goose_publisher.c index 51e455a0..97b536fa 100644 --- a/src/goose/goose_publisher.c +++ b/src/goose/goose_publisher.c @@ -318,7 +318,14 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe while (element != NULL) { MmsValue* dataSetEntry = (MmsValue*) element->data; - dataSetSize += MmsValue_encodeMmsData(dataSetEntry, NULL, 0, false); + if (dataSetEntry) { + dataSetSize += MmsValue_encodeMmsData(dataSetEntry, NULL, 0, false); + } + else { + /* TODO encode MMS NULL */ + if (DEBUG_GOOSE_PUBLISHER) + printf("GOOSE_PUBLISHER: NULL value in data set!\n"); + } element = LinkedList_getNext(element); } @@ -384,7 +391,12 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe while (element != NULL) { MmsValue* dataSetEntry = (MmsValue*) element->data; - bufPos = MmsValue_encodeMmsData(dataSetEntry, buffer, bufPos, true); + if (dataSetEntry) { + bufPos = MmsValue_encodeMmsData(dataSetEntry, buffer, bufPos, true); + } + else { + /* TODO encode MMS NULL */ + } element = LinkedList_getNext(element); } diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 5bed8f1e..5f036e66 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -1828,8 +1828,13 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia ControlObject_getTypeSpec(controlObject), varName); } } - else + else { value = ControlObject_getMmsValue(controlObject); + } + } + else { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Control object not found %s/%s.%s\n", domain->domainName, lnName, objectName); } return value; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 47348dd3..8fa84487 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -3684,15 +3684,15 @@ MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value) void MmsMapping_enableGoosePublishing(MmsMapping* self) { + LinkedList element = LinkedList_getNext(self->gseControls); - LinkedList element = self->gseControls; - - while ((element = LinkedList_getNext(element)) != NULL) { - MmsGooseControlBlock gcb = (MmsGooseControlBlock) element->data; + while (element) { + MmsGooseControlBlock gcb = (MmsGooseControlBlock) LinkedList_getData(element); MmsGooseControlBlock_enable(gcb, self); - } + element = LinkedList_getNext(element); + } } void From 4dc971ba562929ad849d9f90392febc2e73e4de9 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 18 Feb 2021 16:37:02 +0100 Subject: [PATCH 10/68] - MMS server: better data model lock handling for performance improvements --- src/mms/inc_private/iso_server_private.h | 12 ---------- src/mms/iso_mms/server/mms_read_service.c | 8 +++++++ src/mms/iso_mms/server/mms_server.c | 10 ++------ src/mms/iso_mms/server/mms_write_service.c | 11 ++++++--- src/mms/iso_server/iso_connection.c | 4 ---- src/mms/iso_server/iso_server.c | 28 ---------------------- 6 files changed, 18 insertions(+), 55 deletions(-) diff --git a/src/mms/inc_private/iso_server_private.h b/src/mms/inc_private/iso_server_private.h index dac94c72..6249255e 100644 --- a/src/mms/inc_private/iso_server_private.h +++ b/src/mms/inc_private/iso_server_private.h @@ -70,18 +70,6 @@ private_IsoServer_decreaseConnectionCounter(IsoServer self); LIB61850_INTERNAL int private_IsoServer_getConnectionCounter(IsoServer self); -/** - * \brief User provided lock that will be called when higher layer (MMS) is called - */ -LIB61850_INTERNAL void -IsoServer_setUserLock(IsoServer self, Semaphore userLock); - -LIB61850_INTERNAL void -IsoServer_userLock(IsoServer self); - -LIB61850_INTERNAL void -IsoServer_userUnlock(IsoServer self); - LIB61850_INTERNAL bool IsoConnection_isRunning(IsoConnection self); diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index 13d7adce..3fb97675 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -928,11 +928,19 @@ mmsServer_handleReadRequest( request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.read); if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) { + MmsServer_lockModel(connection->server); + handleReadListOfVariablesRequest(connection, request, invokeId, response); + + MmsServer_unlockModel(connection->server); } #if (MMS_DATA_SET_SERVICE == 1) else if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) { + MmsServer_lockModel(connection->server); + handleReadNamedVariableListRequest(connection, request, invokeId, response); + + MmsServer_unlockModel(connection->server); } #endif else { diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 80af245b..0908babb 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -81,10 +81,6 @@ MmsServer_create(MmsDevice* device, TLSConfiguration tlsConfiguration) if (isoServer == NULL) goto exit_error; - #if (CONFIG_MMS_THREADLESS_STACK != 1) - IsoServer_setUserLock(isoServer, self->modelMutex); - #endif - LinkedList_add(self->isoServerList, isoServer); } @@ -132,8 +128,6 @@ MmsServer_addAP(MmsServer self, const char* ipAddr, int tcpPort, TLSConfiguratio if (isoServer) { - IsoServer_setUserLock(isoServer, self->modelMutex); - IsoServer_setLocalIpAddress(isoServer, ipAddr); if (tcpPort != -1) @@ -479,7 +473,8 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va if (self->writeHandler != NULL) { indication = self->writeHandler(self->writeHandlerParameter, domain, itemId, value, connection); - } else { + } + else { MmsValue* cachedValue; if (domain == NULL) @@ -497,7 +492,6 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va return indication; } - MmsValue* mmsServer_getValue(MmsServer self, MmsDomain* domain, char* itemId, MmsServerConnection connection, bool isDirectAccess) { diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index ad1fbf6a..31a171e4 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -501,6 +501,8 @@ mmsServer_handleWriteRequest( goto exit_function; } + MmsServer_lockModel(connection->server); + WriteRequest_t* writeRequest = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write); if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) { @@ -695,13 +697,16 @@ end_of_main_loop: if (sendResponse) mmsServer_createMmsWriteResponse(connection, invokeId, response, numberOfWriteItems, accessResults); } - else { /* unknown request type */ + else { /* unknown request type */ mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); goto exit_function; - } + } exit_function: - asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + + MmsServer_unlockModel(connection->server); + + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); } #endif /* (MMS_WRITE_SERVICE == 1) */ diff --git a/src/mms/iso_server/iso_connection.c b/src/mms/iso_server/iso_connection.c index 5f5b70dc..3551acc6 100644 --- a/src/mms/iso_server/iso_connection.c +++ b/src/mms/iso_server/iso_connection.c @@ -329,7 +329,6 @@ IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread) ByteBuffer mmsResponseBuffer; #if (CONFIG_MMS_THREADLESS_STACK != 1) - IsoServer_userLock(self->isoServer); IsoConnection_lock(self); #endif @@ -370,7 +369,6 @@ IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread) #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_unlock(self); - IsoServer_userUnlock(self->isoServer); #endif } else { @@ -389,7 +387,6 @@ IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread) printf("ISO_SERVER: iso_connection: presentation ok\n"); #if (CONFIG_MMS_THREADLESS_STACK != 1) - IsoServer_userLock(self->isoServer); IsoConnection_lock(self); #endif @@ -418,7 +415,6 @@ IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread) #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_unlock(self); - IsoServer_userUnlock(self->isoServer); #endif } diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index e45224f9..8501e41b 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -84,11 +84,6 @@ struct sIsoServer { IsoConnection openClientConnections[CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS]; #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ -#if (CONFIG_MMS_THREADLESS_STACK != 1) - /* used to control access to server data model */ - Semaphore userLock; -#endif - #if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore openClientConnectionsMutex; /* mutex for openClientConnections list */ Semaphore connectionCounterMutex; @@ -878,26 +873,3 @@ private_IsoServer_getConnectionCounter(IsoServer self) return connectionCounter; } -#if (CONFIG_MMS_THREADLESS_STACK != 1) - -void -IsoServer_setUserLock(IsoServer self, Semaphore userLock) -{ - self->userLock = userLock; -} - -void -IsoServer_userLock(IsoServer self) -{ - if (self->userLock != NULL) - Semaphore_wait(self->userLock); -} - -void -IsoServer_userUnlock(IsoServer self) -{ - if (self->userLock != NULL) - Semaphore_post(self->userLock); -} - -#endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ From 9e27ed5a77909a094944b031de47f073bbb5f4c3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 18 Feb 2021 18:05:01 +0100 Subject: [PATCH 11/68] - MMS server: add compile time configuration options to enable/disable fileDelete and fileRename services (fileRename is now disabled by default) --- config/stack_config.h | 2 ++ config/stack_config.h.cmake | 2 ++ src/mms/iso_mms/server/mms_association_service.c | 4 ++++ src/mms/iso_mms/server/mms_server_connection.c | 9 +++++++++ 4 files changed, 17 insertions(+) diff --git a/config/stack_config.h b/config/stack_config.h index 8bbe5d1e..294eed17 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -215,6 +215,8 @@ #define MMS_IDENTIFY_SERVICE 1 #define MMS_FILE_SERVICE 1 #define MMS_OBTAIN_FILE_SERVICE 1 /* requires MMS_FILE_SERVICE */ +#define MMS_DELETE_FILE_SERVICE 1 /* requires MMS_FILE_SERVICE */ +#define MMS_RENAME_FILE_SERVICE 0 /* requires MMS_FILE_SERVICE */ #endif /* MMS_DEFAULT_PROFILE */ diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 17ac3120..dd93fcce 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -205,6 +205,8 @@ #define MMS_IDENTIFY_SERVICE 1 #define MMS_FILE_SERVICE 1 #define MMS_OBTAIN_FILE_SERVICE 1 +#define MMS_DELETE_FILE_SERVICE 1 +#define MMS_RENAME_FILE_SERVICE 0 #endif /* MMS_DEFAULT_PROFILE */ /* Sort getNameList response according to the MMS specified collation order - this is required by the standard diff --git a/src/mms/iso_mms/server/mms_association_service.c b/src/mms/iso_mms/server/mms_association_service.c index 34aecc73..a3075e27 100644 --- a/src/mms/iso_mms/server/mms_association_service.c +++ b/src/mms/iso_mms/server/mms_association_service.c @@ -137,8 +137,12 @@ encodeInitResponseDetail(MmsServerConnection self, uint8_t* buffer, int bufPos, servicesSupported[9] |= MMS_SERVICE_FILE_OPEN; servicesSupported[9] |= MMS_SERVICE_FILE_READ; servicesSupported[9] |= MMS_SERVICE_FILE_CLOSE; +#if (MMS_RENAME_FILE_SERVICE == 1) servicesSupported[9] |= MMS_SERVICE_FILE_RENAME; +#endif +#if (MMS_DELETE_FILE_SERVICE == 1) servicesSupported[9] |= MMS_SERVICE_FILE_DELETE; +#endif servicesSupported[9] |= MMS_SERVICE_FILE_DIRECTORY; #endif /* (MMS_FILE_SERVICE == 1) */ diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index afb81cef..56f66099 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -244,6 +244,8 @@ handleConfirmedRequestPdu( break; #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#if (MMS_RENAME_FILE_SERVICE == 1) + #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) case 0x4b: /* file-rename-request */ if (self->server->fileServiceEnabled) @@ -257,6 +259,11 @@ handleConfirmedRequestPdu( break; #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#endif /* (MMS_RENAME_FILE_SERVICE == 1) */ + + +#if (MMS_DELETE_FILE_SERVICE == 1) + #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) case 0x4c: /* file-delete-request */ if (self->server->fileServiceEnabled) @@ -270,6 +277,8 @@ handleConfirmedRequestPdu( break; #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#endif /* (MMS_DELETE_FILE_SERVICE == 1) */ + #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) case 0x4d: /* file-directory-request */ if (self->server->fileServiceEnabled) From cc905b501344de10a8735e28849a42d7c9341881 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 23 Feb 2021 12:29:08 +0100 Subject: [PATCH 12/68] - IED server/model: Added additional functions for ModelNode access --- src/iec61850/inc/iec61850_common.h | 1 + src/iec61850/inc/iec61850_model.h | 30 +++++++++++++++++++++++++++++ src/iec61850/server/model/model.c | 31 ++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index c3202ef3..b9a8d32e 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -31,6 +31,7 @@ extern "C" { #include "libiec61850_common_api.h" #include "logging_api.h" +#include "linked_list.h" /** * @defgroup iec61850_common_api_group IEC 61850 API common parts diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 9c23f105..e0d54860 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -420,6 +420,36 @@ ModelNode_getObjectReferenceEx(ModelNode* node, char* objectReference, bool with LIB61850_API ModelNodeType ModelNode_getType(ModelNode* self); +/** + * \brief Get the name of the ModelNode + * + * \param self the ModelNode instance + * + * \return the name of the ModelNode + */ +LIB61850_API const char* +ModelNode_getName(ModelNode* self); + +/** + * \brief Get the parent ModelNode of this ModelNode instance + * + * \param self the ModelNode instance + * + * \return the parent instance, or NULL when the ModelNode has no parent + */ +LIB61850_API ModelNode* +ModelNode_getParent(ModelNode* self); + +/** + * \brief Get the list of direct child nodes + * + * \param self the ModelNode instance + * + * \return the list of private child nodes, or NULL when the node has no children + */ +LIB61850_API LinkedList +ModelNode_getChildren(ModelNode* self); + /** * \brief Set the name of the IED * diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 5d98c7f5..3da07561 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -706,6 +706,37 @@ ModelNode_getType(ModelNode* self) return self->modelType; } +const char* +ModelNode_getName(ModelNode* self) +{ + return self->name; +} + +ModelNode* +ModelNode_getParent(ModelNode* self) +{ + return self->parent; +} + +LinkedList +ModelNode_getChildren(ModelNode* self) +{ + LinkedList childNodes = NULL; + + if (self->firstChild) + childNodes = LinkedList_create(); + + ModelNode* childNode = self->firstChild; + + while (childNode) { + LinkedList_add(childNodes, childNode); + + childNode = childNode->sibling; + } + + return childNodes; +} + LogicalNode* LogicalDevice_getLogicalNode(LogicalDevice* self, const char* nodeName) { From 842bc271cd2eaa5d7ec84601afdc472e4571a9e2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 23 Feb 2021 12:30:39 +0100 Subject: [PATCH 13/68] - IED server: added new function IedServer_handleWriteAccessForComplexAttribute. Changed WriteAccessHandler behavior when ACCESS_POLICY_ALLOW. --- src/iec61850/inc/iec61850_server.h | 27 +++++++++ src/iec61850/server/impl/ied_server.c | 20 +++++++ src/iec61850/server/mms_mapping/mms_mapping.c | 56 ++++++------------- 3 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 04d95a9c..4d111d4e 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -1615,6 +1615,10 @@ typedef MmsDataAccessError * or denied. If a WriteAccessHandler is set for a specific data attribute - the * default write access policy will not be performed for that data attribute. * + * NOTE: If the data attribute has sub data attributes, the WriteAccessHandler is not + * set for the sub data attributes and will not be called when the sub data attribute is + * written directly! + * * \param self the instance of IedServer to operate on. * \param dataAttribute the data attribute to monitor * \param handler the callback function that is invoked if a client tries to write to @@ -1625,6 +1629,29 @@ LIB61850_API void IedServer_handleWriteAccess(IedServer self, DataAttribute* dataAttribute, WriteAccessHandler handler, void* parameter); +/** + * \brief Install a WriteAccessHandler for a data attribute and for all sub data attributes + * + * This instructs the server to monitor write attempts by MMS clients to specific + * data attributes. If a client tries to write to the monitored data attribute the + * handler is invoked. The handler can decide if the write access will be allowed + * or denied. If a WriteAccessHandler is set for a specific data attribute - the + * default write access policy will not be performed for that data attribute. + * + * When the data attribute is a complex attribute then the handler will also be installed + * for all sub data attributes. When the data attribute is a basic data attribute then + * this function behaves like \ref IedServer_handleWriteAccess. + * + * \param self the instance of IedServer to operate on. + * \param dataAttribute the data attribute to monitor + * \param handler the callback function that is invoked if a client tries to write to + * the monitored data attribute. + * \param parameter a user provided parameter that is passed to the WriteAccessHandler when called. + */ +LIB61850_API void +IedServer_handleWriteAccessForComplexAttribute(IedServer self, DataAttribute* dataAttribute, + WriteAccessHandler handler, void* parameter); + typedef enum { ACCESS_POLICY_ALLOW, ACCESS_POLICY_DENY diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index d8d0100c..d2199c2c 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -1512,6 +1512,26 @@ IedServer_handleWriteAccess(IedServer self, DataAttribute* dataAttribute, WriteA } } +void +IedServer_handleWriteAccessForComplexAttribute(IedServer self, DataAttribute* dataAttribute, WriteAccessHandler handler, void* parameter) +{ + if (dataAttribute == NULL) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: IedServer_handleWriteAccessForComplexAttribute - dataAttribute == NULL!\n"); + } + else { + MmsMapping_installWriteAccessHandler(self->mmsMapping, dataAttribute, handler, parameter); + + DataAttribute* subDa = (DataAttribute*) dataAttribute->firstChild; + + while (subDa) { + IedServer_handleWriteAccessForComplexAttribute(self, subDa, handler, parameter); + + subDa = (DataAttribute*) subDa->sibling; + } + } +} + void IedServer_setReadAccessHandler(IedServer self, ReadAccessHandler handler, void* parameter) { diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 8fa84487..01898f18 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1,7 +1,7 @@ /* * mms_mapping.c * - * Copyright 2013-2019 Michael Zillgith + * Copyright 2013-2021 Michael Zillgith * * This file is part of libIEC61850. * @@ -2374,6 +2374,7 @@ writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variable #endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ +#if 0 static MmsValue* checkIfValueBelongsToModelNode(DataAttribute* dataAttribute, MmsValue* value, MmsValue* newValue) { @@ -2408,6 +2409,7 @@ checkIfValueBelongsToModelNode(DataAttribute* dataAttribute, MmsValue* value, Mm return NULL; } +#endif static FunctionalConstraint getFunctionalConstraintForWritableNode(char* separator) @@ -2813,51 +2815,25 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, AttributeAccessHandler* accessHandler = (AttributeAccessHandler*) writeHandlerListElement->data; DataAttribute* dataAttribute = accessHandler->attribute; - if (nodeAccessPolicy == ACCESS_POLICY_ALLOW) { - - MmsValue* matchingValue = checkIfValueBelongsToModelNode(dataAttribute, cachedValue, value); + if (dataAttribute->mmsValue == cachedValue) { - if (matchingValue != NULL) { + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, + connection); - ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, - connection); + MmsDataAccessError handlerResult = + accessHandler->handler(dataAttribute, value, clientConnection, + accessHandler->parameter); - MmsDataAccessError handlerResult = - accessHandler->handler(dataAttribute, matchingValue, clientConnection, - accessHandler->parameter); + if ((handlerResult == DATA_ACCESS_ERROR_SUCCESS) || (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE)) { + handlerFound = true; - if ((handlerResult == DATA_ACCESS_ERROR_SUCCESS) || (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE)) { - handlerFound = true; - - if (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE) - updateValue = false; - } + if (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE) + updateValue = false; - else - return handlerResult; - } - } - else { /* if ACCESS_POLICY_DENY only allow direct access to handled data attribute */ - if (dataAttribute->mmsValue == cachedValue) { - - ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, - connection); - - MmsDataAccessError handlerResult = - accessHandler->handler(dataAttribute, value, clientConnection, - accessHandler->parameter); - - if ((handlerResult == DATA_ACCESS_ERROR_SUCCESS) || (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE)) { - handlerFound = true; - - if (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE) - updateValue = false; - - break; - } - else - return handlerResult; + break; } + else + return handlerResult; } writeHandlerListElement = LinkedList_getNext(writeHandlerListElement); From d5d8b70dc29559e273cc0b14dfa73d27cfc5ff6d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 23 Feb 2021 12:33:11 +0100 Subject: [PATCH 14/68] - .NET API: Added additional ModelNode methods. Added additional method IedServer.HandleWriteAccessForComplexAttribute. Fixed some problems with WriteAccessHandler. --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 2910 ++++++++++------- dotnet/tests/Test.cs | 105 +- dotnet/tests/model2.cfg | 446 +++ dotnet/tests/simpleIO_control_tests.scd | 430 +++ 4 files changed, 2609 insertions(+), 1282 deletions(-) create mode 100644 dotnet/tests/model2.cfg create mode 100644 dotnet/tests/simpleIO_control_tests.scd diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 55903ec8..122fedbd 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -54,41 +54,44 @@ namespace IEC61850 /// /// Representation of the IED server data model /// - public class IedModel : IDisposable - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedModel_create(string name); + public class IedModel : IDisposable + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedModel_create(string name); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedModel_destroy(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedModel_destroy(IntPtr self); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedModel_getModelNodeByObjectReference(IntPtr self, string objectReference); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedModel_getModelNodeByObjectReference(IntPtr self, string objectReference); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedModel_getModelNodeByShortObjectReference(IntPtr self, string objectReference); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedModel_getModelNodeByShortObjectReference(IntPtr self, string objectReference); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int ModelNode_getType(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int ModelNode_getType(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedModel_setIedNameForDynamicModel(IntPtr self, string iedName); internal IntPtr self = IntPtr.Zero; - internal IedModel(IntPtr self) - { - this.self = self; - } + internal IedModel(IntPtr self) + { + this.self = self; + } + + /* cache managed ModelNode instances of the IedModel */ + internal Dictionary modelNodes = new Dictionary(); /// /// Initializes a new instance of the class. /// /// IED name - public IedModel(string name) - { - self = IedModel_create(name); - } + public IedModel(string name) + { + self = IedModel_create(name); + } ~IedModel() { @@ -99,9 +102,9 @@ namespace IEC61850 /// Releases all resource used by the object. /// public void Destroy() - { + { Dispose(); - } + } /// /// Releases all resource used by the object. @@ -128,31 +131,117 @@ namespace IEC61850 /// new instance /// fila name of the configuration (.cfg) file public static IedModel CreateFromFile(string filename) - { - return ConfigFileParser.CreateModelFromConfigFile(filename); - } + { + return ConfigFileParser.CreateModelFromConfigFile(filename); + } - private ModelNode getModelNodeFromNodeRef(IntPtr nodeRef) - { - int nodeType = ModelNode_getType (nodeRef); + /// + /// Get parent node. When not found create the parent node and add to modelNode list + /// + /// The parent node, or when not found + /// the native reference of the model node + private ModelNode GetParent(IntPtr nodeRef) + { + ModelNode parentNode = null; - switch (nodeType) { - case 0: - return new LogicalDevice (nodeRef); + IntPtr parentNodeRef = ModelNode.GetNativeParent(nodeRef); - case 1: - return new LogicalNode (nodeRef); + if (parentNodeRef != IntPtr.Zero) + { + if (modelNodes.TryGetValue(parentNodeRef, out parentNode) == false) + { + int nodeType = ModelNode_getType(parentNodeRef); + + if (nodeType == 0) + { + parentNode = new LogicalDevice(parentNodeRef, this); + } + else + { + ModelNode parentOfParent = GetParent(parentNodeRef); + + if (parentOfParent != null) + { + switch (nodeType) + { + case 1: + parentNode = new LogicalNode(parentNodeRef, parentOfParent); + break; + + case 2: + parentNode = new DataObject(parentNodeRef, parentOfParent); + break; + + case 3: + parentNode = new DataAttribute(parentNodeRef, parentOfParent); + break; + + default: + parentNode = new ModelNode(parentNodeRef, parentOfParent); + break; + } + } + } + + if (parentNode != null) + { + modelNodes.Add(parentNodeRef, parentNode); + } + } + } - case 2: - return new DataObject (nodeRef); + return parentNode; + } + + private ModelNode GetModelNodeFromNodeRef(IntPtr nodeRef) + { + ModelNode modelNode = null; + + modelNodes.TryGetValue(nodeRef, out modelNode); + + if (modelNode == null) + { + int nodeType = ModelNode_getType(nodeRef); + + if (nodeType == 0) + { + modelNode = new LogicalDevice(nodeRef, this); + } + else + { + ModelNode parent = GetParent(nodeRef); + + if (parent != null) + { + switch (nodeType) + { + case 1: + modelNode = new LogicalNode(nodeRef, parent); + break; + + case 2: + modelNode = new DataObject(nodeRef, parent); + break; + + case 3: + modelNode = new DataAttribute(nodeRef, parent); + break; + + default: + modelNode = new ModelNode(nodeRef, parent); + break; + } + } + } - case 3: - return new DataAttribute (nodeRef); + if (modelNode != null) + { + modelNodes.Add(nodeRef, modelNode); + } + } - default: - return new ModelNode (nodeRef); - } - } + return modelNode; + } /// /// Change the IED name of the data model @@ -168,775 +257,971 @@ namespace IEC61850 /// /// The model node /// Full object reference including the IED name - public ModelNode GetModelNodeByObjectReference(string objectReference) - { - IntPtr nodeRef = IedModel_getModelNodeByObjectReference(self, objectReference); + public ModelNode GetModelNodeByObjectReference(string objectReference) + { + IntPtr nodeRef = IedModel_getModelNodeByObjectReference(self, objectReference); - if (nodeRef == IntPtr.Zero) - return null; + if (nodeRef == IntPtr.Zero) + return null; - return getModelNodeFromNodeRef (nodeRef); - } + return GetModelNodeFromNodeRef (nodeRef); + } /// /// Gets the model node by short object reference (without IED name) /// /// The model node /// Object reference without IED name (e.g. LD0/GGIO1.Ind1.stVal) - public ModelNode GetModelNodeByShortObjectReference(string objectReference) - { - IntPtr nodeRef = IedModel_getModelNodeByShortObjectReference(self, objectReference); + public ModelNode GetModelNodeByShortObjectReference(string objectReference) + { + IntPtr nodeRef = IedModel_getModelNodeByShortObjectReference(self, objectReference); - if (nodeRef == IntPtr.Zero) - return null; + if (nodeRef == IntPtr.Zero) + return null; - return getModelNodeFromNodeRef (nodeRef); - } + return GetModelNodeFromNodeRef (nodeRef); + } } public class LogicalDevice : ModelNode - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr LogicalDevice_create(string name, IntPtr parent); - - public LogicalDevice (IntPtr self) : base (self) - { - } - - public LogicalDevice(string name, IedModel parent) - { - self = LogicalDevice_create(name, parent.self); - } - } - - public class LogicalNode : ModelNode - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr LogicalNode_create(string name, IntPtr parent); - - public LogicalNode (IntPtr self) : base(self) - { - } - - public LogicalNode(string name, LogicalDevice parent) - { - base.self = LogicalNode_create(name, parent.self); - } - } - - public enum AccessPolicy { - ACCESS_POLICY_ALLOW = 0, - ACCESS_POLICY_DENY = 1 - } - - public enum DataAttributeType { - BOOLEAN = 0, - INT8 = 1, - INT16 = 2, - INT32 = 3, - INT64 = 4, - INT128 = 5, - INT8U = 6, - INT16U = 7, - INT24U = 8, - INT32U = 9, - FLOAT32 = 10, - FLOAT64 = 11, - ENUMERATED = 12, - OCTET_STRING_64 = 13, - OCTET_STRING_6 = 14, - OCTET_STRING_8 = 15, - VISIBLE_STRING_32 = 16, - VISIBLE_STRING_64 = 17, - VISIBLE_STRING_65 = 18, - VISIBLE_STRING_129 = 19, - VISIBLE_STRING_255 = 20, - UNICODE_STRING_255 = 21, - TIMESTAMP = 22, - QUALITY = 23, - CHECK = 24, - CODEDENUM = 25, - GENERIC_BITSTRING = 26, - CONSTRUCTED = 27, - ENTRY_TIME = 28, - PHYCOMADDR = 29, - CURRENCY = 30 - } - - public enum ModeValues - { - ON = 1, - BLOCKED = 2, - TEST = 3, - TEST_BLOCKED = 4, - OFF = 5 - } - - public enum HealthValues - { - OK = 1, - WARNING = 2, - ALARM = 3 - } - - /// - /// The CDC class contains helper functions to create DataObject instances for the - /// most common Common Data Classes. - /// - public class CDC - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_SPS_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_DPS_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_VSS_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_SEC_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_CMV_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_SAV_create(string name, IntPtr parent, uint options, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ACD_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ACT_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_SPG_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_VSG_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ENG_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ING_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ASG_create(string name, IntPtr parent, uint options, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_WYE_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_DEL_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_HST_create(string name, IntPtr parent, uint options, ushort maxPts); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_INS_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_MV_create(string name, IntPtr parent, uint options, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_INC_create(string name, IntPtr parent, uint options, uint controlOptions); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_LPL_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_DPL_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ENS_create(string name, IntPtr parent, uint options); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_SPC_create(string name, IntPtr parent, uint options, uint controlOptions); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_DPC_create(string name, IntPtr parent, uint options, uint controlOptions); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_BSC_create(string name, IntPtr parent, uint options, uint controlOptions, [MarshalAs(UnmanagedType.I1)] bool hasTransientIndicator); + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr LogicalDevice_create(string name, IntPtr parent); + + private IedModel iedModel = null; + + public IedModel IedModel { get => iedModel; } + + public LogicalDevice (IntPtr self, IedModel iedModel) : base (self) + { + this.iedModel = iedModel; + } + + public LogicalDevice(string name, IedModel parent) + { + this.iedModel = parent; + + self = LogicalDevice_create(name, parent.self); + } + } + + public class LogicalNode : ModelNode + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr LogicalNode_create(string name, IntPtr parent); + + public LogicalNode (IntPtr self, ModelNode parent) : base(self) + { + this.parent = parent; + } + + public LogicalNode(string name, LogicalDevice parent) + { + this.parent = parent; + + base.self = LogicalNode_create(name, parent.self); + } + } + + public enum AccessPolicy { + ACCESS_POLICY_ALLOW = 0, + ACCESS_POLICY_DENY = 1 + } + + public enum DataAttributeType { + BOOLEAN = 0, + INT8 = 1, + INT16 = 2, + INT32 = 3, + INT64 = 4, + INT128 = 5, + INT8U = 6, + INT16U = 7, + INT24U = 8, + INT32U = 9, + FLOAT32 = 10, + FLOAT64 = 11, + ENUMERATED = 12, + OCTET_STRING_64 = 13, + OCTET_STRING_6 = 14, + OCTET_STRING_8 = 15, + VISIBLE_STRING_32 = 16, + VISIBLE_STRING_64 = 17, + VISIBLE_STRING_65 = 18, + VISIBLE_STRING_129 = 19, + VISIBLE_STRING_255 = 20, + UNICODE_STRING_255 = 21, + TIMESTAMP = 22, + QUALITY = 23, + CHECK = 24, + CODEDENUM = 25, + GENERIC_BITSTRING = 26, + CONSTRUCTED = 27, + ENTRY_TIME = 28, + PHYCOMADDR = 29, + CURRENCY = 30 + } + + public enum ModeValues + { + ON = 1, + BLOCKED = 2, + TEST = 3, + TEST_BLOCKED = 4, + OFF = 5 + } + + public enum HealthValues + { + OK = 1, + WARNING = 2, + ALARM = 3 + } + + /// + /// The CDC class contains helper functions to create DataObject instances for the + /// most common Common Data Classes. + /// + public class CDC + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_SPS_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_DPS_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_VSS_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_SEC_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_CMV_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_SAV_create(string name, IntPtr parent, uint options, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ACD_create(string name, IntPtr parent, uint options); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_APC_create(string name, IntPtr parent, uint options, uint controlOptions, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_BCR_create(string name, IntPtr parent, uint options); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ACT_create(string name, IntPtr parent, uint options); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ENC_create(string name, IntPtr parent, uint options, uint controlOptions); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_SPG_create(string name, IntPtr parent, uint options); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_SPV_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasChaManRs); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_VSG_create(string name, IntPtr parent, uint options); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_STV_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasOldStatus); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ENG_create(string name, IntPtr parent, uint options); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_CMD_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasOldStatus, [MarshalAs(UnmanagedType.I1)] bool hasCmTm, [MarshalAs(UnmanagedType.I1)] bool hasCmCt); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ING_create(string name, IntPtr parent, uint options); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_ALM_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasOldStatus); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ASG_create(string name, IntPtr parent, uint options, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_CTE_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasHisRs); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_WYE_create(string name, IntPtr parent, uint options); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr CDC_TMS_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasHisRs); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_DEL_create(string name, IntPtr parent, uint options); - public const int CDC_OPTION_DESC = (1 << 2); - public const int CDC_OPTION_DESC_UNICODE = (1 << 3); - public const int CDC_OPTION_AC_DLNDA = (1 << 4); - public const int CDC_OPTION_AC_DLN = (1 << 5); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_HST_create(string name, IntPtr parent, uint options, ushort maxPts); - // options that are only valid for DPL CDC - public const int CDC_OPTION_DPL_HWREV = (1 << 17); - public const int CDC_OPTION_DPL_SWREV = (1 << 18); - public const int CDC_OPTION_DPL_SERNUM = (1 << 19); - public const int CDC_OPTION_DPL_MODEL = (1 << 20); - public const int CDC_OPTION_DPL_LOCATION = (1 << 21); - - // mandatory data attributes for LLN0 (e.g. LBL configRef) - public const int CDC_OPTION_AC_LN0_M = (1 << 24); - public const int CDC_OPTION_AC_LN0_EX = (1 << 25); - public const int CDC_OPTION_AC_DLD_M = (1 << 26); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_INS_create(string name, IntPtr parent, uint options); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_MV_create(string name, IntPtr parent, uint options, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); - public static DataObject Create_CDC_SPS(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_SPS_create(name, parent.self, options); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_INC_create(string name, IntPtr parent, uint options, uint controlOptions); - if (self != IntPtr.Zero) - return new DataObject (self); - else - return null; - } - - public static DataObject Create_CDC_DPS(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_DPS_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_VSS(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_VSS_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_SEC(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_SEC_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_CMV(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_CMV_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_SAV(ModelNode parent, string name, uint options, bool isIntegerNotFloat) - { - IntPtr self = CDC_SAV_create(name, parent.self, options, isIntegerNotFloat); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_ACD(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_ACD_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_ACT(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_ACT_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_SPG(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_SPG_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_VSG(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_VSG_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_ENG(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_ENG_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_ING(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_ING_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_ASG(ModelNode parent, string name, uint options, bool isIntegerNotFloat) - { - IntPtr self = CDC_ASG_create(name, parent.self, options, isIntegerNotFloat); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_WYE(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_WYE_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_DEL(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_DEL_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_HST(ModelNode parent, string name, uint options, ushort maxPts) - { - IntPtr self = CDC_HST_create(name, parent.self, options, maxPts); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_INS(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_INS_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject (self); - else - return null; - } - - public static DataObject Create_CDC_MV(ModelNode parent, string name, uint options, bool isIntegerNotFloat) - { - IntPtr self = CDC_MV_create(name, parent.self, options, isIntegerNotFloat); - - if (self != IntPtr.Zero) - return new DataObject (self); - else - return null; - } - - public static DataObject Create_CDC_INC(ModelNode parent, string name, uint options, uint controlOptions) - { - IntPtr self = CDC_INC_create(name, parent.self, options, controlOptions); - - if (self != IntPtr.Zero) - return new DataObject (self); - else - return null; - } - - public static DataObject Create_CDC_LPL(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_LPL_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject (self); - else - return null; - } - - public static DataObject Create_CDC_DPL(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_DPL_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject (self); - else - return null; - } - - public static DataObject Create_CDC_ENS(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_ENS_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_SPC(ModelNode parent, string name, uint options, uint controlOptions) - { - IntPtr self = CDC_SPC_create(name, parent.self, options, controlOptions); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_DPC(ModelNode parent, string name, uint options, uint controlOptions) - { - IntPtr self = CDC_DPC_create(name, parent.self, options, controlOptions); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_BSC(ModelNode parent, string name, uint options, uint controlOptions, bool hasTransientIndicator) - { - IntPtr self = CDC_BSC_create(name, parent.self, options, controlOptions, hasTransientIndicator); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_APC(ModelNode parent, string name, uint options, uint controlOptions, bool isIntegerNotFloat) - { - IntPtr self = CDC_APC_create(name, parent.self, options, controlOptions, isIntegerNotFloat); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_BCR(ModelNode parent, string name, uint options) - { - IntPtr self = CDC_BCR_create(name, parent.self, options); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_ENC(ModelNode parent, string name, uint options, uint controlOptions) - { - IntPtr self = CDC_ENC_create(name, parent.self, options, controlOptions); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_SPV(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasChaManRs) - { - IntPtr self = CDC_SPV_create(name, parent.self, options, controlOptions, wpOptions, hasChaManRs); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_STV(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasOldStatus) - { - IntPtr self = CDC_STV_create(name, parent.self, options, controlOptions, wpOptions, hasOldStatus); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_CMD(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasOldStatus, bool hasCmTm, bool hasCmCt) - { - IntPtr self = CDC_CMD_create(name, parent.self, options, controlOptions, wpOptions, hasOldStatus, hasCmTm, hasCmCt); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_ALM(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasOldStatus) - { - IntPtr self = CDC_ALM_create(name, parent.self, options, controlOptions, wpOptions, hasOldStatus); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_CTE(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasHisRs) - { - IntPtr self = CDC_CTE_create(name, parent.self, options, controlOptions, wpOptions, hasHisRs); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - - public static DataObject Create_CDC_TMS(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasHisRs) - { - IntPtr self = CDC_TMS_create(name, parent.self, options, controlOptions, wpOptions, hasHisRs); - - if (self != IntPtr.Zero) - return new DataObject(self); - else - return null; - } - } - - public class DataObject : ModelNode - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr DataObject_create(string name, IntPtr parent, int arrayElements); - - internal DataObject(IntPtr self) : base(self) - { - } - - public DataObject(string name, ModelNode parent) : this(name, parent, 0) - { - } - - public DataObject(string name, ModelNode parent, int arrayElements) - { - self = DataObject_create (name, parent.self, arrayElements); - } - - } - - public class DataAttribute : ModelNode - { - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr DataAttribute_create(string name, IntPtr parent, int type, int fc, - byte triggerOptions, int arrayElements, UInt32 sAddr); - - internal DataAttribute(IntPtr self) : base(self) - { - } - - public DataAttribute (string name, ModelNode parent, DataAttributeType type, FunctionalConstraint fc, TriggerOptions trgOps, - int arrayElements, UInt32 sAddr) - { - self = DataAttribute_create (name, parent.self, (int)type, (int)fc, (byte)trgOps, arrayElements, sAddr); - } - - } - - public class ModelNode - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ModelNode_getChild(IntPtr self, string name); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int ModelNode_getType(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ModelNode_getObjectReference(IntPtr self, IntPtr objectReference); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_LPL_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_DPL_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ENS_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_SPC_create(string name, IntPtr parent, uint options, uint controlOptions); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_DPC_create(string name, IntPtr parent, uint options, uint controlOptions); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_BSC_create(string name, IntPtr parent, uint options, uint controlOptions, [MarshalAs(UnmanagedType.I1)] bool hasTransientIndicator); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_APC_create(string name, IntPtr parent, uint options, uint controlOptions, [MarshalAs(UnmanagedType.I1)] bool isIntegerNotFloat); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_BCR_create(string name, IntPtr parent, uint options); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ENC_create(string name, IntPtr parent, uint options, uint controlOptions); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_SPV_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasChaManRs); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_STV_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasOldStatus); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_CMD_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasOldStatus, [MarshalAs(UnmanagedType.I1)] bool hasCmTm, [MarshalAs(UnmanagedType.I1)] bool hasCmCt); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_ALM_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasOldStatus); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_CTE_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasHisRs); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr CDC_TMS_create(string name, IntPtr parent, uint options, uint controlOptions, uint wpOptions, [MarshalAs(UnmanagedType.I1)] bool hasHisRs); + + public const int CDC_OPTION_DESC = (1 << 2); + public const int CDC_OPTION_DESC_UNICODE = (1 << 3); + public const int CDC_OPTION_AC_DLNDA = (1 << 4); + public const int CDC_OPTION_AC_DLN = (1 << 5); + + // options that are only valid for DPL CDC + public const int CDC_OPTION_DPL_HWREV = (1 << 17); + public const int CDC_OPTION_DPL_SWREV = (1 << 18); + public const int CDC_OPTION_DPL_SERNUM = (1 << 19); + public const int CDC_OPTION_DPL_MODEL = (1 << 20); + public const int CDC_OPTION_DPL_LOCATION = (1 << 21); + + // mandatory data attributes for LLN0 (e.g. LBL configRef) + public const int CDC_OPTION_AC_LN0_M = (1 << 24); + public const int CDC_OPTION_AC_LN0_EX = (1 << 25); + public const int CDC_OPTION_AC_DLD_M = (1 << 26); + + + public static DataObject Create_CDC_SPS(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_SPS_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject (self, parent); + else + return null; + } + + public static DataObject Create_CDC_DPS(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_DPS_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_VSS(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_VSS_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_SEC(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_SEC_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_CMV(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_CMV_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_SAV(ModelNode parent, string name, uint options, bool isIntegerNotFloat) + { + IntPtr self = CDC_SAV_create(name, parent.self, options, isIntegerNotFloat); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_ACD(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_ACD_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_ACT(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_ACT_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_SPG(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_SPG_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_VSG(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_VSG_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_ENG(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_ENG_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_ING(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_ING_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_ASG(ModelNode parent, string name, uint options, bool isIntegerNotFloat) + { + IntPtr self = CDC_ASG_create(name, parent.self, options, isIntegerNotFloat); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_WYE(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_WYE_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_DEL(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_DEL_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_HST(ModelNode parent, string name, uint options, ushort maxPts) + { + IntPtr self = CDC_HST_create(name, parent.self, options, maxPts); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_INS(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_INS_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject (self, parent); + else + return null; + } + + public static DataObject Create_CDC_MV(ModelNode parent, string name, uint options, bool isIntegerNotFloat) + { + IntPtr self = CDC_MV_create(name, parent.self, options, isIntegerNotFloat); + + if (self != IntPtr.Zero) + return new DataObject (self, parent); + else + return null; + } + + public static DataObject Create_CDC_INC(ModelNode parent, string name, uint options, uint controlOptions) + { + IntPtr self = CDC_INC_create(name, parent.self, options, controlOptions); + + if (self != IntPtr.Zero) + return new DataObject (self, parent); + else + return null; + } + + public static DataObject Create_CDC_LPL(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_LPL_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject (self, parent); + else + return null; + } + + public static DataObject Create_CDC_DPL(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_DPL_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject (self, parent); + else + return null; + } + + public static DataObject Create_CDC_ENS(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_ENS_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_SPC(ModelNode parent, string name, uint options, uint controlOptions) + { + IntPtr self = CDC_SPC_create(name, parent.self, options, controlOptions); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_DPC(ModelNode parent, string name, uint options, uint controlOptions) + { + IntPtr self = CDC_DPC_create(name, parent.self, options, controlOptions); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_BSC(ModelNode parent, string name, uint options, uint controlOptions, bool hasTransientIndicator) + { + IntPtr self = CDC_BSC_create(name, parent.self, options, controlOptions, hasTransientIndicator); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_APC(ModelNode parent, string name, uint options, uint controlOptions, bool isIntegerNotFloat) + { + IntPtr self = CDC_APC_create(name, parent.self, options, controlOptions, isIntegerNotFloat); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_BCR(ModelNode parent, string name, uint options) + { + IntPtr self = CDC_BCR_create(name, parent.self, options); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_ENC(ModelNode parent, string name, uint options, uint controlOptions) + { + IntPtr self = CDC_ENC_create(name, parent.self, options, controlOptions); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_SPV(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasChaManRs) + { + IntPtr self = CDC_SPV_create(name, parent.self, options, controlOptions, wpOptions, hasChaManRs); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_STV(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasOldStatus) + { + IntPtr self = CDC_STV_create(name, parent.self, options, controlOptions, wpOptions, hasOldStatus); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_CMD(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasOldStatus, bool hasCmTm, bool hasCmCt) + { + IntPtr self = CDC_CMD_create(name, parent.self, options, controlOptions, wpOptions, hasOldStatus, hasCmTm, hasCmCt); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_ALM(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasOldStatus) + { + IntPtr self = CDC_ALM_create(name, parent.self, options, controlOptions, wpOptions, hasOldStatus); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_CTE(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasHisRs) + { + IntPtr self = CDC_CTE_create(name, parent.self, options, controlOptions, wpOptions, hasHisRs); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + + public static DataObject Create_CDC_TMS(ModelNode parent, string name, uint options, uint controlOptions, uint wpOptions, bool hasHisRs) + { + IntPtr self = CDC_TMS_create(name, parent.self, options, controlOptions, wpOptions, hasHisRs); + + if (self != IntPtr.Zero) + return new DataObject(self, parent); + else + return null; + } + } + + public class DataObject : ModelNode + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr DataObject_create(string name, IntPtr parent, int arrayElements); + + internal DataObject(IntPtr self, ModelNode parent) : base(self) + { + this.parent = parent; + } + + public DataObject(string name, ModelNode parent) : this(name, parent, 0) + { + } + + public DataObject(string name, ModelNode parent, int arrayElements) + { + this.parent = parent; + + self = DataObject_create (name, parent.self, arrayElements); + } + + } + + public class DataAttribute : ModelNode + { + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr DataAttribute_create(string name, IntPtr parent, int type, int fc, + byte triggerOptions, int arrayElements, UInt32 sAddr); + + internal DataAttribute(IntPtr self, ModelNode parent) : base(self) + { + this.parent = parent; + } + + public DataAttribute (string name, ModelNode parent, DataAttributeType type, FunctionalConstraint fc, TriggerOptions trgOps, + int arrayElements, UInt32 sAddr) + { + this.parent = parent; + + self = DataAttribute_create (name, parent.self, (int)type, (int)fc, (byte)trgOps, arrayElements, sAddr); + } + + } + + public class ModelNode + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModelNode_getChild(IntPtr self, string name); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int ModelNode_getType(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModelNode_getObjectReference(IntPtr self, IntPtr objectReference); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ModelNode_getObjectReferenceEx(IntPtr self, IntPtr objectReference, [MarshalAs(UnmanagedType.I1)] bool withoutIedName); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModelNode_getParent(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModelNode_getChildren(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModelNode_getName(IntPtr self); + + /**************** + * LinkedList + ***************/ + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr LinkedList_getNext(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr LinkedList_getData(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void LinkedList_destroyStatic(IntPtr self); + public IntPtr self; - internal ModelNode() - { - } + internal ModelNode parent = null; + + static internal IntPtr GetNativeParent(IntPtr self) + { + return ModelNode_getParent(self); + } + + internal ModelNode() + { + self = IntPtr.Zero; + } + + internal ModelNode(IntPtr self, ModelNode parent) + { + this.self = self; + this.parent = parent; + } + + internal ModelNode(IntPtr self) + { + this.self = self; + } + + /// + /// Gets the IedModel for this ModelNode instance + /// + /// the IedModel instance of this ModelNode. + public IedModel GetIedModel() + { + if (this is LogicalDevice) + { + return (this as LogicalDevice).IedModel; + } + else + { + if (this.parent != null) + return parent.GetIedModel(); + else + return null; + } + } + + /// + /// Gets the name of the model node + /// + /// name of the model node + public string GetName() + { + return Marshal.PtrToStringAnsi(ModelNode_getName(self)); + } + + /// + /// Gets the parent node of this model node + /// + /// The parent node + public ModelNode GetParent() + { + return parent; + } + + + /// + /// Get the child node of this model node with the given name + /// + /// The child node or null when there is no child with the given name + /// name of the child node + public ModelNode GetChild(string name) + { + IntPtr childPtr = ModelNode_getChild(self, name); + + if (childPtr == IntPtr.Zero) + return null; + + ModelNode child = null; + + IedModel iedModel = GetIedModel(); + + if (iedModel != null) + { + iedModel.modelNodes.TryGetValue(childPtr, out child); + } + + if (child == null) + { + int nodeType = ModelNode_getType(childPtr); + + switch (nodeType) + { + case 0: + child = new LogicalDevice(childPtr, iedModel); + break; + + case 1: + child = new LogicalNode(childPtr, this); + break; + + case 2: + child = new DataObject(childPtr, this); + break; + + case 3: + child = new DataAttribute(childPtr, this); + break; - public ModelNode(IntPtr self) - { - this.self = self; - } + default: + child = new ModelNode(childPtr, this); + break; + } + + if (child != null && iedModel != null) + { + iedModel.modelNodes.Add(childPtr, child); + } + } - public ModelNode GetChild(string name) - { - IntPtr childPtr = ModelNode_getChild(self, name); + return child; + } - if (childPtr == IntPtr.Zero) - return null; + internal static ModelNode CreateInstance(IntPtr instPtr, ModelNode parent) + { + int nodeType = ModelNode_getType(instPtr); - int nodeType = ModelNode_getType (childPtr); + ModelNode newInstance = null; - switch (nodeType) { - case 0: - return new LogicalDevice (childPtr); + switch (nodeType) + { + case 1: + newInstance = new LogicalNode(instPtr, parent); + break; - case 1: - return new LogicalNode (childPtr); + case 2: + newInstance = new DataObject(instPtr, parent); + break; + + case 3: + newInstance = new DataAttribute(instPtr, parent); + break; + + default: + newInstance = new ModelNode(instPtr, parent); + break; + } + + return newInstance; + } + + /// + /// Gets the direct child nodes of this ModelNode instance + /// + /// List of child nodes + public LinkedList GetChildren() + { + LinkedList children = new LinkedList(); + + IntPtr childListPtr = ModelNode_getChildren(self); + + if (childListPtr != IntPtr.Zero) + { + IedModel iedModel = GetIedModel(); + + IntPtr listElem = LinkedList_getNext(childListPtr); + + while (listElem != IntPtr.Zero) + { + IntPtr modelNodePtr = LinkedList_getData(listElem); - case 2: - return new DataObject (childPtr); + ModelNode childNode = null; - case 3: - return new DataAttribute (childPtr); + if (iedModel != null) + { + iedModel.modelNodes.TryGetValue(modelNodePtr, out childNode); + } + + if (childNode == null) + { + childNode = ModelNode.CreateInstance(modelNodePtr, this); + + if ((childNode != null) && (iedModel != null)) + { + iedModel.modelNodes.Add(modelNodePtr, childNode); + } + + } + + if (childNode != null) + children.AddLast(childNode); + + listElem = LinkedList_getNext(listElem); + } + + LinkedList_destroyStatic(childListPtr); + } + + return children; + } + + /// + /// Gets the object reference of the model node + /// + /// the object reference + /// If set to true the object reference is created without IED name. + public string GetObjectReference(bool withoutIedName = false) + { + IntPtr objRefPtr = ModelNode_getObjectReferenceEx(self, IntPtr.Zero, withoutIedName); + + if (objRefPtr != IntPtr.Zero) { + string objRef = Marshal.PtrToStringAnsi(objRefPtr); + + Marshal.FreeHGlobal(objRefPtr); + + return objRef; + } + else { + return null; + } + } + + } + + public class DataSet + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr DataSet_create(string name, IntPtr parent); + + public IntPtr self = IntPtr.Zero; + + public DataSet(string name, LogicalNode parent) + { + self = DataSet_create(name, parent.self); + } + } + + public class DataSetEntry + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr DataSetEntry_create(IntPtr dataSet, string variable, int index, string component); + + public IntPtr self = IntPtr.Zero; + + public DataSetEntry(DataSet dataSet, string variable, int index, string component) + { + self = DataSetEntry_create(dataSet.self, variable, index, component); + } + } + + public class ReportControlBlock + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ReportControlBlock_create(string name, IntPtr parent, string rptId, [MarshalAs(UnmanagedType.I1)] bool isBuffered, + string dataSetName, uint confRef, byte trgOps, byte options, uint bufTm, uint intgPd); + + public IntPtr self = IntPtr.Zero; + + public ReportControlBlock(string name, LogicalNode parent, string rptId, bool isBuffered, + string dataSetName, uint confRev, byte trgOps, byte options, uint bufTm, uint intgPd) + { + self = ReportControlBlock_create(name, parent.self, rptId, isBuffered, dataSetName, confRev, trgOps, options, bufTm, intgPd); + } + } + + public class ClientConnection + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientConnection_getPeerAddress(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientConnection_getLocalAddress(IntPtr self); + + internal IntPtr self; + + internal ClientConnection (IntPtr self) { + this.self = self; + } + + public string GetPeerAddress() + { + IntPtr peerAddrPtr = ClientConnection_getPeerAddress (self); - default: - return new ModelNode (childPtr); - } + if (peerAddrPtr != IntPtr.Zero) + return Marshal.PtrToStringAnsi (peerAddrPtr); + else + return null; + } - } + public string GetLocalAddress() + { + IntPtr localAddrPtr = ClientConnection_getLocalAddress(self); - /// - /// Gets the object reference of the model node - /// - /// the object reference - /// If set to true the object reference is created without IED name. - public string GetObjectReference(bool withoutIedName = false) - { - IntPtr objRefPtr = ModelNode_getObjectReferenceEx(self, IntPtr.Zero, withoutIedName); - - if (objRefPtr != IntPtr.Zero) { - string objRef = Marshal.PtrToStringAnsi(objRefPtr); - - Marshal.FreeHGlobal(objRefPtr); - - return objRef; - } - else { - return null; - } - } - - } - - public class DataSet - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr DataSet_create(string name, IntPtr parent); - - public IntPtr self = IntPtr.Zero; - - public DataSet(string name, LogicalNode parent) - { - self = DataSet_create(name, parent.self); - } - } - - public class DataSetEntry - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr DataSetEntry_create(IntPtr dataSet, string variable, int index, string component); - - public IntPtr self = IntPtr.Zero; - - public DataSetEntry(DataSet dataSet, string variable, int index, string component) - { - self = DataSetEntry_create(dataSet.self, variable, index, component); - } - } - - public class ReportControlBlock - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ReportControlBlock_create(string name, IntPtr parent, string rptId, [MarshalAs(UnmanagedType.I1)] bool isBuffered, - string dataSetName, uint confRef, byte trgOps, byte options, uint bufTm, uint intgPd); - - public IntPtr self = IntPtr.Zero; - - public ReportControlBlock(string name, LogicalNode parent, string rptId, bool isBuffered, - string dataSetName, uint confRev, byte trgOps, byte options, uint bufTm, uint intgPd) - { - self = ReportControlBlock_create(name, parent.self, rptId, isBuffered, dataSetName, confRev, trgOps, options, bufTm, intgPd); - } - } - - public class ClientConnection - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ClientConnection_getPeerAddress(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ClientConnection_getLocalAddress(IntPtr self); - - internal IntPtr self; - - internal ClientConnection (IntPtr self) { - this.self = self; - } - - public string GetPeerAddress() - { - IntPtr peerAddrPtr = ClientConnection_getPeerAddress (self); - - if (peerAddrPtr != IntPtr.Zero) - return Marshal.PtrToStringAnsi (peerAddrPtr); - else - return null; - } - - public string GetLocalAddress() - { - IntPtr localAddrPtr = ClientConnection_getLocalAddress(self); - - if (localAddrPtr != IntPtr.Zero) - return Marshal.PtrToStringAnsi(localAddrPtr); - else - return null; - } - } + if (localAddrPtr != IntPtr.Zero) + return Marshal.PtrToStringAnsi(localAddrPtr); + else + return null; + } + } /// /// Represents additional context information of the control action that caused the callback invokation @@ -1089,446 +1374,457 @@ namespace IEC61850 } } - public delegate MmsDataAccessError WriteAccessHandler (DataAttribute dataAttr, MmsValue value, - ClientConnection connection, object parameter); - - /// - /// Reason for the select state change - /// - public enum SelectStateChangedReason - { - /// - /// Control has been selected - /// - SELECT_STATE_REASON_SELECTED = 0, - /// - /// Cancel received for the control - /// - SELECT_STATE_REASON_CANCELED = 1, - /// - /// Unselected due to timeout (sboTimeout) - /// - SELECT_STATE_REASON_TIMEOUT = 2, - /// - /// Unselected due to successful operate - /// - SELECT_STATE_REASON_OPERATED = 3, - /// - /// Unselected due to failed operate - /// - SELECT_STATE_REASON_OPERATE_FAILED = 4, - /// - /// Unselected due to disconnection of selecting client - /// - SELECT_STATE_REASON_DISCONNECTED = 5 - } - - public delegate void ControlSelectStateChangedHandler(ControlAction action, object parameter, bool isSelected, SelectStateChangedReason reason); - - /// - /// Return type of ControlHandler and ControlWaitForExecutionHandler - /// - public enum ControlHandlerResult { - /// - /// check or operation failed - /// - FAILED = 0, - /// - /// check or operation was successful - /// - OK = 1, - /// - /// check or operation is in progress - /// - WAITING = 2 - } - - public delegate ControlHandlerResult ControlWaitForExecutionHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool synchroCheck); - - public delegate ControlHandlerResult ControlHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test); - - public enum CheckHandlerResult { - /// - /// check passed - /// - ACCEPTED = -1, - /// - /// check failed due to hardware fault - /// - HARDWARE_FAULT = 1, - /// - /// control is already selected or operated - /// - TEMPORARILY_UNAVAILABLE = 2, - /// - /// check failed due to access control reason - access denied for this client or state - /// - OBJECT_ACCESS_DENIED = 3, - /// - /// object not visible in this security context ??? - /// - OBJECT_UNDEFINED = 4 - } - - public delegate CheckHandlerResult CheckHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool interlockCheck); - - /// - /// This class acts as the entry point for the IEC 61850 client API. It represents a single - /// (MMS) connection to a server. - /// - public class IedServer : IDisposable - { - [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] - static extern IntPtr IedServer_createWithConfig(IntPtr modelRef, IntPtr tlsConfiguration, IntPtr serverConfiguratio); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_setLocalIpAddress(IntPtr self, string localIpAddress); - - [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] - static extern void IedServer_start(IntPtr self, int tcpPort); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_stop(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_destroy(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.Bool)] - static extern bool IedServer_isRunning(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int IedServer_getNumberOfOpenConnections(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_lockDataModel(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_unlockDataModel(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateAttributeValue(IntPtr self, IntPtr DataAttribute, IntPtr MmsValue); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateBooleanAttributeValue(IntPtr self, IntPtr dataAttribute, [MarshalAs(UnmanagedType.I1)] bool value); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateInt32AttributeValue(IntPtr self, IntPtr dataAttribute, int value); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateInt64AttributeValue(IntPtr self, IntPtr dataAttribute, Int64 value); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateFloatAttributeValue(IntPtr self, IntPtr dataAttribute, float value); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateVisibleStringAttributeValue(IntPtr self, IntPtr dataAttribute, string value); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateUTCTimeAttributeValue(IntPtr self, IntPtr dataAttribute, ulong value); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateTimestampAttributeValue(IntPtr self, IntPtr dataAttribute, IntPtr timestamp); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_updateQuality(IntPtr self, IntPtr dataAttribute, ushort value); + public delegate MmsDataAccessError WriteAccessHandler (DataAttribute dataAttr, MmsValue value, + ClientConnection connection, object parameter); + + /// + /// Reason for the select state change + /// + public enum SelectStateChangedReason + { + /// + /// Control has been selected + /// + SELECT_STATE_REASON_SELECTED = 0, + /// + /// Cancel received for the control + /// + SELECT_STATE_REASON_CANCELED = 1, + /// + /// Unselected due to timeout (sboTimeout) + /// + SELECT_STATE_REASON_TIMEOUT = 2, + /// + /// Unselected due to successful operate + /// + SELECT_STATE_REASON_OPERATED = 3, + /// + /// Unselected due to failed operate + /// + SELECT_STATE_REASON_OPERATE_FAILED = 4, + /// + /// Unselected due to disconnection of selecting client + /// + SELECT_STATE_REASON_DISCONNECTED = 5 + } + + public delegate void ControlSelectStateChangedHandler(ControlAction action, object parameter, bool isSelected, SelectStateChangedReason reason); + + /// + /// Return type of ControlHandler and ControlWaitForExecutionHandler + /// + public enum ControlHandlerResult { + /// + /// check or operation failed + /// + FAILED = 0, + /// + /// check or operation was successful + /// + OK = 1, + /// + /// check or operation is in progress + /// + WAITING = 2 + } + + public delegate ControlHandlerResult ControlWaitForExecutionHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool synchroCheck); + + public delegate ControlHandlerResult ControlHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test); + + public enum CheckHandlerResult { + /// + /// check passed + /// + ACCEPTED = -1, + /// + /// check failed due to hardware fault + /// + HARDWARE_FAULT = 1, + /// + /// control is already selected or operated + /// + TEMPORARILY_UNAVAILABLE = 2, + /// + /// check failed due to access control reason - access denied for this client or state + /// + OBJECT_ACCESS_DENIED = 3, + /// + /// object not visible in this security context ??? + /// + OBJECT_UNDEFINED = 4 + } + + public delegate CheckHandlerResult CheckHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool interlockCheck); + + /// + /// This class acts as the entry point for the IEC 61850 client API. It represents a single + /// (MMS) connection to a server. + /// + public class IedServer : IDisposable + { + [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] + static extern IntPtr IedServer_createWithConfig(IntPtr modelRef, IntPtr tlsConfiguration, IntPtr serverConfiguratio); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setLocalIpAddress(IntPtr self, string localIpAddress); + + [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] + static extern void IedServer_start(IntPtr self, int tcpPort); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_stop(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool IedServer_isRunning(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServer_getNumberOfOpenConnections(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_lockDataModel(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_unlockDataModel(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateAttributeValue(IntPtr self, IntPtr DataAttribute, IntPtr MmsValue); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateBooleanAttributeValue(IntPtr self, IntPtr dataAttribute, [MarshalAs(UnmanagedType.I1)] bool value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateInt32AttributeValue(IntPtr self, IntPtr dataAttribute, int value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateInt64AttributeValue(IntPtr self, IntPtr dataAttribute, Int64 value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateFloatAttributeValue(IntPtr self, IntPtr dataAttribute, float value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateVisibleStringAttributeValue(IntPtr self, IntPtr dataAttribute, string value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateUTCTimeAttributeValue(IntPtr self, IntPtr dataAttribute, ulong value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateTimestampAttributeValue(IntPtr self, IntPtr dataAttribute, IntPtr timestamp); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_updateQuality(IntPtr self, IntPtr dataAttribute, ushort value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServer_setServerIdentity(IntPtr self, string vendor, string model, string revision); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedServer_getAttributeValue(IntPtr self, IntPtr dataAttribute); + static extern IntPtr IedServer_getAttributeValue(IntPtr self, IntPtr dataAttribute); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int InternalControlPerformCheckHandler (IntPtr action, IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test, [MarshalAs(UnmanagedType.I1)] bool interlockCheck); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int InternalControlPerformCheckHandler (IntPtr action, IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test, [MarshalAs(UnmanagedType.I1)] bool interlockCheck); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int InternalControlWaitForExecutionHandler (IntPtr action, IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test, [MarshalAs(UnmanagedType.I1)] bool synchoCheck); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int InternalControlWaitForExecutionHandler (IntPtr action, IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test, [MarshalAs(UnmanagedType.I1)] bool synchoCheck); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int InternalControlHandler (IntPtr action, IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int InternalControlHandler (IntPtr action, IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void InternalSelectStateChangedHandler(IntPtr action, IntPtr parameter, [MarshalAs(UnmanagedType.I1)] bool isSelected, int reason); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void InternalSelectStateChangedHandler(IntPtr action, IntPtr parameter, [MarshalAs(UnmanagedType.I1)] bool isSelected, int reason); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setWaitForExecutionHandler(IntPtr self, IntPtr node, InternalControlWaitForExecutionHandler handler, IntPtr parameter); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_setWaitForExecutionHandler(IntPtr self, IntPtr node, InternalControlWaitForExecutionHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setPerformCheckHandler(IntPtr self, IntPtr node, InternalControlPerformCheckHandler handler, IntPtr parameter); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_setPerformCheckHandler(IntPtr self, IntPtr node, InternalControlPerformCheckHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setControlHandler (IntPtr self, IntPtr node, InternalControlHandler handler, IntPtr parameter); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_setControlHandler (IntPtr self, IntPtr node, InternalControlHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setSelectStateChangedHandler(IntPtr self, IntPtr node, InternalSelectStateChangedHandler handler, IntPtr parameter); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_setSelectStateChangedHandler(IntPtr self, IntPtr node, InternalSelectStateChangedHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setWriteAccessPolicy(IntPtr self, FunctionalConstraint fc, AccessPolicy policy); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_setWriteAccessPolicy(IntPtr self, FunctionalConstraint fc, AccessPolicy policy); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int InternalWriteAccessHandler (IntPtr dataAttribute, IntPtr value, IntPtr connection, IntPtr parameter); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int InternalWriteAccessHandler (IntPtr dataAttribute, IntPtr value, IntPtr connection, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_handleWriteAccess(IntPtr self, IntPtr dataAttribute, + InternalWriteAccessHandler handler, IntPtr parameter); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_handleWriteAccess(IntPtr self, IntPtr dataAttribute, - InternalWriteAccessHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_handleWriteAccessForComplexAttribute(IntPtr self, IntPtr dataAttribute, + InternalWriteAccessHandler handler, IntPtr parameter); - public delegate void ConnectionIndicationHandler(IedServer iedServer, ClientConnection clientConnection, bool connected, object parameter); + public delegate void ConnectionIndicationHandler(IedServer iedServer, ClientConnection clientConnection, bool connected, object parameter); - private ConnectionIndicationHandler connectionHandler = null; - private object connectionHandlerParameter = null; + private ConnectionIndicationHandler connectionHandler = null; + private object connectionHandlerParameter = null; - public void SetConnectionIndicationHandler(ConnectionIndicationHandler handler, object parameter) - { - connectionHandler = handler; - connectionHandlerParameter = parameter; - } + public void SetConnectionIndicationHandler(ConnectionIndicationHandler handler, object parameter) + { + connectionHandler = handler; + connectionHandlerParameter = parameter; + } - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void InternalConnectionHandler (IntPtr iedServer, IntPtr clientConnection, [MarshalAs(UnmanagedType.I1)] bool connected, IntPtr parameter); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void InternalConnectionHandler (IntPtr iedServer, IntPtr clientConnection, [MarshalAs(UnmanagedType.I1)] bool connected, IntPtr parameter); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_setConnectionIndicationHandler(IntPtr self, InternalConnectionHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setConnectionIndicationHandler(IntPtr self, InternalConnectionHandler handler, IntPtr parameter); - private IntPtr self = IntPtr.Zero; + private IntPtr self = IntPtr.Zero; - private InternalControlHandler internalControlHandlerRef = null; - private InternalControlPerformCheckHandler internalControlPerformCheckHandlerRef = null; - private InternalControlWaitForExecutionHandler internalControlWaitForExecutionHandlerRef = null; - private InternalSelectStateChangedHandler internalSelectedStateChangedHandlerRef = null; + private InternalControlHandler internalControlHandlerRef = null; + private InternalControlPerformCheckHandler internalControlPerformCheckHandlerRef = null; + private InternalControlWaitForExecutionHandler internalControlWaitForExecutionHandlerRef = null; + private InternalSelectStateChangedHandler internalSelectedStateChangedHandlerRef = null; - internal class ControlHandlerInfo { - public DataObject controlObject = null; - public GCHandle handle; + internal class ControlHandlerInfo { + public DataObject controlObject = null; + public GCHandle handle; - public ControlHandler controlHandler = null; - public object controlHandlerParameter = null; + public ControlHandler controlHandler = null; + public object controlHandlerParameter = null; - public CheckHandler checkHandler = null; - public object checkHandlerParameter = null; + public CheckHandler checkHandler = null; + public object checkHandlerParameter = null; - public ControlWaitForExecutionHandler waitForExecHandler = null; - public object waitForExecHandlerParameter = null; + public ControlWaitForExecutionHandler waitForExecHandler = null; + public object waitForExecHandlerParameter = null; - public ControlSelectStateChangedHandler selectStateChangedHandler = null; - public object selectStateChangedHandlerParameter = null; + public ControlSelectStateChangedHandler selectStateChangedHandler = null; + public object selectStateChangedHandlerParameter = null; - public ControlHandlerInfo(DataObject controlObject) - { - this.controlObject = controlObject; - this.handle = GCHandle.Alloc(this); - } + public ControlHandlerInfo(DataObject controlObject) + { + this.controlObject = controlObject; + this.handle = GCHandle.Alloc(this); + } - ~ControlHandlerInfo() - { - this.handle.Free(); - } - } + ~ControlHandlerInfo() + { + this.handle.Free(); + } + } - private Dictionary controlHandlers = new Dictionary (); + private Dictionary controlHandlers = new Dictionary (); - int InternalControlHandlerImpl (IntPtr action, IntPtr parameter, IntPtr ctlVal, bool test) - { - GCHandle handle = GCHandle.FromIntPtr (parameter); + int InternalControlHandlerImpl (IntPtr action, IntPtr parameter, IntPtr ctlVal, bool test) + { + GCHandle handle = GCHandle.FromIntPtr (parameter); - ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; + ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; ControlAction controlAction = new ControlAction (action, info, this); - if (info != null && info.controlHandler != null) - return (int)info.controlHandler (controlAction, info.controlHandlerParameter, new MmsValue (ctlVal), test); - else - return (int)ControlHandlerResult.FAILED; - } + if (info != null && info.controlHandler != null) + return (int)info.controlHandler (controlAction, info.controlHandlerParameter, new MmsValue (ctlVal), test); + else + return (int)ControlHandlerResult.FAILED; + } - int InternalCheckHandlerImpl(IntPtr action, IntPtr parameter, IntPtr ctlVal, bool test, bool interlockCheck) - { - GCHandle handle = GCHandle.FromIntPtr (parameter); + int InternalCheckHandlerImpl(IntPtr action, IntPtr parameter, IntPtr ctlVal, bool test, bool interlockCheck) + { + GCHandle handle = GCHandle.FromIntPtr (parameter); - ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; + ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; - if (info != null && info.checkHandler != null) - { + if (info != null && info.checkHandler != null) + { ControlAction controlAction = new ControlAction (action, info, this); - return (int)info.checkHandler (controlAction, info.checkHandlerParameter, new MmsValue (ctlVal), test, interlockCheck); - } else - return (int)CheckHandlerResult.OBJECT_UNDEFINED; - } + return (int)info.checkHandler (controlAction, info.checkHandlerParameter, new MmsValue (ctlVal), test, interlockCheck); + } else + return (int)CheckHandlerResult.OBJECT_UNDEFINED; + } - int InternalControlWaitForExecutionHandlerImpl (IntPtr action, IntPtr parameter, IntPtr ctlVal, bool test, bool synchoCheck) - { - GCHandle handle = GCHandle.FromIntPtr (parameter); + int InternalControlWaitForExecutionHandlerImpl (IntPtr action, IntPtr parameter, IntPtr ctlVal, bool test, bool synchoCheck) + { + GCHandle handle = GCHandle.FromIntPtr (parameter); - ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; + ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; - if (info != null && info.waitForExecHandler != null) - { + if (info != null && info.waitForExecHandler != null) + { ControlAction controlAction = new ControlAction (action, info, this); return (int)info.waitForExecHandler (controlAction, info.waitForExecHandlerParameter, new MmsValue (ctlVal), test, synchoCheck); - } - else - return (int)ControlHandlerResult.FAILED; - } + } + else + return (int)ControlHandlerResult.FAILED; + } - void InternalSelectStateChangedHandlerImpl(IntPtr action, IntPtr parameter, bool isSelected, int reason) - { - GCHandle handle = GCHandle.FromIntPtr(parameter); + void InternalSelectStateChangedHandlerImpl(IntPtr action, IntPtr parameter, bool isSelected, int reason) + { + GCHandle handle = GCHandle.FromIntPtr(parameter); - ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; + ControlHandlerInfo info = (ControlHandlerInfo)handle.Target; - if (info != null && info.selectStateChangedHandler != null) - { - ControlAction controlAction = new ControlAction(action, info, this); + if (info != null && info.selectStateChangedHandler != null) + { + ControlAction controlAction = new ControlAction(action, info, this); - info.selectStateChangedHandler(controlAction, info.selectStateChangedHandlerParameter, isSelected, (SelectStateChangedReason)reason); - } - } + info.selectStateChangedHandler(controlAction, info.selectStateChangedHandlerParameter, isSelected, (SelectStateChangedReason)reason); + } + } - private struct WriteAccessHandlerInfo { - public WriteAccessHandler handler; - public object parameter; - public DataAttribute dataAttribute; + private struct WriteAccessHandlerInfo { + public WriteAccessHandler handler; + public object parameter; + public DataAttribute dataAttribute; - public WriteAccessHandlerInfo (WriteAccessHandler h, object p, DataAttribute da) - { - handler = h; - parameter = p; - dataAttribute = da; - } - } + public WriteAccessHandlerInfo (WriteAccessHandler h, object p, DataAttribute da) + { + handler = h; + parameter = p; + dataAttribute = da; + } + } - int WriteAccessHandlerImpl (IntPtr dataAttribute, IntPtr value, IntPtr connection, IntPtr parameter) - { - WriteAccessHandlerInfo info; + int WriteAccessHandlerImpl (IntPtr dataAttribute, IntPtr value, IntPtr connection, IntPtr parameter) + { + WriteAccessHandlerInfo info; - writeAccessHandlers.TryGetValue (dataAttribute, out info); + if (writeAccessHandlers.TryGetValue(dataAttribute, out info) == true) + { + ClientConnection con = null; - ClientConnection con = null; + clientConnections.TryGetValue(connection, out con); - clientConnections.TryGetValue (connection, out con); + MmsValue mmsValue = new MmsValue(value); - return (int) info.handler (info.dataAttribute, new MmsValue (value), con, info.parameter); - } + return (int)info.handler(info.dataAttribute, mmsValue.Clone(), con, info.parameter); + } + else + { + return (int) MmsDataAccessError.OBJECT_ACCESS_DENIED; + } + } - private Dictionary writeAccessHandlers = new Dictionary (); + private Dictionary writeAccessHandlers = new Dictionary (); - private void ConnectionIndicationHandlerImpl (IntPtr iedServer, IntPtr clientConnection, bool connected, IntPtr parameter) - { - if (connected == false) { - ClientConnection con = null; + private void ConnectionIndicationHandlerImpl (IntPtr iedServer, IntPtr clientConnection, bool connected, IntPtr parameter) + { + if (connected == false) { + ClientConnection con = null; - clientConnections.TryGetValue (clientConnection, out con); + clientConnections.TryGetValue (clientConnection, out con); - if (con != null) { - - if (connectionHandler != null) - connectionHandler (this, con, false, connectionHandlerParameter); + if (con != null) { + + if (connectionHandler != null) + connectionHandler (this, con, false, connectionHandlerParameter); - clientConnections.Remove (clientConnection); - } - } else { - ClientConnection con = new ClientConnection (clientConnection); + clientConnections.Remove (clientConnection); + } + } else { + ClientConnection con = new ClientConnection (clientConnection); - clientConnections.Add (clientConnection, con); + clientConnections.Add (clientConnection, con); - if (connectionHandler != null) - connectionHandler (this, con, true, connectionHandlerParameter); - } - } + if (connectionHandler != null) + connectionHandler (this, con, true, connectionHandlerParameter); + } + } - internal Dictionary clientConnections = new Dictionary (); + internal Dictionary clientConnections = new Dictionary (); /* store IedModel instance to prevent garbage collector */ private IedModel iedModel = null; - public IedServer(IedModel iedModel, IedServerConfig config = null) - { + public IedServer(IedModel iedModel, IedServerConfig config = null) + { this.iedModel = iedModel; - IntPtr nativeConfig = IntPtr.Zero; + IntPtr nativeConfig = IntPtr.Zero; - if (config != null) - nativeConfig = config.self; + if (config != null) + nativeConfig = config.self; - self = IedServer_createWithConfig (iedModel.self, IntPtr.Zero, nativeConfig); - } + self = IedServer_createWithConfig (iedModel.self, IntPtr.Zero, nativeConfig); + } - public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null) - { + public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null) + { this.iedModel = iedModel; IntPtr nativeConfig = IntPtr.Zero; - IntPtr nativeTLSConfig = IntPtr.Zero; - - if (config != null) - nativeConfig = config.self; - - if (tlsConfig != null) - nativeTLSConfig = tlsConfig.GetNativeInstance (); - - self = IedServer_createWithConfig (iedModel.self, nativeTLSConfig, nativeConfig); - } - - private InternalConnectionHandler internalConnectionHandler = null; - - /// - /// Sets the local ip address for listening - /// - /// Local IP address. - public void SetLocalIpAddress(string localIpAddress) - { - IedServer_setLocalIpAddress (self, localIpAddress); - } - - /// - /// Start MMS server - /// - /// Local IP address. - /// TCP port to use - public void Start(string localIpAddress, int tcpPort) - { - SetLocalIpAddress (localIpAddress); - Start (tcpPort); - } - - /// Start MMS server - /// TCP port to use - public void Start(int tcpPort) - { - if (internalConnectionHandler == null) - internalConnectionHandler = new InternalConnectionHandler (ConnectionIndicationHandlerImpl); - - IedServer_setConnectionIndicationHandler (self, internalConnectionHandler, IntPtr.Zero); - - IedServer_start(self, tcpPort); - } - - /// Start MMS server - public void Start () - { - Start(-1); - } - - /// - /// Stop the MMS server. - /// - /// This function will stop the server. This will close the TCP server socket and all client sockets. - public void Stop() - { - IedServer_stop(self); - internalConnectionHandler = null; - } + IntPtr nativeTLSConfig = IntPtr.Zero; + + if (config != null) + nativeConfig = config.self; + + if (tlsConfig != null) + nativeTLSConfig = tlsConfig.GetNativeInstance (); + + self = IedServer_createWithConfig (iedModel.self, nativeTLSConfig, nativeConfig); + } + + private InternalConnectionHandler internalConnectionHandler = null; + + /// + /// Sets the local ip address for listening + /// + /// Local IP address. + public void SetLocalIpAddress(string localIpAddress) + { + IedServer_setLocalIpAddress (self, localIpAddress); + } + + /// + /// Start MMS server + /// + /// Local IP address. + /// TCP port to use + public void Start(string localIpAddress, int tcpPort) + { + SetLocalIpAddress (localIpAddress); + Start (tcpPort); + } + + /// Start MMS server + /// TCP port to use + public void Start(int tcpPort) + { + if (internalConnectionHandler == null) + internalConnectionHandler = new InternalConnectionHandler (ConnectionIndicationHandlerImpl); + + IedServer_setConnectionIndicationHandler (self, internalConnectionHandler, IntPtr.Zero); + + IedServer_start(self, tcpPort); + } + + /// Start MMS server + public void Start () + { + Start(-1); + } + + /// + /// Stop the MMS server. + /// + /// This function will stop the server. This will close the TCP server socket and all client sockets. + public void Stop() + { + IedServer_stop(self); + internalConnectionHandler = null; + } /// /// Release all server resources (same as ) /// /// This function releases all MMS server resources. public void Destroy() - { + { Dispose(); - } + } /// /// Releases all resource used by the object. @@ -1571,176 +1867,246 @@ namespace IEC61850 /// Check if server is running (accepting client connections) /// /// true, if running, false otherwise. - public bool IsRunning() + public bool IsRunning() { return IedServer_isRunning(self); } - /// - /// Get number of open MMS connections - /// - /// the number of open and accepted MMS connections - public int GetNumberOfOpenConnections() - { - return IedServer_getNumberOfOpenConnections(self); - } + /// + /// Get number of open MMS connections + /// + /// the number of open and accepted MMS connections + public int GetNumberOfOpenConnections() + { + return IedServer_getNumberOfOpenConnections(self); + } private ControlHandlerInfo GetControlHandlerInfo(DataObject controlObject) - { - ControlHandlerInfo info; + { + ControlHandlerInfo info; + + controlHandlers.TryGetValue (controlObject, out info); + + if (info == null) { + info = new ControlHandlerInfo (controlObject); + controlHandlers.Add (controlObject, info); + } + + return info; + } + + public void SetControlHandler (DataObject controlObject, ControlHandler handler, object parameter) + { + ControlHandlerInfo info = GetControlHandlerInfo (controlObject); + + info.controlHandler = handler; + info.controlHandlerParameter = parameter; + + if (internalControlHandlerRef == null) + internalControlHandlerRef = new InternalControlHandler (InternalControlHandlerImpl); + + IedServer_setControlHandler(self, controlObject.self, internalControlHandlerRef, GCHandle.ToIntPtr(info.handle)); + } + + public void SetCheckHandler (DataObject controlObject, CheckHandler handler, object parameter) + { + ControlHandlerInfo info = GetControlHandlerInfo (controlObject); + + info.checkHandler = handler; + info.checkHandlerParameter = parameter; + + if (internalControlPerformCheckHandlerRef == null) + internalControlPerformCheckHandlerRef = new InternalControlPerformCheckHandler (InternalCheckHandlerImpl); + + IedServer_setPerformCheckHandler(self, controlObject.self, internalControlPerformCheckHandlerRef, GCHandle.ToIntPtr(info.handle)); + } + + public void SetWaitForExecutionHandler (DataObject controlObject, ControlWaitForExecutionHandler handler, object parameter) + { + ControlHandlerInfo info = GetControlHandlerInfo (controlObject); + + info.waitForExecHandler = handler; + info.waitForExecHandlerParameter = parameter; + + if (internalControlWaitForExecutionHandlerRef == null) + internalControlWaitForExecutionHandlerRef = new InternalControlWaitForExecutionHandler (InternalControlWaitForExecutionHandlerImpl); - controlHandlers.TryGetValue (controlObject, out info); + IedServer_setWaitForExecutionHandler(self, controlObject.self, internalControlWaitForExecutionHandlerRef, GCHandle.ToIntPtr(info.handle)); + } + + /// + /// Set a callback handler for a controllable data object to track select state changes + /// + /// Control object. + /// Handler. + /// A user provided parameter that is passed to the callback handler. + public void SetSelectStateChangedHandler(DataObject controlObject, ControlSelectStateChangedHandler handler, object parameter) + { + ControlHandlerInfo info = GetControlHandlerInfo(controlObject); + + info.selectStateChangedHandler = handler; + info.selectStateChangedHandlerParameter = parameter; + + if (internalSelectedStateChangedHandlerRef == null) + internalSelectedStateChangedHandlerRef = new InternalSelectStateChangedHandler(InternalSelectStateChangedHandlerImpl); + + IedServer_setSelectStateChangedHandler(self, controlObject.self, internalSelectedStateChangedHandlerRef, GCHandle.ToIntPtr(info.handle)); + } + + /// + /// Install a WriteAccessHandler for a data attribute. + /// + /// This instructs the server to monitor write attempts by MMS clients to specific + /// data attributes.If a client tries to write to the monitored data attribute the + /// handler is invoked.The handler can decide if the write access will be allowed + /// or denied.If a WriteAccessHandler is set for a specific data attribute - the + /// default write access policy will not be performed for that data attribute. + /// + /// NOTE: If the data attribute has sub data attributes, the WriteAccessHandler is not + /// set for the sub data attributes and will not be called when the sub data attribute is + /// written directly! + /// + /// the data attribute to monitor + /// the callback function that is invoked if a client tries to write to the monitored data attribute. + /// a user provided parameter that is passed to the WriteAccessHandler when called. + public void HandleWriteAccess (DataAttribute dataAttr, WriteAccessHandler handler, object parameter) + { + writeAccessHandlers.Add (dataAttr.self, new WriteAccessHandlerInfo(handler, parameter, dataAttr)); + + IedServer_handleWriteAccess (self, dataAttr.self, WriteAccessHandlerImpl, IntPtr.Zero); + } + + private void AddHandlerInfoForDataAttributeRecursive(DataAttribute da, WriteAccessHandler handler, object parameter) + { + writeAccessHandlers.Add(da.self, new WriteAccessHandlerInfo(handler, parameter, da)); + + foreach (ModelNode child in da.GetChildren()) + { + if (child is DataAttribute) + { + AddHandlerInfoForDataAttributeRecursive(child as DataAttribute, handler, parameter); + } + } + } + + /// + /// Install a WriteAccessHandler for a data attribute and for all sub data attributes + /// + /// This instructs the server to monitor write attempts by MMS clients to specific + /// data attributes.If a client tries to write to the monitored data attribute the + /// handler is invoked.The handler can decide if the write access will be allowed + /// or denied.If a WriteAccessHandler is set for a specific data attribute - the + /// default write access policy will not be performed for that data attribute. + /// + /// NOTE: When the data attribute is a complex attribute then the handler will also be installed + /// for all sub data attributes. When the data attribute is a basic data attribute then + /// this function behaves like . + /// + /// the data attribute to monitor + /// the callback function that is invoked if a client tries to write to the monitored data attribute. + /// a user provided parameter that is passed to the WriteAccessHandler when called. + public void HandleWriteAccessForComplexAttribute(DataAttribute dataAttr, WriteAccessHandler handler, object parameter) + { + AddHandlerInfoForDataAttributeRecursive(dataAttr, handler, parameter); + + IedServer_handleWriteAccessForComplexAttribute(self, dataAttr.self, WriteAccessHandlerImpl, IntPtr.Zero); + } + + public void SetWriteAccessPolicy(FunctionalConstraint fc, AccessPolicy policy) + { + IedServer_setWriteAccessPolicy (self, fc, policy); + } - if (info == null) { - info = new ControlHandlerInfo (controlObject); - controlHandlers.Add (controlObject, info); - } + /// + /// Locks the data model for data update. + /// + /// This function should be called before the data model is updated. + /// After updating the data model the function should be called. + /// + /// + /// his method should never be called inside of a library callback function. In the context of + /// a library callback the data model is always already locked! Calling this function inside of a + /// library callback may lead to a deadlock condition. + /// + public void LockDataModel() + { + IedServer_lockDataModel(self); + } + + /// + /// Unlocks the data model and process pending client requests. + /// + /// + /// + /// This method should never be called inside of a library callback function. In the context of + /// a library callback the data model is always already locked! + /// + public void UnlockDataModel() + { + IedServer_unlockDataModel(self); + } + + public void UpdateAttributeValue(DataAttribute dataAttr, MmsValue value) + { + IedServer_updateAttributeValue (self, dataAttr.self, value.valueReference); + } - return info; - } + public void UpdateBooleanAttributeValue(DataAttribute dataAttr, bool value) + { + IedServer_updateBooleanAttributeValue(self, dataAttr.self, value); + } - public void SetControlHandler (DataObject controlObject, ControlHandler handler, object parameter) - { - ControlHandlerInfo info = GetControlHandlerInfo (controlObject); + public void UpdateFloatAttributeValue(DataAttribute dataAttr, float value) + { + IedServer_updateFloatAttributeValue(self, dataAttr.self, value); + } - info.controlHandler = handler; - info.controlHandlerParameter = parameter; + public void UpdateInt32AttributeValue(DataAttribute dataAttr, int value) + { + IedServer_updateInt32AttributeValue(self, dataAttr.self, value); + } - if (internalControlHandlerRef == null) - internalControlHandlerRef = new InternalControlHandler (InternalControlHandlerImpl); + public void UpdateInt64AttributeValue(DataAttribute dataAttr, Int64 value) + { + IedServer_updateInt64AttributeValue (self, dataAttr.self, value); + } - IedServer_setControlHandler(self, controlObject.self, internalControlHandlerRef, GCHandle.ToIntPtr(info.handle)); - } + public void UpdateVisibleStringAttributeValue(DataAttribute dataAttr, string value) + { + IedServer_updateVisibleStringAttributeValue(self, dataAttr.self, value); + } - public void SetCheckHandler (DataObject controlObject, CheckHandler handler, object parameter) - { - ControlHandlerInfo info = GetControlHandlerInfo (controlObject); + public void UpdateUTCTimeAttributeValue(DataAttribute dataAttr, DateTime timestamp) + { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + DateTime timestampUTC = timestamp.ToUniversalTime(); - info.checkHandler = handler; - info.checkHandlerParameter = parameter; + TimeSpan timeDiff = timestampUTC - epoch; + ulong timeVal = Convert.ToUInt64(timeDiff.TotalMilliseconds); - if (internalControlPerformCheckHandlerRef == null) - internalControlPerformCheckHandlerRef = new InternalControlPerformCheckHandler (InternalCheckHandlerImpl); + IedServer_updateUTCTimeAttributeValue(self, dataAttr.self, timeVal); + } - IedServer_setPerformCheckHandler(self, controlObject.self, internalControlPerformCheckHandlerRef, GCHandle.ToIntPtr(info.handle)); - } + public void UpdateTimestampAttributeValue(DataAttribute dataAttr, Timestamp timestamp) + { + IedServer_updateTimestampAttributeValue (self, dataAttr.self, timestamp.timestampRef); + } - public void SetWaitForExecutionHandler (DataObject controlObject, ControlWaitForExecutionHandler handler, object parameter) - { - ControlHandlerInfo info = GetControlHandlerInfo (controlObject); + public void UpdateQuality(DataAttribute dataAttr, ushort value) + { + IedServer_updateQuality(self, dataAttr.self, value); + } - info.waitForExecHandler = handler; - info.waitForExecHandlerParameter = parameter; + public MmsValue GetAttributeValue(DataAttribute dataAttr) + { + IntPtr mmsValuePtr = IedServer_getAttributeValue (self, dataAttr.self); - if (internalControlWaitForExecutionHandlerRef == null) - internalControlWaitForExecutionHandlerRef = new InternalControlWaitForExecutionHandler (InternalControlWaitForExecutionHandlerImpl); + if (mmsValuePtr != IntPtr.Zero) + return new MmsValue (mmsValuePtr); + else + return null; + } + } - IedServer_setWaitForExecutionHandler(self, controlObject.self, internalControlWaitForExecutionHandlerRef, GCHandle.ToIntPtr(info.handle)); - } - - /// - /// Set a callback handler for a controllable data object to track select state changes - /// - /// Control object. - /// Handler. - /// A user provided parameter that is passed to the callback handler. - public void SetSelectStateChangedHandler(DataObject controlObject, ControlSelectStateChangedHandler handler, object parameter) - { - ControlHandlerInfo info = GetControlHandlerInfo(controlObject); - - info.selectStateChangedHandler = handler; - info.selectStateChangedHandlerParameter = parameter; - - if (internalSelectedStateChangedHandlerRef == null) - internalSelectedStateChangedHandlerRef = new InternalSelectStateChangedHandler(InternalSelectStateChangedHandlerImpl); - - IedServer_setSelectStateChangedHandler(self, controlObject.self, internalSelectedStateChangedHandlerRef, GCHandle.ToIntPtr(info.handle)); - } - - public void HandleWriteAccess (DataAttribute dataAttr, WriteAccessHandler handler, object parameter) - { - writeAccessHandlers.Add (dataAttr.self, new WriteAccessHandlerInfo(handler, parameter, dataAttr)); - - IedServer_handleWriteAccess (self, dataAttr.self, WriteAccessHandlerImpl, IntPtr.Zero); - } - - public void SetWriteAccessPolicy(FunctionalConstraint fc, AccessPolicy policy) - { - IedServer_setWriteAccessPolicy (self, fc, policy); - } - - - public void LockDataModel() - { - IedServer_lockDataModel(self); - } - - public void UnlockDataModel() - { - IedServer_unlockDataModel(self); - } - - public void UpdateAttributeValue(DataAttribute dataAttr, MmsValue value) - { - IedServer_updateAttributeValue (self, dataAttr.self, value.valueReference); - } - - public void UpdateBooleanAttributeValue(DataAttribute dataAttr, bool value) - { - IedServer_updateBooleanAttributeValue(self, dataAttr.self, value); - } - - public void UpdateFloatAttributeValue(DataAttribute dataAttr, float value) - { - IedServer_updateFloatAttributeValue(self, dataAttr.self, value); - } - - public void UpdateInt32AttributeValue(DataAttribute dataAttr, int value) - { - IedServer_updateInt32AttributeValue(self, dataAttr.self, value); - } - - public void UpdateInt64AttributeValue(DataAttribute dataAttr, Int64 value) - { - IedServer_updateInt64AttributeValue (self, dataAttr.self, value); - } - - public void UpdateVisibleStringAttributeValue(DataAttribute dataAttr, string value) - { - IedServer_updateVisibleStringAttributeValue(self, dataAttr.self, value); - } - - public void UpdateUTCTimeAttributeValue(DataAttribute dataAttr, DateTime timestamp) - { - DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - DateTime timestampUTC = timestamp.ToUniversalTime(); - - TimeSpan timeDiff = timestampUTC - epoch; - ulong timeVal = Convert.ToUInt64(timeDiff.TotalMilliseconds); - - IedServer_updateUTCTimeAttributeValue(self, dataAttr.self, timeVal); - } - - public void UpdateTimestampAttributeValue(DataAttribute dataAttr, Timestamp timestamp) - { - IedServer_updateTimestampAttributeValue (self, dataAttr.self, timestamp.timestampRef); - } - - public void UpdateQuality(DataAttribute dataAttr, ushort value) - { - IedServer_updateQuality(self, dataAttr.self, value); - } - - public MmsValue GetAttributeValue(DataAttribute dataAttr) - { - IntPtr mmsValuePtr = IedServer_getAttributeValue (self, dataAttr.self); - - if (mmsValuePtr != IntPtr.Zero) - return new MmsValue (mmsValuePtr); - else - return null; - } - } - - } + } } diff --git a/dotnet/tests/Test.cs b/dotnet/tests/Test.cs index 8b945f7d..de3fad63 100644 --- a/dotnet/tests/Test.cs +++ b/dotnet/tests/Test.cs @@ -244,7 +244,9 @@ namespace tests Assert.AreEqual (list.ToArray () [0], "simpleIOGenericIO"); - iedServer.Stop (); + Assert.IsTrue(iedServer.IsRunning()); + + iedServer.Stop (); iedServer.Destroy (); } @@ -289,7 +291,35 @@ namespace tests Assert.IsNotNull (modelNode); } - [Test ()] + [Test()] + public void AccessDataModelServerSideNavigateModelNode() + { + IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("../../model.cfg"); + + ModelNode modelNode = iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.AnIn1"); + + Assert.IsNotNull(modelNode); + + Assert.IsTrue(modelNode.GetType().Equals(typeof(DataObject))); + + var children = modelNode.GetChildren(); + + Assert.AreEqual(3, children.Count); + + ModelNode mag = children.First.Value; + + Assert.AreEqual("mag", mag.GetName()); + + ModelNode t = children.Last.Value; + + Assert.AreEqual("t", t.GetName()); + + //modelNode = iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.AnIn1.mag.f"); + + //Assert.IsTrue(modelNode.GetType().Equals(typeof(IEC61850.Server.DataAttribute))); + } + + [Test ()] public void AccessDataModelClientServer() { IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("../../model.cfg"); @@ -382,10 +412,62 @@ namespace tests iedServer.Destroy (); } - [Test()] + [Test()] + public void ControlWriteAccessComplexDAToServer() + { + IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("../../model2.cfg"); + + IEC61850.Server.DataAttribute setAnVal_setMag = (IEC61850.Server.DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/LLN0.SetAnVal.setMag"); + + IedServer iedServer = new IedServer(iedModel); + + int handlerCalled = 0; + + MmsValue receivedValue = null; + + iedServer.SetWriteAccessPolicy(FunctionalConstraint.SP, AccessPolicy.ACCESS_POLICY_DENY); + + iedServer.HandleWriteAccessForComplexAttribute(setAnVal_setMag, delegate (IEC61850.Server.DataAttribute dataAttr, MmsValue value, ClientConnection con, object parameter) { + receivedValue = value; + handlerCalled++; + return MmsDataAccessError.SUCCESS; + }, null); + + iedServer.Start(10002); + + IedConnection connection = new IedConnection(); + + connection.Connect("localhost", 10002); + + MmsValue complexValue = MmsValue.NewEmptyStructure(1); + complexValue.SetElement(0, new MmsValue((float)1.0)); + + connection.WriteValue("simpleIOGenericIO/LLN0.SetAnVal.setMag", FunctionalConstraint.SP, complexValue); + + Assert.NotNull(receivedValue); + Assert.AreEqual(MmsType.MMS_STRUCTURE, receivedValue.GetType()); + Assert.AreEqual(1.0, receivedValue.GetElement(0).ToFloat()); + + receivedValue.Dispose(); + + receivedValue = null; + + connection.WriteValue("simpleIOGenericIO/LLN0.SetAnVal.setMag.f", FunctionalConstraint.SP, new MmsValue((float)2.0)); + + Assert.NotNull(receivedValue); + Assert.AreEqual(MmsType.MMS_FLOAT, receivedValue.GetType()); + Assert.AreEqual(2.0, receivedValue.ToFloat()); + + connection.Abort(); + + iedServer.Stop(); + + iedServer.Dispose(); + } + + [Test()] public void WriteAccessPolicy() { - IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("../../model.cfg"); IEC61850.Server.DataAttribute opDlTmms = (IEC61850.Server.DataAttribute) iedModel.GetModelNodeByShortObjectReference("GenericIO/PDUP1.OpDlTmms.setVal"); @@ -393,7 +475,7 @@ namespace tests IedServer iedServer = new IedServer (iedModel); - iedServer.HandleWriteAccess (opDlTmms, delegate(IEC61850.Server.DataAttribute dataAttr, MmsValue value, ClientConnection con, object parameter) { + iedServer.HandleWriteAccess (opDlTmms, delegate(IEC61850.Server.DataAttribute dataAttr, MmsValue value, ClientConnection con, object parameter) { return MmsDataAccessError.SUCCESS; }, null); @@ -404,7 +486,9 @@ namespace tests connection.Connect ("localhost", 10002); - connection.WriteValue ("simpleIOGenericIO/PDUP1.RsDlTmms.setVal", FunctionalConstraint.SP, new MmsValue ((int)1234)); + iedServer.SetWriteAccessPolicy(FunctionalConstraint.SP, AccessPolicy.ACCESS_POLICY_ALLOW); + + connection.WriteValue ("simpleIOGenericIO/PDUP1.RsDlTmms.setVal", FunctionalConstraint.SP, new MmsValue ((int)1234)); iedServer.SetWriteAccessPolicy (FunctionalConstraint.SP, AccessPolicy.ACCESS_POLICY_DENY); @@ -425,11 +509,10 @@ namespace tests iedServer.Stop (); - iedServer.Destroy (); + iedServer.Dispose(); } [Test()] - [Ignore("has to be fixed")] public void ControlHandler() { IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("../../model.cfg"); @@ -476,7 +559,7 @@ namespace tests iedServer.Stop (); - iedServer.Destroy (); + iedServer.Dispose(); } @@ -525,6 +608,8 @@ namespace tests Assert.AreEqual ("127.0.0.1:", ipAddress.Substring (0, 10)); iedServer.Stop (); + + iedServer.Dispose(); } [Test()] @@ -555,7 +640,7 @@ namespace tests } [Test()] - public void MmsValaueCreateStructureAndAddElement() + public void MmsValueCreateStructureAndAddElement() { MmsValue structure1 = MmsValue.NewEmptyStructure(1); MmsValue structure2 = MmsValue.NewEmptyStructure(1); diff --git a/dotnet/tests/model2.cfg b/dotnet/tests/model2.cfg new file mode 100644 index 00000000..1aa07669 --- /dev/null +++ b/dotnet/tests/model2.cfg @@ -0,0 +1,446 @@ +MODEL(simpleIO){ +LD(GenericIO){ +LN(LLN0){ +DO(Mod 0){ +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=0; +} +DO(Beh 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Health 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(NamPlt 0){ +DA(vendor 0 20 5 0 0); +DA(swRev 0 20 5 0 0); +DA(d 0 20 5 0 0); +DA(configRev 0 20 5 0 0); +DA(ldNs 0 20 11 0 0); +} +DO(SetInt1 0){ +DA(setVal 0 3 2 1 0); +} +DO(SetInt2 0){ +DA(setVal 0 3 2 1 0); +} +DO(SetInt3 0){ +DA(setVal 0 3 2 1 0); +} +DO(SetInt4 0){ +DA(setVal 0 3 2 1 0); +} +DO(SetBool1 0){ +DA(setVal 0 0 2 1 0); +} +DO(SetBool2 0){ +DA(setVal 0 0 2 1 0); +} +DO(SetBool3 0){ +DA(setVal 0 0 2 1 0); +} +DO(SetBool4 0){ +DA(setVal 0 0 2 1 0); +} +DO(Int64 0){ +DA(stVal 0 4 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(SetAnVal 0){ +DA(setMag 0 27 2 1 0){ +DA(f 0 10 2 1 0); +} +} +DS(Events){ +DE(GGIO1$ST$SPCSO1$stVal); +DE(GGIO1$ST$SPCSO2$stVal); +DE(GGIO1$ST$SPCSO3$stVal); +DE(GGIO1$ST$SPCSO4$stVal); +} +DS(EventsFCDO){ +DE(GGIO1$ST$SPCSO1); +DE(GGIO1$ST$SPCSO2); +DE(GGIO1$ST$SPCSO3); +DE(GGIO1$ST$SPCSO4); +} +DS(Booleans){ +DE(LLN0$SP$SetBool1$setVal); +DE(LLN0$SP$SetBool2$setVal); +DE(LLN0$SP$SetBool3$setVal); +DE(LLN0$SP$SetBool4$setVal); +} +DS(Integers){ +DE(LLN01$SP$SetInt1$setVal); +DE(LLN01$SP$SetInt2$setVal); +DE(LLN01$SP$SetInt3$setVal); +DE(LLN01$SP$SetInt4$setVal); +} +RC(EventsRCB01 Events1 0 Events 1 24 239 50 1000); +RC(EventsRCB02 Events1 0 Events 1 24 239 50 1000); +RC(BufferedRCB01 Events1 1 Events 1 24 239 50 1000); +RC(BufferedRCB02 Events1 1 Events 1 24 239 50 1000); +GC(gcbEvents events Events 1 0 1000 3000 ){ +PA(4 1 4096 010ccd010001); +} +} +LN(LPHD1){ +DO(PhyNam 0){ +DA(vendor 0 20 5 0 0); +} +DO(PhyHealth 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Proxy 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +} +LN(GGIO1){ +DO(Mod 0){ +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=0; +} +DO(Beh 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Health 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(NamPlt 0){ +DA(vendor 0 20 5 0 0); +DA(swRev 0 20 5 0 0); +DA(d 0 20 5 0 0); +DA(dU 0 21 5 0 0); +} +DO(AnIn1 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(AnIn2 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(AnIn3 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(AnIn4 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(SPCSO1 0){ +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=1; +} +DO(SPCSO2 0){ +DA(SBO 0 17 12 0 0); +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Cancel 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +} +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=2; +DA(sboClass 0 12 4 0 0); +} +DO(SPCSO3 0){ +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Cancel 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +} +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=3; +} +DO(SPCSO4 0){ +DA(SBOw 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Cancel 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +} +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=4; +} +DO(SPCSO5 0){ +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=1; +DA(Cancel 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +} +} +DO(SPCSO6 0){ +DA(SBO 0 17 12 0 0); +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Cancel 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +} +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=2; +} +DO(SPCSO7 0){ +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Cancel 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +} +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=3; +} +DO(SPCSO8 0){ +DA(SBOw 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(Cancel 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(operTm 0 22 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +} +DA(origin 0 27 0 0 0){ +DA(orCat 0 12 0 0 0); +DA(orIdent 0 13 0 0 0); +} +DA(ctlNum 0 6 0 0 0); +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=4; +} +DO(Ind1 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Ind2 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Ind3 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Ind4 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(SchdAbsTm 0){ +DA(val 24 10 2 1 0); +} +} +LN(MHAI1){ +DO(HA 0){ +DO(phsAHar 16){ +DA(cVal 0 27 1 5 0){ +DA(mag 0 27 1 5 0){ +DA(f 0 10 1 5 0); +} +DA(ang 0 27 1 5 0){ +DA(f 0 10 1 5 0); +} +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DA(numHar 0 7 4 1 0)=16; +DA(numCyc 0 7 4 1 0); +DA(evalTm 0 7 4 1 0); +DA(frequency 0 10 4 1 0); +} +} +} +} diff --git a/dotnet/tests/simpleIO_control_tests.scd b/dotnet/tests/simpleIO_control_tests.scd new file mode 100644 index 00000000..57a242d5 --- /dev/null +++ b/dotnet/tests/simpleIO_control_tests.scd @@ -0,0 +1,430 @@ + + +
+
+ + + Station bus + 10 + +
+

10.0.0.2

+

255.255.255.0

+

10.0.0.1

+

0001

+

00000001

+

0001

+
+ +
+

1

+

4

+

01-0c-cd-01-00-01

+

1000

+
+ 1000 + 3000 +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + status-only + + + + + direct-with-normal-security + + + + + sbo-with-normal-security + + + + + direct-with-enhanced-security + + + + + sbo-with-enhanced-security + + + + + direct-with-normal-security + + + + + sbo-with-normal-security + + + + + direct-with-enhanced-security + + + + + sbo-with-enhanced-security + + + + + + + 16 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + operate-once + operate-many + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
From 42bb617841294c8ea2e338258ca9d5d824c82f7e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 23 Feb 2021 17:14:46 +0100 Subject: [PATCH 15/68] - HAL: implemented Hal_setTimeInNs for windows --- hal/inc/hal_time.h | 8 ++++++++ hal/time/win32/time.c | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/hal/inc/hal_time.h b/hal/inc/hal_time.h index 52782959..7d5a942c 100644 --- a/hal/inc/hal_time.h +++ b/hal/inc/hal_time.h @@ -71,6 +71,14 @@ Hal_getTimeInMs(void); PAL_API nsSinceEpoch Hal_getTimeInNs(void); +/** +* Set the system time from ns time +* +* The time value returned as 64-bit unsigned integer should represent the nanoseconds +* since the UNIX epoch (1970/01/01 00:00 UTC). +* +* \return true on success, otherwise false +*/ PAL_API bool Hal_setTimeInNs(nsSinceEpoch nsTime); diff --git a/hal/time/win32/time.c b/hal/time/win32/time.c index 17eaf65d..1e7adfe3 100644 --- a/hal/time/win32/time.c +++ b/hal/time/win32/time.c @@ -57,3 +57,21 @@ Hal_getTimeInNs() return nsTime; } + +bool +Hal_setTimeInNs(nsSinceEpoch nsTime) +{ + uint64_t t = (nsTime / 100ULL) + 116444736000000000ULL; + + FILETIME ft; + + ft.dwLowDateTime = (uint32_t)(t & 0xffffffff); + ft.dwHighDateTime = (uint32_t)(t >> 32); + + SYSTEMTIME st; + + FileTimeToSystemTime(&ft, &st); + + return SetSystemTime(&st); +} + From dc22dc76eccdc9f26bb2c52822b9916b4cda3746 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 27 Feb 2021 11:54:21 +0100 Subject: [PATCH 16/68] - IED server: fixed bug in log service - old-entry and old-entry-time not updated --- src/iec61850/inc_private/logging.h | 3 +++ src/iec61850/server/mms_mapping/logging.c | 15 +++++++++++++-- src/logging/log_storage.c | 6 ++++++ src/logging/logging_api.h | 10 ++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 9ec8f936..74caf056 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -81,6 +81,9 @@ LogInstance_create(LogicalNode* parentLN, const char* name); LIB61850_INTERNAL void LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage); +LIB61850_INTERNAL void +LogInstance_updateStatus(LogInstance* self); + LIB61850_INTERNAL void LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag); diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 6d9e4551..e27d5f89 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -163,13 +163,21 @@ LogInstance_logEntryFinished(LogInstance* self, uint64_t entryID) self->locked = false; } +void +LogInstance_updateStatus(LogInstance* self) +{ + if (self->logStorage) { + LogStorage_getOldestAndNewestEntries(self->logStorage, &(self->newEntryId), &(self->newEntryTime), + &(self->oldEntryId), &(self->oldEntryTime)); + } +} + void LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage) { self->logStorage = logStorage; - LogStorage_getOldestAndNewestEntries(logStorage, &(self->newEntryId), &(self->newEntryTime), - &(self->oldEntryId), &(self->oldEntryTime)); + LogInstance_updateStatus(self); } LogControl* @@ -357,6 +365,9 @@ updateLogStatusInLCB(LogControl* self) LogInstance* logInstance = self->logInstance; if (logInstance != NULL) { + + LogInstance_updateStatus(logInstance); + MmsValue_setBinaryTime(self->oldEntrTm, logInstance->oldEntryTime); MmsValue_setBinaryTime(self->newEntrTm, logInstance->newEntryTime); diff --git a/src/logging/log_storage.c b/src/logging/log_storage.c index 40335513..a4a52f22 100644 --- a/src/logging/log_storage.c +++ b/src/logging/log_storage.c @@ -30,6 +30,12 @@ LogStorage_setMaxLogEntries(LogStorage self, int maxEntries) self->maxLogEntries = maxEntries; } +int +LogStorage_getMaxLogEntries(LogStorage self) +{ + return self->maxLogEntries; +} + uint64_t LogStorage_addEntry(LogStorage self, uint64_t timestamp) { diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h index a4493535..7f5327fc 100644 --- a/src/logging/logging_api.h +++ b/src/logging/logging_api.h @@ -107,6 +107,16 @@ struct sLogStorage { LIB61850_API void LogStorage_setMaxLogEntries(LogStorage self, int maxEntries); +/** + * \brief Get the maximum allowed number of log entries for this log + * + * \param self the pointer of the LogStorage instance + * + * \return the maximum number of log entries + */ +LIB61850_API int +LogStorage_getMaxLogEntries(LogStorage self); + /** * \brief Add an entry to the log * From 91f3ed7989ad3e0aef13779f7f0bd01a772a29a5 Mon Sep 17 00:00:00 2001 From: davkor Date: Mon, 22 Feb 2021 09:54:43 +0000 Subject: [PATCH 17/68] Added fuzzer for oss-fuzz integration. --- fuzz/fuzz_mms_decode.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 fuzz/fuzz_mms_decode.c diff --git a/fuzz/fuzz_mms_decode.c b/fuzz/fuzz_mms_decode.c new file mode 100644 index 00000000..6c4e4530 --- /dev/null +++ b/fuzz/fuzz_mms_decode.c @@ -0,0 +1,15 @@ +#include +#include + +#include "iec61850_server.h" +#include "hal_thread.h" + +int LLVMFuzzerTestOneInput(const char *data, size_t size) { + int out; + MmsValue* value = NULL; + value = MmsValue_decodeMmsData(data, 0, size, &out); + if (value != NULL) { + free(value); + } + return 0; +} From 0fd1176edef8ab9e44d6c87d073fec1033f823b0 Mon Sep 17 00:00:00 2001 From: davkor Date: Mon, 22 Feb 2021 09:57:16 +0000 Subject: [PATCH 18/68] Change tabs to spaces. --- fuzz/fuzz_mms_decode.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fuzz/fuzz_mms_decode.c b/fuzz/fuzz_mms_decode.c index 6c4e4530..f29831ad 100644 --- a/fuzz/fuzz_mms_decode.c +++ b/fuzz/fuzz_mms_decode.c @@ -5,11 +5,11 @@ #include "hal_thread.h" int LLVMFuzzerTestOneInput(const char *data, size_t size) { - int out; - MmsValue* value = NULL; - value = MmsValue_decodeMmsData(data, 0, size, &out); - if (value != NULL) { - free(value); - } - return 0; + int out; + MmsValue* value = NULL; + value = MmsValue_decodeMmsData(data, 0, size, &out); + if (value != NULL) { + free(value); + } + return 0; } From 7f381b54ba4dba81bd61d7a4f84fbacec7aefe53 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 24 Feb 2021 15:24:37 +0100 Subject: [PATCH 19/68] - fixed bug in oss-fuzz adaptor --- fuzz/fuzz_mms_decode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fuzz/fuzz_mms_decode.c b/fuzz/fuzz_mms_decode.c index f29831ad..3a2d9370 100644 --- a/fuzz/fuzz_mms_decode.c +++ b/fuzz/fuzz_mms_decode.c @@ -8,8 +8,10 @@ int LLVMFuzzerTestOneInput(const char *data, size_t size) { int out; MmsValue* value = NULL; value = MmsValue_decodeMmsData(data, 0, size, &out); + if (value != NULL) { - free(value); + MmsValue_delete(value); } + return 0; } From e04e424b027a2f2ebdcad318bd8e2f498220788a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 24 Feb 2021 16:07:01 +0100 Subject: [PATCH 20/68] - fixed oss-fuzz issues 31399, 31340, 31341, 31344, 31346 --- src/mms/asn1/ber_decode.c | 5 ++++- src/mms/iso_mms/server/mms_access_result.c | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/mms/asn1/ber_decode.c b/src/mms/asn1/ber_decode.c index 454de0d4..f19ecdca 100644 --- a/src/mms/asn1/ber_decode.c +++ b/src/mms/asn1/ber_decode.c @@ -30,7 +30,7 @@ getIndefiniteLength(uint8_t* buffer, int bufPos, int maxBufPos) int length = 0; while (bufPos < maxBufPos) { - if ((buffer[bufPos] == 0) && (buffer[bufPos+1] == 0)) { + if ((buffer[bufPos] == 0) && ((bufPos + 1) < maxBufPos) && (buffer[bufPos+1] == 0)) { return length + 2; } else { @@ -80,6 +80,9 @@ BerDecoder_decodeLength(uint8_t* buffer, int* length, int bufPos, int maxBufPos) if (bufPos >= maxBufPos) return -1; + if (bufPos + (*length) > maxBufPos) + return -1; + *length <<= 8; *length += buffer[bufPos++]; } diff --git a/src/mms/iso_mms/server/mms_access_result.c b/src/mms/iso_mms/server/mms_access_result.c index 0beda10f..d7d78975 100644 --- a/src/mms/iso_mms/server/mms_access_result.c +++ b/src/mms/iso_mms/server/mms_access_result.c @@ -159,6 +159,9 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu int dataEndBufPos = bufferLength; + if (bufferLength < 1) + goto exit_with_error; + uint8_t tag = buffer[bufPos++]; int dataLength; @@ -168,6 +171,10 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu if (bufPos < 0) goto exit_with_error; + /* if not indefinite length end tag, data length must be > 0 */ + if ((tag != 0) && (dataLength == 0)) + goto exit_with_error; + switch (tag) { case 0xa1: /* MMS_ARRAY */ @@ -253,6 +260,7 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu value = MmsValue_newUnsigned(dataLength * 8); memcpy(value->value.integer->octets, buffer + bufPos, dataLength); value->value.integer->size = dataLength; + bufPos += dataLength; break; From ed810fde0f8396dc017dc9849d1b9e00b7e74290 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 27 Feb 2021 12:39:55 +0100 Subject: [PATCH 21/68] - MmsValue_equalTypes: check parameters for NULL to avoid dereferencing NULL pointer --- src/mms/iso_mms/common/mms_value.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 3e155320..02d66814 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -193,6 +193,9 @@ MmsValue_equals(const MmsValue* self, const MmsValue* otherValue) bool MmsValue_equalTypes(const MmsValue* self, const MmsValue* otherValue) { + if ((self == NULL) || (otherValue == NULL)) + return false; + if (self->type == otherValue->type) { switch (self->type) { From d546ebac6f8fe0e7ddb7588413ad014e90e33bae Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 1 Mar 2021 16:52:05 +0100 Subject: [PATCH 22/68] - restrict maximum recursion depth in BerDecoder_decodeLength when indefinite length encoding is used to avoid stack overflow when receiving malformed messages --- src/mms/asn1/ber_decode.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/mms/asn1/ber_decode.c b/src/mms/asn1/ber_decode.c index f19ecdca..12078610 100644 --- a/src/mms/asn1/ber_decode.c +++ b/src/mms/asn1/ber_decode.c @@ -25,8 +25,16 @@ #include "ber_decode.h" static int -getIndefiniteLength(uint8_t* buffer, int bufPos, int maxBufPos) +BerDecoder_decodeLengthRecursive(uint8_t* buffer, int* length, int bufPos, int maxBufPos, int depth, int maxDepth); + +static int +getIndefiniteLength(uint8_t* buffer, int bufPos, int maxBufPos, int depth, int maxDepth) { + depth++; + + if (depth > maxDepth) + return -1; + int length = 0; while (bufPos < maxBufPos) { @@ -44,7 +52,7 @@ getIndefiniteLength(uint8_t* buffer, int bufPos, int maxBufPos) int subLength = -1; - int newBufPos = BerDecoder_decodeLength(buffer, &subLength, bufPos, maxBufPos); + int newBufPos = BerDecoder_decodeLengthRecursive(buffer, &subLength, bufPos, maxBufPos, depth, maxDepth); if (newBufPos == -1) return -1; @@ -58,8 +66,8 @@ getIndefiniteLength(uint8_t* buffer, int bufPos, int maxBufPos) return -1; } -int -BerDecoder_decodeLength(uint8_t* buffer, int* length, int bufPos, int maxBufPos) +static int +BerDecoder_decodeLengthRecursive(uint8_t* buffer, int* length, int bufPos, int maxBufPos, int depth, int maxDepth) { if (bufPos >= maxBufPos) return -1; @@ -70,7 +78,7 @@ BerDecoder_decodeLength(uint8_t* buffer, int* length, int bufPos, int maxBufPos) int lenLength = len1 & 0x7f; if (lenLength == 0) { /* indefinite length form */ - *length = getIndefiniteLength(buffer, bufPos, maxBufPos); + *length = getIndefiniteLength(buffer, bufPos, maxBufPos, depth, maxDepth); } else { *length = 0; @@ -105,6 +113,12 @@ BerDecoder_decodeLength(uint8_t* buffer, int* length, int bufPos, int maxBufPos) return bufPos; } +int +BerDecoder_decodeLength(uint8_t* buffer, int* length, int bufPos, int maxBufPos) +{ + return BerDecoder_decodeLengthRecursive(buffer, length, bufPos, maxBufPos, 0, 50); +} + char* BerDecoder_decodeString(uint8_t* buffer, int strlen, int bufPos, int maxBufPos) { From fd3847dcc5b7a3d2a25dd72f48e6d23197a01393 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 2 Mar 2021 09:26:01 +0100 Subject: [PATCH 23/68] - IED server: make presence of BRCB.ResvTms configurable at runtime with function IedServerConfig_enableResvTmsForBRCB (F1558) --- dotnet/IEC61850forCSharp/IedServerConfig.cs | 577 ++++++++++-------- src/iec61850/inc/iec61850_server.h | 19 + src/iec61850/inc_private/ied_server_private.h | 1 + src/iec61850/server/impl/ied_server.c | 6 + src/iec61850/server/impl/ied_server_config.c | 14 + src/iec61850/server/mms_mapping/reporting.c | 56 +- 6 files changed, 383 insertions(+), 290 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs index d2b4f71b..acf7cbf3 100644 --- a/dotnet/IEC61850forCSharp/IedServerConfig.cs +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -27,275 +27,316 @@ using IEC61850.Common; namespace IEC61850.Server { - /// - /// IedServer configuration object - /// - public class IedServerConfig : IDisposable - { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedServerConfig_create(); + /// + /// IedServer configuration object + /// + public class IedServerConfig : IDisposable + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedServerConfig_create(); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedServerConfig_destroy(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedServerConfig_destroy(IntPtr self); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setReportBufferSize(IntPtr self, int reportBufferSize); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int IedServerConfig_getReportBufferSize(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setReportBufferSizeForURCBs(IntPtr self, int reportBufferSize); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int IedServerConfig_getReportBufferSizeForURCBs(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setFileServiceBasePath(IntPtr self, string basepath); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedServerConfig_getFileServiceBasePath(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_enableFileService(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); - - [return: MarshalAs(UnmanagedType.I1)] - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern bool IedServerConfig_isFileServiceEnabled(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setEdition(IntPtr self, byte edition); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern byte IedServerConfig_getEdition(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setMaxMmsConnections(IntPtr self, int maxConnections); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int IedServerConfig_getMaxMmsConnections(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - static extern bool IedServerConfig_isDynamicDataSetServiceEnabled(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_enableDynamicDataSetService(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setMaxAssociationSpecificDataSets(IntPtr self, int maxDataSets); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int IedServerConfig_getMaxAssociationSpecificDataSets(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setMaxDomainSpecificDataSets(IntPtr self, int maxDataSets); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int IedServerConfig_getMaxDomainSpecificDataSets(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_setMaxDataSetEntries(IntPtr self, int maxDataSetEntries); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int IedServerConfig_getMaxDatasSetEntries(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServerConfig_enableLogService(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - static extern bool IedServerConfig_isLogServiceEnabled(IntPtr self); - - internal IntPtr self; - - public IedServerConfig () - { - self = IedServerConfig_create (); - } - - /// - /// Gets or sets the size of the report buffer for buffered report control blocks - /// - /// The size of the report buffer. - public int ReportBufferSize - { - get { - return IedServerConfig_getReportBufferSize (self); - } - set { - IedServerConfig_setReportBufferSize (self, value); - } - } - - /// - /// Gets or sets the size of the report buffer for unbuffered report control blocks - /// - /// The size of the report buffer. - public int ReportBufferSizeForURCBs - { - get - { - return IedServerConfig_getReportBufferSizeForURCBs(self); - } - set - { - IedServerConfig_setReportBufferSizeForURCBs(self, value); - } - } - - /// - /// Gets or sets the file service base path. - /// - /// The file service base path. - public string FileServiceBasePath - { - get { - return Marshal.PtrToStringAnsi (IedServerConfig_getFileServiceBasePath (self)); - } - set { - IedServerConfig_setFileServiceBasePath (self, value); - } - } - - /// - /// Enable/Disable file service for MMS - /// - /// true if file service is enabled; otherwise, false. - public bool FileServiceEnabled - { - get - { - return IedServerConfig_isFileServiceEnabled(self); - } - set - { - IedServerConfig_enableFileService(self, value); - } - } - - /// - /// Gets or sets the edition of the IEC 61850 standard to use - /// - /// The IEC 61850 edition to use. - public Iec61850Edition Edition - { - get { - return (Iec61850Edition)IedServerConfig_getEdition (self); - } - set { - IedServerConfig_setEdition (self, (byte) value); - } - } - - /// - /// Gets or sets maximum number of MMS clients - /// - /// The max number of MMS client connections. - public int MaxMmsConnections - { - get { - return IedServerConfig_getMaxMmsConnections (self); - } - set { - IedServerConfig_setMaxMmsConnections (self, value); - } - } - - /// - /// Enable/Disable dynamic data set service for MMS - /// - /// true if dynamic data set service enabled; otherwise, false. - public bool DynamicDataSetServiceEnabled - { - get { - return IedServerConfig_isDynamicDataSetServiceEnabled (self); - } - set { - IedServerConfig_enableDynamicDataSetService (self, value); - } - } - - /// - /// Gets or sets the maximum number of data set entries for dynamic data sets - /// - /// The max. number data set entries. - public int MaxDataSetEntries - { - get { - return IedServerConfig_getMaxDatasSetEntries (self); - } - set { - IedServerConfig_setMaxDataSetEntries (self, value); - } - } - - /// - /// Gets or sets the maximum number of association specific (non-permanent) data sets. - /// - /// The max. number of association specific data sets. - public int MaxAssociationSpecificDataSets - { - get { - return IedServerConfig_getMaxAssociationSpecificDataSets (self); - } - set { - IedServerConfig_setMaxAssociationSpecificDataSets (self, value); - } - } - - /// - /// Gets or sets the maximum number of domain specific (permanent) data sets. - /// - /// The max. numebr of domain specific data sets. - public int MaxDomainSpecificDataSets - { - get { - return IedServerConfig_getMaxDomainSpecificDataSets (self); - } - set { - IedServerConfig_setMaxDomainSpecificDataSets (self, value); - } - } - - /// - /// Enable/Disable log service for MMS - /// - /// true if log service is enabled; otherwise, false. - public bool LogServiceEnabled - { - get - { - return IedServerConfig_isLogServiceEnabled(self); - } - set - { - IedServerConfig_enableLogService(self, value); - } - } - - /// - /// Releases all resource used by the object. - /// - /// Call when you are finished using the . The - /// method leaves the in an unusable state. After - /// calling , you must release all references to the - /// so the garbage collector can reclaim the memory that the - /// was occupying. - public void Dispose() - { - lock (this) { - if (self != IntPtr.Zero) { - IedServerConfig_destroy (self); - self = IntPtr.Zero; - } - } - } - - ~IedServerConfig() - { - Dispose (); - } - } + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setReportBufferSize(IntPtr self, int reportBufferSize); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServerConfig_getReportBufferSize(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setReportBufferSizeForURCBs(IntPtr self, int reportBufferSize); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServerConfig_getReportBufferSizeForURCBs(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setFileServiceBasePath(IntPtr self, string basepath); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedServerConfig_getFileServiceBasePath(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_enableFileService(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); + + [return: MarshalAs(UnmanagedType.I1)] + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool IedServerConfig_isFileServiceEnabled(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setEdition(IntPtr self, byte edition); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern byte IedServerConfig_getEdition(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setMaxMmsConnections(IntPtr self, int maxConnections); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServerConfig_getMaxMmsConnections(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool IedServerConfig_isDynamicDataSetServiceEnabled(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_enableDynamicDataSetService(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setMaxAssociationSpecificDataSets(IntPtr self, int maxDataSets); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServerConfig_getMaxAssociationSpecificDataSets(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setMaxDomainSpecificDataSets(IntPtr self, int maxDataSets); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServerConfig_getMaxDomainSpecificDataSets(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setMaxDataSetEntries(IntPtr self, int maxDataSetEntries); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServerConfig_getMaxDatasSetEntries(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_enableLogService(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool IedServerConfig_isLogServiceEnabled(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_enableResvTmsForBRCB(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool IedServerConfig_isResvTmsForBRCBEnabled(IntPtr self); + + internal IntPtr self; + + public IedServerConfig() + { + self = IedServerConfig_create(); + } + + /// + /// Gets or sets the size of the report buffer for buffered report control blocks + /// + /// The size of the report buffer. + public int ReportBufferSize + { + get + { + return IedServerConfig_getReportBufferSize(self); + } + set + { + IedServerConfig_setReportBufferSize(self, value); + } + } + + /// + /// Gets or sets the size of the report buffer for unbuffered report control blocks + /// + /// The size of the report buffer. + public int ReportBufferSizeForURCBs + { + get + { + return IedServerConfig_getReportBufferSizeForURCBs(self); + } + set + { + IedServerConfig_setReportBufferSizeForURCBs(self, value); + } + } + + /// + /// Gets or sets the file service base path. + /// + /// The file service base path. + public string FileServiceBasePath + { + get + { + return Marshal.PtrToStringAnsi(IedServerConfig_getFileServiceBasePath(self)); + } + set + { + IedServerConfig_setFileServiceBasePath(self, value); + } + } + + /// + /// Enable/Disable file service for MMS + /// + /// true if file service is enabled; otherwise, false. + public bool FileServiceEnabled + { + get + { + return IedServerConfig_isFileServiceEnabled(self); + } + set + { + IedServerConfig_enableFileService(self, value); + } + } + + /// + /// Gets or sets the edition of the IEC 61850 standard to use + /// + /// The IEC 61850 edition to use. + public Iec61850Edition Edition + { + get + { + return (Iec61850Edition)IedServerConfig_getEdition(self); + } + set + { + IedServerConfig_setEdition(self, (byte)value); + } + } + + /// + /// Gets or sets maximum number of MMS clients + /// + /// The max number of MMS client connections. + public int MaxMmsConnections + { + get + { + return IedServerConfig_getMaxMmsConnections(self); + } + set + { + IedServerConfig_setMaxMmsConnections(self, value); + } + } + + /// + /// Enable/Disable dynamic data set service for MMS + /// + /// true if dynamic data set service enabled; otherwise, false. + public bool DynamicDataSetServiceEnabled + { + get + { + return IedServerConfig_isDynamicDataSetServiceEnabled(self); + } + set + { + IedServerConfig_enableDynamicDataSetService(self, value); + } + } + + /// + /// Gets or sets the maximum number of data set entries for dynamic data sets + /// + /// The max. number data set entries. + public int MaxDataSetEntries + { + get + { + return IedServerConfig_getMaxDatasSetEntries(self); + } + set + { + IedServerConfig_setMaxDataSetEntries(self, value); + } + } + + /// + /// Gets or sets the maximum number of association specific (non-permanent) data sets. + /// + /// The max. number of association specific data sets. + public int MaxAssociationSpecificDataSets + { + get + { + return IedServerConfig_getMaxAssociationSpecificDataSets(self); + } + set + { + IedServerConfig_setMaxAssociationSpecificDataSets(self, value); + } + } + + /// + /// Gets or sets the maximum number of domain specific (permanent) data sets. + /// + /// The max. numebr of domain specific data sets. + public int MaxDomainSpecificDataSets + { + get + { + return IedServerConfig_getMaxDomainSpecificDataSets(self); + } + set + { + IedServerConfig_setMaxDomainSpecificDataSets(self, value); + } + } + + /// + /// Enable/Disable log service for MMS + /// + /// true if log service is enabled; otherwise, false. + public bool LogServiceEnabled + { + get + { + return IedServerConfig_isLogServiceEnabled(self); + } + set + { + IedServerConfig_enableLogService(self, value); + } + } + + /// + /// Enable/Disable the presence of ResvTms attribute in BRCBs (buffered report control blocks) + /// + /// true if BRCB has ResvTms; otherwise, false. + public bool BRCBHasResvTms + { + get + { + return IedServerConfig_isResvTmsForBRCBEnabled(self); + } + set + { + IedServerConfig_enableResvTmsForBRCB(self, value); + } + } + + /// + /// Releases all resource used by the object. + /// + /// Call when you are finished using the . The + /// method leaves the in an unusable state. After + /// calling , you must release all references to the + /// so the garbage collector can reclaim the memory that the + /// was occupying. + public void Dispose() + { + lock (this) + { + if (self != IntPtr.Zero) + { + IedServerConfig_destroy(self); + self = IntPtr.Zero; + } + } + } + + ~IedServerConfig() + { + Dispose(); + } + } } diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 4d111d4e..0615defa 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -89,6 +89,9 @@ struct sIedServerConfig /** enable visibility of SGCB.ResvTms (default: true) */ bool enableResvTmsForSGCB; + + /** BRCB has resvTms attribute - only edition 2 (default: true) */ + bool enableResvTmsForBRCB; }; /** @@ -304,6 +307,22 @@ IedServerConfig_enableEditSG(IedServerConfig self, bool enable); LIB61850_API void IedServerConfig_enableResvTmsForSGCB(IedServerConfig self, bool enable); +/** + * \brief Enable/disable the presence of BRCB.ResvTms + * + * \param[in] enable set true to enable, otherwise false (default value it true) + */ +LIB61850_API void +IedServerConfig_enableResvTmsForBRCB(IedServerConfig self, bool enable); + +/** + * \brief ResvTms for BRCB enabled (visible) + * + * \return true if enabled, false otherwise + */ +LIB61850_API bool +IedServerConfig_isResvTmsForBRCBEnabled(IedServerConfig self); + /** * \brief Enable/disable using the integrated GOOSE publisher for configured GoCBs * diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index 6b1aad8a..0c05efe2 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -47,6 +47,7 @@ struct sIedServer #if (CONFIG_IEC61850_REPORT_SERVICE == 1) int reportBufferSizeBRCBs; int reportBufferSizeURCBs; + bool enableBRCBResvTms; #endif #if (CONFIG_MMS_THREADLESS_STACK != 1) diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index d2199c2c..c24ee4a9 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -482,10 +482,16 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio if (serverConfiguration) { self->reportBufferSizeBRCBs = serverConfiguration->reportBufferSize; self->reportBufferSizeURCBs = serverConfiguration->reportBufferSizeURCBs; + self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB; } else { self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; +#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) + self->enableBRCBResvTms = true; +#else + self->enableBRCBResvTms = false; +#endif } #endif diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index 4f79819d..6d294765 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -55,6 +55,7 @@ IedServerConfig_create() self->edition = IEC_61850_EDITION_2; self->maxMmsConnections = 5; self->enableEditSG = true; + self->enableResvTmsForBRCB = true; } return self; @@ -199,6 +200,19 @@ IedServerConfig_enableResvTmsForSGCB(IedServerConfig self, bool enable) self->enableResvTmsForSGCB = enable; } +void +IedServerConfig_enableResvTmsForBRCB(IedServerConfig self, bool enable) +{ + self->enableResvTmsForBRCB = enable; +} + +bool +IedServerConfig_isResvTmsForBRCBEnabled(IedServerConfig self) +{ + return self->enableResvTmsForBRCB; +} + + void IedServerConfig_useIntegratedGoosePublisher(IedServerConfig self, bool enable) { diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 40454739..1aa0cffa 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -269,10 +269,16 @@ ReportControl_getRCBValue(ReportControl* rc, char* elementName) if (rc->server->edition >= IEC_61850_EDITION_2) { #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - if (strcmp(elementName, "ResvTms") == 0) - return MmsValue_getElement(rc->rcbValues, 13); - if (strcmp(elementName, "Owner") == 0) - return MmsValue_getElement(rc->rcbValues, 14); + if (rc->server->enableBRCBResvTms) { + if (strcmp(elementName, "ResvTms") == 0) + return MmsValue_getElement(rc->rcbValues, 13); + if (strcmp(elementName, "Owner") == 0) + return MmsValue_getElement(rc->rcbValues, 14); + } + else { + if (strcmp(elementName, "Owner") == 0) + return MmsValue_getElement(rc->rcbValues, 13); + } #else if (strcmp(elementName, "Owner") == 0) return MmsValue_getElement(rc->rcbValues, 13); @@ -1118,7 +1124,8 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, if (reportControl->server->edition >= IEC_61850_EDITION_2) { #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - brcbElementCount++; + if (reportControl->server->enableBRCBResvTms) + brcbElementCount++; #endif if (reportControl->hasOwner) @@ -1249,16 +1256,17 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, int currentIndex = 13; #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - int resvTmsIndex = currentIndex; - namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); - namedVariable->name = StringUtils_copyString("ResvTms"); - namedVariable->type = MMS_INTEGER; - namedVariable->typeSpec.integer = 16; - rcb->typeSpec.structure.elements[currentIndex] = namedVariable; - mmsValue->value.structure.components[currentIndex] = MmsValue_newInteger(16); - currentIndex++; + if (reportControl->server->enableBRCBResvTms) { + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = StringUtils_copyString("ResvTms"); + namedVariable->type = MMS_INTEGER; + namedVariable->typeSpec.integer = 16; + rcb->typeSpec.structure.elements[currentIndex] = namedVariable; + mmsValue->value.structure.components[currentIndex] = MmsValue_newInteger(16); + currentIndex++; + } #endif /* (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) */ if (reportControl->hasOwner) { @@ -1281,7 +1289,9 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, } #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - MmsValue_setInt16(mmsValue->value.structure.components[resvTmsIndex], reportControl->resvTms); + if (reportControl->server->enableBRCBResvTms) { + MmsValue_setInt16(mmsValue->value.structure.components[resvTmsIndex], reportControl->resvTms); + } #endif } @@ -1569,9 +1579,11 @@ checkReservationTimeout(MmsMapping* self, ReportControl* rc) printf("IED_SERVER: reservation timeout expired for %s.%s\n", rc->parentLN->name, rc->name); #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); - if (resvTmsVal) - MmsValue_setInt16(resvTmsVal, rc->resvTms); + if (self->iedServer->enableBRCBResvTms) { + MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); + if (resvTmsVal) + MmsValue_setInt16(resvTmsVal, rc->resvTms); + } #endif rc->reservationTimeout = 0; @@ -1641,9 +1653,11 @@ reserveRcb(ReportControl* rc, MmsServerConnection connection) if (rc->buffered) { #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); - if (resvTmsVal) - MmsValue_setInt16(resvTmsVal, rc->resvTms); + if (rc->server->enableBRCBResvTms) { + MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); + if (resvTmsVal) + MmsValue_setInt16(resvTmsVal, rc->resvTms); + } #endif } else { @@ -1655,7 +1669,6 @@ reserveRcb(ReportControl* rc, MmsServerConnection connection) updateOwner(rc, connection); } -#if 1 MmsDataAccessError Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value, MmsServerConnection connection) @@ -2087,7 +2100,6 @@ exit_function: return retVal; } -#endif void Reporting_deactivateReportsForConnection(MmsMapping* self, MmsServerConnection connection) From ea268b46a7130b8b05e4678332a0ffdff92bb60a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 2 Mar 2021 11:34:27 +0100 Subject: [PATCH 24/68] - IED server: make presence of RCB.Owner configurable at runtime with function IedServerConfig_enableOwnerForRCB (B1502/S1634) --- dotnet/IEC61850forCSharp/IedServerConfig.cs | 25 ++++++++++++++++++- src/iec61850/inc/iec61850_common.h | 3 --- src/iec61850/inc/iec61850_server.h | 23 +++++++++++++++-- src/iec61850/inc_private/ied_server_private.h | 1 + src/iec61850/server/impl/ied_server.c | 2 ++ src/iec61850/server/impl/ied_server_config.c | 12 +++++++++ src/iec61850/server/mms_mapping/reporting.c | 6 ++--- 7 files changed, 62 insertions(+), 10 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs index acf7cbf3..368dc894 100644 --- a/dotnet/IEC61850forCSharp/IedServerConfig.cs +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -114,6 +114,13 @@ namespace IEC61850.Server [return: MarshalAs(UnmanagedType.I1)] static extern bool IedServerConfig_isResvTmsForBRCBEnabled(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_enableOwnerForRCB(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool IedServerConfig_isOwnerForRCBEnabled(IntPtr self); + internal IntPtr self; public IedServerConfig() @@ -300,7 +307,7 @@ namespace IEC61850.Server /// /// Enable/Disable the presence of ResvTms attribute in BRCBs (buffered report control blocks) /// - /// true if BRCB has ResvTms; otherwise, false. + /// true if BRCB has ResvTms; otherwise, false. Defaults to true public bool BRCBHasResvTms { get @@ -313,6 +320,22 @@ namespace IEC61850.Server } } + /// + /// Enable/Disable the presence of Owner attribute in RCBs (report control blocks) + /// + /// true if RCB has Owner; otherwise, false. Defaults to false + public bool RCBHasOwner + { + get + { + return IedServerConfig_isOwnerForRCBEnabled(self); + } + set + { + IedServerConfig_enableOwnerForRCB(self, value); + } + } + /// /// Releases all resource used by the object. /// diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index b9a8d32e..957883dd 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -108,9 +108,6 @@ typedef enum { /** Report will be triggered by GI (general interrogation) request */ #define TRG_OPT_GI 16 -/** RCB has the owner attribute */ -#define RPT_OPT_HAS_OWNER 64 - /** Report will be triggered only on rising edge (transient variable */ #define TRG_OPT_TRANSIENT 128 /** @} */ diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 0615defa..92e55429 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -92,6 +92,9 @@ struct sIedServerConfig /** BRCB has resvTms attribute - only edition 2 (default: true) */ bool enableResvTmsForBRCB; + + /** RCB has owner attribute (default: true) */ + bool enableOwnerForRCB; }; /** @@ -308,9 +311,9 @@ LIB61850_API void IedServerConfig_enableResvTmsForSGCB(IedServerConfig self, bool enable); /** - * \brief Enable/disable the presence of BRCB.ResvTms + * \brief Enable/disable the presence of BRCB.ResvTms (default value is true) * - * \param[in] enable set true to enable, otherwise false (default value it true) + * \param[in] enable set true to enable, otherwise false */ LIB61850_API void IedServerConfig_enableResvTmsForBRCB(IedServerConfig self, bool enable); @@ -323,6 +326,22 @@ IedServerConfig_enableResvTmsForBRCB(IedServerConfig self, bool enable); LIB61850_API bool IedServerConfig_isResvTmsForBRCBEnabled(IedServerConfig self); +/** + * \brief Enable/disable the presence of owner in report control blocks (default value is false); + * + * \param[in] enable set true to enable, otherwise false + */ +LIB61850_API void +IedServerConfig_enableOwnerForRCB(IedServerConfig self, bool enable); + +/** + * \brief Owner for RCBs enabled (visible) + * + * \return true if enabled, false otherwise + */ +LIB61850_API bool +IedServerConfig_isOwnerForRCBEnabled(IedServerConfig self); + /** * \brief Enable/disable using the integrated GOOSE publisher for configured GoCBs * diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index 0c05efe2..ed6af512 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -48,6 +48,7 @@ struct sIedServer int reportBufferSizeBRCBs; int reportBufferSizeURCBs; bool enableBRCBResvTms; + bool enableOwnerForRCB; #endif #if (CONFIG_MMS_THREADLESS_STACK != 1) diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index c24ee4a9..c55c115c 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -483,10 +483,12 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->reportBufferSizeBRCBs = serverConfiguration->reportBufferSize; self->reportBufferSizeURCBs = serverConfiguration->reportBufferSizeURCBs; self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB; + self->enableOwnerForRCB = serverConfiguration->enableOwnerForRCB; } else { self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; + self->enableOwnerForRCB = false; #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) self->enableBRCBResvTms = true; #else diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index 6d294765..999ab4cd 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -56,6 +56,7 @@ IedServerConfig_create() self->maxMmsConnections = 5; self->enableEditSG = true; self->enableResvTmsForBRCB = true; + self->enableOwnerForRCB = false; } return self; @@ -212,6 +213,17 @@ IedServerConfig_isResvTmsForBRCBEnabled(IedServerConfig self) return self->enableResvTmsForBRCB; } +void +IedServerConfig_enableOwnerForRCB(IedServerConfig self, bool enable) +{ + self->enableOwnerForRCB = enable; +} + +bool +IedServerConfig_isOwnerForRCBEnabled(IedServerConfig self) +{ + return self->enableOwnerForRCB; +} void IedServerConfig_useIntegratedGoosePublisher(IedServerConfig self, bool enable) diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 1aa0cffa..25823dc7 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1354,8 +1354,7 @@ Reporting_createMmsBufferedRCBs(MmsMapping* self, MmsDomain* domain, ReportControlBlock* reportControlBlock = getRCBForLogicalNodeWithIndex( self, logicalNode, currentReport, true); - if (reportControlBlock->trgOps & RPT_OPT_HAS_OWNER) - rc->hasOwner = true; + rc->hasOwner = self->iedServer->enableOwnerForRCB; rc->name = StringUtils_createString(3, logicalNode->name, "$BR$", reportControlBlock->name); @@ -1396,8 +1395,7 @@ Reporting_createMmsUnbufferedRCBs(MmsMapping* self, MmsDomain* domain, ReportControlBlock* reportControlBlock = getRCBForLogicalNodeWithIndex( self, logicalNode, currentReport, false); - if (reportControlBlock->trgOps & RPT_OPT_HAS_OWNER) - rc->hasOwner = true; + rc->hasOwner = self->iedServer->enableOwnerForRCB; rc->name = StringUtils_createString(3, logicalNode->name, "$RP$", reportControlBlock->name); From f49be0d844c9fcb4d94038f486b00390bfd1f79d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 8 Mar 2021 16:02:09 +0100 Subject: [PATCH 25/68] - added server example for dead band handling --- examples/CMakeLists.txt | 1 + examples/Makefile | 1 + .../server_example_deadband/CMakeLists.txt | 21 + examples/server_example_deadband/Makefile | 31 + .../cid_example_deadband.cid | 442 ++++++ .../server_example_deadband.c | 221 +++ .../server_example_deadband/static_model.c | 1288 +++++++++++++++++ .../server_example_deadband/static_model.h | 207 +++ 8 files changed, 2212 insertions(+) create mode 100644 examples/server_example_deadband/CMakeLists.txt create mode 100644 examples/server_example_deadband/Makefile create mode 100644 examples/server_example_deadband/cid_example_deadband.cid create mode 100644 examples/server_example_deadband/server_example_deadband.c create mode 100644 examples/server_example_deadband/static_model.c create mode 100644 examples/server_example_deadband/static_model.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 03326ad9..d787fb9b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(server_example_logging) add_subdirectory(server_example_files) add_subdirectory(server_example_substitution) add_subdirectory(server_example_service_tracking) +add_subdirectory(server_example_deadband) add_subdirectory(iec61850_client_example1) add_subdirectory(iec61850_client_example2) diff --git a/examples/Makefile b/examples/Makefile index a6d6d5d9..87cb1d8e 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -20,6 +20,7 @@ EXAMPLE_DIRS += server_example_threadless EXAMPLE_DIRS += server_example_setting_groups EXAMPLE_DIRS += server_example_files EXAMPLE_DIRS += server_example_substitution +EXAMPLE_DIRS += server_example_deadband EXAMPLE_DIRS += goose_subscriber EXAMPLE_DIRS += goose_publisher EXAMPLE_DIRS += sv_subscriber diff --git a/examples/server_example_deadband/CMakeLists.txt b/examples/server_example_deadband/CMakeLists.txt new file mode 100644 index 00000000..bab251a5 --- /dev/null +++ b/examples/server_example_deadband/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories( + . +) + +set(server_example_SRCS + server_example_deadband.c + static_model.c +) + +IF(MSVC) +set_source_files_properties(${server_example_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(server_example_deadband + ${server_example_SRCS} +) + +target_link_libraries(server_example_deadband + iec61850 +) diff --git a/examples/server_example_deadband/Makefile b/examples/server_example_deadband/Makefile new file mode 100644 index 00000000..e1d5674c --- /dev/null +++ b/examples/server_example_deadband/Makefile @@ -0,0 +1,31 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_deadband + +PROJECT_SOURCES = server_example_deadband.c +PROJECT_SOURCES += static_model.c + +PROJECT_ICD_FILE = cid_example_deadband.cid + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +LDLIBS += -lm + +CP = cp + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) + + + diff --git a/examples/server_example_deadband/cid_example_deadband.cid b/examples/server_example_deadband/cid_example_deadband.cid new file mode 100644 index 00000000..e023dc0d --- /dev/null +++ b/examples/server_example_deadband/cid_example_deadband.cid @@ -0,0 +1,442 @@ +īģŋ + +
+ + + +
+ + + +
+

1,1,1,999,1

+

12

+

00000001

+

0001

+

0001

+

0.0.0.0

+

255.255.255.0

+

0.0.0.0

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + + + + + + on + + + + + 10000 + + + 10 + + + 1000 + + + 10 + + + + + 1000 + + + 10 + + + 1000 + + + 10 + + + + + 10000 + + + 1000 + + + + + -5 + + + + + 5 + + + + + + + 1000 + + + 1000 + + + + + -100 + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + e1 + e2 + e3 + e4 + e5 + + + ExternalAreaClock + LocalAreaClock + GlobalAreaClock + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + operate-once + operate-many + + + none + m + kg + s + A + K + mol + cd + deg + rad + sr + Gy + q + °C + Sv + F + C + S + H + V + ohm + J + N + Hz + lx + Lm + Wb + T + W + Pa + m² + mÂŗ + m/s + m/s² + mÂŗ/s + m/mÂŗ + M + kg/mÂŗ + m²/s + W/m K + J/K + ppm + 1/s + rad/s + VA + Watts + VAr + theta + cos(theta) + Vs + V² + As + A² + A²t + VAh + Wh + VArh + V/Hz + + + Yocto + Zepto + Atto + Femto + Pico + Nano + Micro + Milli + Centi + Deci + zeroNoValue + Deca + Hecto + Kilo + Mega + Giga + Tera + Petra + Exa + Zetta + Yotta + + + normal + high + low + high-high + low-low + + + pulse + persistent + + + Ok + Warning + Alarm + + + on + blocked + test + test/blocked + off + + + on + blocked + test + test/blocked + off + + +
\ No newline at end of file diff --git a/examples/server_example_deadband/server_example_deadband.c b/examples/server_example_deadband/server_example_deadband.c new file mode 100644 index 00000000..a7d18bde --- /dev/null +++ b/examples/server_example_deadband/server_example_deadband.c @@ -0,0 +1,221 @@ +/* + * server_example_deadband.c + * + * - How to handle dead bands for measured values (MV) + * - Use variable and fixed dead band with db and dbRef (edition 2.1) + * - Use fixed dead band with db and rangeC (edition 2) + */ + +#include "iec61850_server.h" +#include "hal_thread.h" +#include +#include +#include +#include + +#include "static_model.h" + +/* import IEC 61850 device model created from SCL-File */ +extern IedModel iedModel; + +static int running = 0; +static IedServer iedServer = NULL; + +void +sigint_handler(int signalId) +{ + running = 0; +} + +static void +connectionHandler (IedServer self, ClientConnection connection, bool connected, void* parameter) +{ + if (connected) + printf("Connection opened\n"); + else + printf("Connection closed\n"); +} + +int +main(int argc, char** argv) +{ + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); + + /* Create new server configuration object */ + IedServerConfig config = IedServerConfig_create(); + + /* Set buffer size for buffered report control blocks to 200000 bytes */ + IedServerConfig_setReportBufferSize(config, 200000); + + /* Set stack compliance to a specific edition of the standard (WARNING: data model has also to be checked for compliance) */ + IedServerConfig_setEdition(config, IEC_61850_EDITION_2); + + /* disable MMS file service */ + IedServerConfig_enableFileService(config, false); + + /* enable dynamic data set service */ + IedServerConfig_enableDynamicDataSetService(config, true); + + /* disable log service */ + IedServerConfig_enableLogService(config, false); + + /* set maximum number of clients */ + IedServerConfig_setMaxMmsConnections(config, 5); + + /* Create a new IEC 61850 server instance */ + iedServer = IedServer_createWithConfig(&iedModel, NULL, config); + + /* configuration object is no longer required */ + IedServerConfig_destroy(config); + + /* set the identity values for MMS identify service */ + IedServer_setServerIdentity(iedServer, "libiec61850.com", "deadband example", "1.5.0"); + + IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); + + /* Allow write access to CF parameters (here "db" and "rangeC") */ + IedServer_setWriteAccessPolicy(iedServer, IEC61850_FC_CF, ACCESS_POLICY_ALLOW); + + /* MMS server will be instructed to start listening for client connections. */ + IedServer_start(iedServer, 102); + + if (!IedServer_isRunning(iedServer)) { + printf("Starting server failed (maybe need root permissions or another server is already using the port)! Exit.\n"); + IedServer_destroy(iedServer); + exit(-1); + } + + running = 1; + + signal(SIGINT, sigint_handler); + + float t = 0.f; + + float an1InstMag = 5 * sinf(t); + float an2InstMag = 5 * sinf(t + 1.f); + float an3InstMag = 5 * sinf(t + 2.f); + float an4InstMag = 5 * sinf(t + 3.f); + + float an1Mag = an1InstMag; + float an2Mag = an2InstMag; + float an3Mag = an3InstMag; + float an4Mag = an4InstMag; + + float anIn1DbRef = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn1_dbRef)); + float anIn1ZeroDbRef = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn1_zeroDbRef)); + float anIn2DbRef = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn2_dbRef)); + float anIn2ZeroDbRef = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn2_zeroDbRef)); + + while (running) { + uint64_t timestamp = Hal_getTimeInMs(); + + t += 0.001f; + + an1InstMag = 5 * sinf(t); + an2InstMag = 5 * sinf(t + 1.f); + an3InstMag = 5 * sinf(t + 2.f); + an4InstMag = 5 * sinf(t + 3.f); + + Timestamp iecTimestamp; + + Timestamp_clearFlags(&iecTimestamp); + Timestamp_setTimeInMilliseconds(&iecTimestamp, timestamp); + Timestamp_setLeapSecondKnown(&iecTimestamp, true); + + float dbValF; + float minValF; + float maxValF; + + IedServer_lockDataModel(iedServer); + + /* handle AnIn1 (using db and dbRef as in edition 2.1) */ + + dbValF = (float)MmsValue_toUint32(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn1_db)) / 1000.f; + + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn1_instMag_f, an1InstMag); + + if (anIn1DbRef == 0) { /* dbRef = 0 */ + if (fabsf(an1InstMag - an1Mag) > (fabsf(an1Mag) * dbValF * 0.01f)) { + /* dead band condition -> updated "mag" attribute */ + an1Mag = an1InstMag; + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn1_mag_f, an1Mag); + } + } + else { + if (fabsf(an1InstMag - an1Mag) > (fabsf(anIn1DbRef) * dbValF * 0.01f)) { + /* dead band condition -> updated "mag" attribute */ + an1Mag = an1InstMag; + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn1_mag_f, an1Mag); + } + } + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn1_t, &iecTimestamp); + + /* handle AnIn2 (using db and dbRef as in edition 2.1) */ + + dbValF = (float)MmsValue_toUint32(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn2_db)) / 1000.f; + + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn2_instMag_f, an2InstMag); + + if (anIn1DbRef == 0) { /* dbRef = 0 */ + if (fabsf(an2InstMag - an2Mag) > (fabsf(an2Mag) * dbValF * 0.01f)) { + /* dead band condition -> updated "mag" attribute */ + an2Mag = an2InstMag; + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn2_mag_f, an2Mag); + } + } + else { + if (fabsf(an2InstMag - an2Mag) > (fabsf(anIn2DbRef) * dbValF * 0.01f)) { + /* dead band condition -> updated "mag" attribute */ + an2Mag = an2InstMag; + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn2_mag_f, an2Mag); + } + } + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn2_t, &iecTimestamp); + + /* handle AnIn3 (using db and rangeC as in edition 2) */ + + dbValF = (float)MmsValue_toUint32(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn3_db)) / 1000.f; + minValF = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn3_rangeC_min_f)); + maxValF = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn3_rangeC_max_f)); + + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn3_instMag_f, an3InstMag); + + if (fabsf(an3InstMag - an3Mag) > (fabsf(maxValF - minValF) * dbValF * 0.01f)) { + /* dead band condition -> updated "mag" attribute */ + an3Mag = an3InstMag; + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn3_mag_f, an3Mag); + } + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn3_t, &iecTimestamp); + + /* handle AnIn4 (using db and rangeC as in edition 2) */ + + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn4_instMag_f, an4InstMag); + + dbValF = (float)MmsValue_toUint32(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn4_db)) / 1000.f; + minValF = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn4_rangeC_min_f)); + maxValF = MmsValue_toFloat(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn4_rangeC_max_f)); + + if (fabsf(an4InstMag - an4Mag) > (fabsf(maxValF - minValF) * dbValF * 0.01f)) { + /* dead band condition -> updated "mag" attribute */ + an4Mag = an4InstMag; + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn4_mag_f, an4Mag); + } + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_LD1_AnInGGIO1_AnIn4_t, &iecTimestamp); + + IedServer_unlockDataModel(iedServer); + + Thread_sleep(10); + } + + /* stop MMS server - close TCP server socket and all client sockets */ + IedServer_stop(iedServer); + + /* Cleanup - free all resources */ + IedServer_destroy(iedServer); + + return 0; +} /* main() */ diff --git a/examples/server_example_deadband/static_model.c b/examples/server_example_deadband/static_model.c new file mode 100644 index 00000000..77faa997 --- /dev/null +++ b/examples/server_example_deadband/static_model.c @@ -0,0 +1,1288 @@ +/* + * static_model.c + * + * automatically generated from cid_example_deadband.cid + */ +#include "static_model.h" + +static void initializeValues(); + +extern DataSet iedModelds_LD1_LLN0_AnalogEvents; + + +extern DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda0; +extern DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda1; +extern DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda2; +extern DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda3; + +DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda0 = { + "LD1", + false, + "AnInGGIO1$MX$AnIn1", + -1, + NULL, + NULL, + &iedModelds_LD1_LLN0_AnalogEvents_fcda1 +}; + +DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda1 = { + "LD1", + false, + "AnInGGIO1$MX$AnIn2", + -1, + NULL, + NULL, + &iedModelds_LD1_LLN0_AnalogEvents_fcda2 +}; + +DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda2 = { + "LD1", + false, + "AnInGGIO1$MX$AnIn3", + -1, + NULL, + NULL, + &iedModelds_LD1_LLN0_AnalogEvents_fcda3 +}; + +DataSetEntry iedModelds_LD1_LLN0_AnalogEvents_fcda3 = { + "LD1", + false, + "AnInGGIO1$MX$AnIn4", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_LD1_LLN0_AnalogEvents = { + "LD1", + "LLN0$AnalogEvents", + 4, + &iedModelds_LD1_LLN0_AnalogEvents_fcda0, + NULL +}; + +LogicalDevice iedModel_LD1 = { + LogicalDeviceModelType, + "LD1", + (ModelNode*) &iedModel, + NULL, + (ModelNode*) &iedModel_LD1_LLN0 +}; + +LogicalNode iedModel_LD1_LLN0 = { + LogicalNodeModelType, + "LLN0", + (ModelNode*) &iedModel_LD1, + (ModelNode*) &iedModel_LD1_LPHD1, + (ModelNode*) &iedModel_LD1_LLN0_Mod, +}; + +DataObject iedModel_LD1_LLN0_Mod = { + DataObjectModelType, + "Mod", + (ModelNode*) &iedModel_LD1_LLN0, + (ModelNode*) &iedModel_LD1_LLN0_Beh, + (ModelNode*) &iedModel_LD1_LLN0_Mod_stVal, + 0 +}; + +DataAttribute iedModel_LD1_LLN0_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_LLN0_Mod, + (ModelNode*) &iedModel_LD1_LLN0_Mod_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_Mod_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_LLN0_Mod, + (ModelNode*) &iedModel_LD1_LLN0_Mod_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_Mod_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_LLN0_Mod, + (ModelNode*) &iedModel_LD1_LLN0_Mod_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_Mod_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_LD1_LLN0_Mod, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_LLN0_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_LD1_LLN0, + (ModelNode*) &iedModel_LD1_LLN0_Health, + (ModelNode*) &iedModel_LD1_LLN0_Beh_stVal, + 0 +}; + +DataAttribute iedModel_LD1_LLN0_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_LLN0_Beh, + (ModelNode*) &iedModel_LD1_LLN0_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_LLN0_Beh, + (ModelNode*) &iedModel_LD1_LLN0_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_LLN0_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_LLN0_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_LD1_LLN0, + (ModelNode*) &iedModel_LD1_LLN0_NamPlt, + (ModelNode*) &iedModel_LD1_LLN0_Health_stVal, + 0 +}; + +DataAttribute iedModel_LD1_LLN0_Health_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_LLN0_Health, + (ModelNode*) &iedModel_LD1_LLN0_Health_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_Health_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_LLN0_Health, + (ModelNode*) &iedModel_LD1_LLN0_Health_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_Health_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_LLN0_Health, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_LLN0_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_LD1_LLN0, + NULL, + (ModelNode*) &iedModel_LD1_LLN0_NamPlt_vendor, + 0 +}; + +DataAttribute iedModel_LD1_LLN0_NamPlt_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_LD1_LLN0_NamPlt, + (ModelNode*) &iedModel_LD1_LLN0_NamPlt_swRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_LD1_LLN0_NamPlt, + (ModelNode*) &iedModel_LD1_LLN0_NamPlt_d, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_LD1_LLN0_NamPlt, + (ModelNode*) &iedModel_LD1_LLN0_NamPlt_configRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LLN0_NamPlt_configRev = { + DataAttributeModelType, + "configRev", + (ModelNode*) &iedModel_LD1_LLN0_NamPlt, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +LogicalNode iedModel_LD1_LPHD1 = { + LogicalNodeModelType, + "LPHD1", + (ModelNode*) &iedModel_LD1, + (ModelNode*) &iedModel_LD1_AnInGGIO1, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, +}; + +DataObject iedModel_LD1_LPHD1_PhyNam = { + DataObjectModelType, + "PhyNam", + (ModelNode*) &iedModel_LD1_LPHD1, + (ModelNode*) &iedModel_LD1_LPHD1_PhyHealth, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_vendor, + 0 +}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_hwRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_hwRev = { + DataAttributeModelType, + "hwRev", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_swRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_serNum, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_serNum = { + DataAttributeModelType, + "serNum", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_model, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_model = { + DataAttributeModelType, + "model", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_location, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_location = { + DataAttributeModelType, + "location", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_name, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_name = { + DataAttributeModelType, + "name", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam_owner, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyNam_owner = { + DataAttributeModelType, + "owner", + (ModelNode*) &iedModel_LD1_LPHD1_PhyNam, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_LPHD1_PhyHealth = { + DataObjectModelType, + "PhyHealth", + (ModelNode*) &iedModel_LD1_LPHD1, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + (ModelNode*) &iedModel_LD1_LPHD1_PhyHealth_stVal, + 0 +}; + +DataAttribute iedModel_LD1_LPHD1_PhyHealth_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_LPHD1_PhyHealth, + (ModelNode*) &iedModel_LD1_LPHD1_PhyHealth_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyHealth_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_LPHD1_PhyHealth, + (ModelNode*) &iedModel_LD1_LPHD1_PhyHealth_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_PhyHealth_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_LPHD1_PhyHealth, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_LPHD1_Proxy = { + DataObjectModelType, + "Proxy", + (ModelNode*) &iedModel_LD1_LPHD1, + NULL, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy_stVal, + 0 +}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy_d, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +LogicalNode iedModel_LD1_AnInGGIO1 = { + LogicalNodeModelType, + "AnInGGIO1", + (ModelNode*) &iedModel_LD1, + NULL, + (ModelNode*) &iedModel_LD1_AnInGGIO1_Beh, +}; + +DataObject iedModel_LD1_AnInGGIO1_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_LD1_AnInGGIO1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_Beh_stVal, + 0 +}; + +DataAttribute iedModel_LD1_AnInGGIO1_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_AnInGGIO1_Beh, + (ModelNode*) &iedModel_LD1_AnInGGIO1_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_AnInGGIO1_Beh, + (ModelNode*) &iedModel_LD1_AnInGGIO1_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_AnInGGIO1_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_AnInGGIO1_AnIn1 = { + DataObjectModelType, + "AnIn1", + (ModelNode*) &iedModel_LD1_AnInGGIO1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_instMag, + 0 +}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_instMag = { + DataAttributeModelType, + "instMag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_mag, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_instMag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_instMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_instMag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_q, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_db, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_db = { + DataAttributeModelType, + "db", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_zeroDb, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_zeroDb = { + DataAttributeModelType, + "zeroDb", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_dbRef, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_dbRef = { + DataAttributeModelType, + "dbRef", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1_zeroDbRef, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_zeroDbRef = { + DataAttributeModelType, + "zeroDbRef", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn1, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataObject iedModel_LD1_AnInGGIO1_AnIn2 = { + DataObjectModelType, + "AnIn2", + (ModelNode*) &iedModel_LD1_AnInGGIO1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_instMag, + 0 +}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_instMag = { + DataAttributeModelType, + "instMag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_mag, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_instMag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_instMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_instMag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_q, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_db, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_db = { + DataAttributeModelType, + "db", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_zeroDb, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_zeroDb = { + DataAttributeModelType, + "zeroDb", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_dbRef, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_dbRef = { + DataAttributeModelType, + "dbRef", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2_zeroDbRef, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_zeroDbRef = { + DataAttributeModelType, + "zeroDbRef", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn2, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataObject iedModel_LD1_AnInGGIO1_AnIn3 = { + DataObjectModelType, + "AnIn3", + (ModelNode*) &iedModel_LD1_AnInGGIO1, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_instMag, + 0 +}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_instMag = { + DataAttributeModelType, + "instMag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_mag, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_instMag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_instMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_instMag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_q, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_db, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_db = { + DataAttributeModelType, + "db", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_zeroDb, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_zeroDb = { + DataAttributeModelType, + "zeroDb", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC = { + DataAttributeModelType, + "rangeC", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3, + NULL, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min, + 0, + IEC61850_FC_CF, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min = { + DataAttributeModelType, + "min", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min_f, + 0, + IEC61850_FC_CF, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max = { + DataAttributeModelType, + "max", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC, + NULL, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max_f, + 0, + IEC61850_FC_CF, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataObject iedModel_LD1_AnInGGIO1_AnIn4 = { + DataObjectModelType, + "AnIn4", + (ModelNode*) &iedModel_LD1_AnInGGIO1, + NULL, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_instMag, + 0 +}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_instMag = { + DataAttributeModelType, + "instMag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_mag, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_instMag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_instMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_instMag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_q, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_db, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_db = { + DataAttributeModelType, + "db", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_zeroDb, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_zeroDb = { + DataAttributeModelType, + "zeroDb", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT32U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC = { + DataAttributeModelType, + "rangeC", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4, + NULL, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min, + 0, + IEC61850_FC_CF, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min = { + DataAttributeModelType, + "min", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min_f, + 0, + IEC61850_FC_CF, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max = { + DataAttributeModelType, + "max", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC, + NULL, + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max_f, + 0, + IEC61850_FC_CF, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +extern ReportControlBlock iedModel_LD1_LLN0_report0; +extern ReportControlBlock iedModel_LD1_LLN0_report1; +extern ReportControlBlock iedModel_LD1_LLN0_report2; +extern ReportControlBlock iedModel_LD1_LLN0_report3; + +ReportControlBlock iedModel_LD1_LLN0_report0 = {&iedModel_LD1_LLN0, "BRCB_Events01", NULL, true, "AnalogEvents", 1, 19, 247, 0, 5000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_LD1_LLN0_report1}; +ReportControlBlock iedModel_LD1_LLN0_report1 = {&iedModel_LD1_LLN0, "BRCB_Events02", NULL, true, "AnalogEvents", 1, 19, 247, 0, 5000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_LD1_LLN0_report2}; +ReportControlBlock iedModel_LD1_LLN0_report2 = {&iedModel_LD1_LLN0, "URCB_Events01", NULL, false, "AnalogEvents", 1, 19, 183, 0, 5000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_LD1_LLN0_report3}; +ReportControlBlock iedModel_LD1_LLN0_report3 = {&iedModel_LD1_LLN0, "URCB_Events02", NULL, false, "AnalogEvents", 1, 19, 183, 0, 5000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; + + + + + + + +IedModel iedModel = { + "IED1", + &iedModel_LD1, + &iedModelds_LD1_LLN0_AnalogEvents, + &iedModel_LD1_LLN0_report0, + NULL, + NULL, + NULL, + NULL, + NULL, + initializeValues +}; + +static void +initializeValues() +{ + +iedModel_LD1_LLN0_Mod_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_LD1_AnInGGIO1_Beh_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_LD1_AnInGGIO1_AnIn1_db.mmsValue = MmsValue_newUnsignedFromUint32(10000); + +iedModel_LD1_AnInGGIO1_AnIn1_zeroDb.mmsValue = MmsValue_newUnsignedFromUint32(1000); + +iedModel_LD1_AnInGGIO1_AnIn1_dbRef.mmsValue = MmsValue_newFloat(10.0); + +iedModel_LD1_AnInGGIO1_AnIn1_zeroDbRef.mmsValue = MmsValue_newFloat(10.0); + +iedModel_LD1_AnInGGIO1_AnIn2_db.mmsValue = MmsValue_newUnsignedFromUint32(1000); + +iedModel_LD1_AnInGGIO1_AnIn2_zeroDb.mmsValue = MmsValue_newUnsignedFromUint32(1000); + +iedModel_LD1_AnInGGIO1_AnIn2_dbRef.mmsValue = MmsValue_newFloat(10.0); + +iedModel_LD1_AnInGGIO1_AnIn2_zeroDbRef.mmsValue = MmsValue_newFloat(10.0); + +iedModel_LD1_AnInGGIO1_AnIn3_db.mmsValue = MmsValue_newUnsignedFromUint32(10000); + +iedModel_LD1_AnInGGIO1_AnIn3_zeroDb.mmsValue = MmsValue_newUnsignedFromUint32(1000); + +iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min_f.mmsValue = MmsValue_newFloat(-5.0); + +iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max_f.mmsValue = MmsValue_newFloat(5.0); + +iedModel_LD1_AnInGGIO1_AnIn4_db.mmsValue = MmsValue_newUnsignedFromUint32(1000); + +iedModel_LD1_AnInGGIO1_AnIn4_zeroDb.mmsValue = MmsValue_newUnsignedFromUint32(1000); + +iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min_f.mmsValue = MmsValue_newFloat(-100.0); + +iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max_f.mmsValue = MmsValue_newFloat(100.0); +} diff --git a/examples/server_example_deadband/static_model.h b/examples/server_example_deadband/static_model.h new file mode 100644 index 00000000..3be3f25a --- /dev/null +++ b/examples/server_example_deadband/static_model.h @@ -0,0 +1,207 @@ +/* + * static_model.h + * + * automatically generated from cid_example_deadband.cid + */ + +#ifndef STATIC_MODEL_H_ +#define STATIC_MODEL_H_ + +#include +#include "iec61850_model.h" + +extern IedModel iedModel; +extern LogicalDevice iedModel_LD1; +extern LogicalNode iedModel_LD1_LLN0; +extern DataObject iedModel_LD1_LLN0_Mod; +extern DataAttribute iedModel_LD1_LLN0_Mod_stVal; +extern DataAttribute iedModel_LD1_LLN0_Mod_q; +extern DataAttribute iedModel_LD1_LLN0_Mod_t; +extern DataAttribute iedModel_LD1_LLN0_Mod_ctlModel; +extern DataObject iedModel_LD1_LLN0_Beh; +extern DataAttribute iedModel_LD1_LLN0_Beh_stVal; +extern DataAttribute iedModel_LD1_LLN0_Beh_q; +extern DataAttribute iedModel_LD1_LLN0_Beh_t; +extern DataObject iedModel_LD1_LLN0_Health; +extern DataAttribute iedModel_LD1_LLN0_Health_stVal; +extern DataAttribute iedModel_LD1_LLN0_Health_q; +extern DataAttribute iedModel_LD1_LLN0_Health_t; +extern DataObject iedModel_LD1_LLN0_NamPlt; +extern DataAttribute iedModel_LD1_LLN0_NamPlt_vendor; +extern DataAttribute iedModel_LD1_LLN0_NamPlt_swRev; +extern DataAttribute iedModel_LD1_LLN0_NamPlt_d; +extern DataAttribute iedModel_LD1_LLN0_NamPlt_configRev; +extern LogicalNode iedModel_LD1_LPHD1; +extern DataObject iedModel_LD1_LPHD1_PhyNam; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_vendor; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_hwRev; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_swRev; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_serNum; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_model; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_location; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_name; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_owner; +extern DataObject iedModel_LD1_LPHD1_PhyHealth; +extern DataAttribute iedModel_LD1_LPHD1_PhyHealth_stVal; +extern DataAttribute iedModel_LD1_LPHD1_PhyHealth_q; +extern DataAttribute iedModel_LD1_LPHD1_PhyHealth_t; +extern DataObject iedModel_LD1_LPHD1_Proxy; +extern DataAttribute iedModel_LD1_LPHD1_Proxy_stVal; +extern DataAttribute iedModel_LD1_LPHD1_Proxy_q; +extern DataAttribute iedModel_LD1_LPHD1_Proxy_t; +extern DataAttribute iedModel_LD1_LPHD1_Proxy_d; +extern LogicalNode iedModel_LD1_AnInGGIO1; +extern DataObject iedModel_LD1_AnInGGIO1_Beh; +extern DataAttribute iedModel_LD1_AnInGGIO1_Beh_stVal; +extern DataAttribute iedModel_LD1_AnInGGIO1_Beh_q; +extern DataAttribute iedModel_LD1_AnInGGIO1_Beh_t; +extern DataObject iedModel_LD1_AnInGGIO1_AnIn1; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_instMag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_instMag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_mag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_mag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_q; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_t; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_db; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_zeroDb; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_dbRef; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn1_zeroDbRef; +extern DataObject iedModel_LD1_AnInGGIO1_AnIn2; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_instMag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_instMag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_mag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_mag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_q; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_t; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_db; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_zeroDb; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_dbRef; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn2_zeroDbRef; +extern DataObject iedModel_LD1_AnInGGIO1_AnIn3; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_instMag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_instMag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_mag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_mag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_q; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_t; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_db; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_zeroDb; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max_f; +extern DataObject iedModel_LD1_AnInGGIO1_AnIn4; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_instMag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_instMag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_mag; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_mag_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_q; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_t; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_db; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_zeroDb; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min_f; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max; +extern DataAttribute iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max_f; + + + +#define IEDMODEL_LD1 (&iedModel_LD1) +#define IEDMODEL_LD1_LLN0 (&iedModel_LD1_LLN0) +#define IEDMODEL_LD1_LLN0_Mod (&iedModel_LD1_LLN0_Mod) +#define IEDMODEL_LD1_LLN0_Mod_stVal (&iedModel_LD1_LLN0_Mod_stVal) +#define IEDMODEL_LD1_LLN0_Mod_q (&iedModel_LD1_LLN0_Mod_q) +#define IEDMODEL_LD1_LLN0_Mod_t (&iedModel_LD1_LLN0_Mod_t) +#define IEDMODEL_LD1_LLN0_Mod_ctlModel (&iedModel_LD1_LLN0_Mod_ctlModel) +#define IEDMODEL_LD1_LLN0_Beh (&iedModel_LD1_LLN0_Beh) +#define IEDMODEL_LD1_LLN0_Beh_stVal (&iedModel_LD1_LLN0_Beh_stVal) +#define IEDMODEL_LD1_LLN0_Beh_q (&iedModel_LD1_LLN0_Beh_q) +#define IEDMODEL_LD1_LLN0_Beh_t (&iedModel_LD1_LLN0_Beh_t) +#define IEDMODEL_LD1_LLN0_Health (&iedModel_LD1_LLN0_Health) +#define IEDMODEL_LD1_LLN0_Health_stVal (&iedModel_LD1_LLN0_Health_stVal) +#define IEDMODEL_LD1_LLN0_Health_q (&iedModel_LD1_LLN0_Health_q) +#define IEDMODEL_LD1_LLN0_Health_t (&iedModel_LD1_LLN0_Health_t) +#define IEDMODEL_LD1_LLN0_NamPlt (&iedModel_LD1_LLN0_NamPlt) +#define IEDMODEL_LD1_LLN0_NamPlt_vendor (&iedModel_LD1_LLN0_NamPlt_vendor) +#define IEDMODEL_LD1_LLN0_NamPlt_swRev (&iedModel_LD1_LLN0_NamPlt_swRev) +#define IEDMODEL_LD1_LLN0_NamPlt_d (&iedModel_LD1_LLN0_NamPlt_d) +#define IEDMODEL_LD1_LLN0_NamPlt_configRev (&iedModel_LD1_LLN0_NamPlt_configRev) +#define IEDMODEL_LD1_LPHD1 (&iedModel_LD1_LPHD1) +#define IEDMODEL_LD1_LPHD1_PhyNam (&iedModel_LD1_LPHD1_PhyNam) +#define IEDMODEL_LD1_LPHD1_PhyNam_vendor (&iedModel_LD1_LPHD1_PhyNam_vendor) +#define IEDMODEL_LD1_LPHD1_PhyNam_hwRev (&iedModel_LD1_LPHD1_PhyNam_hwRev) +#define IEDMODEL_LD1_LPHD1_PhyNam_swRev (&iedModel_LD1_LPHD1_PhyNam_swRev) +#define IEDMODEL_LD1_LPHD1_PhyNam_serNum (&iedModel_LD1_LPHD1_PhyNam_serNum) +#define IEDMODEL_LD1_LPHD1_PhyNam_model (&iedModel_LD1_LPHD1_PhyNam_model) +#define IEDMODEL_LD1_LPHD1_PhyNam_location (&iedModel_LD1_LPHD1_PhyNam_location) +#define IEDMODEL_LD1_LPHD1_PhyNam_name (&iedModel_LD1_LPHD1_PhyNam_name) +#define IEDMODEL_LD1_LPHD1_PhyNam_owner (&iedModel_LD1_LPHD1_PhyNam_owner) +#define IEDMODEL_LD1_LPHD1_PhyHealth (&iedModel_LD1_LPHD1_PhyHealth) +#define IEDMODEL_LD1_LPHD1_PhyHealth_stVal (&iedModel_LD1_LPHD1_PhyHealth_stVal) +#define IEDMODEL_LD1_LPHD1_PhyHealth_q (&iedModel_LD1_LPHD1_PhyHealth_q) +#define IEDMODEL_LD1_LPHD1_PhyHealth_t (&iedModel_LD1_LPHD1_PhyHealth_t) +#define IEDMODEL_LD1_LPHD1_Proxy (&iedModel_LD1_LPHD1_Proxy) +#define IEDMODEL_LD1_LPHD1_Proxy_stVal (&iedModel_LD1_LPHD1_Proxy_stVal) +#define IEDMODEL_LD1_LPHD1_Proxy_q (&iedModel_LD1_LPHD1_Proxy_q) +#define IEDMODEL_LD1_LPHD1_Proxy_t (&iedModel_LD1_LPHD1_Proxy_t) +#define IEDMODEL_LD1_LPHD1_Proxy_d (&iedModel_LD1_LPHD1_Proxy_d) +#define IEDMODEL_LD1_AnInGGIO1 (&iedModel_LD1_AnInGGIO1) +#define IEDMODEL_LD1_AnInGGIO1_Beh (&iedModel_LD1_AnInGGIO1_Beh) +#define IEDMODEL_LD1_AnInGGIO1_Beh_stVal (&iedModel_LD1_AnInGGIO1_Beh_stVal) +#define IEDMODEL_LD1_AnInGGIO1_Beh_q (&iedModel_LD1_AnInGGIO1_Beh_q) +#define IEDMODEL_LD1_AnInGGIO1_Beh_t (&iedModel_LD1_AnInGGIO1_Beh_t) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1 (&iedModel_LD1_AnInGGIO1_AnIn1) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_instMag (&iedModel_LD1_AnInGGIO1_AnIn1_instMag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_instMag_f (&iedModel_LD1_AnInGGIO1_AnIn1_instMag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_mag (&iedModel_LD1_AnInGGIO1_AnIn1_mag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_mag_f (&iedModel_LD1_AnInGGIO1_AnIn1_mag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_q (&iedModel_LD1_AnInGGIO1_AnIn1_q) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_t (&iedModel_LD1_AnInGGIO1_AnIn1_t) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_db (&iedModel_LD1_AnInGGIO1_AnIn1_db) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_zeroDb (&iedModel_LD1_AnInGGIO1_AnIn1_zeroDb) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_dbRef (&iedModel_LD1_AnInGGIO1_AnIn1_dbRef) +#define IEDMODEL_LD1_AnInGGIO1_AnIn1_zeroDbRef (&iedModel_LD1_AnInGGIO1_AnIn1_zeroDbRef) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2 (&iedModel_LD1_AnInGGIO1_AnIn2) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_instMag (&iedModel_LD1_AnInGGIO1_AnIn2_instMag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_instMag_f (&iedModel_LD1_AnInGGIO1_AnIn2_instMag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_mag (&iedModel_LD1_AnInGGIO1_AnIn2_mag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_mag_f (&iedModel_LD1_AnInGGIO1_AnIn2_mag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_q (&iedModel_LD1_AnInGGIO1_AnIn2_q) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_t (&iedModel_LD1_AnInGGIO1_AnIn2_t) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_db (&iedModel_LD1_AnInGGIO1_AnIn2_db) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_zeroDb (&iedModel_LD1_AnInGGIO1_AnIn2_zeroDb) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_dbRef (&iedModel_LD1_AnInGGIO1_AnIn2_dbRef) +#define IEDMODEL_LD1_AnInGGIO1_AnIn2_zeroDbRef (&iedModel_LD1_AnInGGIO1_AnIn2_zeroDbRef) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3 (&iedModel_LD1_AnInGGIO1_AnIn3) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_instMag (&iedModel_LD1_AnInGGIO1_AnIn3_instMag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_instMag_f (&iedModel_LD1_AnInGGIO1_AnIn3_instMag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_mag (&iedModel_LD1_AnInGGIO1_AnIn3_mag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_mag_f (&iedModel_LD1_AnInGGIO1_AnIn3_mag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_q (&iedModel_LD1_AnInGGIO1_AnIn3_q) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_t (&iedModel_LD1_AnInGGIO1_AnIn3_t) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_db (&iedModel_LD1_AnInGGIO1_AnIn3_db) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_zeroDb (&iedModel_LD1_AnInGGIO1_AnIn3_zeroDb) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_rangeC (&iedModel_LD1_AnInGGIO1_AnIn3_rangeC) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_rangeC_min (&iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_rangeC_min_f (&iedModel_LD1_AnInGGIO1_AnIn3_rangeC_min_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_rangeC_max (&iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max) +#define IEDMODEL_LD1_AnInGGIO1_AnIn3_rangeC_max_f (&iedModel_LD1_AnInGGIO1_AnIn3_rangeC_max_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4 (&iedModel_LD1_AnInGGIO1_AnIn4) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_instMag (&iedModel_LD1_AnInGGIO1_AnIn4_instMag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_instMag_f (&iedModel_LD1_AnInGGIO1_AnIn4_instMag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_mag (&iedModel_LD1_AnInGGIO1_AnIn4_mag) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_mag_f (&iedModel_LD1_AnInGGIO1_AnIn4_mag_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_q (&iedModel_LD1_AnInGGIO1_AnIn4_q) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_t (&iedModel_LD1_AnInGGIO1_AnIn4_t) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_db (&iedModel_LD1_AnInGGIO1_AnIn4_db) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_zeroDb (&iedModel_LD1_AnInGGIO1_AnIn4_zeroDb) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_rangeC (&iedModel_LD1_AnInGGIO1_AnIn4_rangeC) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_rangeC_min (&iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_rangeC_min_f (&iedModel_LD1_AnInGGIO1_AnIn4_rangeC_min_f) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_rangeC_max (&iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max) +#define IEDMODEL_LD1_AnInGGIO1_AnIn4_rangeC_max_f (&iedModel_LD1_AnInGGIO1_AnIn4_rangeC_max_f) + +#endif /* STATIC_MODEL_H_ */ + From 4e15343f5b7d0a6e1af3fb16175fb909601f39fb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 9 Mar 2021 17:11:20 +0100 Subject: [PATCH 26/68] - IED server: integrated GOOSE publisher - lock data model during GOOSE retransmission to avoid corrupted GOOSE data --- src/iec61850/inc_private/mms_goose.h | 2 +- src/iec61850/server/mms_mapping/mms_goose.c | 40 +++++++++++-------- src/iec61850/server/mms_mapping/mms_mapping.c | 2 +- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/iec61850/inc_private/mms_goose.h b/src/iec61850/inc_private/mms_goose.h index e2c6f93f..65faa077 100644 --- a/src/iec61850/inc_private/mms_goose.h +++ b/src/iec61850/inc_private/mms_goose.h @@ -55,7 +55,7 @@ LIB61850_INTERNAL bool MmsGooseControlBlock_isEnabled(MmsGooseControlBlock self); LIB61850_INTERNAL void -MmsGooseControlBlock_checkAndPublish(MmsGooseControlBlock self, uint64_t currentTime); +MmsGooseControlBlock_checkAndPublish(MmsGooseControlBlock self, uint64_t currentTime, MmsMapping* mapping); LIB61850_INTERNAL void MmsGooseControlBlock_setStateChangePending(MmsGooseControlBlock self); diff --git a/src/iec61850/server/mms_mapping/mms_goose.c b/src/iec61850/server/mms_mapping/mms_goose.c index 730bbfff..d33c0ece 100644 --- a/src/iec61850/server/mms_mapping/mms_goose.c +++ b/src/iec61850/server/mms_mapping/mms_goose.c @@ -506,37 +506,45 @@ MmsGooseControlBlock_disable(MmsGooseControlBlock self, MmsMapping* mmsMapping) void -MmsGooseControlBlock_checkAndPublish(MmsGooseControlBlock self, uint64_t currentTime) +MmsGooseControlBlock_checkAndPublish(MmsGooseControlBlock self, uint64_t currentTime, MmsMapping* mapping) { if (self->publisher) { if (currentTime >= self->nextPublishTime) { + IedServer_lockDataModel(mapping->iedServer); + + if (currentTime >= self->nextPublishTime) { + #if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_wait(self->publisherMutex); + Semaphore_wait(self->publisherMutex); #endif - GoosePublisher_publish(self->publisher, self->dataSetValues); + GoosePublisher_publish(self->publisher, self->dataSetValues); - if (self->retransmissionsLeft > 0) { - self->nextPublishTime = currentTime + self->minTime; + if (self->retransmissionsLeft > 0) { + self->nextPublishTime = currentTime + self->minTime; - if (self->retransmissionsLeft > 1) - GoosePublisher_setTimeAllowedToLive(self->publisher, self->minTime * 3); - else - GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3); + if (self->retransmissionsLeft > 1) + GoosePublisher_setTimeAllowedToLive(self->publisher, self->minTime * 3); + else + GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3); - self->retransmissionsLeft--; - } - else { - GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3); + self->retransmissionsLeft--; + } + else { + GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3); - self->nextPublishTime = currentTime + self->maxTime; - } + self->nextPublishTime = currentTime + self->maxTime; + } #if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_post(self->publisherMutex); + Semaphore_post(self->publisherMutex); #endif + } + + IedServer_unlockDataModel(mapping->iedServer); + } else if ((self->nextPublishTime - currentTime) > ((uint32_t) self->maxTime * 2)) { self->nextPublishTime = currentTime + self->minTime; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 01898f18..32471c8f 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -3748,7 +3748,7 @@ GOOSE_processGooseEvents(MmsMapping* self, uint64_t currentTimeInMs) MmsGooseControlBlock mmsGCB = (MmsGooseControlBlock) element->data; if (MmsGooseControlBlock_isEnabled(mmsGCB)) { - MmsGooseControlBlock_checkAndPublish(mmsGCB, currentTimeInMs); + MmsGooseControlBlock_checkAndPublish(mmsGCB, currentTimeInMs, self); } element = LinkedList_getNext(element); From bd4bd0fab6e6a2b300e3e136abebcc607939e4e7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 15 Mar 2021 11:12:04 +0100 Subject: [PATCH 27/68] - IED server: GoCB has invalid data set reference when datSet="" in SCL file --- src/iec61850/server/mms_mapping/mms_goose.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iec61850/server/mms_mapping/mms_goose.c b/src/iec61850/server/mms_mapping/mms_goose.c index d33c0ece..5a9ead7d 100644 --- a/src/iec61850/server/mms_mapping/mms_goose.c +++ b/src/iec61850/server/mms_mapping/mms_goose.c @@ -754,7 +754,7 @@ GOOSE_createGOOSEControlBlocks(MmsMapping* self, MmsDomain* domain, mmsGCB->goId = StringUtils_copyString(gooseControlBlock->appId); } - if (gooseControlBlock->dataSetName != NULL) + if ((gooseControlBlock->dataSetName != NULL) && (gooseControlBlock->dataSetName[0] != 0)) mmsGCB->dataSetRef = createDataSetReference(MmsDomain_getName(domain), logicalNode->name, gooseControlBlock->dataSetName); else From 2b1104c0d3c468c641bd91eaa92e5fb4d3c226d5 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 17 Mar 2021 10:57:08 +0100 Subject: [PATCH 28/68] - IED server - control model - send AddCause with operate- for DOes, SBOes control models --- src/iec61850/server/mms_mapping/control.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 5f036e66..c7be0319 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -2211,6 +2211,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari /* enter state Perform Test */ setOpRcvd(controlObject, true); + controlObject->errorValue = CONTROL_ERROR_NO_ERROR; controlObject->addCauseValue = ADD_CAUSE_UNKNOWN; controlObject->mmsConnection = connection; @@ -2241,6 +2242,12 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari setOpRcvd(controlObject, false); abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED); + + if ((controlObject->ctlModel == 3) || (controlObject->ctlModel == 4)) { + ControlObject_sendLastApplError(controlObject, connection, "Oper", + controlObject->errorValue, controlObject->addCauseValue, + ctlNum, origin, true); + } } } From c4dcd37449c204c8892a5f8734d3dd1cb921a41a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 19 Mar 2021 06:56:59 +0100 Subject: [PATCH 29/68] - added new function DataAttribute_setValue --- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 4 +++- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 12 +++++++++++- src/iec61850/inc/iec61850_dynamic_model.h | 9 +++++++++ src/iec61850/server/model/dynamic_model.c | 11 +++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 6b3b0b2b..574d6c82 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -123,7 +123,9 @@ namespace IEC61850 /** periodic transmission of all data set values */ INTEGRITY = 8, /** general interrogation (on client request) */ - GI = 16 + GI = 16, + /** Report will be triggered only on rising edge (transient variable) */ + TRG_OPT_TRANSIENT = 128 } /// diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 122fedbd..e820d2db 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -871,11 +871,13 @@ namespace IEC61850 public class DataAttribute : ModelNode { - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr DataAttribute_create(string name, IntPtr parent, int type, int fc, byte triggerOptions, int arrayElements, UInt32 sAddr); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void DataAttribute_setValue(IntPtr self, IntPtr mmsValue); + internal DataAttribute(IntPtr self, ModelNode parent) : base(self) { this.parent = parent; @@ -889,6 +891,14 @@ namespace IEC61850 self = DataAttribute_create (name, parent.self, (int)type, (int)fc, (byte)trgOps, arrayElements, sAddr); } + /// + /// Set the value of the data attribute (can be used to set default values before server is created) + /// + /// New value for the data attribute + public void SetValue(MmsValue value) + { + DataAttribute_setValue(self, value.valueReference); + } } public class ModelNode diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index c7da23c0..872ee425 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -137,6 +137,15 @@ LIB61850_API DataAttribute* DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type, FunctionalConstraint fc, uint8_t triggerOptions, int arrayElements, uint32_t sAddr); +/** + * \brief Set the value of the data attribute (can be used to set default values before server is created) + * + * \param self the data attribute instance + * \param value the new default value + */ +LIB61850_API void +DataAttribute_setValue(DataAttribute* self, MmsValue* value); + /** * \brief create a new report control block (RCB) * diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 4377de8b..ea68d36d 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -607,6 +607,17 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type return self; } +void +DataAttribute_setValue(DataAttribute* self, MmsValue* value) +{ + if (self->mmsValue) { + MmsValue_update(self->mmsValue, value); + } + else { + self->mmsValue = MmsValue_clone(value); + } +} + DataSet* DataSet_create(const char* name, LogicalNode* parent) { From 3532623319c5001f16c739a9701b1d6528c87a5f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 22 Mar 2021 16:55:48 +0100 Subject: [PATCH 30/68] - IED server: Goose publisher - set NdsCom when data set not configured or GoEna fails because of large data set --- src/iec61850/client/client_goose_control.c | 10 +- src/iec61850/inc/iec61850_server.h | 3 + src/iec61850/inc_private/mms_goose.h | 2 +- src/iec61850/server/mms_mapping/mms_goose.c | 177 +++++++++++++----- src/iec61850/server/mms_mapping/mms_mapping.c | 20 +- src/mms/inc/mms_value.h | 18 ++ src/mms/iso_mms/server/mms_access_result.c | 164 +++++++++++++++- 7 files changed, 336 insertions(+), 58 deletions(-) diff --git a/src/iec61850/client/client_goose_control.c b/src/iec61850/client/client_goose_control.c index 7fa44444..f6e0743a 100644 --- a/src/iec61850/client/client_goose_control.c +++ b/src/iec61850/client/client_goose_control.c @@ -569,6 +569,8 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo if (singleRequest) { LinkedList accessResults = NULL; + *error = IED_ERROR_OK; + MmsConnection_writeMultipleVariables(self->connection, &mmsError, domainId, itemIds, values, &accessResults); if (accessResults != NULL) { @@ -577,8 +579,12 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo while (element != NULL) { MmsValue* accessResult = (MmsValue*) element->data; + MmsDataAccessError resErr = MmsValue_getDataAccessError(accessResult); + if (MmsValue_getDataAccessError(accessResult) != DATA_ACCESS_ERROR_SUCCESS) { - mmsError = MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT; + + *error = iedConnection_mapDataAccessErrorToIedError(resErr); + break; } @@ -588,8 +594,6 @@ IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGoo LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction) MmsValue_delete); } - *error = iedConnection_mapMmsErrorToIedError(mmsError); - goto exit_function; } else { diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 92e55429..098bc1f2 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -1607,6 +1607,9 @@ MmsGooseControlBlock_getMaxTime(MmsGooseControlBlock self); LIB61850_API bool MmsGooseControlBlock_getFixedOffs(MmsGooseControlBlock self); +LIB61850_API bool +MmsGooseControlBlock_getNdsCom(MmsGooseControlBlock self); + /**@}*/ /** diff --git a/src/iec61850/inc_private/mms_goose.h b/src/iec61850/inc_private/mms_goose.h index 65faa077..98923c2b 100644 --- a/src/iec61850/inc_private/mms_goose.h +++ b/src/iec61850/inc_private/mms_goose.h @@ -63,7 +63,7 @@ MmsGooseControlBlock_setStateChangePending(MmsGooseControlBlock self); LIB61850_INTERNAL void MmsGooseControlBlock_publishNewState(MmsGooseControlBlock self); -LIB61850_INTERNAL void +LIB61850_INTERNAL bool MmsGooseControlBlock_enable(MmsGooseControlBlock self, MmsMapping* mmsMapping); LIB61850_INTERNAL void diff --git a/src/iec61850/server/mms_mapping/mms_goose.c b/src/iec61850/server/mms_mapping/mms_goose.c index 5a9ead7d..df7a79ac 100644 --- a/src/iec61850/server/mms_mapping/mms_goose.c +++ b/src/iec61850/server/mms_mapping/mms_goose.c @@ -1,7 +1,7 @@ /* * mms_goose.c * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2021 Michael Zillgith * * This file is part of libIEC61850. * @@ -25,6 +25,8 @@ #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) +#define GOOSE_MAX_MESSAGE_SIZE 1518 + #include "libiec61850_platform_includes.h" #include "mms_mapping.h" #include "linked_list.h" @@ -80,6 +82,35 @@ struct sMmsGooseControlBlock { bool stateChangePending; }; +static void +setNdsCom(MmsGooseControlBlock mmsGCB, bool value) +{ + MmsValue* ndsComValue = MmsValue_getElement(mmsGCB->mmsValue, 4); + + if (ndsComValue) { + MmsValue_setBoolean(ndsComValue, value); + } +} + +static bool +getNdsCom(MmsGooseControlBlock mmsGCB) +{ + bool ndsCom = true; + + MmsValue* ndsComValue = MmsValue_getElement(mmsGCB->mmsValue, 4); + + if (ndsComValue) + ndsCom = MmsValue_getBoolean(ndsComValue); + + return ndsCom; +} + +bool +MmsGooseControlBlock_getNdsCom(MmsGooseControlBlock self) +{ + return getNdsCom(self); +} + bool MmsGooseControlBlock_getGoEna(MmsGooseControlBlock self) { @@ -341,9 +372,27 @@ MmsGooseControlBlock_isEnabled(MmsGooseControlBlock self) return self->goEna; } -void +static int +calculateMaxDataSetSize(DataSet* dataSet) +{ + int dataSetSize = 0; + + DataSetEntry* dataSetEntry = dataSet->fcdas; + + while (dataSetEntry) { + dataSetSize += MmsValue_getMaxEncodedSize(dataSetEntry->value); + + dataSetEntry = dataSetEntry->sibling; + } + + return dataSetSize; +} + +bool MmsGooseControlBlock_enable(MmsGooseControlBlock self, MmsMapping* mmsMapping) { + bool retVal = false; + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->publisherMutex); #endif @@ -388,85 +437,112 @@ MmsGooseControlBlock_enable(MmsGooseControlBlock self, MmsMapping* mmsMapping) if (self->dataSet != NULL) { - MmsValue* goEna = MmsValue_getElement(self->mmsValue, 0); + int dataSetSize = calculateMaxDataSetSize(self->dataSet); - MmsValue_setBoolean(goEna, true); + /* Calculate maximum GOOSE message size */ + int maxGooseMessageSize = 26 + 51 + 6; + maxGooseMessageSize += strlen(self->goCBRef); + if (self->goId) + maxGooseMessageSize += strlen(self->goId); + else + maxGooseMessageSize += strlen(self->goCBRef); + maxGooseMessageSize += strlen(self->dataSetRef); + maxGooseMessageSize += dataSetSize; - MmsValue* dstAddress = MmsValue_getElement(self->mmsValue, 5); + if (maxGooseMessageSize > GOOSE_MAX_MESSAGE_SIZE) { + setNdsCom(self, true); - CommParameters commParameters; - commParameters.appId = MmsValue_toInt32(MmsValue_getElement(dstAddress, 3)); - commParameters.vlanId = MmsValue_toInt32(MmsValue_getElement(dstAddress, 2)); - commParameters.vlanPriority = MmsValue_toInt32(MmsValue_getElement(dstAddress, 1)); +#if (CONFIG_IEC61850_SERVICE_TRACKING == 1) + copyGCBValuesToTrackingObject(self); + updateGenericTrackingObjectValues(self, IEC61850_SERVICE_TYPE_SET_GOCB_VALUES, DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID); +#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ + } + else { + MmsValue* goEna = MmsValue_getElement(self->mmsValue, 0); - MmsValue* macAddress = MmsValue_getElement(dstAddress, 0); + MmsValue_setBoolean(goEna, true); - memcpy(commParameters.dstAddress, MmsValue_getOctetStringBuffer(macAddress), 6); - if (mmsMapping->useIntegratedPublisher) { + MmsValue* dstAddress = MmsValue_getElement(self->mmsValue, 5); - if (self->gooseInterfaceId) - self->publisher = GoosePublisher_createEx(&commParameters, self->gooseInterfaceId, self->useVlanTag); - else - self->publisher = GoosePublisher_createEx(&commParameters, self->mmsMapping->gooseInterfaceId, self->useVlanTag); + CommParameters commParameters; + commParameters.appId = MmsValue_toInt32(MmsValue_getElement(dstAddress, 3)); + commParameters.vlanId = MmsValue_toInt32(MmsValue_getElement(dstAddress, 2)); + commParameters.vlanPriority = MmsValue_toInt32(MmsValue_getElement(dstAddress, 1)); - if (self->publisher) { - self->minTime = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 6)); - self->maxTime = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 7)); + MmsValue* macAddress = MmsValue_getElement(dstAddress, 0); - GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3); + memcpy(commParameters.dstAddress, MmsValue_getOctetStringBuffer(macAddress), 6); + + if (mmsMapping->useIntegratedPublisher) { - GoosePublisher_setDataSetRef(self->publisher, self->dataSetRef); + if (self->gooseInterfaceId) + self->publisher = GoosePublisher_createEx(&commParameters, self->gooseInterfaceId, self->useVlanTag); + else + self->publisher = GoosePublisher_createEx(&commParameters, self->mmsMapping->gooseInterfaceId, self->useVlanTag); - GoosePublisher_setGoCbRef(self->publisher, self->goCBRef); + if (self->publisher) { + self->minTime = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 6)); + self->maxTime = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 7)); - uint32_t confRev = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 3)); + GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3); - GoosePublisher_setConfRev(self->publisher, confRev); + GoosePublisher_setDataSetRef(self->publisher, self->dataSetRef); - bool needsCom = MmsValue_getBoolean(MmsValue_getElement(self->mmsValue, 4)); + GoosePublisher_setGoCbRef(self->publisher, self->goCBRef); - GoosePublisher_setNeedsCommission(self->publisher, needsCom); + uint32_t confRev = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 3)); - if (self->goId != NULL) - GoosePublisher_setGoID(self->publisher, self->goId); + GoosePublisher_setConfRev(self->publisher, confRev); - /* prepare data set values */ - self->dataSetValues = LinkedList_create(); + bool needsCom = MmsValue_getBoolean(MmsValue_getElement(self->mmsValue, 4)); - DataSetEntry* dataSetEntry = self->dataSet->fcdas; + GoosePublisher_setNeedsCommission(self->publisher, needsCom); - while (dataSetEntry != NULL) { - LinkedList_add(self->dataSetValues, dataSetEntry->value); - dataSetEntry = dataSetEntry->sibling; - } + if (self->goId != NULL) + GoosePublisher_setGoID(self->publisher, self->goId); + /* prepare data set values */ + self->dataSetValues = LinkedList_create(); + + DataSetEntry* dataSetEntry = self->dataSet->fcdas; + + while (dataSetEntry != NULL) { + LinkedList_add(self->dataSetValues, dataSetEntry->value); + dataSetEntry = dataSetEntry->sibling; + } + + } + else { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Failed to create GOOSE publisher!\n"); + } } - else { - if (DEBUG_IED_SERVER) - printf("IED_SERVER: Failed to create GOOSE publisher!\n"); - } - } - self->goEna = true; + self->goEna = true; + + retVal = true; #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) - MmsDataAccessError retVal = DATA_ACCESS_ERROR_SUCCESS; - copyGCBValuesToTrackingObject(self); - updateGenericTrackingObjectValues(self, IEC61850_SERVICE_TYPE_SET_GOCB_VALUES, retVal); + copyGCBValuesToTrackingObject(self); + updateGenericTrackingObjectValues(self, IEC61850_SERVICE_TYPE_SET_GOCB_VALUES, DATA_ACCESS_ERROR_SUCCESS); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ + } } } else { - printf("GoCB already enabled!\n"); + if (DEBUG_IED_SERVER) + printf("GoCB already enabled!\n"); } #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->publisherMutex); #endif + + return retVal; } void @@ -754,11 +830,13 @@ GOOSE_createGOOSEControlBlocks(MmsMapping* self, MmsDomain* domain, mmsGCB->goId = StringUtils_copyString(gooseControlBlock->appId); } - if ((gooseControlBlock->dataSetName != NULL) && (gooseControlBlock->dataSetName[0] != 0)) + if ((gooseControlBlock->dataSetName != NULL) && (gooseControlBlock->dataSetName[0] != 0)) { mmsGCB->dataSetRef = createDataSetReference(MmsDomain_getName(domain), logicalNode->name, gooseControlBlock->dataSetName); - else + } + else { mmsGCB->dataSetRef = NULL; + } MmsValue* dataSetRef = MmsValue_getElement(gseValues, 2); @@ -823,6 +901,11 @@ GOOSE_createGOOSEControlBlocks(MmsMapping* self, MmsDomain* domain, MmsValue* maxTime = MmsValue_getElement(gseValues, 7); MmsValue_setUint32(maxTime, mmsGCB->maxTime); + if (mmsGCB->dataSetRef) + setNdsCom(mmsGCB, false); + else + setNdsCom(mmsGCB, true); + mmsGCB->mmsMapping = self; mmsGCB->stateChangePending = false; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 32471c8f..131ac030 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2276,11 +2276,17 @@ writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variable if (MmsValue_getType(value) != MMS_BOOLEAN) return DATA_ACCESS_ERROR_TYPE_INCONSISTENT; - if (MmsValue_getBoolean(value)) { - MmsGooseControlBlock_enable(mmsGCB, self); + if (MmsGooseControlBlock_getNdsCom(mmsGCB)) + return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; - if (self->goCbHandler) - self->goCbHandler(mmsGCB, IEC61850_GOCB_EVENT_ENABLE, self->goCbHandlerParameter); + if (MmsValue_getBoolean(value)) { + if (MmsGooseControlBlock_enable(mmsGCB, self)) { + if (self->goCbHandler) + self->goCbHandler(mmsGCB, IEC61850_GOCB_EVENT_ENABLE, self->goCbHandlerParameter); + } + else { + return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + } } else { MmsGooseControlBlock_disable(mmsGCB, self); @@ -3618,7 +3624,6 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) } if (DataSet_isMemberValue(rc->dataSet, value, &index)) { - ReportControl_valueUpdated(rc, index, flag, modelLocked); } } @@ -3665,7 +3670,10 @@ MmsMapping_enableGoosePublishing(MmsMapping* self) while (element) { MmsGooseControlBlock gcb = (MmsGooseControlBlock) LinkedList_getData(element); - MmsGooseControlBlock_enable(gcb, self); + if (MmsGooseControlBlock_enable(gcb, self) == false) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: failed to enable GoCB %s\n", MmsGooseControlBlock_getName(gcb)); + } element = LinkedList_getNext(element); } diff --git a/src/mms/inc/mms_value.h b/src/mms/inc/mms_value.h index 57e2f39e..28b1fab4 100644 --- a/src/mms/inc/mms_value.h +++ b/src/mms/inc/mms_value.h @@ -985,6 +985,24 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu LIB61850_API int MmsValue_encodeMmsData(MmsValue* self, uint8_t* buffer, int bufPos, bool encode); +/** + * \brief Get the maximum possible BER encoded size of the MMS data element + * + * \param self the MmsValue instance + * + * \return the maximum encoded size in bytes of the MMS data element + */ +LIB61850_API int +MmsValue_getMaxEncodedSize(MmsValue* self); + +/** + * \brief Calculate the maximum encoded size of a variable of this type + * + * \param self the MMS variable specification instance + */ +LIB61850_API int +MmsVariableSpecification_getMaxEncodedSize(MmsVariableSpecification* self); + /**@}*/ /**@}*/ diff --git a/src/mms/iso_mms/server/mms_access_result.c b/src/mms/iso_mms/server/mms_access_result.c index d7d78975..b17461f0 100644 --- a/src/mms/iso_mms/server/mms_access_result.c +++ b/src/mms/iso_mms/server/mms_access_result.c @@ -334,6 +334,168 @@ exit_with_error: return NULL; } +static int +MmsValue_getMaxStructSize(MmsValue* self) +{ + int componentsSize = 0; + int i; + int size; + + int componentCount = self->value.structure.size; + + MmsValue** components = self->value.structure.components; + + for (i = 0; i < componentCount; i++) + componentsSize += MmsValue_getMaxEncodedSize(components[i]); + + size = 1 + componentsSize + BerEncoder_determineLengthSize(componentsSize); + + return size; +} + +int +MmsValue_getMaxEncodedSize(MmsValue* self) +{ + int size = 0; + int elementSize = 0; + + switch (self->type) + { + case MMS_STRUCTURE: + size = MmsValue_getMaxStructSize(self); + break; + case MMS_ARRAY: + size = MmsValue_getMaxStructSize(self); + break; + case MMS_BOOLEAN: + size = 3; + break; + case MMS_DATA_ACCESS_ERROR: + size = 7; /* TL * size of uint32 max */ + break; + case MMS_VISIBLE_STRING: + elementSize = abs(self->value.visibleString.size); + size = 1 + elementSize + BerEncoder_determineLengthSize(elementSize); + break; + case MMS_UNSIGNED: + size = 2 + self->value.integer->maxSize; + break; + case MMS_INTEGER: + size = 2 + self->value.integer->maxSize; + break; + case MMS_UTC_TIME: + size = 10; + break; + case MMS_BIT_STRING: + elementSize = abs(self->value.bitString.size); + size = BerEncoder_determineEncodedBitStringSize(elementSize); + break; + case MMS_BINARY_TIME: + size = 2 + self->value.binaryTime.size; + break; + case MMS_OCTET_STRING: + elementSize = abs(self->value.octetString.maxSize); + size = 1 + BerEncoder_determineLengthSize(elementSize) + elementSize; + break; + case MMS_FLOAT: + elementSize = (self->value.floatingPoint.formatWidth / 8) + 1; + size = elementSize + 2; /* 2 for tag and length */ + break; + case MMS_STRING: + elementSize = abs(self->value.visibleString.size); + size = 1 + elementSize + BerEncoder_determineLengthSize(elementSize); + break; + default: + if (DEBUG_MMS_SERVER) + printf("MmsVariableSpecification_getMaxEncodedSize: error unsupported type!\n"); + break; + } + + return size; +} + +static int +getMaxStructSize(MmsVariableSpecification* variable) +{ + int componentsSize = 0; + int i; + int size; + + int componentCount = variable->typeSpec.structure.elementCount; + + MmsVariableSpecification** components = variable->typeSpec.structure.elements; + + for (i = 0; i < componentCount; i++) + componentsSize += MmsVariableSpecification_getMaxEncodedSize(components[i]); + + size = 1 + componentsSize + BerEncoder_determineLengthSize(componentsSize); + + return size; +} + +int +MmsVariableSpecification_getMaxEncodedSize(MmsVariableSpecification* self) +{ + int size = 0; + int elementSize = 0; + + switch (self->type) + { + case MMS_STRUCTURE: + size = getMaxStructSize(self); + break; + case MMS_ARRAY: + elementSize = MmsVariableSpecification_getMaxEncodedSize(self->typeSpec.array.elementTypeSpec) + * self->typeSpec.array.elementCount; + size = 1 + elementSize + BerEncoder_determineLengthSize(elementSize); + break; + case MMS_BOOLEAN: + size = 3; + break; + case MMS_DATA_ACCESS_ERROR: + size = 7; /* TL * size of uint32 max */ + break; + case MMS_VISIBLE_STRING: + elementSize = abs(self->typeSpec.visibleString); + size = 1 + elementSize + BerEncoder_determineLengthSize(elementSize); + break; + case MMS_UNSIGNED: + size = 2 + (self->typeSpec.unsignedInteger / 8) + 1; + break; + case MMS_INTEGER: + size = 2 + (self->typeSpec.integer / 8) + 1; + break; + case MMS_UTC_TIME: + size = 10; + break; + case MMS_BIT_STRING: + elementSize = abs(self->typeSpec.bitString); + size = BerEncoder_determineEncodedBitStringSize(elementSize); + break; + case MMS_BINARY_TIME: + size = 2 + self->typeSpec.binaryTime; + break; + case MMS_OCTET_STRING: + elementSize = abs(self->typeSpec.octetString); + size = 1 + BerEncoder_determineLengthSize(elementSize) + elementSize; + break; + case MMS_FLOAT: + elementSize = (self->typeSpec.floatingpoint.formatWidth / 8) + 1; + size = elementSize + 2; /* 2 for tag and length */ + break; + case MMS_STRING: + elementSize = abs(self->typeSpec.mmsString); + size = 1 + elementSize + BerEncoder_determineLengthSize(elementSize); + break; + default: + if (DEBUG_MMS_SERVER) + printf("MmsVariableSpecification_getMaxEncodedSize: error unsupported type!\n"); + break; + } + + return size; +} + int MmsValue_encodeMmsData(MmsValue* self, uint8_t* buffer, int bufPos, bool encode) { @@ -444,7 +606,7 @@ MmsValue_encodeMmsData(MmsValue* self, uint8_t* buffer, int bufPos, bool encode) break; default: if (DEBUG_MMS_SERVER) - printf("encodeAccessResult: error unsupported type!\n"); + printf("MmsValue_encodeMmsData: error unsupported type!\n"); size = 0; break; } From 5ba428fa5b72fd9ec42691e2e7bccc4faf727c34 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 22 Mar 2021 22:47:39 +0100 Subject: [PATCH 31/68] - .NET API: MmsValue - added functions to create empty visible string and set visible string value - .NET API: DataAttribute - save data attribute type --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 14 +++++++ dotnet/IEC61850forCSharp/MmsValue.cs | 41 +++++++++++++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index e820d2db..905e600c 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -878,6 +878,8 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void DataAttribute_setValue(IntPtr self, IntPtr mmsValue); + private DataAttributeType daType; + internal DataAttribute(IntPtr self, ModelNode parent) : base(self) { this.parent = parent; @@ -887,10 +889,22 @@ namespace IEC61850 int arrayElements, UInt32 sAddr) { this.parent = parent; + this.daType = type; self = DataAttribute_create (name, parent.self, (int)type, (int)fc, (byte)trgOps, arrayElements, sAddr); } + /// + /// Get IEC 61850 data attribute type of the data attribute + /// + public DataAttributeType Type + { + get + { + return daType; + } + } + /// /// Set the value of the data attribute (can be used to set default values before server is created) /// diff --git a/dotnet/IEC61850forCSharp/MmsValue.cs b/dotnet/IEC61850forCSharp/MmsValue.cs index 5d6a90c7..3b5e06ee 100644 --- a/dotnet/IEC61850forCSharp/MmsValue.cs +++ b/dotnet/IEC61850forCSharp/MmsValue.cs @@ -147,6 +147,12 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_newVisibleString(string value); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_newVisibleStringWithSize(int size); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void MmsValue_setVisibleString(IntPtr self, string value); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_createArray(IntPtr elementType, int size); @@ -201,7 +207,7 @@ namespace IEC61850 internal IntPtr valueReference; /* reference to native MmsValue instance */ - private bool responsableForDeletion; /* if .NET wrapper is responsable for the deletion of the native MmsValue instance */ + private bool responsableForDeletion = false; /* if .NET wrapper is responsable for the deletion of the native MmsValue instance */ internal MmsValue (IntPtr value) { @@ -415,13 +421,34 @@ namespace IEC61850 return new MmsValue (newValue, true); } - /// - /// Gets the type of the value + /// + /// Create a new MmsValue instance of type MMS_VISIBLE_STRING - empty string with given maximum size /// - /// - /// The type. - /// - public new MmsType GetType () + /// The maximum size + /// + public static MmsValue NewVisibleString(int size, bool responsibleForDeletion = false) + { + IntPtr newValue = MmsValue_newVisibleStringWithSize(size); + + return new MmsValue(newValue, responsibleForDeletion); + } + + /// + /// Set the value of an MmsValue instance of type MMS_VISIBLE_STRING + /// + /// the new string value + public void SetVisibleString(string value) + { + MmsValue_setVisibleString(valueReference, value); + } + + /// + /// Gets the type of the value + /// + /// + /// The type. + /// + public new MmsType GetType () { return (MmsType)MmsValue_getType (valueReference); } From 56bda24641afe3f09f64087b2943dbf40e3aedbf Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 24 Mar 2021 16:13:03 +0100 Subject: [PATCH 32/68] - .NET API: Added method ReportControlBlock.SetPreconfiguredClient --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 905e600c..d5184020 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -1203,6 +1203,9 @@ namespace IEC61850 static extern IntPtr ReportControlBlock_create(string name, IntPtr parent, string rptId, [MarshalAs(UnmanagedType.I1)] bool isBuffered, string dataSetName, uint confRef, byte trgOps, byte options, uint bufTm, uint intgPd); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void ReportControlBlock_setPreconfiguredClient(IntPtr self, byte type, [Out] byte[] buf); + public IntPtr self = IntPtr.Zero; public ReportControlBlock(string name, LogicalNode parent, string rptId, bool isBuffered, @@ -1210,6 +1213,14 @@ namespace IEC61850 { self = ReportControlBlock_create(name, parent.self, rptId, isBuffered, dataSetName, confRev, trgOps, options, bufTm, intgPd); } + + public void SetPreconfiguredClient(byte[] clientAddress) + { + if (clientAddress.Length == 4) + ReportControlBlock_setPreconfiguredClient(self, 4, clientAddress); + else if (clientAddress.Length == 6) + ReportControlBlock_setPreconfiguredClient(self, 6, clientAddress); + } } public class ClientConnection From ee9d6656b35311600fe108c7ce29148d812955c1 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 29 Mar 2021 17:50:38 +0200 Subject: [PATCH 33/68] - .NET API: Added GSEControlBlock class to add GoCBs to server data model --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index d5184020..3dd82129 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -1223,6 +1223,40 @@ namespace IEC61850 } } + public class GSEControlBlock + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr GSEControlBlock_create(string name, IntPtr parent, string appId, string dataSet, UInt32 confRev, + [MarshalAs(UnmanagedType.I1)] bool fixedOffs, int minTime, int maxTime); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void GSEControlBlock_addPhyComAddress(IntPtr self, IntPtr phyComAddress); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr PhyComAddress_create(byte vlanPriority, UInt16 vlanId, UInt16 appId, [Out] byte[] buf); + + internal IntPtr self = IntPtr.Zero; + + public GSEControlBlock(string name, LogicalNode parent, string appId, string dataSetName, UInt32 confRev, bool fixedOffs, int minTime, int maxTime) + { + self = GSEControlBlock_create(name, parent.self, appId, dataSetName, confRev, fixedOffs, minTime, maxTime); + } + + public void AddPhyComAddress(PhyComAddress addr) + { + IntPtr phyComAddrPtr = PhyComAddress_create(addr.vlanPriority, addr.vlanId, addr.appId, addr.dstAddress); + + if (phyComAddrPtr != IntPtr.Zero) + { + GSEControlBlock_addPhyComAddress(self, phyComAddrPtr); + } + else + { + Console.WriteLine("ERROR: Failed to create native PhyComAddress instance!"); + } + } + } + public class ClientConnection { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] From cda2eba93b7580b55031f2ba2363bf67c1703f7d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 30 Mar 2021 17:27:42 +0200 Subject: [PATCH 34/68] - .NET API: added LogControlBlock and SettingGroupControlBlock classed to added LCBs and setting groups to server data model --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 3dd82129..93d32b3a 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -1197,6 +1197,9 @@ namespace IEC61850 } } + /// + /// Report control block (RCB) instance for server data model + /// public class ReportControlBlock { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -1223,6 +1226,9 @@ namespace IEC61850 } } + /// + /// GOOSE/GSE control block instance for server data model + /// public class GSEControlBlock { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -1257,6 +1263,39 @@ namespace IEC61850 } } + /// + /// Log control block (LCB) instance for server data model + /// + public class LogControlBlock + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr LogControlBlock_create(string name, IntPtr parent, string dataSetName, string logRef, byte trgOps, UInt32 intPeriod, + [MarshalAs(UnmanagedType.I1)] bool logEna, [MarshalAs(UnmanagedType.I1)] bool reasonCode); + + internal IntPtr self = IntPtr.Zero; + + public LogControlBlock(string name, LogicalNode parent, string dataSet, string logRef, byte trgOps, UInt32 intPerdiod, bool logEna, bool reasonCode) + { + self = LogControlBlock_create(name, parent.self, dataSet, logRef, trgOps, intPerdiod, logEna, reasonCode); + } + } + + /// + /// Setting group control block for server data model + /// + public class SettingGroupControlBlock + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr SettingGroupControlBlock_create(IntPtr parent, byte actSG, byte numOfSGs); + + internal IntPtr self = IntPtr.Zero; + + public SettingGroupControlBlock(LogicalNode parent, UInt32 actSG, UInt32 numOfSGs) + { + self = SettingGroupControlBlock_create(parent.self, (byte) actSG, (byte) numOfSGs); + } + } + public class ClientConnection { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] From abd26eedca9d722e9c881ea2805f21b62bfc0db3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 3 Apr 2021 17:33:43 +0200 Subject: [PATCH 35/68] - IED client: fixed memory leak when reusing IedConnection --- src/mms/iso_client/iso_client_connection.c | 18 ++-- src/mms/iso_cotp/cotp.c | 3 +- src/mms/iso_server/iso_connection.c | 99 ++++++++++++---------- 3 files changed, 67 insertions(+), 53 deletions(-) diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c index f6cc4b42..6e71263a 100644 --- a/src/mms/iso_client/iso_client_connection.c +++ b/src/mms/iso_client/iso_client_connection.c @@ -147,7 +147,6 @@ IsoClientConnection_create(IsoConnectionParameters parameters, IsoIndicationCall IsoClientConnection self = (IsoClientConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoClientConnection)); if (self) { - self->parameters = parameters; self->callback = callback; self->callbackParameter = callbackParameter; @@ -194,6 +193,13 @@ IsoClientConnection_create(IsoConnectionParameters parameters, IsoIndicationCall static bool sendConnectionRequestMessage(IsoClientConnection self) { + if (self->cotpConnection) { + /* Destroy existing handle set when connection is reused */ + if (self->cotpConnection->handleSet) + Handleset_destroy(self->cotpConnection->handleSet); + self->cotpConnection->handleSet = NULL; + } + /* COTP (ISO transport) handshake */ CotpConnection_init(self->cotpConnection, self->socket, self->receiveBuffer, self->cotpReadBuffer, self->cotpWriteBuffer); @@ -280,10 +286,10 @@ releaseSocket(IsoClientConnection self) if (self->socket) { #if (CONFIG_MMS_SUPPORT_TLS == 1) - if (self->cotpConnection->tlsSocket) { - TLSSocket_close(self->cotpConnection->tlsSocket); - self->cotpConnection->tlsSocket = NULL; - } + if (self->cotpConnection->tlsSocket) { + TLSSocket_close(self->cotpConnection->tlsSocket); + self->cotpConnection->tlsSocket = NULL; + } #endif Socket_destroy(self->socket); @@ -750,7 +756,7 @@ void IsoClientConnection_destroy(IsoClientConnection self) { if (DEBUG_ISO_CLIENT) - printf("ISO_CLIENT: IsoClientConnection_destroy\n"); + printf("ISO_CLIENT: IsoClientConnection_destroy(%p)\n", self); int state = getState(self); diff --git a/src/mms/iso_cotp/cotp.c b/src/mms/iso_cotp/cotp.c index b34bc0dd..68ce1c1f 100644 --- a/src/mms/iso_cotp/cotp.c +++ b/src/mms/iso_cotp/cotp.c @@ -451,8 +451,9 @@ CotpConnection_init(CotpConnection* self, Socket socket, { self->state = 0; self->socket = socket; - self->handleSet = Handleset_new( ); + self->handleSet = Handleset_new(); Handleset_addSocket( self->handleSet, self->socket ); + #if (CONFIG_MMS_SUPPORT_TLS == 1) self->tlsSocket = NULL; #endif diff --git a/src/mms/iso_server/iso_connection.c b/src/mms/iso_server/iso_connection.c index 3551acc6..7e2a5b3f 100644 --- a/src/mms/iso_server/iso_connection.c +++ b/src/mms/iso_server/iso_connection.c @@ -485,82 +485,84 @@ IsoConnection IsoConnection_create(Socket socket, IsoServer isoServer, bool isSingleThread) { IsoConnection self = (IsoConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoConnection)); - self->socket = socket; + + if (self) { + self->socket = socket; #if (CONFIG_MMS_SUPPORT_TLS == 1) - if (IsoServer_getTLSConfiguration(isoServer) != NULL) { - self->tlsSocket = TLSSocket_create(socket, IsoServer_getTLSConfiguration(isoServer), true); + if (IsoServer_getTLSConfiguration(isoServer) != NULL) { + self->tlsSocket = TLSSocket_create(socket, IsoServer_getTLSConfiguration(isoServer), true); - if (self->tlsSocket == NULL) { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: IsoConnection - TLS initialization failed\n"); + if (self->tlsSocket == NULL) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: IsoConnection - TLS initialization failed\n"); - GLOBAL_FREEMEM(self); + GLOBAL_FREEMEM(self); - return NULL; + return NULL; + } } - } #endif /* (CONFIG_MMS_SUPPORT_TLS == 1) */ - - self->receiveBuffer = (uint8_t*) GLOBAL_MALLOC(RECEIVE_BUF_SIZE); - self->sendBuffer = (uint8_t*) GLOBAL_MALLOC(SEND_BUF_SIZE); - self->msgRcvdHandler = NULL; - self->tickHandler = NULL; - self->handlerParameter = NULL; - self->isoServer = isoServer; - self->state = ISO_CON_STATE_RUNNING; - self->clientAddress = Socket_getPeerAddress(self->socket); - self->localAddress = Socket_getLocalAddress(self->socket); + self->receiveBuffer = (uint8_t*) GLOBAL_MALLOC(RECEIVE_BUF_SIZE); + self->sendBuffer = (uint8_t*) GLOBAL_MALLOC(SEND_BUF_SIZE); + self->msgRcvdHandler = NULL; + self->tickHandler = NULL; + self->handlerParameter = NULL; + self->isoServer = isoServer; + self->state = ISO_CON_STATE_RUNNING; + self->clientAddress = Socket_getPeerAddress(self->socket); + self->localAddress = Socket_getLocalAddress(self->socket); #if (CONFIG_MMS_THREADLESS_STACK != 1) - self->conMutex = Semaphore_create(1); + self->conMutex = Semaphore_create(1); #endif - ByteBuffer_wrap(&(self->rcvBuffer), self->receiveBuffer, 0, RECEIVE_BUF_SIZE); + ByteBuffer_wrap(&(self->rcvBuffer), self->receiveBuffer, 0, RECEIVE_BUF_SIZE); - self->cotpReadBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); - self->cotpWriteBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + self->cotpReadBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + self->cotpWriteBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); - ByteBuffer_wrap(&(self->cotpReadBuffer), self->cotpReadBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); - ByteBuffer_wrap(&(self->cotpWriteBuffer), self->cotpWriteBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + ByteBuffer_wrap(&(self->cotpReadBuffer), self->cotpReadBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + ByteBuffer_wrap(&(self->cotpWriteBuffer), self->cotpWriteBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); - self->cotpConnection = (CotpConnection*) GLOBAL_CALLOC(1, sizeof(CotpConnection)); - CotpConnection_init(self->cotpConnection, self->socket, &(self->rcvBuffer), &(self->cotpReadBuffer), &(self->cotpWriteBuffer)); + self->cotpConnection = (CotpConnection*) GLOBAL_CALLOC(1, sizeof(CotpConnection)); + CotpConnection_init(self->cotpConnection, self->socket, &(self->rcvBuffer), &(self->cotpReadBuffer), &(self->cotpWriteBuffer)); #if (CONFIG_MMS_SUPPORT_TLS == 1) - if (self->tlsSocket) - self->cotpConnection->tlsSocket = self->tlsSocket; + if (self->tlsSocket) + self->cotpConnection->tlsSocket = self->tlsSocket; #endif /* (CONFIG_MMS_SUPPORT_TLS == 1) */ - self->session = (IsoSession*) GLOBAL_CALLOC(1, sizeof(IsoSession)); - IsoSession_init(self->session); + self->session = (IsoSession*) GLOBAL_CALLOC(1, sizeof(IsoSession)); + IsoSession_init(self->session); - self->presentation = (IsoPresentation*) GLOBAL_CALLOC(1, sizeof(IsoPresentation)); - IsoPresentation_init(self->presentation); + self->presentation = (IsoPresentation*) GLOBAL_CALLOC(1, sizeof(IsoPresentation)); + IsoPresentation_init(self->presentation); - self->acseConnection = (AcseConnection*) GLOBAL_CALLOC(1, sizeof(AcseConnection)); + self->acseConnection = (AcseConnection*) GLOBAL_CALLOC(1, sizeof(AcseConnection)); #if (CONFIG_MMS_SUPPORT_TLS == 1) - AcseConnection_init(self->acseConnection, IsoServer_getAuthenticator(self->isoServer), - IsoServer_getAuthenticatorParameter(self->isoServer), self->tlsSocket); + AcseConnection_init(self->acseConnection, IsoServer_getAuthenticator(self->isoServer), + IsoServer_getAuthenticatorParameter(self->isoServer), self->tlsSocket); #else - AcseConnection_init(self->acseConnection, IsoServer_getAuthenticator(self->isoServer), - IsoServer_getAuthenticatorParameter(self->isoServer), NULL); + AcseConnection_init(self->acseConnection, IsoServer_getAuthenticator(self->isoServer), + IsoServer_getAuthenticatorParameter(self->isoServer), NULL); #endif - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: IsoConnection: Start to handle connection for client %s\n", self->clientAddress); + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: IsoConnection: Start to handle connection for client %s\n", self->clientAddress); #if (CONFIG_MMS_SINGLE_THREADED == 0) #if (CONFIG_MMS_THREADLESS_STACK == 0) - if (isSingleThread == false) { - self->handleSet = Handleset_new(); - Handleset_addSocket(self->handleSet, self->socket); - self->thread = Thread_create((ThreadExecutionFunction) handleTcpConnection, self, false); - } -#endif + if (isSingleThread == false) { + self->handleSet = Handleset_new(); + Handleset_addSocket(self->handleSet, self->socket); + self->thread = Thread_create((ThreadExecutionFunction) handleTcpConnection, self, false); + } + #endif #endif + } return self; } @@ -592,6 +594,11 @@ IsoConnection_destroy(IsoConnection self) if (self->socket != NULL) Socket_destroy(self->socket); +#if (CONFIG_MMS_SINGLE_THREADED != 1) || (CONFIG_MMS_THREADLESS_STACK == 1) + if (self->handleSet) + Handleset_destroy(self->handleSet); +#endif + if (self->cotpConnection) { if (self->cotpConnection->handleSet) Handleset_destroy(self->cotpConnection->handleSet); From 7540b6a8d7bd4327f8ddcae7e10ab43361595300 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 12 Mar 2021 10:58:23 +0100 Subject: [PATCH 36/68] - Fixed problem with installed headers - application code does not compile only with installed headers --- Makefile | 1 + .../Makefile.standalone | 35 +++++++++++++++++++ .../Makefile.standalone | 33 +++++++++++++++++ src/iec61850/inc/iec61850_server.h | 1 + .../drivers/sqlite/log_storage_sqlite.c | 2 +- 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 examples/server_example_basic_io/Makefile.standalone create mode 100644 examples/server_example_logging/Makefile.standalone diff --git a/Makefile b/Makefile index 60a798f6..792aafa6 100644 --- a/Makefile +++ b/Makefile @@ -105,6 +105,7 @@ LIB_API_HEADER_FILES += src/mms/inc/mms_common.h LIB_API_HEADER_FILES += src/mms/inc/mms_types.h LIB_API_HEADER_FILES += src/mms/inc/mms_type_spec.h LIB_API_HEADER_FILES += src/mms/inc/mms_client_connection.h +LIB_API_HEADER_FILES += src/mms/inc/mms_server.h LIB_API_HEADER_FILES += src/mms/inc/iso_connection_parameters.h LIB_API_HEADER_FILES += src/goose/goose_subscriber.h LIB_API_HEADER_FILES += src/goose/goose_receiver.h diff --git a/examples/server_example_basic_io/Makefile.standalone b/examples/server_example_basic_io/Makefile.standalone new file mode 100644 index 00000000..87e4c9af --- /dev/null +++ b/examples/server_example_basic_io/Makefile.standalone @@ -0,0 +1,35 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_basic_io +PROJECT_SOURCES = server_example_basic_io.c +PROJECT_SOURCES += static_model.c + +PROJECT_ICD_FILE = simpleIO_direct_control.cid + +all: $(PROJECT_BINARY_NAME) + +LDLIBS += -lm -lpthread + +CP = cp + +LIBIEC61850_INSTALL_DIR = ../../.install + +LIBIEC61850_LIB_DIR = $(LIBIEC61850_INSTALL_DIR)/lib +LIBIEC61850_INC_DIR = $(LIBIEC61850_INSTALL_DIR)/include +LIBIEC61850_INCLUDES = -I$(LIBIEC61850_INC_DIR) + +INCLUDES += $(LIBIEC61850_INCLUDES) + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) -L$(LIBIEC61850_LIB_DIR) -liec61850 $(LDLIBS) + mkdir -p vmd-filestore + $(CP) $(PROJECT_BINARY_NAME) vmd-filestore/IEDSERVER.BIN + +clean: + rm -f $(PROJECT_BINARY_NAME) + rm -f vmd-filestore/IEDSERVER.BIN + + diff --git a/examples/server_example_logging/Makefile.standalone b/examples/server_example_logging/Makefile.standalone new file mode 100644 index 00000000..cf61501b --- /dev/null +++ b/examples/server_example_logging/Makefile.standalone @@ -0,0 +1,33 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_logging +PROJECT_SOURCES = server_example_logging.c +PROJECT_SOURCES += static_model.c +PROJECT_SOURCES += $(LIBIEC_HOME)/src/logging/drivers/sqlite/log_storage_sqlite.c + +PROJECT_ICD_FILE = simpleIO_direct_control.cid + +all: $(PROJECT_BINARY_NAME) + +LDLIBS += -lm -lpthread -lsqlite3 + +CP = cp + +LIBIEC61850_INSTALL_DIR = ../../.install + +LIBIEC61850_LIB_DIR = $(LIBIEC61850_INSTALL_DIR)/lib +LIBIEC61850_INC_DIR = $(LIBIEC61850_INSTALL_DIR)/include +LIBIEC61850_INCLUDES = -I$(LIBIEC61850_INC_DIR) + +INCLUDES += $(LIBIEC61850_INCLUDES) + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) -L$(LIBIEC61850_LIB_DIR) -liec61850 $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) + + diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 098bc1f2..3f503cba 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -39,6 +39,7 @@ extern "C" { #include "iec61850_dynamic_model.h" #include "iec61850_model.h" #include "hal_filesystem.h" +#include "iso_connection_parameters.h" #include "iec61850_config_file_parser.h" /** diff --git a/src/logging/drivers/sqlite/log_storage_sqlite.c b/src/logging/drivers/sqlite/log_storage_sqlite.c index fcb02ec9..49094441 100644 --- a/src/logging/drivers/sqlite/log_storage_sqlite.c +++ b/src/logging/drivers/sqlite/log_storage_sqlite.c @@ -23,9 +23,9 @@ #include "logging_api.h" -#include "libiec61850_platform_includes.h" #include "sqlite3.h" +#include #ifndef DEBUG_LOG_STORAGE_DRIVER #define DEBUG_LOG_STORAGE_DRIVER 0 From 5aec4c94b138ea882c25071c51dc984eb0ec3ac5 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 5 Apr 2021 17:35:07 +0200 Subject: [PATCH 37/68] - added tool support for transient data objects (genmodel/genconfig) --- tools/model_generator/genconfig.jar | Bin 96983 -> 97368 bytes tools/model_generator/genmodel.jar | Bin 96982 -> 97367 bytes .../libiec61850/scl/DataObjectDefinition.java | 11 ++++++++ .../com/libiec61850/scl/model/DataObject.java | 6 +++++ .../tools/DynamicModelGenerator.java | 24 +++++++++++++----- .../tools/StaticModelGenerator.java | 24 +++++++++++++----- 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 5ad24fcb1263b8e55f0ed455e78783669def976b..8b2670b0ae18d3cad1be5ad7149f3c630a501b0d 100644 GIT binary patch delta 30181 zcmZ6xQ*fY7v^AVe?1__!ZQHhO+tvh6CblQGZQHhO+nDHk&-rgo{e820U+n6x>RP>4 z_g*Vc5UZ~ch>9{`;AkMw(9j_7;laxBh+N?R8)fRi|NWOIfZ`+nPXzft<7Z+#DC+;# z!GeH5gZyt@q9Q2c{}!eY!h#Ye3c}zd_JHC5{`RT(oJCR@mGHxos3qViP|lWr^chF$ zO4DpKVg?4c3a`Lq1Cac|=k2*<7g*&i=iQqsq&A>Z!3jWMs*z_7h;^HE6=A5}K6iG! z8$Y|BcNhR~c*4NHoaBty16akzs_Eu@w0qDKO2bUUPT~eBom`d{1%e{ zi#%huVWw%Ou@BW=V-U*^?6$kAv$F~{JH6#Bwl(IB;=Ggjhc)cB3Z47(1twaq8?WlM zYcKJK<94E-+Ej3&3TMey8QWHWy35YU{y6^m2Mez*yP@c9V>e)DZ5L?``765^5TSXM zZz22Xz{#hsIBvHOu#hgeO(W(Amaey)~)Fsf-S11rTZY-FZA$cJ_Ci2a!i=qo}m1TzQ7$Z+`D1 zBBXT829w>?{R}i-x}%oeG@K~qd|Pzpl&z_~J;{lVYnI>pg|PXxn-RMRgjJq;1*Mmo zVQWz`TvNVt(_CfB(ojX3ZL+)>kf&BdW4%}3JFDELq+i-;brRgXe3w6&wITuwXv^XAS7W%Xx=~bMttJB&5&j1 zDOhFFMDWF=gSQVM%}@CTY@w8W2SpC};@&L@os|5D-g9kpJ@k3)e)e zP{Kqf6wd}C4xEv8S~rrI<~NIw_p|cdfU-HATawiAo5l4^aD) zx$e1xaCKGQRI&aQbKXX`QU|wo1bhUnH}ScRIPhd^!w>Y@JqeF|7_K0P*`EKnduTp! z-@FYFbal5EAym)r{xN{wa)8y9m5iFIK2eaKCvLfRGY9Wm(t;+*{-JNkQ$gR7llwf| zRKdHHzTotO<}r|Er3wdwPwrwhd&iAurCh%Css+80%99QPucqXdG+TNP1)!!Q2rfSd8oBjy?`z`;ZfRD&Ern#`0tK@ zlF_A>#m>06Cp---gVS^19{A0B^l`aqe#8`-RZz7MWv*6%DOW|y_(A|l?KkVrle{9p z;OcMOZbT0UuV@PA-IlsB(Lb+G&taAI`79Bv^4`!l`01 zuwA%KQR?L_RjXSMebHS1{a3;IRKCm@x%BkNst2hsdqg;&=u_Jk#F;(Jdc-1lNS?16 z;Mg79`tEzrPTcZ}`3YI4c_lMQ|Kt|uDp)SeP01!-E-4o`TtqIl@cx^?XlI3QsJrI- zr?>DwrRTy&qi2%&1>lnEjO0>eUScg)n!to0nSVMv*)Pgh^dI

Epqr$vnl{NKuZV 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%0DTyhi-}9IjUH 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⪼Z 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 diff --git a/tools/model_generator/src/com/libiec61850/scl/DataObjectDefinition.java b/tools/model_generator/src/com/libiec61850/scl/DataObjectDefinition.java index 29c01204..56c9477c 100644 --- a/tools/model_generator/src/com/libiec61850/scl/DataObjectDefinition.java +++ b/tools/model_generator/src/com/libiec61850/scl/DataObjectDefinition.java @@ -28,11 +28,18 @@ import org.w3c.dom.Node; public class DataObjectDefinition { private String name; private String type; + private boolean trans = false; /* transient attribute value */ private int count = 0; public DataObjectDefinition(Node dataObjectNode) throws SclParserException { this.name = ParserUtils.parseAttribute(dataObjectNode, "name"); this.type = ParserUtils.parseAttribute(dataObjectNode, "type"); + + + Boolean isTransient = ParserUtils.parseBooleanAttribute(dataObjectNode, "transient"); + + if (isTransient != null) + this.trans = isTransient; if ((this.type == null) || (this.name == null)) throw new SclParserException(dataObjectNode, "DO misses required attribute."); @@ -53,5 +60,9 @@ public class DataObjectDefinition { public int getCount() { return count; } + + public boolean isTransient() { + return trans; + } } diff --git a/tools/model_generator/src/com/libiec61850/scl/model/DataObject.java b/tools/model_generator/src/com/libiec61850/scl/model/DataObject.java index c7fae5e0..d61ef972 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/DataObject.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/DataObject.java @@ -42,11 +42,13 @@ public class DataObject implements DataModelNode { private List subDataObjects = null; private SclType sclType; private DataModelNode parent; + private boolean trans = false; public DataObject(DataObjectDefinition doDefinition, TypeDeclarations typeDeclarations, DataModelNode parent) throws SclParserException { this.name = doDefinition.getName(); this.count = doDefinition.getCount(); this.parent = parent; + this.trans = doDefinition.isTransient(); this.dataAttributes = new LinkedList(); this.subDataObjects = new LinkedList(); @@ -109,6 +111,10 @@ public class DataObject implements DataModelNode { public int getCount() { return count; } + + public boolean isTransient() { + return trans; + } @Override public DataModelNode getChildByName(String childName) { diff --git a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java index 44d68303..d5ec7ffc 100644 --- a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java @@ -132,7 +132,7 @@ public class DynamicModelGenerator { for (DataObject dataObject : logicalNode.getDataObjects()) { output.print("DO(" + dataObject.getName() + " " + dataObject.getCount() + "){\n"); - exportDataObject(output, dataObject); + exportDataObject(output, dataObject, false); output.println("}"); } @@ -369,27 +369,37 @@ public class DynamicModelGenerator { output.println("}"); } - private void exportDataObject(PrintStream output, DataObject dataObject) { + private void exportDataObject(PrintStream output, DataObject dataObject, boolean isTransient) { + + if (dataObject.isTransient()) + isTransient = true; + for (DataObject subDataObject : dataObject.getSubDataObjects()) { output.print("DO(" + subDataObject.getName() + " " + subDataObject.getCount() + "){\n"); - exportDataObject(output, subDataObject); + exportDataObject(output, subDataObject, isTransient); output.println("}"); } for (DataAttribute dataAttribute : dataObject.getDataAttributes()) { - exportDataAttribute(output, dataAttribute); + exportDataAttribute(output, dataAttribute, isTransient); } } - private void exportDataAttribute(PrintStream output, DataAttribute dataAttribute) { + private void exportDataAttribute(PrintStream output, DataAttribute dataAttribute, boolean isTransient) { output.print("DA(" + dataAttribute.getName() + " "); output.print(dataAttribute.getCount() + " "); output.print(dataAttribute.getType().getIntValue() + " "); output.print(dataAttribute.getFc().getIntValue() + " "); - output.print(dataAttribute.getTriggerOptions().getIntValue() + " "); + + int trgOpsVal = dataAttribute.getTriggerOptions().getIntValue(); + + if (isTransient) + trgOpsVal += 128; + + output.print(trgOpsVal + " "); Long sAddr = null; @@ -471,7 +481,7 @@ public class DynamicModelGenerator { output.println("{"); for (DataAttribute subDataAttribute : dataAttribute.getSubDataAttributes()) { - exportDataAttribute(output, subDataAttribute); + exportDataAttribute(output, subDataAttribute, isTransient); } output.println("}"); diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index c17b86a4..144c23a0 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -588,7 +588,7 @@ public class StaticModelGenerator { cOut.println("};\n"); - printDataObjectDefinitions(lnName, logicalNode.getDataObjects(), null); + printDataObjectDefinitions(lnName, logicalNode.getDataObjects(), null, false); printReportControlBlocks(lnName, logicalNode); @@ -604,7 +604,7 @@ public class StaticModelGenerator { } } - private void printDataObjectDefinitions(String lnName, List dataObjects, String dataAttributeSibling) { + private void printDataObjectDefinitions(String lnName, List dataObjects, String dataAttributeSibling, boolean isTransient) { for (int i = 0; i < dataObjects.size(); i++) { DataObject dataObject = dataObjects.get(i); @@ -644,17 +644,26 @@ public class StaticModelGenerator { cOut.println(" " + dataObject.getCount()); cOut.println("};\n"); + + boolean isDoTransient = false; + + if (isTransient) + isDoTransient = true; + else { + if (dataObject.isTransient()) + isDoTransient = true; + } if (dataObject.getSubDataObjects() != null) - printDataObjectDefinitions(doName, dataObject.getSubDataObjects(), firstDataAttributeName); + printDataObjectDefinitions(doName, dataObject.getSubDataObjects(), firstDataAttributeName, isDoTransient); if (dataObject.getDataAttributes() != null) - printDataAttributeDefinitions(doName, dataObject.getDataAttributes()); + printDataAttributeDefinitions(doName, dataObject.getDataAttributes(), isDoTransient); } } - private void printDataAttributeDefinitions(String doName, List dataAttributes) { + private void printDataAttributeDefinitions(String doName, List dataAttributes, boolean isTransient) { for (int i = 0; i < dataAttributes.size(); i++) { DataAttribute dataAttribute = dataAttributes.get(i); @@ -709,6 +718,9 @@ public class StaticModelGenerator { if (trgOps.isQchg()) cOut.print(" + TRG_OPT_QUALITY_CHANGED"); + + if (isTransient) + cOut.print(" + TRG_OPT_TRANSIENT"); cOut.println(","); @@ -731,7 +743,7 @@ public class StaticModelGenerator { cOut.println("};\n"); if (dataAttribute.getSubDataAttributes() != null) - printDataAttributeDefinitions(daName, dataAttribute.getSubDataAttributes()); + printDataAttributeDefinitions(daName, dataAttribute.getSubDataAttributes(), isTransient); DataModelValue value = dataAttribute.getValue(); From f6110c11581b2a7d40fcdf7bff89d47da9d4e0ab Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 6 Apr 2021 11:31:51 +0200 Subject: [PATCH 38/68] - updated API documentation for command termination handler --- src/iec61850/inc/iec61850_client.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index c5c51818..d3b58d1a 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -2255,7 +2255,12 @@ ControlObjectClient_setSynchroCheck(ControlObjectClient self, bool value); * * This callback is invoked whenever a CommandTermination+ or CommandTermination- message is received. * To distinguish between a CommandTermination+ and CommandTermination- please use the - * ControlObjectClient_getLastApplError function. + * \ref ControlObjectClient_getLastApplError function. + * + * In case of CommandTermination+ the return value + * of \ref ControlObjectClient_getLastApplError has error=CONTROL_ERROR_NO_ERROR and + * addCause=ADD_CAUSE_UNKNOWN set. When addCause is different from ADD_CAUSE_UNKNOWN then the client + * received a CommandTermination- message. * * NOTE: Do not call \ref ControlObjectClient_destroy inside of this callback! Doing so will cause a dead-lock. * @@ -2269,7 +2274,10 @@ typedef void (*CommandTerminationHandler) (void* parameter, ControlObjectClient * * This callback is invoked whenever a CommandTermination+ or CommandTermination- message is received. * To distinguish between a CommandTermination+ and CommandTermination- please use the - * ControlObjectClient_getLastApplError function. + * \ref ControlObjectClient_getLastApplError function. In case of CommandTermination+ the return value + * of \ref ControlObjectClient_getLastApplError has error=CONTROL_ERROR_NO_ERROR and + * addCause=ADD_CAUSE_UNKNOWN set. When addCause is different from ADD_CAUSE_UNKNOWN then the client + * received a CommandTermination- message. * * \param self the ControlObjectClient instance * \param handler the callback function to be used From 530cdc038325ddc584f228af2fc4e33ad7c98165 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 6 Apr 2021 18:27:32 +0200 Subject: [PATCH 39/68] - .NET API: added function IedModel.GetDeviceByInst --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 93d32b3a..e1f5cbe7 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -74,6 +74,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedModel_setIedNameForDynamicModel(IntPtr self, string iedName); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedModel_getDeviceByInst(IntPtr self, string ldInst); + internal IntPtr self = IntPtr.Zero; internal IedModel(IntPtr self) @@ -193,6 +196,18 @@ namespace IEC61850 return parentNode; } + public LogicalDevice GetDeviceByInst(string ldInst) + { + IntPtr ldPtr = IedModel_getDeviceByInst(self, ldInst); + + if (ldPtr != IntPtr.Zero) + { + return new LogicalDevice(ldPtr, this); + } + else + return null; + } + private ModelNode GetModelNodeFromNodeRef(IntPtr nodeRef) { ModelNode modelNode = null; From cb3f460fcf02a202b47334fffbdfa7d021216ec6 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 7 Apr 2021 17:04:42 +0200 Subject: [PATCH 40/68] - .NET API: added functions to access DataAttribute properties --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 63 ++++++++++++++++++- src/iec61850/inc/iec61850_dynamic_model.h | 34 +++++++++- src/iec61850/server/model/dynamic_model.c | 24 +++++++ 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index e1f5cbe7..d005c815 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -893,22 +893,57 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void DataAttribute_setValue(IntPtr self, IntPtr mmsValue); - private DataAttributeType daType; + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int DataAttribute_getType(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern byte DataAttribute_getTrgOps(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int DataAttribute_getFC(IntPtr self); internal DataAttribute(IntPtr self, ModelNode parent) : base(self) { this.parent = parent; } + ///

+ /// Create a new data attribute and add it to a parent model node + /// + /// The parent model node has to be of type DataObject or DataAttribute + /// the name of the data attribute (e.g. "stVal") + /// the parent model node (of type DataObject or DataAttribute) + /// the type of the data attribute (CONSTRUCTED if the type contains sub data attributes) + /// the functional constraint (FC) of the data attribute + /// the trigger options (dupd, dchg, qchg) that cause an event notification + /// the number of array elements if the data attribute is an array or 0 + /// an optional short address (deprecated) public DataAttribute (string name, ModelNode parent, DataAttributeType type, FunctionalConstraint fc, TriggerOptions trgOps, int arrayElements, UInt32 sAddr) { this.parent = parent; - this.daType = type; self = DataAttribute_create (name, parent.self, (int)type, (int)fc, (byte)trgOps, arrayElements, sAddr); } + /// + /// Create a new data attribute and add it to a parent model node + /// + /// The parent model node has to be of type DataObject or DataAttribute + /// the name of the data attribute (e.g. "stVal") + /// the parent model node (of type DataObject or DataAttribute) + /// the type of the data attribute (CONSTRUCTED if the type contains sub data attributes) + /// the functional constraint (FC) of the data attribute + /// the trigger options (dupd, dchg, qchg) that cause an event notification + /// the number of array elements if the data attribute is an array or 0 + public DataAttribute(string name, ModelNode parent, DataAttributeType type, FunctionalConstraint fc, TriggerOptions trgOps, + int arrayElements) + { + this.parent = parent; + + self = DataAttribute_create(name, parent.self, (int)type, (int)fc, (byte)trgOps, arrayElements, 0); + } + /// /// Get IEC 61850 data attribute type of the data attribute /// @@ -916,7 +951,29 @@ namespace IEC61850 { get { - return daType; + return (DataAttributeType)DataAttribute_getType(self); + } + } + + /// + /// The trigger options (dchg, qchg, dupd) of the data attribute + /// + public TriggerOptions TrgOps + { + get + { + return (TriggerOptions)DataAttribute_getTrgOps(self); + } + } + + /// + /// The functional constraint (FC) of the data attribute + /// + public FunctionalConstraint FC + { + get + { + return (FunctionalConstraint)DataAttribute_getFC(self); } } diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index 872ee425..e28ac506 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -121,12 +121,12 @@ DataObject_create(const char* name, ModelNode* parent, int arrayElements); /** * \brief create a new data attribute and add it to a parent model node * - * The parent model node has to be of type LogicalNode or DataObject + * The parent model node has to be of type DataObject or DataAttribute * * \param name the name of the data attribute (e.g. "stVal") * \param parent the parent model node * \param type the type of the data attribute (CONSTRUCTED if the type contains sub data attributes) - * \param fc the functional constraint (FC) of the data attribte + * \param fc the functional constraint (FC) of the data attribute * \param triggerOptions the trigger options (dupd, dchg, qchg) that cause an event notification * \param arrayElements the number of array elements if the data attribute is an array or 0 * \param sAddr an optional short address @@ -137,6 +137,36 @@ LIB61850_API DataAttribute* DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type, FunctionalConstraint fc, uint8_t triggerOptions, int arrayElements, uint32_t sAddr); +/** + * \brief Get the data type of the data attribute + * + * \param self the data attribute instance + * + * \return the data attribute type + */ +LIB61850_API DataAttributeType +DataAttribute_getType(DataAttribute* self); + +/** + * \brief Get the functional constraint (FC) of the data attribute + * + * \param self the data attribute instance + * + * \return the functional constraint (FC) of the data attribute + */ +LIB61850_API FunctionalConstraint +DataAttribute_getFC(DataAttribute* self); + +/** + * \brief Get the trigger options of the data attribute + * + * \param self the data attribute instance + * + * \return the trigger options (dupd, dchg, qchg) that cause an event notification + */ +LIB61850_API uint8_t +DataAttribute_getTrgOps(DataAttribute* self); + /** * \brief Set the value of the data attribute (can be used to set default values before server is created) * diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index ea68d36d..33e79ef0 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -607,6 +607,30 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type return self; } +const char* +DataAttribute_getName(DataAttribute* self) +{ + return self->name; +} + +DataAttributeType +DataAttribute_getType(DataAttribute* self) +{ + return self->type; +} + +FunctionalConstraint +DataAttribute_getFC(DataAttribute* self) +{ + return self->fc; +} + +uint8_t +DataAttribute_getTrgOps(DataAttribute* self) +{ + return self->triggerOptions; +} + void DataAttribute_setValue(DataAttribute* self, MmsValue* value) { From e0b4a720ddcfa91a064e17f3ae2f9aabd190eff2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 8 Apr 2021 11:54:38 +0200 Subject: [PATCH 41/68] - added/updated comments in SV publisher example --- examples/sv_publisher/sv_publisher_example.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/sv_publisher/sv_publisher_example.c b/examples/sv_publisher/sv_publisher_example.c index aa088fe9..48975351 100644 --- a/examples/sv_publisher/sv_publisher_example.c +++ b/examples/sv_publisher/sv_publisher_example.c @@ -1,8 +1,7 @@ /* - * sv_subscriber_example.c - * - * Example program for Sampled Values (SV) subscriber + * sv_publisher_example.c * + * Example program for Sampled Values (SV) publisher */ #include @@ -35,12 +34,16 @@ main(int argc, char** argv) if (svPublisher) { + /* Create first ASDU and add data points */ + SVPublisher_ASDU asdu1 = SVPublisher_addASDU(svPublisher, "svpub1", NULL, 1); int float1 = SVPublisher_ASDU_addFLOAT(asdu1); int float2 = SVPublisher_ASDU_addFLOAT(asdu1); int ts1 = SVPublisher_ASDU_addTimestamp(asdu1); + /* Create second ASDU and add data points */ + SVPublisher_ASDU asdu2 = SVPublisher_addASDU(svPublisher, "svpub2", NULL, 1); int float3 = SVPublisher_ASDU_addFLOAT(asdu2); @@ -57,6 +60,8 @@ main(int argc, char** argv) Timestamp_clearFlags(&ts); Timestamp_setTimeInMilliseconds(&ts, Hal_getTimeInMs()); + /* update the values in the SV ASDUs */ + SVPublisher_ASDU_setFLOAT(asdu1, float1, fVal1); SVPublisher_ASDU_setFLOAT(asdu1, float2, fVal2); SVPublisher_ASDU_setTimestamp(asdu1, ts1, ts); @@ -65,14 +70,22 @@ main(int argc, char** argv) SVPublisher_ASDU_setFLOAT(asdu2, float4, fVal2 * 2); SVPublisher_ASDU_setTimestamp(asdu2, ts2, ts); + /* update the sample counters */ + SVPublisher_ASDU_increaseSmpCnt(asdu1); SVPublisher_ASDU_increaseSmpCnt(asdu2); fVal1 += 1.1f; fVal2 += 0.1f; + /* send the SV message */ SVPublisher_publish(svPublisher); + /* + * For real applications this sleep time has to be adjusted to match the SV sample rate! + * Platform specific functions like usleep or timer interrupt service routines have to be used instead + * to realize the required time accuracy for sending messages. + */ Thread_sleep(50); } From 9e6e3487da2257710bae846109e09c45894aa5e4 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 8 Apr 2021 12:02:32 +0200 Subject: [PATCH 42/68] - removed compiler warnings --- src/iec61850/server/model/dynamic_model.c | 6 ------ src/mms/iso_mms/server/mms_access_result.c | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 33e79ef0..56ad9658 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -607,12 +607,6 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type return self; } -const char* -DataAttribute_getName(DataAttribute* self) -{ - return self->name; -} - DataAttributeType DataAttribute_getType(DataAttribute* self) { diff --git a/src/mms/iso_mms/server/mms_access_result.c b/src/mms/iso_mms/server/mms_access_result.c index b17461f0..06b50d28 100644 --- a/src/mms/iso_mms/server/mms_access_result.c +++ b/src/mms/iso_mms/server/mms_access_result.c @@ -394,7 +394,7 @@ MmsValue_getMaxEncodedSize(MmsValue* self) size = 2 + self->value.binaryTime.size; break; case MMS_OCTET_STRING: - elementSize = abs(self->value.octetString.maxSize); + elementSize = self->value.octetString.maxSize; size = 1 + BerEncoder_determineLengthSize(elementSize) + elementSize; break; case MMS_FLOAT: From 72feb2f614e250b34cb77d1840082abb14e75a09 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 8 Apr 2021 22:18:11 +0200 Subject: [PATCH 43/68] - updated Makefiles to compile code for latest version of MacOs --- Makefile | 7 +++++++ make/target_system.mk | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 792aafa6..db06c7f8 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,13 @@ LIB_SOURCE_DIRS += hal/ethernet/bsd LIB_SOURCE_DIRS += hal/filesystem/linux LIB_SOURCE_DIRS += hal/time/unix LIB_SOURCE_DIRS += hal/memory +else ifeq ($(HAL_IMPL), MACOS) +LIB_SOURCE_DIRS += hal/socket/bsd +LIB_SOURCE_DIRS += hal/thread/macos +LIB_SOURCE_DIRS += hal/ethernet/bsd +LIB_SOURCE_DIRS += hal/filesystem/linux +LIB_SOURCE_DIRS += hal/time/unix +LIB_SOURCE_DIRS += hal/memory endif LIB_INCLUDE_DIRS += config LIB_INCLUDE_DIRS += hal/inc diff --git a/make/target_system.mk b/make/target_system.mk index 8e9b7c1e..1c35c642 100644 --- a/make/target_system.mk +++ b/make/target_system.mk @@ -20,7 +20,7 @@ TARGET=POSIX else ifeq ($(findstring MINGW,$(UNAME)), MINGW) TARGET=WIN32 else ifeq ($(UNAME), Darwin) -TARGET=BSD +TARGET=MACOS else ifeq ($(UNAME), FreeBSD) TARGET=BSD endif @@ -117,10 +117,14 @@ endif else ifeq ($(TARGET), BSD) HAL_IMPL = BSD +else ifeq ($(TARGET), MACOS) +HAL_IMPL = MACOS else HAL_IMPL = POSIX endif + + LDLIBS = -lpthread ifeq ($(TARGET), LINUX-MIPSEL) @@ -174,7 +178,11 @@ else ifeq ($(TARGET), BSD) DYN_LIB_NAME = $(LIB_OBJS_DIR)/libiec61850.dylib else +ifeq ($(TARGET), MACOS) +DYN_LIB_NAME = $(LIB_OBJS_DIR)/libiec61850.dylib +else DYN_LIB_NAME = $(LIB_OBJS_DIR)/libiec61850.so endif +endif endif From 91bb816621fede5ccacf525631e08a478fa11241 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 12 Apr 2021 08:02:24 +0200 Subject: [PATCH 44/68] - updated macos semaphore handling - updated CHANGELOG --- CHANGELOG | 25 ++++++++++++++ hal/thread/macos/thread_macos.c | 58 +++++++++++++++++++++++++-------- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0ea01043..d69c0705 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,31 @@ Changes to version 1.5.0 - IEC 61850 server: control models - allow delaying select response with check handler (new handler return value CONTROL_WAITING_FOR_SELECT) - added support for server to listen on multiple ports - added support for service tracking +- added tool support for transient data objects +- .NET API: added more functions to create and access server data model +- IED server - control model - send AddCause with operate- for DOes, SBOes control models +- IED server: integrated GOOSE publisher - lock data model during GOOSE retransmission to avoid corrupted GOOSE data +- added server example for dead band handling +- IED server: make presence of RCB.Owner configurable at runtime with function IedServerConfig_enableOwnerForRCB (B1502/S1634) +- IED server: make presence of BRCB.ResvTms configurable at runtime with function IedServerConfig_enableResvTmsForBRCB (F1558) +- restrict maximum recursion depth in BerDecoder_decodeLength when indefinite length encoding is used to avoid stack overflow when receiving malformed messages +- fixed oss-fuzz issues 31399, 31340, 31341, 31344, 31346 +- IED server: fixed bug in log service - old-entry and old-entry-time not updated +- IED server: added new function IedServer_handleWriteAccessForComplexAttribute. Changed WriteAccessHandler behavior when ACCESS_POLICY_ALLOW. +- MMS server: add compile time configuration options to enable/disable fileDelete and fileRename services (fileRename is now disabled by default) +- MMS server: better data model lock handling for performance improvements +- Linux - Ethernet: replace IFF_PROMISC by IFF_ALLMULTI +- improvements in Python wrapper code +- IED server: control models - fixed bug that only one control is unselected when connection closes +- IED server: fixed bug - logs (journals) are added to all logical devices instead of just the parents +- IED Server: prevent integrated GOOSE publisher to crash when ethernet socket cannot be created +- IED server: make compatible with tissue 1178 +- IED server: reporting - implemented behavior according to tissue 1432 +- IED server: WriteAccessHandler can tell the stack not to update the value when returning DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE +- IED server: fixed problem that BL FC is not writable (#287) +- IEC 61850 client: fixed dead lock in IedConnection_getFileAsync when fileRead times out (#285) +- IED server: added ControlSelectStateChangedHandler callback for control mode + Changes to version 1.4.2.1 -------------------------- diff --git a/hal/thread/macos/thread_macos.c b/hal/thread/macos/thread_macos.c index e51035d7..64d9a6dd 100644 --- a/hal/thread/macos/thread_macos.c +++ b/hal/thread/macos/thread_macos.c @@ -28,43 +28,75 @@ struct sThread { bool autodestroy; }; +typedef struct sSemaphore* mSemaphore; + +struct sSemaphore +{ + sem_t* sem; +}; + Semaphore Semaphore_create(int initialValue) { + mSemaphore self = NULL; + char tmpname[] = {"/tmp/libiec61850.XXXXXX"}; - mktemp(tmpname); + char* res = mktemp(tmpname); - Semaphore self = (Semaphore) sem_open(tmpname, O_CREAT, 0666, initialValue); + if (res) { + self = (mSemaphore) GLOBAL_CALLOC(1, sizeof(struct sSemaphore)); - if (self == SEM_FAILED) { - printf("ERROR: Failed to create semaphore (errno = %i)\n", errno); - } - - int ret = sem_unlink(tmpname); + if (self) { + self->sem = sem_open(tmpname, O_CREAT, 0666, initialValue); + + if (self->sem == SEM_FAILED) { + printf("ERROR: Failed to create semaphore (errno = %i)\n", errno); - if (ret == -1) - printf("ERROR: Failed to unlink semaphore %s\n", tmpname); + GLOBAL_FREEMEM(self); + self = NULL; + } + else { + int ret = sem_unlink(tmpname); - return self; + if (ret == -1) + printf("ERROR: Failed to unlink semaphore %s\n", tmpname); + } + } + } + + return (Semaphore)self; } /* Wait until semaphore value is more than zero. Then decrease the semaphore value. */ void Semaphore_wait(Semaphore self) { - sem_wait((sem_t*) self); + mSemaphore mSelf = (mSemaphore) self; + + sem_wait(mSelf->sem); } void Semaphore_post(Semaphore self) { - sem_post((sem_t*) self); + mSemaphore mSelf = (mSemaphore) self; + + sem_post(mSelf->sem); } void Semaphore_destroy(Semaphore self) { - sem_close(self); + if (self) { + mSemaphore mSelf = (mSemaphore) self; + + int ret = sem_close(mSelf->sem); + + if (ret == -1) + printf("ERROR: Failed to close semaphore (errno = %i)\n", errno); + + GLOBAL_FREEMEM(mSelf); + } } Thread From fcefc746fea286aeaa40d2f62240216da81c85e5 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 12 Apr 2021 17:28:24 +0200 Subject: [PATCH 45/68] - updated CHANGELOG --- CHANGELOG | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d69c0705..5137cd5c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,7 @@ Changes to version 1.5.0 ------------------------ - added support for time with ns resolution - IEC 61850 server: control models - allow delaying select response with check handler (new handler return value CONTROL_WAITING_FOR_SELECT) -- added support for server to listen on multiple ports +- IEC 61850 server: added support to listen on multiple IP addresses and ports (new function IedServer_addAccessPoint) - added support for service tracking - added tool support for transient data objects - .NET API: added more functions to create and access server data model @@ -28,7 +28,31 @@ Changes to version 1.5.0 - IED server: fixed problem that BL FC is not writable (#287) - IEC 61850 client: fixed dead lock in IedConnection_getFileAsync when fileRead times out (#285) - IED server: added ControlSelectStateChangedHandler callback for control mode - +- Client: fixed - IedConnection_getRCBValues doesn't check type of server response (#283) +- GOOSE subscriber: changed maximum GoID size according to tissue 770 (129 bytes) +- IED server: send AddCause for invalid origin also in case of direct control models +- IED server: support for configuration of EditSG service and online visibility of SGCB.ResvTms at runtime +- IED server: changed types TrkOps and OptFlds to variable length bit strings +- MMS: changed handling of variable sized bit strings (now also accepts bit strings of larger size, ignoring the bits that exceed the specified size) +- IED server: add support for correct CBB handling (required for test case sAss4) and initiate error PDU +- IED server: add support for tissue 807 (owner attribute in RCB is only present when ReportSettings@owner attribute is true) +- IED server: implemented tissue 1453 also for writing to "RptId" (purgeBuf only executed when value changes) +- GOOSE subscriber: always copy GoID and DatSet from GOOSE message; always create new MmsValue instance for GOOSE data set when subscriber is observer +- IED server: added configuration file support for data set entries with array elements or array element components +- fixed problems in handling array elements and array element components +- fixed bug in MmsConnection_readMultipleVariables: send invaid messsage and memory access errors when too many items are passed to the function exhausting MMS payload size +- IEC 61850 server: fixed problem with test case sRp4 - RCB RptID attribute is not empty after writing empty string +- fixed program crash when normal mode parameers are missing in presentation layer (#252) +- IED Server/GOOSE: Don't send GOOSE message with new event while data model is locked +- GOOSE: added GOOSE observer feature (GooseSubscriber listening to all GOOSE messages) and GOOSE observer example +- COTP: fixed possible heap buffer overflow when handling message with invalid (zero) value in length field (#250) +- IEC 61850 server: fixed - cancel command for time activated control returns object-access-denied even in case of success +- IEC 61850 client: fixed bug - IedConnection_setRCBValuesAsync always return 0 instead of invoke-ID +- MMS: fixed problem in handling of indefinite length encoded BER elements +- IEC 61850 client: reporting - support data set entries with multiple reasons for inclusion +- Java tools: moved minTime, maxTime from GSEControl to GSE; updated GOOSE server example CID file +- IEC 61850 server: Added ControlAction_setError function - with this function the user application can control the error code used in LastApplError and CommandTermination messages +- IEC 61850 server: fixed problem with logging when log data set contains FCDO (#225) Changes to version 1.4.2.1 -------------------------- From b2f417bdbf069df595e0c5eb8d2b5ef1768c8bdf Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 18 Apr 2021 16:35:51 +0200 Subject: [PATCH 46/68] - updated comments and readme --- README.md | 10 ++++++++++ dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 387a87a3..cee30b14 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,16 @@ Depending on the system you don't have to provide a generator to the cmake comma To select some configuration options you can use ccmake or cmake-gui. +For newer version of Visual Studio you can use one of the following commands (for 64 bit builds): + +For Visual Studio 2017: + + cmake -G "Visual Studio 15 2017 Win64" .. + +For Visual Studio 2019 (new way to specify the x64 platform): + + cmake -G "Visual Studio 16 2019" .. -A x64 + ## Using the log service with sqlite diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index aff8fc67..29315143 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -548,7 +548,7 @@ namespace IEC61850 /// /// Called when there is a change in the connection state /// - public delegate void StateChangedHandler(IedConnection connection,IedConnectionState newState); + public delegate void StateChangedHandler(IedConnection connection, IedConnectionState newState); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_installStateChangedHandler(IntPtr connection, InternalStateChangedHandler handler, IntPtr parameter); @@ -1788,7 +1788,7 @@ namespace IEC61850 /// by a prior function call. /// The user provided callback handler /// This exception is thrown if there is a connection or service error - [Obsolete("ConnectionClosedHandler is deprecated, please use ConnectionEventHandler instead")] + [Obsolete("ConnectionClosedHandler is deprecated, please use StateChangedHandler instead")] public void InstallConnectionClosedHandler(ConnectionClosedHandler handler) { if (connectionClosedHandler == null) From efe4513d11b0a2f56c49e63f914accb327d6405e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 18 Apr 2021 16:56:45 +0200 Subject: [PATCH 47/68] - .NET API: fully implemented dispose pattern in DataSet class --- dotnet/IEC61850forCSharp/DataSet.cs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/dotnet/IEC61850forCSharp/DataSet.cs b/dotnet/IEC61850forCSharp/DataSet.cs index 2fcb1040..d7304bc9 100644 --- a/dotnet/IEC61850forCSharp/DataSet.cs +++ b/dotnet/IEC61850forCSharp/DataSet.cs @@ -34,6 +34,11 @@ namespace IEC61850 /// This class is used to represent a data set. It is used to store the values /// of a data set. ///
+ /// + /// This class manages native resource. Take care that the finalizer/Dispose is not + /// called while running a method or the object is still in use by another object. + /// If in doubt please use the "using" statement. + /// public class DataSet : IDisposable { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -108,21 +113,34 @@ namespace IEC61850 return ClientDataSet_getDataSetSize(nativeObject); } - public void Dispose() + private bool disposed = false; + + protected virtual void Dispose(bool disposing) { lock (this) { - if (nativeObject != IntPtr.Zero) + if (!disposed) { - ClientDataSet_destroy(nativeObject); - nativeObject = IntPtr.Zero; + if (nativeObject != IntPtr.Zero) + { + ClientDataSet_destroy(nativeObject); + nativeObject = IntPtr.Zero; + } + + disposed = true; } } } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + ~DataSet() { - Dispose(); + Dispose(false); } internal IntPtr getNativeInstance() From f866132e840cb624d198cb3cd5124898aabffca7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 20 Apr 2021 09:31:43 +0200 Subject: [PATCH 48/68] - IED server: Reject Cancel/SBOw in WaitForChange state - fixed problem with test case sCtl26 --- src/iec61850/server/mms_mapping/control.c | 63 ++++++++++++++++------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index c7be0319..452ac7fc 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -1,7 +1,7 @@ /* * control.c * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2021 Michael Zillgith * * This file is part of libIEC61850. * @@ -39,13 +39,13 @@ #define DEBUG_IED_SERVER 0 #endif -#define STATE_UNSELECTED 0 -#define STATE_READY 1 -#define STATE_WAIT_FOR_ACTIVATION_TIME 2 -#define STATE_PERFORM_TEST 3 -#define STATE_WAIT_FOR_EXECUTION 4 -#define STATE_OPERATE 5 -#define STATE_WAIT_FOR_SELECT 6 +#define STATE_UNSELECTED 0 /* idle state for SBO controls */ +#define STATE_READY 1 /* idle state for direct controls, or selected state for SBO controls */ +#define STATE_WAIT_FOR_ACTIVATION_TIME 2 /* time activated control is waiting for execution time */ +#define STATE_PERFORM_TEST 3 /* waiting for application to perform tests */ +#define STATE_WAIT_FOR_EXECUTION 4 /* control is scheduled and waiting for execution */ +#define STATE_OPERATE 5 /* waiting for application to execute the command */ +#define STATE_WAIT_FOR_SELECT 6 /* waiting for application to perform/confirm selection */ #define PENDING_EVENT_SELECTED 1 #define PENDING_EVENT_UNSELECTED 2 @@ -1989,20 +1989,37 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari goto free_and_return; } + int state = getState(controlObject); + uint64_t currentTime = Hal_getTimeInMs(); checkSelectTimeout(controlObject, currentTime); - int state = getState(controlObject); - if (state != STATE_UNSELECTED) { - indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; - ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, - ADD_CAUSE_OBJECT_ALREADY_SELECTED, ctlNum, origin, true); + if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) { + indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; - if (DEBUG_IED_SERVER) - printf("IED_SERVER: SBOw - select failed!\n"); + ControlObject_sendLastApplError(controlObject, connection, "SBOw", + CONTROL_ERROR_NO_ERROR, ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION, + ctlNum, origin, true); + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: SBOw - select failed - already in execution!\n"); + + goto free_and_return; + } + else { + indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + + ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, + ADD_CAUSE_OBJECT_ALREADY_SELECTED, ctlNum, origin, true); + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: SBOw - select failed - already selected!\n"); + + goto free_and_return; + } } else { @@ -2282,11 +2299,11 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari serviceType = IEC61850_SERVICE_TYPE_CANCEL; - if (DEBUG_IED_SERVER) - printf("IED_SERVER: control received cancel!\n"); - int state = getState(controlObject); + if (DEBUG_IED_SERVER) + printf("IED_SERVER: control received cancel (state: %i)!\n", state); + MmsValue* ctlNum = getCancelParameterCtlNum(value); MmsValue* origin = getCancelParameterOrigin(value); @@ -2297,6 +2314,16 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari goto free_and_return; } + if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) { + indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + + ControlObject_sendLastApplError(controlObject, connection, "Cancel", + CONTROL_ERROR_NO_ERROR, ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION, + ctlNum, origin, true); + + goto free_and_return; + } + if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) { if (state != STATE_UNSELECTED) { if (controlObject->mmsConnection == connection) { From 1b2d194694c70814d7d690bf9db5b785f2dcdbe9 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 20 Apr 2021 10:12:42 +0200 Subject: [PATCH 49/68] - IED server: For SBOes check test flag match when accepting operate (sSBOes8) --- src/iec61850/client/client_control.c | 6 ++++++ src/iec61850/inc/iec61850_client.h | 6 ++++++ src/iec61850/server/mms_mapping/control.c | 6 +++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/iec61850/client/client_control.c b/src/iec61850/client/client_control.c index ad31648d..4cd837ac 100644 --- a/src/iec61850/client/client_control.c +++ b/src/iec61850/client/client_control.c @@ -1285,6 +1285,12 @@ ControlObjectClient_getLastApplError(ControlObjectClient self) return self->lastApplError; } +void +ControlObjectClient_setCtlNum(ControlObjectClient self, uint8_t ctlNum) +{ + self->ctlNum = ctlNum; +} + void controlObjectClient_invokeCommandTerminationHandler(ControlObjectClient self) { diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index d3b58d1a..9d1946e9 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -2231,6 +2231,12 @@ ControlObjectClient_enableInterlockCheck(ControlObjectClient self); LIB61850_API DEPRECATED void ControlObjectClient_enableSynchroCheck(ControlObjectClient self); +/** + * \deprecated Do not use (ctlNum is handled automatically by the library)! Intended for test purposes only. + */ +LIB61850_API DEPRECATED void +ControlObjectClient_setCtlNum(ControlObjectClient self, uint8_t ctlNum); + /** * \brief Set the value of the interlock check flag when a control command is sent * diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 452ac7fc..adb57611 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -2144,8 +2144,6 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari bool testCondition = MmsValue_getBoolean(test); - controlObject->testMode = testCondition; - if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) { if (controlObject->mmsConnection != connection) { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; @@ -2163,7 +2161,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari MmsValue_equals(origin, controlObject->origin) && MmsValue_equals(ctlNum, controlObject->ctlNum) && (controlObject->interlockCheck == interlockCheck) && - (controlObject->synchroCheck == synchroCheck) + (controlObject->synchroCheck == synchroCheck) && + (controlObject->testMode == testCondition) ) == false) { indication = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; @@ -2191,6 +2190,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari controlObject->synchroCheck = synchroCheck; controlObject->interlockCheck = interlockCheck; controlObject->mmsConnection = connection; + controlObject->testMode = testCondition; CheckHandlerResult checkResult = CONTROL_ACCEPTED; if (controlObject->checkHandler != NULL) { /* perform operative tests */ From 8578344e85d59d0d84a43b7b46df28db80786b48 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 20 Apr 2021 11:10:34 +0200 Subject: [PATCH 50/68] - GOOSE subscriber: restore old behavior when data set array is provided by user --- .../goose_subscriber_example.c | 22 ++++++++++--------- src/goose/goose_receiver.c | 10 +++++---- src/goose/goose_subscriber.c | 4 +++- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/examples/goose_subscriber/goose_subscriber_example.c b/examples/goose_subscriber/goose_subscriber_example.c index 0f044dff..bea2280e 100644 --- a/examples/goose_subscriber/goose_subscriber_example.c +++ b/examples/goose_subscriber/goose_subscriber_example.c @@ -9,6 +9,7 @@ #include "goose_receiver.h" #include "goose_subscriber.h" #include "hal_thread.h" +#include "linked_list.h" #include #include @@ -16,12 +17,13 @@ static int running = 1; -void sigint_handler(int signalId) +static void +sigint_handler(int signalId) { running = 0; } -void +static void gooseListener(GooseSubscriber subscriber, void* parameter) { printf("GOOSE event:\n"); @@ -48,14 +50,14 @@ main(int argc, char** argv) { GooseReceiver receiver = GooseReceiver_create(); - if (argc > 1) { - printf("Set interface id: %s\n", argv[1]); - GooseReceiver_setInterfaceId(receiver, argv[1]); - } - else { - printf("Using interface eth0\n"); - GooseReceiver_setInterfaceId(receiver, "eth0"); - } + if (argc > 1) { + printf("Set interface id: %s\n", argv[1]); + GooseReceiver_setInterfaceId(receiver, argv[1]); + } + else { + printf("Using interface eth0\n"); + GooseReceiver_setInterfaceId(receiver, "eth0"); + } GooseSubscriber subscriber = GooseSubscriber_create("simpleIOGenericIO/LLN0$GO$gcbAnalogValues", NULL); diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index bed369df..36a1229d 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -760,10 +760,12 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) matchingSubscriber->ndsCom = ndsCom; matchingSubscriber->simulation = simulation; - /* when confRev changed replaced old data set */ - if ((matchingSubscriber->dataSetValues != NULL) && (matchingSubscriber->confRev != confRev)) { - MmsValue_delete(matchingSubscriber->dataSetValues); - matchingSubscriber->dataSetValues = NULL; + if (matchingSubscriber->dataSetValuesSelfAllocated) { + /* when confRev changed replaced old data set */ + if ((matchingSubscriber->dataSetValues != NULL) && (matchingSubscriber->confRev != confRev)) { + MmsValue_delete(matchingSubscriber->dataSetValues); + matchingSubscriber->dataSetValues = NULL; + } } matchingSubscriber->confRev = confRev; diff --git a/src/goose/goose_subscriber.c b/src/goose/goose_subscriber.c index 3659aa36..198ba595 100644 --- a/src/goose/goose_subscriber.c +++ b/src/goose/goose_subscriber.c @@ -47,8 +47,10 @@ GooseSubscriber_create(char* goCbRef, MmsValue* dataSetValues) self->timestamp = MmsValue_newUtcTime(0); self->dataSetValues = dataSetValues; - if (dataSetValues != NULL) + if (dataSetValues) self->dataSetValuesSelfAllocated = false; + else + self->dataSetValuesSelfAllocated = true; memset(self->dstMac, 0xFF, 6); self->dstMacSet = false; From 3514e082526347224fa84d75ecc17a0c8a51ca1a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 20 Apr 2021 14:37:11 +0200 Subject: [PATCH 51/68] - Linux Ethernet: fixed uninitialized memory --- hal/ethernet/linux/ethernet_linux.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index 3555c66a..57d4d3b2 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -124,6 +124,7 @@ static int getInterfaceIndex(int sock, const char* deviceName) { struct ifreq ifr; + memset(&ifr, 0, sizeof(struct ifreq)); strncpy(ifr.ifr_name, deviceName, IFNAMSIZ - 1); From 1a09b9548ac5d105e4dfc63a94bd03f13978212d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 22 Apr 2021 10:16:50 +0200 Subject: [PATCH 52/68] - IED server: control handling - fixed problem in test flag handling --- src/iec61850/server/mms_mapping/control.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index adb57611..74d9a49c 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -953,7 +953,6 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* printf("IED_SERVER: control object %s/%s.%s has no ctlVal element!\n", domain->domainName, lnName, name); } - MmsVariableSpecification* originSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "origin", NULL); if (originSpec) { @@ -2178,6 +2177,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari } } + controlObject->testMode = testCondition; + updateControlParameters(controlObject, ctlVal, ctlNum, origin, synchroCheck, interlockCheck); MmsValue* operTm = getOperParameterOperTime(value); @@ -2185,12 +2186,11 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if (operTm != NULL) { controlObject->operateTime = MmsValue_getUtcTimeInMs(operTm); - if (controlObject->operateTime != 0) { + if (controlObject->operateTime > currentTime) { controlObject->timeActivatedOperate = true; controlObject->synchroCheck = synchroCheck; controlObject->interlockCheck = interlockCheck; controlObject->mmsConnection = connection; - controlObject->testMode = testCondition; CheckHandlerResult checkResult = CONTROL_ACCEPTED; if (controlObject->checkHandler != NULL) { /* perform operative tests */ From 8b82cd34e1188c1e8037f6be2e7a8e43b365567f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 22 Apr 2021 10:49:38 +0200 Subject: [PATCH 53/68] - IedConnection: fixed problem - outstanding calls not released in function deleteFileAndSetFileHandler (#322) --- src/iec61850/client/ied_connection.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 3b9b4361..e8eb0923 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -2171,10 +2171,11 @@ deleteFileAndSetFileHandler (uint32_t invokeId, void* parameter, MmsError mmsErr IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); if (call) { - IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler) call->callback; handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(mmsError)); + + iedConnection_releaseOutstandingCall(self, call); } else { if (DEBUG_IED_CLIENT) From 1bb76893a225f37df8b4732b37c1271bb517cb44 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 29 Apr 2021 14:14:29 +0200 Subject: [PATCH 54/68] - fixed compilation problem when compiling without GOOSE support (#325) --- src/iec61850/server/mms_mapping/mms_mapping.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 131ac030..aee8a908 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -763,7 +763,9 @@ MmsMapping_configureSettingGroups(MmsMapping* self) void MmsMapping_useIntegratedGoosePublisher(MmsMapping* self, bool enable) { +#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) self->useIntegratedPublisher = enable; +#endif } void From c8078e3eb124a61ff8936a73516cef163bbcb332 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 12 May 2021 16:46:52 +0200 Subject: [PATCH 55/68] - fixed problem in BSD ethernet layer (#328) - fixed bug in cmake file for BSD --- hal/CMakeLists.txt | 2 +- hal/ethernet/bsd/ethernet_bsd.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt index 2ca0ea94..6bf01739 100644 --- a/hal/CMakeLists.txt +++ b/hal/CMakeLists.txt @@ -65,7 +65,7 @@ endif(WITH_WPCAP) set (libhal_bsd_SRCS ${CMAKE_CURRENT_LIST_DIR}/socket/bsd/socket_bsd.c ${CMAKE_CURRENT_LIST_DIR}/ethernet/bsd/ethernet_bsd.c - ${CMAKE_CURRENT_LIST_DIR}/thread/bsd/thread_macos.c + ${CMAKE_CURRENT_LIST_DIR}/thread/bsd/thread_bsd.c ${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c ${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c ${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c diff --git a/hal/ethernet/bsd/ethernet_bsd.c b/hal/ethernet/bsd/ethernet_bsd.c index 6b51ea44..536de0fb 100644 --- a/hal/ethernet/bsd/ethernet_bsd.c +++ b/hal/ethernet/bsd/ethernet_bsd.c @@ -176,7 +176,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr) /* Get info about all local network interfaces. */ if (getifaddrs(&ifap)) { - printf("Error getting network interfaces list!"); + printf("Error getting network interfaces list!\n"); return; } @@ -197,7 +197,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr) memcpy(addr, LLADDR(link), link->sdl_alen); } else - printf("Could not find the network interface %s!", interfaceId); + printf("Could not find the network interface %s!\n", interfaceId); /* Free network interface info structure. */ freeifaddrs(ifap); @@ -313,7 +313,8 @@ Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress) } /* Activate immediate mode. */ - if (ioctl(self->bpf, BIOCIMMEDIATE, &self->bpfBufferSize) == -1) + optval = 1; + if (ioctl(self->bpf, BIOCIMMEDIATE, &optval) == -1) { printf("Unable to activate immediate mode!\n"); GLOBAL_FREEMEM(self->bpfProgram.bf_insns); From de3aba0cb66a4acfd75be0d82f6fa2bafadf7df7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 19 May 2021 18:38:46 +0200 Subject: [PATCH 56/68] - .NET API: added support for server integrated GOOSE publisher - IED server: fixed bug in GoCBEventHandler --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 262 ++++++++++++++++- dotnet/IEC61850forCSharp/IedServerConfig.cs | 15 + dotnet/dotnet.sln | 139 ++++----- dotnet/server1/server1.csproj | 2 +- dotnet/server_goose_publisher/App.config | 6 + .../Properties/AssemblyInfo.cs | 36 +++ .../ServerExampleWithGoosePublisher.cs | 129 ++++++++ .../server_goose_publisher.csproj | 63 ++++ .../simpleIO_direct_control_goose.cfg | 217 ++++++++++++++ .../simpleIO_direct_control_goose.cid | 275 ++++++++++++++++++ src/iec61850/inc/iec61850_dynamic_model.h | 10 + src/iec61850/inc/iec61850_server.h | 12 +- src/iec61850/server/mms_mapping/mms_mapping.c | 2 +- src/iec61850/server/model/dynamic_model.c | 6 + 14 files changed, 1104 insertions(+), 70 deletions(-) create mode 100644 dotnet/server_goose_publisher/App.config create mode 100644 dotnet/server_goose_publisher/Properties/AssemblyInfo.cs create mode 100644 dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs create mode 100644 dotnet/server_goose_publisher/server_goose_publisher.csproj create mode 100644 dotnet/server_goose_publisher/simpleIO_direct_control_goose.cfg create mode 100644 dotnet/server_goose_publisher/simpleIO_direct_control_goose.cid diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index d005c815..18afac22 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -208,7 +208,7 @@ namespace IEC61850 return null; } - private ModelNode GetModelNodeFromNodeRef(IntPtr nodeRef) + internal ModelNode GetModelNodeFromNodeRef(IntPtr nodeRef) { ModelNode modelNode = null; @@ -1248,12 +1248,28 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr DataSet_create(string name, IntPtr parent); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr DataSet_getName(IntPtr self); + public IntPtr self = IntPtr.Zero; + internal DataSet(IntPtr dataSetPtr) + { + self = dataSetPtr; + } + public DataSet(string name, LogicalNode parent) { self = DataSet_create(name, parent.self); } + + public string Name + { + get + { + return Marshal.PtrToStringAnsi(DataSet_getName(self)); + } + } } public class DataSetEntry @@ -1403,6 +1419,121 @@ namespace IEC61850 } } + public class MmsGooseControlBlock + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsGooseControlBlock_getName(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsGooseControlBlock_getLogicalNode(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsGooseControlBlock_getDataSet(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool MmsGooseControlBlock_getGoEna(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int MmsGooseControlBlock_getMinTime(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int MmsGooseControlBlock_getMaxTime(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool MmsGooseControlBlock_getFixedOffs(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool MmsGooseControlBlock_getNdsCom(IntPtr self); + + private IntPtr self; + private LogicalNode ln = null; + private DataSet dataSet = null; + + internal MmsGooseControlBlock(IntPtr self, IedModel iedModel) + { + this.self = self; + + IntPtr lnPtr = MmsGooseControlBlock_getLogicalNode(self); + + ModelNode lnModelNode = iedModel.GetModelNodeFromNodeRef(lnPtr); + + if (lnModelNode != null && lnModelNode is LogicalNode) + { + this.ln = lnModelNode as LogicalNode; + } + } + + public string Name + { + get + { + return Marshal.PtrToStringAnsi(MmsGooseControlBlock_getName(self)); + } + } + + public LogicalNode LN + { + get + { + return ln; + } + } + + public DataSet DataSet + { + get + { + if (dataSet == null) + dataSet = new DataSet(MmsGooseControlBlock_getDataSet(self)); + + return dataSet; + } + } + + public bool GoEna + { + get + { + return MmsGooseControlBlock_getGoEna(self); + } + } + + public int MinTime + { + get + { + return MmsGooseControlBlock_getMinTime(self); + } + } + + public int MaxTime + { + get + { + return MmsGooseControlBlock_getMaxTime(self); + } + } + + public bool FixedOffs + { + get + { + return MmsGooseControlBlock_getFixedOffs(self); + } + } + + public bool NdsCom + { + get + { + return MmsGooseControlBlock_getNdsCom(self); + } + } + } + /// /// Represents additional context information of the control action that caused the callback invokation /// @@ -1554,6 +1685,8 @@ namespace IEC61850 } } + public delegate void GoCBEventHandler(MmsGooseControlBlock goCB, int cbEvent, object parameter); + public delegate MmsDataAccessError WriteAccessHandler (DataAttribute dataAttr, MmsValue value, ClientConnection connection, object parameter); @@ -1759,6 +1892,27 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServer_setConnectionIndicationHandler(IntPtr self, InternalConnectionHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_enableGoosePublishing(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_disableGoosePublishing(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setGooseInterfaceId(IntPtr self, string interfaceId); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setGooseInterfaceIdEx(IntPtr self, IntPtr ln, string gcbName, string interfaceId); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_useGooseVlanTag(IntPtr self, IntPtr ln, string gcbName, [MarshalAs(UnmanagedType.I1)] bool useVlanTag); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void InternalGoCBEventHandler(IntPtr goCB, int cbEvent, IntPtr parameter); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setGoCBHandler(IntPtr self, InternalGoCBEventHandler handler, IntPtr parameter); + private IntPtr self = IntPtr.Zero; private InternalControlHandler internalControlHandlerRef = null; @@ -2286,6 +2440,112 @@ namespace IEC61850 else return null; } + + /// + /// Enable all GOOSE control blocks. + /// + /// This will set the GoEna attribute of all configured GOOSE control blocks + /// to true. If this method is not called at the startup or reset of the server + /// then configured GOOSE control blocks keep inactive until a MMS client enables + /// them by writing to the GOOSE control block. + public void EnableGoosePublishing() + { + IedServer_enableGoosePublishing(self); + } + + /// + /// Disable all GOOSE control blocks. + /// + /// This will set the GoEna attribute of all configured GOOSE control blocks + /// to false. This will stop GOOSE transmission. + public void DisableGoosePublishing() + { + IedServer_disableGoosePublishing(self); + } + + /// + /// Set the Ethernet interface to be used by GOOSE publishing + /// + /// + /// This function can be used to set the GOOSE interface ID. If not used or set to null the + /// default interface ID from stack_config.h is used.Note the interface ID is operating system + /// specific! + /// + /// the ID of the ethernet interface to be used for GOOSE publishing + public void SetGooseInterfaceId(string interfaceId) + { + IedServer_setGooseInterfaceId(self, interfaceId); + } + + /// + /// Set the Ethernet interface to be used by GOOSE publishing + /// + /// + /// This function can be used to set the GOOSE interface ID for all GCBs (parameter ln = null) or for + /// a specific GCB specified by the logical node instance and the GCB name. + /// + /// ln the logical node that contains the GCB or null to set the ethernet interface ID for all GCBs + /// the name (not object reference!) of the GCB + /// the ID of the ethernet interface to be used for GOOSE publishing + public void SetGooseInterfaceId(LogicalNode ln, string gcbName, string interfaceId) + { + if (ln == null) + IedServer_setGooseInterfaceIdEx(self, IntPtr.Zero, gcbName, interfaceId); + else + IedServer_setGooseInterfaceIdEx(self, ln.self, gcbName, interfaceId); + } + + /// + /// Enable/disable the use of VLAN tags in GOOSE messages + /// + /// + /// This function can be used to enable/disable VLAN tagging for all GCBs (parameter ln = null) or for + /// a specific GCB specified by the logical node instance and the GCB name. + /// + /// the logical node that contains the GCB or null to enable/disable VLAN tagging for all GCBs + /// the name (not object reference!) of the GCB + /// true to enable VLAN tagging, false otherwise + public void UseGooseVlanTag(LogicalNode ln, string gcbName, bool useVlanTag) + { + if (ln == null) + IedServer_useGooseVlanTag(self, IntPtr.Zero, gcbName, useVlanTag); + else + IedServer_useGooseVlanTag(self, ln.self, gcbName, useVlanTag); + } + + private GoCBEventHandler goCbEventHandler = null; + private object goCbEventHandlerParameter = null; + + private InternalGoCBEventHandler internalGoCBEventHandler = null; + + private void InternalGoCBEventHandlerImplementation(IntPtr goCB, int cbEvent, IntPtr parameter) + { + if (goCbEventHandler != null) + { + MmsGooseControlBlock mmsGoCb = new MmsGooseControlBlock(goCB, iedModel); + + goCbEventHandler.Invoke(mmsGoCb, cbEvent, goCbEventHandlerParameter); + } + } + + /// + /// Set a callback handler for GoCB events (enabled/disabled) + /// + /// the callback handler + /// user provided parameter that is passed to the callback handler + public void SetGoCBHandler(GoCBEventHandler handler, object parameter) + { + goCbEventHandler = handler; + goCbEventHandlerParameter = parameter; + + if (internalGoCBEventHandler == null) + { + internalGoCBEventHandler = new InternalGoCBEventHandler(InternalGoCBEventHandlerImplementation); + + IedServer_setGoCBHandler(self, internalGoCBEventHandler, IntPtr.Zero); + } + } + } } diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs index 368dc894..9738f4ad 100644 --- a/dotnet/IEC61850forCSharp/IedServerConfig.cs +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -121,6 +121,9 @@ namespace IEC61850.Server [return: MarshalAs(UnmanagedType.I1)] static extern bool IedServerConfig_isOwnerForRCBEnabled(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_useIntegratedGoosePublisher(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool enable); + internal IntPtr self; public IedServerConfig() @@ -336,6 +339,18 @@ namespace IEC61850.Server } } + /// + /// Enable/disable using the integrated GOOSE publisher for configured GoCBs + /// + /// true when integrated GOOSE publisher is used; otherwise, false. Defaults to true + public bool UseIntegratedGoosePublisher + { + set + { + IedServerConfig_useIntegratedGoosePublisher(self, value); + } + } + /// /// Releases all resource used by the object. /// diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln index c2ab42d8..ba4aec17 100644 --- a/dotnet/dotnet.sln +++ b/dotnet/dotnet.sln @@ -1,7 +1,7 @@ īģŋ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -VisualStudioVersion = 12.0.40629.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.779 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IEC61850.NET", "IEC61850forCSharp\IEC61850.NET.csproj", "{C35D624E-5506-4560-8074-1728F1FA1A4D}" EndProject @@ -48,94 +48,104 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_setting_grou EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_async", "client_example_async\client_example_async.csproj", "{71902641-776A-47D8-9C0E-9ACBBEAC1370}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server_goose_publisher", "server_goose_publisher\server_goose_publisher.csproj", "{C14BB883-86B8-401C-B3D6-B655F55F3298}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.Build.0 = Release|Any CPU - {0DA95476-B149-450B-AC36-01CEECFC1A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0DA95476-B149-450B-AC36-01CEECFC1A43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0DA95476-B149-450B-AC36-01CEECFC1A43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0DA95476-B149-450B-AC36-01CEECFC1A43}.Release|Any CPU.Build.0 = Release|Any CPU - {1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1285372C-2E62-494A-A661-8D5D3873318C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1285372C-2E62-494A-A661-8D5D3873318C}.Release|Any CPU.Build.0 = Release|Any CPU - {14C71267-2F38-460D-AA55-6803EE80AFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14C71267-2F38-460D-AA55-6803EE80AFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14C71267-2F38-460D-AA55-6803EE80AFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14C71267-2F38-460D-AA55-6803EE80AFB4}.Release|Any CPU.Build.0 = Release|Any CPU - {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Release|Any CPU.Build.0 = Release|Any CPU - {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Release|Any CPU.Build.0 = Release|Any CPU + {C35D624E-5506-4560-8074-1728F1FA1A4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C35D624E-5506-4560-8074-1728F1FA1A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C35D624E-5506-4560-8074-1728F1FA1A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C35D624E-5506-4560-8074-1728F1FA1A4D}.Release|Any CPU.Build.0 = Release|Any CPU + {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Release|Any CPU.Build.0 = Release|Any CPU {59B85486-F48D-4978-BD35-8F5C3A8288D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59B85486-F48D-4978-BD35-8F5C3A8288D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {59B85486-F48D-4978-BD35-8F5C3A8288D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {59B85486-F48D-4978-BD35-8F5C3A8288D4}.Release|Any CPU.Build.0 = Release|Any CPU + {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Release|Any CPU.Build.0 = Release|Any CPU + {C351CFA4-E54E-49A1-86CE-69643535541A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C351CFA4-E54E-49A1-86CE-69643535541A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C351CFA4-E54E-49A1-86CE-69643535541A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C351CFA4-E54E-49A1-86CE-69643535541A}.Release|Any CPU.Build.0 = Release|Any CPU + {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Release|Any CPU.Build.0 = Release|Any CPU + {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A226B6D-1D1F-4BFE-B8CC-158116F71270}.Release|Any CPU.Build.0 = Release|Any CPU + {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.Build.0 = Release|Any CPU + {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Release|Any CPU.Build.0 = Release|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Release|Any CPU.Build.0 = Release|Any CPU {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Release|Any CPU.Build.0 = Release|Any CPU - {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Release|Any CPU.Build.0 = Release|Any CPU {71485F99-2976-45E6-B73D-4946E594C15C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {71485F99-2976-45E6-B73D-4946E594C15C}.Debug|Any CPU.Build.0 = Debug|Any CPU {71485F99-2976-45E6-B73D-4946E594C15C}.Release|Any CPU.ActiveCfg = Release|Any CPU {71485F99-2976-45E6-B73D-4946E594C15C}.Release|Any CPU.Build.0 = Release|Any CPU - {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Release|Any CPU.Build.0 = Release|Any CPU - {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Release|Any CPU.Build.0 = Release|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14C71267-2F38-460D-AA55-6803EE80AFB4}.Release|Any CPU.Build.0 = Release|Any CPU {9286D2AB-96ED-4631-AB3C-ED20FF5D6E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9286D2AB-96ED-4631-AB3C-ED20FF5D6E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU {9286D2AB-96ED-4631-AB3C-ED20FF5D6E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU {9286D2AB-96ED-4631-AB3C-ED20FF5D6E6C}.Release|Any CPU.Build.0 = Release|Any CPU - {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Release|Any CPU.Build.0 = Release|Any CPU + {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}.Release|Any CPU.Build.0 = Release|Any CPU + {1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1285372C-2E62-494A-A661-8D5D3873318C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1285372C-2E62-494A-A661-8D5D3873318C}.Release|Any CPU.Build.0 = Release|Any CPU + {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44651D2D-3252-4FD5-8B8B-5552DBE1B499}.Release|Any CPU.Build.0 = Release|Any CPU {B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Debug|Any CPU.Build.0 = Debug|Any CPU {B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Release|Any CPU.ActiveCfg = Release|Any CPU {B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}.Release|Any CPU.Build.0 = Release|Any CPU - {C351CFA4-E54E-49A1-86CE-69643535541A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C351CFA4-E54E-49A1-86CE-69643535541A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C351CFA4-E54E-49A1-86CE-69643535541A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C351CFA4-E54E-49A1-86CE-69643535541A}.Release|Any CPU.Build.0 = Release|Any CPU - {C35D624E-5506-4560-8074-1728F1FA1A4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C35D624E-5506-4560-8074-1728F1FA1A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C35D624E-5506-4560-8074-1728F1FA1A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C35D624E-5506-4560-8074-1728F1FA1A4D}.Release|Any CPU.Build.0 = Release|Any CPU - {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C616A6DF-831E-443C-9310-3F343A6E3D1A}.Release|Any CPU.Build.0 = Release|Any CPU - {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}.Release|Any CPU.Build.0 = Release|Any CPU - {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBDFE530-DBEB-474B-BA54-9AB287DD57B3}.Release|Any CPU.Build.0 = Release|Any CPU + {0DA95476-B149-450B-AC36-01CEECFC1A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DA95476-B149-450B-AC36-01CEECFC1A43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DA95476-B149-450B-AC36-01CEECFC1A43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DA95476-B149-450B-AC36-01CEECFC1A43}.Release|Any CPU.Build.0 = Release|Any CPU + {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71902641-776A-47D8-9C0E-9ACBBEAC1370}.Release|Any CPU.Build.0 = Release|Any CPU + {C14BB883-86B8-401C-B3D6-B655F55F3298}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C14BB883-86B8-401C-B3D6-B655F55F3298}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C14BB883-86B8-401C-B3D6-B655F55F3298}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C14BB883-86B8-401C-B3D6-B655F55F3298}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9F590B86-C80C-4658-83BC-855A87751CEF} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 @@ -149,7 +159,4 @@ Global $2.inheritsScope = text/plain StartupItem = IEC61850forCSharp\IEC61850forCSharp.csproj EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection EndGlobal diff --git a/dotnet/server1/server1.csproj b/dotnet/server1/server1.csproj index d1c45eeb..ab6d9c85 100644 --- a/dotnet/server1/server1.csproj +++ b/dotnet/server1/server1.csproj @@ -48,4 +48,4 @@ PreserveNewest - + \ No newline at end of file diff --git a/dotnet/server_goose_publisher/App.config b/dotnet/server_goose_publisher/App.config new file mode 100644 index 00000000..731f6de6 --- /dev/null +++ b/dotnet/server_goose_publisher/App.config @@ -0,0 +1,6 @@ +īģŋ + + + + + \ No newline at end of file diff --git a/dotnet/server_goose_publisher/Properties/AssemblyInfo.cs b/dotnet/server_goose_publisher/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..181aecad --- /dev/null +++ b/dotnet/server_goose_publisher/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +īģŋusing System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("server_goose_publisher")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("server_goose_publisher")] +[assembly: AssemblyCopyright("Copyright Š 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c14bb883-86b8-401c-b3d6-b655f55f3298")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs b/dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs new file mode 100644 index 00000000..4af291da --- /dev/null +++ b/dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs @@ -0,0 +1,129 @@ +īģŋusing System; +using IEC61850.Server; +using IEC61850.Common; +using System.Threading; + +namespace server_goose_publisher +{ + /// + /// This example shows how to use the server integrated GOOSE publisher from .NET + /// + /// + /// This example requires that the native library (libiec61850) is compiled with GOOSE support. + /// + class ServerExampleWithGoosePublisher + { + public static void Main(string[] args) + { + bool running = true; + + /* run until Ctrl-C is pressed */ + Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e) + { + e.Cancel = true; + running = false; + }; + + IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("simpleIO_direct_control_goose.cfg"); + + if (iedModel == null) + { + Console.WriteLine("No valid data model found!"); + return; + } + + iedModel.SetIedName("TestIED"); + + DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO1"); + + IedServerConfig config = new IedServerConfig(); + config.ReportBufferSize = 100000; + + IedServer iedServer = new IedServer(iedModel, config); + + // Set GOOSE interface (e.g. "eth0" for Linux or "0" for Windows (first interface) + iedServer.SetGooseInterfaceId("0"); + + // The GoCBEventHandler can be used to track GoCB events + iedServer.SetGoCBHandler(delegate (MmsGooseControlBlock goCB, int cbEvent, object parameter) + { + if (cbEvent == 1) + { + Console.WriteLine("GCB " + goCB.LN.GetName() + ":" + goCB.Name + " enabled"); + } + else + { + Console.WriteLine("GCB " + goCB.LN.GetName() + ":" + goCB.Name + " disabled"); + } + }, null); + + iedServer.SetCheckHandler(spcso1, delegate (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool interlockCheck) + { + + Console.WriteLine("Received binary control command:"); + Console.WriteLine(" ctlNum: " + action.GetCtlNum()); + Console.WriteLine(" execution-time: " + action.GetControlTimeAsDataTimeOffset().ToString()); + + return CheckHandlerResult.ACCEPTED; + }, null); + + iedServer.SetControlHandler(spcso1, delegate (ControlAction action, object parameter, MmsValue ctlVal, bool test) + { + bool val = ctlVal.GetBoolean(); + + if (val) + Console.WriteLine("execute binary control command: on"); + else + Console.WriteLine("execute binary control command: off"); + + return ControlHandlerResult.OK; + }, null); + + DataObject spcso2 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO2"); + + iedServer.SetSelectStateChangedHandler(spcso2, delegate (ControlAction action, object parameter, bool isSelected, SelectStateChangedReason reason) + { + DataObject cObj = action.GetControlObject(); + + Console.WriteLine("Control object " + cObj.GetObjectReference() + (isSelected ? " selected" : " unselected") + " reason: " + reason.ToString()); + + }, null); + + iedServer.Start(102); + + // Enable GOOSE publishing for all GOOSE control blocks + iedServer.EnableGoosePublishing(); + + if (iedServer.IsRunning()) + { + Console.WriteLine("Server started"); + + GC.Collect(); + + DataObject ggio1AnIn1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.AnIn1"); + + DataAttribute ggio1AnIn1magF = (DataAttribute)ggio1AnIn1.GetChild("mag.f"); + DataAttribute ggio1AnIn1T = (DataAttribute)ggio1AnIn1.GetChild("t"); + + float floatVal = 1.0f; + + while (running) + { + floatVal += 1f; + iedServer.UpdateTimestampAttributeValue(ggio1AnIn1T, new Timestamp(DateTime.Now)); + iedServer.UpdateFloatAttributeValue(ggio1AnIn1magF, floatVal); + Thread.Sleep(100); + } + + iedServer.Stop(); + Console.WriteLine("Server stopped"); + } + else + { + Console.WriteLine("Failed to start server"); + } + + iedServer.Destroy(); + } + } +} diff --git a/dotnet/server_goose_publisher/server_goose_publisher.csproj b/dotnet/server_goose_publisher/server_goose_publisher.csproj new file mode 100644 index 00000000..5cf3a856 --- /dev/null +++ b/dotnet/server_goose_publisher/server_goose_publisher.csproj @@ -0,0 +1,63 @@ +īģŋ + + + + Debug + AnyCPU + {C14BB883-86B8-401C-B3D6-B655F55F3298} + Exe + server_goose_publisher + server_goose_publisher + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + Always + + + + + {c35d624e-5506-4560-8074-1728f1fa1a4d} + IEC61850.NET + + + + \ No newline at end of file diff --git a/dotnet/server_goose_publisher/simpleIO_direct_control_goose.cfg b/dotnet/server_goose_publisher/simpleIO_direct_control_goose.cfg new file mode 100644 index 00000000..1ebb7159 --- /dev/null +++ b/dotnet/server_goose_publisher/simpleIO_direct_control_goose.cfg @@ -0,0 +1,217 @@ +MODEL(simpleIO){ +LD(GenericIO){ +LN(LLN0){ +DO(Mod 0){ +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=0; +} +DO(Beh 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Health 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(NamPlt 0){ +DA(vendor 0 20 5 0 0); +DA(swRev 0 20 5 0 0); +DA(d 0 20 5 0 0); +DA(configRev 0 20 5 0 0); +DA(ldNs 0 20 11 0 0); +} +DS(Events){ +DE(GGIO1$ST$SPCSO1$stVal); +DE(GGIO1$ST$SPCSO2$stVal); +DE(GGIO1$ST$SPCSO3$stVal); +DE(GGIO1$ST$SPCSO4$stVal); +} +DS(Events2){ +DE(GGIO1$ST$SPCSO1); +DE(GGIO1$ST$SPCSO2); +DE(GGIO1$ST$SPCSO3); +DE(GGIO1$ST$SPCSO4); +} +DS(Events3){ +DE(GGIO1$ST$SPCSO1$stVal); +DE(GGIO1$ST$SPCSO1$q); +DE(GGIO1$ST$SPCSO2$stVal); +DE(GGIO1$ST$SPCSO2$q); +DE(GGIO1$ST$SPCSO3$stVal); +DE(GGIO1$ST$SPCSO3$q); +DE(GGIO1$ST$SPCSO4$stVal); +DE(GGIO1$ST$SPCSO4$q); +} +DS(AnalogValues){ +DE(GGIO1$MX$AnIn1); +DE(GGIO1$MX$AnIn2); +DE(GGIO1$MX$AnIn3); +DE(GGIO1$MX$AnIn4); +} +RC(EventsRCB01 Events 0 Events 1 24 175 50 1000); +RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 24 175 50 1000); +GC(gcbEvents events Events3 2 0 1000 3000 ){ +PA(4 1 4096 010ccd010001); +} +GC(gcbAnalogValues analog AnalogValues 2 0 -1 -1 ){ +PA(4 1 4096 010ccd010001); +} +} +LN(LPHD1){ +DO(PhyNam 0){ +DA(vendor 0 20 5 0 0); +} +DO(PhyHealth 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Proxy 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +} +LN(GGIO1){ +DO(Mod 0){ +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=0; +} +DO(Beh 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Health 0){ +DA(stVal 0 3 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(NamPlt 0){ +DA(vendor 0 20 5 0 0); +DA(swRev 0 20 5 0 0); +DA(d 0 20 5 0 0); +} +DO(AnIn1 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(AnIn2 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(AnIn3 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(AnIn4 0){ +DA(mag 0 27 1 1 0){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 0); +} +DO(SPCSO1 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(ctlModel 0 12 4 0 0)=1; +DA(t 0 22 0 0 0); +} +DO(SPCSO2 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(ctlModel 0 12 4 0 0)=1; +DA(t 0 22 0 0 0); +} +DO(SPCSO3 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(ctlModel 0 12 4 0 0)=1; +DA(t 0 22 0 0 0); +} +DO(SPCSO4 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(Oper 0 27 12 0 0){ +DA(ctlVal 0 0 12 0 0); +DA(origin 0 27 12 0 0){ +DA(orCat 0 12 12 0 0); +DA(orIdent 0 13 12 0 0); +} +DA(ctlNum 0 6 12 0 0); +DA(T 0 22 12 0 0); +DA(Test 0 0 12 0 0); +DA(Check 0 24 12 0 0); +} +DA(ctlModel 0 12 4 0 0)=1; +DA(t 0 22 0 0 0); +} +DO(Ind1 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Ind2 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Ind3 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Ind4 0){ +DA(stVal 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +} +} +} diff --git a/dotnet/server_goose_publisher/simpleIO_direct_control_goose.cid b/dotnet/server_goose_publisher/simpleIO_direct_control_goose.cid new file mode 100644 index 00000000..e25303f7 --- /dev/null +++ b/dotnet/server_goose_publisher/simpleIO_direct_control_goose.cid @@ -0,0 +1,275 @@ + + +
+
+ + + +
+

10.0.0.2

+

255.255.255.0

+

10.0.0.1

+

0001

+

00000001

+

0001

+
+ +
+

1

+

4

+

01-0c-cd-01-00-01

+

1000

+
+ 1000 + 3000 +
+ +
+

1

+

4

+

01-0c-cd-01-00-01

+

1000

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + status-only + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index e28ac506..4138d13c 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -329,6 +329,16 @@ PhyComAddress_create(uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint LIB61850_API DataSet* DataSet_create(const char* name, LogicalNode* parent); +/** + * \brief Get the name of the data set + * + * \param self the instance of the data set + * + * \returns the name of the data set (not the object reference). + */ +LIB61850_API const char* +DataSet_getName(DataSet* self); + /** * \brief returns the number of elements (entries) of the data set * diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 3f503cba..e69f2c0e 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -637,7 +637,7 @@ IedServer_setGooseInterfaceId(IedServer self, const char* interfaceId); * Note: This function has no effect when CONFIG_INCLUDE_GOOSE_SUPPORT is not set. * * \param self the instance of IedServer to operate on. - * \param ln the logical node that contains the GCB or NULL to enable/disable VLAN tagging for all GCBs + * \param ln the logical node that contains the GCB or NULL to set the ethernet interface ID for all GCBs * \param gcbName the name (not object reference!) of the GCB * \param interfaceId the ID of the ethernet interface to be used for GOOSE publishing */ @@ -1584,6 +1584,16 @@ typedef struct sMmsGooseControlBlock* MmsGooseControlBlock; typedef void (*GoCBEventHandler) (MmsGooseControlBlock goCb, int event, void* parameter); +/** + * \brief Set a callback handler for GoCB events (enabled/disabled) + * + * The callback handler is called whenever a GOOSE control block is enabled or disabled. + * It can be used to integrate the external GOOSE publisher with the IEC 61850/MMS server. + * + * \param self the instance of IedServer to operate on. + * \param handler the callback handler + * \param parameter user provided parameter that is passed to the callback handler + */ LIB61850_API void IedServer_setGoCBHandler(IedServer self, GoCBEventHandler handler, void* parameter); diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index aee8a908..b89898fc 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2294,7 +2294,7 @@ writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variable MmsGooseControlBlock_disable(mmsGCB, self); if (self->goCbHandler) - self->goCbHandler(mmsGCB, IEC61850_GOCB_EVENT_ENABLE, self->goCbHandlerParameter); + self->goCbHandler(mmsGCB, IEC61850_GOCB_EVENT_DISABLE, self->goCbHandlerParameter); } return DATA_ACCESS_ERROR_SUCCESS; diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 56ad9658..b8e9816b 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -654,6 +654,12 @@ DataSet_create(const char* name, LogicalNode* parent) return self; } +const char* +DataSet_getName(DataSet* self) +{ + return self->name; +} + int DataSet_getSize(DataSet* self) { From 7185c3b8d4753c292464429e05b8a313ffcb695a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 21 May 2021 11:13:47 +0200 Subject: [PATCH 57/68] - IED server: add support for SMV control blocks ("SMVC") in config file parser --- .../server/model/config_file_parser.c | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index a55cab2e..ee9d2aec 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -130,6 +130,7 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) ModelNode* currentModelNode = NULL; DataSet* currentDataSet = NULL; GSEControlBlock* currentGoCB = NULL; + SVControlBlock* currentSMVCB = NULL; char nameString[130]; char nameString2[130]; @@ -289,6 +290,23 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) indendation = 4; } + else if (StringUtils_startsWith((char*) lineBuffer, "SMVC")) { + uint32_t confRev; + int smpMod; + int smpRate; + int optFlds; + int isUnicast; + + int matchedItems = sscanf((char*) lineBuffer, "SMVC(%s %s %s %u %i %i %i %i)", + nameString, nameString2, nameString3, &confRev, &smpMod, &smpRate, &optFlds, &isUnicast); + + if (matchedItems < 5) goto exit_error; + + currentSMVCB = SVControlBlock_create(nameString, currentLN, nameString2, nameString3, confRev, smpMod, smpRate, optFlds, (bool) isUnicast); + + indendation = 4; + + } #if (CONFIG_IEC61850_SETTING_GROUPS == 1) else if (StringUtils_startsWith((char*) lineBuffer, "SG")) { @@ -442,7 +460,7 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) } } - int lineLength = strlen((char*) lineBuffer); + int lineLength = (int) strlen((char*) lineBuffer); if (lineBuffer[lineLength - 1] == '{') { indendation++; @@ -488,7 +506,7 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) int matchedItems = sscanf((char*) lineBuffer, "PA(%u %u %u %s)", &vlanPrio, &vlanId, &appId, nameString); - if ((matchedItems != 4) || (currentGoCB == NULL)) goto exit_error; + if ((matchedItems != 4) || ((currentGoCB == NULL) && (currentSMVCB == NULL))) goto exit_error; terminateString(nameString, ')'); @@ -502,8 +520,13 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) PhyComAddress_create((uint8_t) vlanPrio, (uint16_t) vlanId, (uint16_t) appId, (uint8_t*) nameString2); - GSEControlBlock_addPhyComAddress(currentGoCB, dstAddress); + if (currentGoCB) { + GSEControlBlock_addPhyComAddress(currentGoCB, dstAddress); + } + if (currentSMVCB) { + SVControlBlock_addPhyComAddress(currentSMVCB, dstAddress); + } } else goto exit_error; @@ -535,7 +558,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) exit_error: if (DEBUG_IED_SERVER) - printf("IED_SERVER: error parsing line %i (indendation level = %i)\n", currentLine, indendation); + printf("IED_SERVER: error parsing line %i (indentation level = %i)\n", currentLine, indendation); + IedModel_destroy(model); return NULL; } From da08489bc329e6db07f3fbba665756da777d12b3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 21 May 2021 12:07:05 +0200 Subject: [PATCH 58/68] - .NET API: Fixed memory release problem in method ModelNode.GetObjectReference --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 8 ++++++-- .../ServerExampleWithGoosePublisher.cs | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 18afac22..79bcd0a5 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -1227,16 +1227,20 @@ namespace IEC61850 /// If set to true the object reference is created without IED name. public string GetObjectReference(bool withoutIedName = false) { - IntPtr objRefPtr = ModelNode_getObjectReferenceEx(self, IntPtr.Zero, withoutIedName); + IntPtr nativeMemory = Marshal.AllocHGlobal(130); + + IntPtr objRefPtr = ModelNode_getObjectReferenceEx(self, nativeMemory, withoutIedName); if (objRefPtr != IntPtr.Zero) { string objRef = Marshal.PtrToStringAnsi(objRefPtr); - Marshal.FreeHGlobal(objRefPtr); + Marshal.FreeHGlobal(nativeMemory); return objRef; } else { + Marshal.FreeHGlobal(nativeMemory); + return null; } } diff --git a/dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs b/dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs index 4af291da..dba14bf1 100644 --- a/dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs +++ b/dotnet/server_goose_publisher/ServerExampleWithGoosePublisher.cs @@ -49,11 +49,11 @@ namespace server_goose_publisher { if (cbEvent == 1) { - Console.WriteLine("GCB " + goCB.LN.GetName() + ":" + goCB.Name + " enabled"); + Console.WriteLine("GCB " + goCB.LN.GetObjectReference() + ":" + goCB.Name + " enabled"); } else { - Console.WriteLine("GCB " + goCB.LN.GetName() + ":" + goCB.Name + " disabled"); + Console.WriteLine("GCB " + goCB.LN.GetObjectReference() + ":" + goCB.Name + " disabled"); } }, null); From ef1895c2bef94ab18c3254852558fc5e1ad7266b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 21 May 2021 18:33:02 +0200 Subject: [PATCH 59/68] - fixed problem in BER integer decoder (problem with GOOSE fixed length message decoding) --- src/mms/asn1/ber_integer.c | 66 +++++++++++-------------------- src/mms/inc_private/ber_integer.h | 6 +-- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/mms/asn1/ber_integer.c b/src/mms/asn1/ber_integer.c index 39bac880..9a7e8030 100644 --- a/src/mms/asn1/ber_integer.c +++ b/src/mms/asn1/ber_integer.c @@ -203,63 +203,45 @@ BerInteger_createFromInt64(int64_t value) return asn1Value; } -int /* 1 - if conversion is possible, 0 - out of range */ +void BerInteger_toInt32(Asn1PrimitiveValue* self, int32_t* nativeValue) { - if (self->size < 5) { - uint8_t* buf = self->octets; - int i; - - if (buf[0] & 0x80) /* sign extension */ - *nativeValue = 0xffffffff; - else - *nativeValue = 0; - - for (i = 0; i < self->size; i++) - *nativeValue = (*nativeValue << 8) | buf[i]; + uint8_t* buf = self->octets; + int i; - return 1; - } + if (buf[0] & 0x80) /* sign extension */ + *nativeValue = 0xffffffff; else - return 0; + *nativeValue = 0; + + for (i = 0; i < self->size; i++) + *nativeValue = (*nativeValue << 8) | buf[i]; } -int /* 1 - if conversion is possible, 0 - out of range */ +void BerInteger_toUint32(Asn1PrimitiveValue* self, uint32_t* nativeValue) { - if (self->size < 6) { - uint8_t* buf = self->octets; - int i; - - *nativeValue = 0; + uint8_t* buf = self->octets; + int i; - for (i = 0; i < self->size; i++) - *nativeValue = (*nativeValue << 8) | buf[i]; + *nativeValue = 0; - return 1; - } - else - return 0; + for (i = 0; i < self->size; i++) + *nativeValue = (*nativeValue << 8) | buf[i]; } -int /* 1 - if conversion is possible, 0 - out of range */ +void BerInteger_toInt64(Asn1PrimitiveValue* self, int64_t* nativeValue) { - if (self->size < 9) { - uint8_t* buf = self->octets; - int i; - - if (buf[0] & 0x80) /* sign extension */ - *nativeValue = 0xffffffffffffffff; - else - *nativeValue = 0; - - for (i = 0; i < self->size; i++) - *nativeValue = (*nativeValue << 8) | buf[i]; + uint8_t* buf = self->octets; + int i; - return 1; - } + if (buf[0] & 0x80) /* sign extension */ + *nativeValue = 0xffffffffffffffff; else - return 0; + *nativeValue = 0; + + for (i = 0; i < self->size; i++) + *nativeValue = (*nativeValue << 8) | buf[i]; } diff --git a/src/mms/inc_private/ber_integer.h b/src/mms/inc_private/ber_integer.h index 0e751bb5..054ad729 100644 --- a/src/mms/inc_private/ber_integer.h +++ b/src/mms/inc_private/ber_integer.h @@ -66,13 +66,13 @@ BerInteger_createInt64(void); LIB61850_INTERNAL int BerInteger_setInt64(Asn1PrimitiveValue* self, int64_t value); -LIB61850_INTERNAL int /* 1 - if conversion is possible, 0 - out of range */ +LIB61850_INTERNAL void BerInteger_toInt32(Asn1PrimitiveValue* self, int32_t* nativeValue); -LIB61850_INTERNAL int /* 1 - if conversion is possible, 0 - out of range */ +LIB61850_INTERNAL void BerInteger_toUint32(Asn1PrimitiveValue* self, uint32_t* nativeValue); -LIB61850_INTERNAL int /* 1 - if conversion is possible, 0 - out of range */ +LIB61850_INTERNAL void BerInteger_toInt64(Asn1PrimitiveValue* self, int64_t* nativeValue); #ifdef __cplusplus From 40b8f992012604b18efca3a38656b20398e7b314 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 29 May 2021 13:14:41 +0200 Subject: [PATCH 60/68] - Ethernet Socket (Windows): fixed bug and added workaround for problem on Windows (most GOOSE/SV messages are not received when waiting with WaitForMultipleObjects - observed with winpcap 4.1.3 and Windows 10 --- hal/ethernet/win32/ethernet_win32.c | 24 ++++++++++++++++++++++-- src/sampled_values/sv_subscriber.c | 6 +++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/hal/ethernet/win32/ethernet_win32.c b/hal/ethernet/win32/ethernet_win32.c index d2a1f2f5..c6269f37 100644 --- a/hal/ethernet/win32/ethernet_win32.c +++ b/hal/ethernet/win32/ethernet_win32.c @@ -33,6 +33,9 @@ #define DEBUG_HAL_ETHERNET 1 #endif +// Set to 1 to workaround WaitForMutlipleObjects problem (returns timeout even when packets are received) +#define ETHERNET_WIN32_DISABLE_ETHERNET_HANDLESET 1 + #if (CONFIG_INCLUDE_ETHERNET_WINDOWS == 1) @@ -137,6 +140,8 @@ EthernetHandleSet_new(void) void EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock) { +#if ETHERNET_WIN32_DISABLE_ETHERNET_HANDLESET == 1 +#else if (self != NULL && sock != NULL) { int i = self->nhandles++; @@ -145,11 +150,14 @@ EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock) self->handles[i] = pcap_getevent(sock->rawSocket); } +#endif } void EthernetHandleSet_removeSocket(EthernetHandleSet self, const EthernetSocket sock) { +#if ETHERNET_WIN32_DISABLE_ETHERNET_HANDLESET == 1 +#else if ((self != NULL) && (sock != NULL)) { HANDLE h = pcap_getevent(sock->rawSocket); @@ -163,21 +171,33 @@ EthernetHandleSet_removeSocket(EthernetHandleSet self, const EthernetSocket sock } } } +#endif } int EthernetHandleSet_waitReady(EthernetHandleSet self, unsigned int timeoutMs) { +#if ETHERNET_WIN32_DISABLE_ETHERNET_HANDLESET == 1 + return 1; +#else int result; if ((self != NULL) && (self->nhandles > 0)) { - result = WaitForMultipleObjects(self->nhandles, self->handles, 0, timeoutMs); + DWORD ret = WaitForMultipleObjects(self->nhandles, self->handles, 0, timeoutMs); + + if (ret == WAIT_TIMEOUT) + result = 0; + else if (ret == WAIT_FAILED) + result = -1; + else + result = (int)ret; } else { result = -1; } return result; +#endif } void @@ -328,7 +348,7 @@ Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress) char* interfaceName = getInterfaceName(interfaceIndex); - if ((pcapSocket = pcap_open_live(interfaceName, 65536, PCAP_OPENFLAG_PROMISCUOUS, 10, errbuf)) == NULL) + if ((pcapSocket = pcap_open_live(interfaceName, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf)) == NULL) { printf("Open ethernet socket failed for device %s\n", interfaceName); free(interfaceName); diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index f37b3fbc..ee0e25a2 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -21,6 +21,7 @@ * See COPYING file for the complete license text. */ +#define __STDC_FORMAT_MACROS 1 #include "stack_config.h" #include @@ -356,9 +357,12 @@ parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length) if (SVSubscriber_ASDU_hasDatSet(&asdu)) printf("SV_SUBSCRIBER: DatSet: %s\n", asdu.datSet); -#ifdef PRIu64 + if (SVSubscriber_ASDU_hasRefrTm(&asdu)) +#ifndef _MSC_VER printf("SV_SUBSCRIBER: RefrTm[ns]: %"PRIu64"\n", SVSubscriber_ASDU_getRefrTmAsNs(&asdu)); +#else + printf("SV_SUBSCRIBER: RefrTm[ns]: %llu\n", SVSubscriber_ASDU_getRefrTmAsNs(&asdu)); #endif if (SVSubscriber_ASDU_hasSmpMod(&asdu)) printf("SV_SUBSCRIBER: SmpMod: %d\n", SVSubscriber_ASDU_getSmpMod(&asdu)); From 78cd0ed39112d59f0d1a5f5e948284e89cd6053c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 5 Jun 2021 16:50:38 +0200 Subject: [PATCH 61/68] - IedServer: fixed bug - in executeControlTask MmsMapping* is used instead of ControlObject* for ControlAction parameter of checkHandler (#333) --- src/iec61850/server/mms_mapping/control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 74d9a49c..17dbf725 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -1399,7 +1399,7 @@ Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) controlObject->errorValue = CONTROL_ERROR_NO_ERROR; controlObject->addCauseValue = ADD_CAUSE_BLOCKED_BY_INTERLOCKING; - checkResult = controlObject->checkHandler((ControlAction) self, + checkResult = controlObject->checkHandler((ControlAction) controlObject, controlObject->checkHandlerParameter, controlObject->ctlVal, controlObject->testMode, controlObject->interlockCheck); } From 8bec67d8d66966575fdfdfa20390aa64e1da88dc Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 8 Jun 2021 21:32:02 +0200 Subject: [PATCH 62/68] Create codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..c657fd28 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ v1.5 ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ v1.5 ] + schedule: + - cron: '38 23 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 161e88a3efdb8e308319d7ce2e2efa3f96c84e64 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 11 Jun 2021 17:10:23 +0200 Subject: [PATCH 63/68] - MMS server: fixed potential crash when client connection closed during file upload (LIB61850-2) - MMS client: fixed problem - doesn't close file when the setFile (obtainFile) service is interrupted e.g. due to connection loss (LIB61850-230) --- src/mms/inc_private/mms_client_internal.h | 11 +++-- src/mms/inc_private/mms_server_internal.h | 3 ++ src/mms/inc_private/mms_server_libinternal.h | 3 ++ .../iso_mms/client/mms_client_connection.c | 22 ++++++++++ src/mms/iso_mms/client/mms_client_files.c | 13 ++++++ src/mms/iso_mms/server/mms_file_service.c | 17 +++++--- src/mms/iso_mms/server/mms_server.c | 43 ++++++++++++++++--- 7 files changed, 96 insertions(+), 16 deletions(-) diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 81f68e91..4dd10ded 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -370,10 +370,13 @@ mmsClient_handleFileReadRequest( LIB61850_INTERNAL void mmsClient_handleFileCloseRequest( -MmsConnection connection, -uint8_t* buffer, int bufPos, int maxBufPos, -uint32_t invokeId, -ByteBuffer* response); + MmsConnection connection, + uint8_t* buffer, int bufPos, int maxBufPos, + uint32_t invokeId, + ByteBuffer* response); + +LIB61850_INTERNAL void +mmsClient_closeOutstandingOpenFiles(MmsConnection connection); LIB61850_INTERNAL MmsOutstandingCall mmsClient_getMatchingObtainFileRequest(MmsConnection self, const char* filename); diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index df06d35f..5e2f458d 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -98,6 +98,9 @@ struct sMmsObtainFileTask { uint64_t nextTimeout; int32_t frmsId; int state; +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore taskLock; +#endif }; #endif /* (MMS_OBTAIN_FILE_SERVICE == 1) */ diff --git a/src/mms/inc_private/mms_server_libinternal.h b/src/mms/inc_private/mms_server_libinternal.h index 2e18f4a2..ef8f95f4 100644 --- a/src/mms/inc_private/mms_server_libinternal.h +++ b/src/mms/inc_private/mms_server_libinternal.h @@ -193,4 +193,7 @@ MmsServer_getConnectionCounter(MmsServer self); LIB61850_INTERNAL void MmsServer_stopListeningThreadless(MmsServer self); +LIB61850_INTERNAL const char* +MmsServer_getFilesystemBasepath(MmsServer self); + #endif /* MMS_SERVER_LIBINTERNAL_H_ */ diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 5644912d..241ddcb4 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1035,6 +1035,25 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (self->connectionLostHandler != NULL) self->connectionLostHandler(self, self->connectionLostHandlerParameter); + + /* Cleanup outstanding calls */ + { + int i; + + for (i = 0; i < OUTSTANDING_CALLS; i++) { + if (self->outstandingCalls[i].isUsed) { + + if (self->outstandingCalls[i].type != MMS_CALL_TYPE_NONE) + handleAsyncResponse(self, NULL, 0, &(self->outstandingCalls[i]), MMS_ERROR_SERVICE_TIMEOUT); + + self->outstandingCalls[i].isUsed = false; + break; + } + } + } + + Semaphore_post(self->outstandingCallsLock); + return true; } @@ -1541,6 +1560,9 @@ MmsConnection_destroy(MmsConnection self) if (self->filestoreBasepath != NULL) GLOBAL_FREEMEM(self->filestoreBasepath); #endif + + /* Close outstanding open files */ + mmsClient_closeOutstandingOpenFiles(self); #endif GLOBAL_FREEMEM(self); diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index d9215abc..307ab534 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -224,6 +224,19 @@ mmsClient_handleFileCloseRequest( mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); } +void +mmsClient_closeOutstandingOpenFiles(MmsConnection connection) +{ + int i; + + for (i = 0; i < CONFIG_MMS_MAX_NUMBER_OF_OPEN_FILES_PER_CONNECTION; i++) { + if (connection->frsms[i].fileHandle != NULL) { + FileSystem_closeFile(connection->frsms[i].fileHandle); + connection->frsms[i].fileHandle = NULL; + } + } +} + #endif /* (MMS_OBTAIN_FILE_SERVICE == 1) */ diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index d569fab6..eb3cfbab 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -431,7 +431,7 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) FileSystem_closeFile(task->fileHandle); task->fileHandle = NULL; } - deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + deleteFile(MmsServer_getFilesystemBasepath(self), task->destinationFilename); } } break; @@ -471,7 +471,7 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) FileSystem_closeFile(task->fileHandle); task->fileHandle = NULL; } - deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + deleteFile(MmsServer_getFilesystemBasepath(self), task->destinationFilename); } break; @@ -510,7 +510,7 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) FileSystem_closeFile(task->fileHandle); task->fileHandle = NULL; - deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + deleteFile(MmsServer_getFilesystemBasepath(self), task->destinationFilename); } break; @@ -536,7 +536,7 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) task->fileHandle = NULL; } - deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + deleteFile(MmsServer_getFilesystemBasepath(self), task->destinationFilename); if (DEBUG_MMS_SERVER) printf("MMS_SERVER: ObtainFile service: failed to open file from client\n"); @@ -565,7 +565,7 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) task->fileHandle = NULL; if (task->destinationFilename[0]) - deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + deleteFile(MmsServer_getFilesystemBasepath(self), task->destinationFilename); } if (DEBUG_MMS_SERVER) @@ -606,7 +606,7 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) task->fileHandle = NULL; if (task->destinationFilename[0]) - deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + deleteFile(MmsServer_getFilesystemBasepath(self), task->destinationFilename); } task->state = MMS_FILE_UPLOAD_STATE_NOT_USED; } @@ -628,8 +628,13 @@ mmsServerConnection_stopFileUploadTasks(MmsServerConnection self) if (server->fileUploadTasks[i].state != 0) { if (server->fileUploadTasks[i].connection == self) { + + Semaphore_wait(server->fileUploadTasks[i].taskLock); + /* stop file upload task */ server->fileUploadTasks[i].state = MMS_FILE_UPLOAD_STATE_INTERRUPTED; + + Semaphore_post(server->fileUploadTasks[i].taskLock); } } diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 0908babb..cfed8824 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -296,6 +296,8 @@ MmsServer_getObtainFileTask(MmsServer self) if (self->fileUploadTasks[i].state == 0) { self->fileUploadTasks[i].state = 1; + if (self->fileUploadTasks[i].taskLock == NULL) + self->fileUploadTasks[i].taskLock = Semaphore_create(1); return &(self->fileUploadTasks[i]); } @@ -409,7 +411,7 @@ MmsServer_destroy(MmsServer self) Map_deleteDeep(self->openConnections, false, closeConnection); Map_deleteDeep(self->valueCaches, false, (void (*) (void*)) deleteSingleCache); - #if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) if (self->openConnectionsLock) Semaphore_destroy(self->openConnectionsLock); @@ -418,15 +420,23 @@ MmsServer_destroy(MmsServer self) if (self->transmitBufferMutex) Semaphore_destroy(self->transmitBufferMutex); - #endif +#endif if (self->transmitBuffer) ByteBuffer_destroy(self->transmitBuffer); - #if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) +#if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) if (self->filestoreBasepath != NULL) GLOBAL_FREEMEM(self->filestoreBasepath); - #endif +#endif + +#if (MMS_OBTAIN_FILE_SERVICE == 1) + int i; + for (i = 0; i < CONFIG_MMS_SERVER_MAX_GET_FILE_TASKS; i++) { + if (self->fileUploadTasks[i].taskLock) + Semaphore_destroy(self->fileUploadTasks[i].taskLock); + } +#endif GLOBAL_FREEMEM(self); } @@ -699,8 +709,15 @@ MmsServer_handleBackgroundTasks(MmsServer self) int i; for (i = 0; i < CONFIG_MMS_SERVER_MAX_GET_FILE_TASKS; i++) { - if (self->fileUploadTasks[i].state != 0) - mmsServer_fileUploadTask(self, &(self->fileUploadTasks[i])); + if (self->fileUploadTasks[i].state != 0) { + + Semaphore_wait(self->fileUploadTasks[i].taskLock); + + if (self->fileUploadTasks[i].state != 0) + mmsServer_fileUploadTask(self, &(self->fileUploadTasks[i])); + + Semaphore_post(self->fileUploadTasks[i].taskLock); + } } #endif /* (MMS_OBTAIN_FILE_SERVICE == 1) */ @@ -750,3 +767,17 @@ MmsServer_stopListeningThreadless(MmsServer self) } } +const char* +MmsServer_getFilesystemBasepath(MmsServer self) +{ +#if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) + if (self->filestoreBasepath != NULL) + return self->filestoreBasepath; + else + return CONFIG_VIRTUAL_FILESTORE_BASEPATH; +#else + return CONFIG_VIRTUAL_FILESTORE_BASEPATH; +#endif +} + + From 982b1097fcb6999f8e52f6f043fb359830aba475 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 22 Jun 2021 17:17:07 +0200 Subject: [PATCH 64/68] - .NET API: fixed bug - server write access handler causes "CallbackOnCollectedDelegate" exception (LIB61850-236) --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 79bcd0a5..4cdfbf11 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2015,14 +2015,16 @@ namespace IEC61850 private struct WriteAccessHandlerInfo { public WriteAccessHandler handler; + public InternalWriteAccessHandler internalHandler; public object parameter; public DataAttribute dataAttribute; - public WriteAccessHandlerInfo (WriteAccessHandler h, object p, DataAttribute da) + public WriteAccessHandlerInfo (WriteAccessHandler h, object p, DataAttribute da, InternalWriteAccessHandler internalHandler) { handler = h; parameter = p; dataAttribute = da; + this.internalHandler = internalHandler; } } @@ -2309,20 +2311,22 @@ namespace IEC61850 /// a user provided parameter that is passed to the WriteAccessHandler when called. public void HandleWriteAccess (DataAttribute dataAttr, WriteAccessHandler handler, object parameter) { - writeAccessHandlers.Add (dataAttr.self, new WriteAccessHandlerInfo(handler, parameter, dataAttr)); + InternalWriteAccessHandler internalHandler = new InternalWriteAccessHandler(WriteAccessHandlerImpl); + + writeAccessHandlers.Add (dataAttr.self, new WriteAccessHandlerInfo(handler, parameter, dataAttr, internalHandler)); - IedServer_handleWriteAccess (self, dataAttr.self, WriteAccessHandlerImpl, IntPtr.Zero); + IedServer_handleWriteAccess (self, dataAttr.self, internalHandler, IntPtr.Zero); } - private void AddHandlerInfoForDataAttributeRecursive(DataAttribute da, WriteAccessHandler handler, object parameter) + private void AddHandlerInfoForDataAttributeRecursive(DataAttribute da, WriteAccessHandler handler, object parameter, InternalWriteAccessHandler internalHandler) { - writeAccessHandlers.Add(da.self, new WriteAccessHandlerInfo(handler, parameter, da)); + writeAccessHandlers.Add(da.self, new WriteAccessHandlerInfo(handler, parameter, da, internalHandler)); foreach (ModelNode child in da.GetChildren()) { if (child is DataAttribute) { - AddHandlerInfoForDataAttributeRecursive(child as DataAttribute, handler, parameter); + AddHandlerInfoForDataAttributeRecursive(child as DataAttribute, handler, parameter, internalHandler); } } } @@ -2345,11 +2349,18 @@ namespace IEC61850 /// a user provided parameter that is passed to the WriteAccessHandler when called. public void HandleWriteAccessForComplexAttribute(DataAttribute dataAttr, WriteAccessHandler handler, object parameter) { - AddHandlerInfoForDataAttributeRecursive(dataAttr, handler, parameter); + InternalWriteAccessHandler internalHandler = new InternalWriteAccessHandler(WriteAccessHandlerImpl); - IedServer_handleWriteAccessForComplexAttribute(self, dataAttr.self, WriteAccessHandlerImpl, IntPtr.Zero); + AddHandlerInfoForDataAttributeRecursive(dataAttr, handler, parameter, internalHandler); + + IedServer_handleWriteAccessForComplexAttribute(self, dataAttr.self, internalHandler, IntPtr.Zero); } + /// + /// Set the defualt write access policy for a specific FC. The default policy is applied when no handler is installed for a data attribute + /// + /// The functional constraint (FC) + /// The new default access policy public void SetWriteAccessPolicy(FunctionalConstraint fc, AccessPolicy policy) { IedServer_setWriteAccessPolicy (self, fc, policy); From 4ffed8de119117e598c9c2121d9c0c331106c4a6 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 25 Jun 2021 12:55:50 +0200 Subject: [PATCH 65/68] - IED server: fixed crash when IEDName+LDInst is too long --- src/iec61850/server/impl/ied_server.c | 68 +++++++++++-------- src/iec61850/server/mms_mapping/mms_mapping.c | 44 ++++++++---- 2 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index c55c115c..b1dcdd17 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -250,6 +250,7 @@ installDefaultValuesForDataAttribute(IedServer self, DataAttribute* dataAttribut char domainName[65]; strncpy(domainName, self->model->name, 64); + domainName[64] = 0; MmsMapping_getMmsDomainFromObjectReference(objectReference, domainName + strlen(domainName)); @@ -451,7 +452,6 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio IedServer self = (IedServer) GLOBAL_CALLOC(1, sizeof(struct sIedServer)); if (self) { - self->model = dataModel; self->running = false; @@ -510,56 +510,63 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->mmsMapping = MmsMapping_create(dataModel, self); - self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping); + if (self->mmsMapping) { + + self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping); - self->mmsServer = MmsServer_create(self->mmsDevice, tlsConfiguration); + self->mmsServer = MmsServer_create(self->mmsDevice, tlsConfiguration); #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) - if (serverConfiguration) { - MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); - MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); - MmsServer_setMaxAssociationSpecificDataSets(self->mmsServer, serverConfiguration->maxAssociationSpecificDataSets); - MmsServer_setMaxDomainSpecificDataSets(self->mmsServer, serverConfiguration->maxDomainSpecificDataSets); - MmsServer_setMaxDataSetEntries(self->mmsServer, serverConfiguration->maxDataSetEntries); - MmsServer_enableJournalService(self->mmsServer, serverConfiguration->enableLogService); - MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); - MmsServer_setMaxConnections(self->mmsServer, serverConfiguration->maxMmsConnections); - } + if (serverConfiguration) { + MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); + MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); + MmsServer_setMaxAssociationSpecificDataSets(self->mmsServer, serverConfiguration->maxAssociationSpecificDataSets); + MmsServer_setMaxDomainSpecificDataSets(self->mmsServer, serverConfiguration->maxDomainSpecificDataSets); + MmsServer_setMaxDataSetEntries(self->mmsServer, serverConfiguration->maxDataSetEntries); + MmsServer_enableJournalService(self->mmsServer, serverConfiguration->enableLogService); + MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); + MmsServer_setMaxConnections(self->mmsServer, serverConfiguration->maxMmsConnections); + } #endif - MmsMapping_setMmsServer(self->mmsMapping, self->mmsServer); + MmsMapping_setMmsServer(self->mmsMapping, self->mmsServer); - MmsMapping_installHandlers(self->mmsMapping); + MmsMapping_installHandlers(self->mmsMapping); - createMmsServerCache(self); + createMmsServerCache(self); - dataModel->initializer(); + dataModel->initializer(); - installDefaultValuesInCache(self); /* This will also connect cached MmsValues to DataAttributes */ + installDefaultValuesInCache(self); /* This will also connect cached MmsValues to DataAttributes */ - updateDataSetsWithCachedValues(self); + updateDataSetsWithCachedValues(self); - self->clientConnections = LinkedList_create(); + self->clientConnections = LinkedList_create(); - /* default write access policy allows access to SP, SE and SV FCDAs but denies access to DC and CF FCDAs */ - self->writeAccessPolicies = ALLOW_WRITE_ACCESS_SP | ALLOW_WRITE_ACCESS_SV | ALLOW_WRITE_ACCESS_SE; + /* default write access policy allows access to SP, SE and SV FCDAs but denies access to DC and CF FCDAs */ + self->writeAccessPolicies = ALLOW_WRITE_ACCESS_SP | ALLOW_WRITE_ACCESS_SV | ALLOW_WRITE_ACCESS_SE; - MmsMapping_initializeControlObjects(self->mmsMapping); + MmsMapping_initializeControlObjects(self->mmsMapping); #if (CONFIG_IEC61850_REPORT_SERVICE == 1) - Reporting_activateBufferedReports(self->mmsMapping); + Reporting_activateBufferedReports(self->mmsMapping); #endif #if (CONFIG_IEC61850_SETTING_GROUPS == 1) - MmsMapping_configureSettingGroups(self->mmsMapping); + MmsMapping_configureSettingGroups(self->mmsMapping); #endif #if (CONFIG_INCLUDE_GOOSE_SUPPORT) - if (serverConfiguration) { - MmsMapping_useIntegratedGoosePublisher(self->mmsMapping, serverConfiguration->useIntegratedGoosePublisher); - } - + if (serverConfiguration) { + MmsMapping_useIntegratedGoosePublisher(self->mmsMapping, serverConfiguration->useIntegratedGoosePublisher); + } #endif + + } + else { + IedServer_destroy(self); + self = NULL; + } } return self; @@ -602,7 +609,8 @@ IedServer_destroy(IedServer self) if (self->localIpAddress != NULL) GLOBAL_FREEMEM(self->localIpAddress); - MmsMapping_destroy(self->mmsMapping); + if (self->mmsMapping) + MmsMapping_destroy(self->mmsMapping); LinkedList_destroyDeep(self->clientConnections, (LinkedListValueDeleteFunction) private_ClientConnection_destroy); diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index b89898fc..384b8616 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1712,8 +1712,6 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, } #endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ - - if (LogicalNode_hasFCData(logicalNode, IEC61850_FC_SV)) { namedVariable->typeSpec.structure.elements[currentComponent] = createFCNamedVariable(logicalNode, IEC61850_FC_SV); @@ -1765,7 +1763,6 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ - currentComponent++; } @@ -1793,12 +1790,19 @@ createMmsDomainFromIedDevice(MmsMapping* self, LogicalDevice* logicalDevice) char domainName[65]; int modelNameLength = strlen(self->model->name); + int ldInstName = strlen(logicalDevice->name); + + if ((modelNameLength + ldInstName) > 64) { + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Resulting domain name (IEDName+LDInst) too long (%i)\n", modelNameLength + ldInstName); - if (modelNameLength > 64) goto exit_function; + } strncpy(domainName, self->model->name, 64); strncat(domainName, logicalDevice->name, 64 - modelNameLength); + domainName[64] = 0; domain = MmsDomain_create(domainName); @@ -1868,22 +1872,29 @@ exit_function: return domain; } -static void +static bool createMmsDataModel(MmsMapping* self, int iedDeviceCount, MmsDevice* mmsDevice, IedModel* iedModel) { - mmsDevice->domains = (MmsDomain**) GLOBAL_MALLOC((iedDeviceCount) * sizeof(MmsDomain*)); + mmsDevice->domains = (MmsDomain**) GLOBAL_CALLOC(1, (iedDeviceCount) * sizeof(MmsDomain*)); mmsDevice->domainCount = iedDeviceCount; LogicalDevice* logicalDevice = iedModel->firstChild; int i = 0; while (logicalDevice != NULL) { - mmsDevice->domains[i] = createMmsDomainFromIedDevice(self, - logicalDevice); + mmsDevice->domains[i] = createMmsDomainFromIedDevice(self, logicalDevice); + + if (mmsDevice->domains[i] == NULL) { + mmsDevice->domainCount = i; + return false; + } + i++; logicalDevice = (LogicalDevice*) logicalDevice->sibling; } + + return true; } static void @@ -1962,9 +1973,13 @@ createMmsModelFromIedModel(MmsMapping* self, IedModel* iedModel) int iedDeviceCount = IedModel_getLogicalDeviceCount(iedModel); - createMmsDataModel(self, iedDeviceCount, mmsDevice, iedModel); - - createDataSets(mmsDevice, iedModel); + if (createMmsDataModel(self, iedDeviceCount, mmsDevice, iedModel)) { + createDataSets(mmsDevice, iedModel); + } + else { + MmsDevice_destroy(mmsDevice); + mmsDevice = NULL; + } } return mmsDevice; @@ -2015,6 +2030,11 @@ MmsMapping_create(IedModel* model, IedServer iedServer) /* create data model specification */ self->mmsDevice = createMmsModelFromIedModel(self, model); + if (self->mmsDevice == false) { + MmsMapping_destroy(self); + self = NULL; + } + return self; } @@ -2029,7 +2049,7 @@ MmsMapping_destroy(MmsMapping* self) } #endif - if (self->mmsDevice != NULL) + if (self->mmsDevice) MmsDevice_destroy(self->mmsDevice); #if (CONFIG_IEC61850_REPORT_SERVICE == 1) From ca58c703421a9dec014522d85ea763764e8fa21d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 25 Jun 2021 13:02:05 +0200 Subject: [PATCH 66/68] - MMS server: fixed data race bug in transmitBuffer handling (#338) --- src/mms/iso_mms/server/mms_server.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index cfed8824..c387cb8b 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -279,10 +279,11 @@ MmsServer_reserveTransmitBuffer(MmsServer self) void MmsServer_releaseTransmitBuffer(MmsServer self) { + self->transmitBuffer->size = 0; + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->transmitBufferMutex); #endif - self->transmitBuffer->size = 0; } #if (MMS_OBTAIN_FILE_SERVICE == 1) From 2ace50b712bdfc00cd8145772897fa8b55bd2766 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 19 Jul 2021 18:44:15 +0200 Subject: [PATCH 67/68] - .NET API: Fixed problem with AccessViolationException in GooseControlBlock.GetDstAddress --- dotnet/IEC61850forCSharp/GooseControlBlock.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/dotnet/IEC61850forCSharp/GooseControlBlock.cs b/dotnet/IEC61850forCSharp/GooseControlBlock.cs index e0bfbe1c..1e91d6fe 100644 --- a/dotnet/IEC61850forCSharp/GooseControlBlock.cs +++ b/dotnet/IEC61850forCSharp/GooseControlBlock.cs @@ -31,7 +31,7 @@ namespace IEC61850 namespace Client { - public class GooseControlBlock { + public class GooseControlBlock : IDisposable { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ClientGooseControlBlock_create (string dataAttributeReference); @@ -84,6 +84,18 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern PhyComAddress ClientGooseControlBlock_getDstAddress (IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientGooseControlBlock_getDstAddress_addr(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern byte ClientGooseControlBlock_getDstAddress_priority(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt16 ClientGooseControlBlock_getDstAddress_vid(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt16 ClientGooseControlBlock_getDstAddress_appid(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void ClientGooseControlBlock_setDstAddress (IntPtr self, PhyComAddress value); @@ -232,7 +244,23 @@ namespace IEC61850 public PhyComAddress GetDstAddress() { - return ClientGooseControlBlock_getDstAddress (self); + PhyComAddress addr = new PhyComAddress(); + + IntPtr value = ClientGooseControlBlock_getDstAddress_addr(self); + + MmsValue mmsValue = new MmsValue(value); + + byte[] dstMacAddr = mmsValue.getOctetString(); + + dstMacAddr.CopyTo(addr.dstAddress, 0); + + addr.dstAddress = dstMacAddr; + + addr.appId = ClientGooseControlBlock_getDstAddress_appid(self); + addr.vlanId = ClientGooseControlBlock_getDstAddress_vid(self); + addr.vlanPriority = ClientGooseControlBlock_getDstAddress_priority(self); + + return addr; } public void SetDstAddress(PhyComAddress value) From bb64d9d8fef7b0ff687a26bc9ff332318b19e58a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 19 Jul 2021 18:59:40 +0200 Subject: [PATCH 68/68] - .NET API: GooseSubscriber - added GetGoId, GetGoCbRef, GetFataSet methods - .NET API: GooseReceiver - store references to all added GooseSubscribers to prevent garbage collection - update documentation comments for GooseSubscriber API --- dotnet/IEC61850forCSharp/GooseSubscriber.cs | 62 +++++++++++++++++-- dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 2 +- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 6 +- dotnet/goose_subscriber/Program.cs | 22 +++++-- src/goose/goose_subscriber.h | 47 +++++++++++++- 5 files changed, 123 insertions(+), 16 deletions(-) diff --git a/dotnet/IEC61850forCSharp/GooseSubscriber.cs b/dotnet/IEC61850forCSharp/GooseSubscriber.cs index 9b2c6e69..29b64c01 100644 --- a/dotnet/IEC61850forCSharp/GooseSubscriber.cs +++ b/dotnet/IEC61850forCSharp/GooseSubscriber.cs @@ -22,6 +22,7 @@ */ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using IEC61850.Common; @@ -69,6 +70,8 @@ namespace IEC61850 private bool isDisposed = false; + private List subscribers = new List(); + public GooseReceiver() { self = GooseReceiver_create (); @@ -79,14 +82,29 @@ namespace IEC61850 GooseReceiver_setInterfaceId (self, interfaceId); } + /// + /// Add the subscriber to be handled by this receiver instance + /// + /// A GooseSubscriber can only be added to one GooseReceiver! + /// public void AddSubscriber(GooseSubscriber subscriber) { - GooseReceiver_addSubscriber (self, subscriber.self); + if (subscriber.attachedToReceiver == false) + { + subscriber.attachedToReceiver = true; + GooseReceiver_addSubscriber(self, subscriber.self); + subscribers.Add(subscriber); + } } public void RemoveSubscriber(GooseSubscriber subscriber) { - GooseReceiver_removeSubscriber (self, subscriber.self); + if (subscriber.attachedToReceiver) + { + GooseReceiver_removeSubscriber(self, subscriber.self); + subscribers.Remove(subscriber); + subscriber.attachedToReceiver = false; + } } public void Start() @@ -175,10 +193,22 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void GooseSubscriber_setListener (IntPtr self, InternalGooseListener listener, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GooseSubscriber_getGoId(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GooseSubscriber_getGoCbRef(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GooseSubscriber_getDataSet(IntPtr self); + internal IntPtr self; private bool isDisposed = false; + // don't call native destructor when attached to a receiver + internal bool attachedToReceiver = false; + private GooseListener listener = null; private object listenerParameter = null; @@ -214,7 +244,6 @@ namespace IEC61850 return GooseSubscriber_isValid (self); } - public void SetListener(GooseListener listener, object parameter) { this.listener = listener; @@ -227,6 +256,21 @@ namespace IEC61850 } } + public string GetGoId() + { + return Marshal.PtrToStringAnsi(GooseSubscriber_getGoId(self)); + } + + public string GetGoCbRef() + { + return Marshal.PtrToStringAnsi(GooseSubscriber_getGoCbRef(self)); + } + + public string GetDataSet() + { + return Marshal.PtrToStringAnsi(GooseSubscriber_getDataSet(self)); + } + public UInt32 GetStNum() { return GooseSubscriber_getStNum (self); @@ -300,12 +344,20 @@ namespace IEC61850 { if (isDisposed == false) { isDisposed = true; - GooseSubscriber_destroy (self); + + if (attachedToReceiver == false) + GooseSubscriber_destroy (self); + self = IntPtr.Zero; } } - } + ~GooseSubscriber() + { + Dispose(); + } + + } } diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 29315143..58bc9cbd 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -886,7 +886,7 @@ namespace IEC61850 /// Creates a new SampledValuesControlBlock instance. ///
/// The new GoCB instance - /// The object reference of the GoCB + /// The object reference of the GoCB (e.g. "simpleIOGenericIO/LLN0.gcbAnalogValues") public GooseControlBlock GetGooseControlBlock(string gocbObjectReference) { return new GooseControlBlock(gocbObjectReference, connection); diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 574d6c82..df4103ed 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -86,9 +86,9 @@ namespace IEC61850 public UInt16 vlanId; public UInt16 appId; - [MarshalAs(UnmanagedType.ByValArray, SizeConst=6)] - public byte[] dstAddress = new byte[6]; - } + [MarshalAs(UnmanagedType.ByValArray, SizeConst=6)] + public byte[] dstAddress = new byte[6]; + } /// /// MMS data access error for MmsValue type MMS_DATA_ACCESS_ERROR diff --git a/dotnet/goose_subscriber/Program.cs b/dotnet/goose_subscriber/Program.cs index df84f032..bfcdaff6 100644 --- a/dotnet/goose_subscriber/Program.cs +++ b/dotnet/goose_subscriber/Program.cs @@ -4,6 +4,9 @@ using IEC61850.GOOSE.Subscriber; using System.Threading; using IEC61850.Common; +/// +/// This example is intended to be +/// namespace goose_subscriber { class MainClass @@ -11,9 +14,10 @@ namespace goose_subscriber private static void gooseListener (GooseSubscriber subscriber, object parameter) { Console.WriteLine ("Received GOOSE message:\n-------------------------"); - + Console.WriteLine (" GoID: " + subscriber.GetGoId()); + Console.WriteLine (" GoCbRef: " + subscriber.GetGoCbRef()); + Console.WriteLine (" DatSet: " + subscriber.GetDataSet()); Console.WriteLine (" stNum: " + subscriber.GetStNum ()); - Console.WriteLine (" sqNum: " + subscriber.GetSqNum ()); @@ -33,17 +37,27 @@ namespace goose_subscriber GooseReceiver receiver = new GooseReceiver (); receiver.SetInterfaceId ("eth0"); + //receiver.SetInterfaceId("0"); // on windows use the interface index starting with 0 GooseSubscriber subscriber = new GooseSubscriber ("simpleIOGenericIO/LLN0$GO$gcbAnalogValues"); - subscriber.SetAppId(1000); - + // APP-ID has to match the APP-ID of the publisher + subscriber.SetAppId(4096); subscriber.SetListener (gooseListener, null); receiver.AddSubscriber (subscriber); + GooseSubscriber subscriber2 = new GooseSubscriber("simpleIOGenericIO/LLN0$GO$gcbEvents"); + + subscriber2.SetAppId(4096); + subscriber2.SetListener(gooseListener, null); + + receiver.AddSubscriber(subscriber2); + receiver.Start (); + subscriber = null; + if (receiver.IsRunning ()) { bool running = true; diff --git a/src/goose/goose_subscriber.h b/src/goose/goose_subscriber.h index f88432dd..e58f4f96 100644 --- a/src/goose/goose_subscriber.h +++ b/src/goose/goose_subscriber.h @@ -1,7 +1,7 @@ /* * goose_subscriber.h * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2021 Michael Zillgith * * This file is part of libIEC61850. * @@ -82,12 +82,27 @@ typedef void (*GooseListener)(GooseSubscriber subscriber, void* parameter); LIB61850_API GooseSubscriber GooseSubscriber_create(char* goCbRef, MmsValue* dataSetValues); +/** + * \brief Get the GoId value of the received GOOSE message + * + * \param self GooseSubscriber instance to operate on. + */ LIB61850_API char* GooseSubscriber_getGoId(GooseSubscriber self); +/** + * \brief Get the GOOSE Control Block reference value of the received GOOSE message + * + * \param self GooseSubscriber instance to operate on. + */ LIB61850_API char* GooseSubscriber_getGoCbRef(GooseSubscriber self); +/** + * \brief Get the DatSet value of the received GOOSE message + * + * \param self GooseSubscriber instance to operate on. + */ LIB61850_API char* GooseSubscriber_getDataSet(GooseSubscriber self); @@ -133,6 +148,14 @@ GooseSubscriber_isValid(GooseSubscriber self); LIB61850_API GooseParseError GooseSubscriber_getParseError(GooseSubscriber self); +/** + * \brief Destroy the GooseSubscriber instance + * + * Do not call this function when the GooseSubscriber instance was added to a GooseReceiver. + * The GooseReceiver will call the destructor when \ref GooseReceiver_destroy is called! + * + * \param self GooseSubscriber instance to operate on. + */ LIB61850_API void GooseSubscriber_destroy(GooseSubscriber self); @@ -146,14 +169,32 @@ GooseSubscriber_destroy(GooseSubscriber self); LIB61850_API void GooseSubscriber_setListener(GooseSubscriber self, GooseListener listener, void* parameter); +/** + * \brief Get the APPID value of the received GOOSE message + * + * \param self GooseSubscriber instance to operate on. + */ LIB61850_API int32_t GooseSubscriber_getAppId(GooseSubscriber self); +/** + * \brief Get the source MAC address of the received GOOSE message + * + * \param self GooseSubscriber instance to operate on. + * \param buffer buffer to store the MAC address (at least 6 byte) + */ LIB61850_API void -GooseSubscriber_getSrcMac(GooseSubscriber self, uint8_t *buffer); +GooseSubscriber_getSrcMac(GooseSubscriber self, uint8_t* buffer); +/** + * \brief Get the destination MAC address of the received GOOSE message + * + * \param self GooseSubscriber instance to operate on. + * \param buffer buffer to store the MAC address (at least 6 byte) + */ LIB61850_API void -GooseSubscriber_getDstMac(GooseSubscriber self, uint8_t *buffer); +GooseSubscriber_getDstMac(GooseSubscriber self, uint8_t* buffer); + /** * \brief return the state number (stNum) of the last received GOOSE message. *