From ac3a07c5a454a17e5f5b5c2e9ca519d1632d58f3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 18:00:58 +0100 Subject: [PATCH 001/123] - fixed bug in win32 ethernet driver --- src/hal/ethernet/linux/ethernet_linux.c | 6 +++++- src/hal/ethernet/win32/ethernet_win32.c | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/hal/ethernet/linux/ethernet_linux.c b/src/hal/ethernet/linux/ethernet_linux.c index 6715c526d..538a5e0ee 100644 --- a/src/hal/ethernet/linux/ethernet_linux.c +++ b/src/hal/ethernet/linux/ethernet_linux.c @@ -63,7 +63,9 @@ void EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock) { if (self != NULL && sock != NULL) { + int i = self->nhandles++; + self->handles = realloc(self->handles, self->nhandles * sizeof(struct pollfd)); self->handles[i].fd = sock->rawSocket; @@ -75,7 +77,9 @@ void EthernetHandleSet_removeSocket(EthernetHandleSet self, const EthernetSocket sock) { if ((self != NULL) && (sock != NULL)) { - unsigned i; + + int i; + for (i = 0; i < self->nhandles; i++) { if (self->handles[i].fd == sock->rawSocket) { memmove(&self->handles[i], &self->handles[i+1], sizeof(struct pollfd) * (self->nhandles - i - 1)); diff --git a/src/hal/ethernet/win32/ethernet_win32.c b/src/hal/ethernet/win32/ethernet_win32.c index df76eaabf..4b4f10ca9 100644 --- a/src/hal/ethernet/win32/ethernet_win32.c +++ b/src/hal/ethernet/win32/ethernet_win32.c @@ -138,7 +138,9 @@ void EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock) { if (self != NULL && sock != NULL) { + int i = self->nhandles++; + self->handles = (HANDLE *) realloc(self->handles, self->nhandles * sizeof(HANDLE)); self->handles[i] = pcap_getevent(sock->rawSocket); @@ -149,9 +151,10 @@ void EthernetHandleSet_removeSocket(EthernetHandleSet self, const EthernetSocket sock) { if ((self != NULL) && (sock != NULL)) { - HANDLE h = pcap_getevent(socket->rawSocket); + HANDLE h = pcap_getevent(sock->rawSocket); + + int i; - unsigned i; for (i = 0; i < self->nhandles; i++) { if (self->handles[i] == h) { memmove(&self->handles[i], &self->handles[i+1], sizeof(HANDLE) * (self->nhandles - i - 1)); From 81e18261bc70ec6709591a4d839494dd595877d4 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 20:07:01 +0100 Subject: [PATCH 002/123] - .NET API: Added support for SVCB handling --- dotnet/IEC61850forCSharp/IEC61850.NET.csproj | 1 + dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 23 +++++++++++++++++++ dotnet/IEC61850forCSharp/Reporting.cs | 13 +++++++++++ src/iec61850/client/client_sv_control.c | 8 +++---- src/iec61850/inc/iec61850_client.h | 10 ++++++-- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj index cbdff48b8..550a89b94 100644 --- a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj +++ b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj @@ -46,6 +46,7 @@ + \ No newline at end of file diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 0e8007159..d5f5bfc7b 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -85,6 +85,29 @@ public enum TriggerOptions { GI = 16 } + /// + /// SmpMod values + /// + public enum SmpMod { + SAMPLES_PER_PERIOD = 0, + SAMPLES_PER_SECOND = 1, + SECONDS_PER_SAMPLE = 2 + } + + /// + /// Values for Sampled Values (SV) OptFlds + /// + [Flags] + public enum SVOptions { + NONE = 0, + REFRESH_TIME = 1, + SAMPLE_SYNC = 2, + SAMPLE_RATE = 4, + DATA_SET = 8, + SECURITY = 16, + ALL = 31 + } + [Flags] public enum ReportOptions { NONE = 0, diff --git a/dotnet/IEC61850forCSharp/Reporting.cs b/dotnet/IEC61850forCSharp/Reporting.cs index dfbf6d6b3..85bcd2495 100644 --- a/dotnet/IEC61850forCSharp/Reporting.cs +++ b/dotnet/IEC61850forCSharp/Reporting.cs @@ -46,6 +46,19 @@ private void cleanupRCBs() } + /// + /// Creates a new SampledValuesControlBlock instance. + /// + /// > + /// This function will also read the SVCB values from the server. + /// + /// The new SVCB instamce + /// The object reference of the SVCB + public SampledValuesControlBlock GetSvControlBlock (string svcbObjectReference) + { + return new SampledValuesControlBlock (connection, svcbObjectReference); + } + public ReportControlBlock GetReportControlBlock (string rcbObjectReference) { var newRCB = new ReportControlBlock (rcbObjectReference, this, connection); diff --git a/src/iec61850/client/client_sv_control.c b/src/iec61850/client/client_sv_control.c index 7c3cd3c94..55e8ae5fc 100644 --- a/src/iec61850/client/client_sv_control.c +++ b/src/iec61850/client/client_sv_control.c @@ -115,16 +115,16 @@ setBooleanVariable(ClientSVControlBlock self, const char* varName, bool value) } bool -ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool svEna) +ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool value) { - return setBooleanVariable(self, "SvEna", svEna); + return setBooleanVariable(self, "SvEna", value); } bool -ClientSVControlBlock_setResv(ClientSVControlBlock self, bool svEna) +ClientSVControlBlock_setResv(ClientSVControlBlock self, bool value) { if (self->isMulticast == false) - return setBooleanVariable(self, "SvEna", svEna); + return setBooleanVariable(self, "SvEna", value); else return false; } diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index be85acb04..e790cad1f 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -344,6 +344,12 @@ IedConnection_getMmsConnection(IedConnection self); /** SV ASDU contains attribute Security */ #define IEC61850_SV_OPT_SECURITY 16 +#define IEC61850_SV_SMPMOD_SAMPLES_PER_PERIOD 0 + +#define IEC61850_SV_SMPMOD_SAMPLES_PER_SECOND 1 + +#define IEC61850_SV_SMPMOD_SECONDS_PER_SAMPLE 2 + /** an opaque handle to the instance data of a ClientSVControlBlock object */ typedef struct sClientSVControlBlock* ClientSVControlBlock; @@ -385,13 +391,13 @@ ClientSVControlBlock_getLastComError(ClientSVControlBlock self); bool -ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool svEna); +ClientSVControlBlock_setSvEna(ClientSVControlBlock self, bool value); bool ClientSVControlBlock_getSvEna(ClientSVControlBlock self); bool -ClientSVControlBlock_setResv(ClientSVControlBlock self, bool svEna); +ClientSVControlBlock_setResv(ClientSVControlBlock self, bool value); bool ClientSVControlBlock_getResv(ClientSVControlBlock self); From 5cabd4370228dd28490a9f90070616a50099aedb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 20:23:08 +0100 Subject: [PATCH 003/123] - added missing file --- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 25 ++- .../SampledValuesControlBlock.cs | 195 ++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index d5f5bfc7b..1e1d8617c 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -1,4 +1,27 @@ -using System; +/* + * IEC61850CommonAPI.cs + * + * Copyright 2014-2017 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +using System; using System.Runtime.InteropServices; namespace IEC61850 diff --git a/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs new file mode 100644 index 000000000..bf4cd213d --- /dev/null +++ b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs @@ -0,0 +1,195 @@ +/* + * SampledValuesControlBlock.cs + * + * Copyright 2017 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ +using System; +using System.Runtime.InteropServices; +using System.Diagnostics; + +using IEC61850.Common; + +namespace IEC61850 +{ + namespace Client + { + /// + /// Sampled values control bloc (SvCB) representation. + /// + /// + /// This class is used as a client side representation (copy) of a sampled values control block (SvCB). + /// + public class SampledValuesControlBlock : IDisposable + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientSVControlBlock_create (IntPtr iedConnection, string reference); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void ClientSVControlBlock_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int ClientSVControlBlock_getLastComError (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientSVControlBlock_isMulticast (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientSVControlBlock_setSvEna (IntPtr self, bool value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientSVControlBlock_setResv (IntPtr self, bool value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientSVControlBlock_getSvEna (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientSVControlBlock_getResv (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientSVControlBlock_getMsvID (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientSVControlBlock_getDatSet (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 ClientSVControlBlock_getConfRev (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt16 ClientSVControlBlock_getSmpRate (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int ClientSVControlBlock_getOptFlds (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern byte ClientSVControlBlock_getSmpMod(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int ClientSVControlBlock_getNoASDU (IntPtr self); + + private IntPtr self; + private string objectReference; + + private bool isDisposed = false; + + + internal SampledValuesControlBlock(IntPtr iedConnection, string objectReference) + { + self = ClientSVControlBlock_create (iedConnection, objectReference); + this.objectReference = objectReference; + } + + public string GetObjectReference () + { + return this.objectReference; + } + + public IedClientError GetLastComError() + { + return (IedClientError)ClientSVControlBlock_getLastComError (self); + } + + public bool IsMulticast() + { + return ClientSVControlBlock_isMulticast (self); + } + + public bool GetResv() + { + return ClientSVControlBlock_getResv (self); + } + + public bool SetResv(bool value) + { + return ClientSVControlBlock_setResv (self, value); + } + + public bool GetSvEna() + { + return ClientSVControlBlock_getSvEna (self); + } + + public bool SetSvEna(bool value) + { + return ClientSVControlBlock_setSvEna (self, value); + } + + public string GetMsvID () + { + IntPtr msvIdPtr = ClientSVControlBlock_getMsvID (self); + + return Marshal.PtrToStringAnsi (msvIdPtr); + } + + public string GetDatSet () + { + IntPtr datSetPtr = ClientSVControlBlock_getDatSet (self); + + return Marshal.PtrToStringAnsi (datSetPtr); + } + + public UInt32 GetConfRev () + { + return ClientSVControlBlock_getConfRev (self); + } + + public UInt16 GetSmpRate () + { + return ClientSVControlBlock_getSmpRate (self); + } + + public SVOptions GetOptFlds () + { + return (SVOptions)ClientSVControlBlock_getOptFlds (self); + } + + public SmpMod GetSmpMod () + { + return (SmpMod)ClientSVControlBlock_getSmpMod (self); + } + + public int GetNoASDU () + { + return ClientSVControlBlock_getNoASDU (self); + } + + public void Dispose() + { + if (isDisposed == false) { + isDisposed = true; + ClientSVControlBlock_destroy (self); + self = IntPtr.Zero; + } + } + + ~SampledValuesControlBlock() + { + Dispose (); + } + + } + + + } +} From ce0ed1d39c1a3d748e8e43227fb223d35c036f93 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 20:57:13 +0100 Subject: [PATCH 004/123] - SV publisher/subscriber: removed deprecated header files. Moved wrapper functions for deprecated API to .c files --- CMakeLists.txt | 2 - src/Doxyfile.in | 1 - src/sampled_values/sv_publisher.c | 137 ++++++++++++ src/sampled_values/sv_publisher.h | 89 +++++++- src/sampled_values/sv_publisher_deprecated.h | 211 ------------------ src/sampled_values/sv_subscriber.c | 96 ++++++++ src/sampled_values/sv_subscriber.h | 67 +++++- src/sampled_values/sv_subscriber_deprecated.h | 167 -------------- 8 files changed, 385 insertions(+), 385 deletions(-) delete mode 100644 src/sampled_values/sv_publisher_deprecated.h delete mode 100644 src/sampled_values/sv_subscriber_deprecated.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e6b0160e3..095e70063 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,8 +127,6 @@ set(API_HEADERS src/goose/goose_publisher.h src/sampled_values/sv_subscriber.h src/sampled_values/sv_publisher.h - src/sampled_values/sv_publisher_deprecated.h - src/sampled_values/sv_subscriber_deprecated.h src/logging/logging_api.h src/tls/tls_api.h ${CMAKE_CURRENT_BINARY_DIR}/config/stack_config.h diff --git a/src/Doxyfile.in b/src/Doxyfile.in index 645161fce..bc9fe0734 100644 --- a/src/Doxyfile.in +++ b/src/Doxyfile.in @@ -794,7 +794,6 @@ INPUT = "iec61850/inc/iec61850_client.h" \ "goose/goose_receiver.h" \ "sampled_values/sv_subscriber.h" \ "sampled_values/sv_publisher.h" \ - "sampled_values/sv_publisher_deprecated.h" \ "mms/inc/mms_device_model.h" \ "mms/inc/mms_types.h" \ "mms/inc/mms_common.h" \ diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index 578a1826a..7c681899e 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -649,3 +649,140 @@ SVPublisher_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate) self->hasSmpRate = true; self->smpRate = smpRate; } + + +/******************************************************************* + * Wrapper functions to support old API (remove in future versions) + *******************************************************************/ + +SVPublisher +SampledValuesPublisher_create(CommParameters* parameters, const char* interfaceId) +{ + return SVPublisher_create(parameters, interfaceId); +} + +SVPublisher_ASDU +SampledValuesPublisher_addASDU(SVPublisher self, char* svID, char* datset, uint32_t confRev) +{ + return SVPublisher_addASDU(self, svID, datset, confRev); +} + +void +SampledValuesPublisher_setupComplete(SVPublisher self) +{ + SVPublisher_setupComplete(self); +} + +void +SampledValuesPublisher_publish(SVPublisher self) +{ + SVPublisher_publish(self); +} + +void +SampledValuesPublisher_destroy(SVPublisher self) +{ + SVPublisher_destroy(self); +} + +void +SV_ASDU_resetBuffer(SVPublisher_ASDU self) +{ + SVPublisher_ASDU_resetBuffer(self); +} + +int +SV_ASDU_addINT8(SVPublisher_ASDU self) +{ + return SVPublisher_ASDU_addINT8(self); +} + +void +SV_ASDU_setINT8(SVPublisher_ASDU self, int index, int8_t value) +{ + SVPublisher_ASDU_setINT8(self, index, value); +} + +int +SV_ASDU_addINT32(SVPublisher_ASDU self) +{ + return SVPublisher_ASDU_addINT32(self); +} + +void +SV_ASDU_setINT32(SVPublisher_ASDU self, int index, int32_t value) +{ + SVPublisher_ASDU_setINT32(self, index, value); +} + +int +SV_ASDU_addINT64(SVPublisher_ASDU self) +{ + return SVPublisher_ASDU_addINT64(self); +} + +void +SV_ASDU_setINT64(SVPublisher_ASDU self, int index, int64_t value) +{ + SVPublisher_ASDU_setINT64(self, index, value); +} + +int +SV_ASDU_addFLOAT(SVPublisher_ASDU self) +{ + return SVPublisher_ASDU_addFLOAT(self); +} + +void +SV_ASDU_setFLOAT(SVPublisher_ASDU self, int index, float value) +{ + SVPublisher_ASDU_setFLOAT(self, index, value); +} + +int +SV_ASDU_addFLOAT64(SVPublisher_ASDU self) +{ + return SVPublisher_ASDU_addFLOAT64(self); +} + +void +SV_ASDU_setFLOAT64(SVPublisher_ASDU self, int index, double value) +{ + SVPublisher_ASDU_setFLOAT64(self, index, value); +} + +void +SV_ASDU_setSmpCnt(SVPublisher_ASDU self, uint16_t value) +{ + SVPublisher_ASDU_setSmpCnt(self, value); +} + +uint16_t +SV_ASDU_getSmpCnt(SVPublisher_ASDU self) +{ + return SVPublisher_ASDU_getSmpCnt(self); +} + +void +SV_ASDU_increaseSmpCnt(SVPublisher_ASDU self) +{ + SVPublisher_ASDU_increaseSmpCnt(self); +} + +void +SV_ASDU_setRefrTm(SVPublisher_ASDU self, uint64_t refrTm) +{ + SVPublisher_ASDU_setRefrTm(self, refrTm); +} + +void +SV_ASDU_setSmpMod(SVPublisher_ASDU self, uint8_t smpMod) +{ + SVPublisher_ASDU_setSmpMod(self, smpMod); +} + +void +SV_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate) +{ + SVPublisher_ASDU_setSmpRate(self, smpRate); +} diff --git a/src/sampled_values/sv_publisher.h b/src/sampled_values/sv_publisher.h index 5ea8676ba..82c0cdfcc 100644 --- a/src/sampled_values/sv_publisher.h +++ b/src/sampled_values/sv_publisher.h @@ -287,10 +287,95 @@ SVPublisher_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate); /**@} @}*/ +#ifndef DEPRECATED +#if defined(__GNUC__) || defined(__clang__) + #define DEPRECATED __attribute__((deprecated)) +#else + #define DEPRECATED +#endif +#endif + +/** + * \addtogroup sv_publisher_deprecated_api_group Deprecated API + * \ingroup sv_publisher_api_group IEC 61850 Sampled Values (SV) publisher API + * \deprecated + * @{ + */ + +typedef struct sSVPublisher* SampledValuesPublisher; + +typedef struct sSV_ASDU* SV_ASDU; + +DEPRECATED SVPublisher +SampledValuesPublisher_create(CommParameters* parameters, const char* interfaceId); + +DEPRECATED SVPublisher_ASDU +SampledValuesPublisher_addASDU(SVPublisher self, char* svID, char* datset, uint32_t confRev); + +DEPRECATED void +SampledValuesPublisher_setupComplete(SVPublisher self); + +DEPRECATED void +SampledValuesPublisher_publish(SVPublisher self); + +DEPRECATED void +SampledValuesPublisher_destroy(SVPublisher self); + +DEPRECATED void +SV_ASDU_resetBuffer(SVPublisher_ASDU self); + +DEPRECATED int +SV_ASDU_addINT8(SVPublisher_ASDU self); + +DEPRECATED void +SV_ASDU_setINT8(SVPublisher_ASDU self, int index, int8_t value); + +DEPRECATED int +SV_ASDU_addINT32(SVPublisher_ASDU self); + +DEPRECATED void +SV_ASDU_setINT32(SVPublisher_ASDU self, int index, int32_t value); + +DEPRECATED int +SV_ASDU_addINT64(SVPublisher_ASDU self); + +DEPRECATED void +SV_ASDU_setINT64(SVPublisher_ASDU self, int index, int64_t value); + +DEPRECATED int +SV_ASDU_addFLOAT(SVPublisher_ASDU self); + +DEPRECATED void +SV_ASDU_setFLOAT(SVPublisher_ASDU self, int index, float value); + +DEPRECATED int +SV_ASDU_addFLOAT64(SVPublisher_ASDU self); + +DEPRECATED void +SV_ASDU_setFLOAT64(SVPublisher_ASDU self, int index, double value); + +void DEPRECATED +SV_ASDU_setSmpCnt(SVPublisher_ASDU self, uint16_t value); + +DEPRECATED uint16_t +SV_ASDU_getSmpCnt(SVPublisher_ASDU self); + +DEPRECATED void +SV_ASDU_increaseSmpCnt(SVPublisher_ASDU self); + +DEPRECATED void +SV_ASDU_setRefrTm(SVPublisher_ASDU self, uint64_t refrTm); + +DEPRECATED void +SV_ASDU_setSmpMod(SVPublisher_ASDU self, uint8_t smpMod); + +DEPRECATED void +SV_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate); + +/**@}*/ + #ifdef __cplusplus } #endif -#include "sv_publisher_deprecated.h" - #endif /* LIBIEC61850_SRC_SAMPLED_VALUES_SV_PUBLISHER_H_ */ diff --git a/src/sampled_values/sv_publisher_deprecated.h b/src/sampled_values/sv_publisher_deprecated.h deleted file mode 100644 index 3fe31ede6..000000000 --- a/src/sampled_values/sv_publisher_deprecated.h +++ /dev/null @@ -1,211 +0,0 @@ -/* - * sv_publisher.h - * - * Copyright 2016 Michael Zillgith - * - * This file is part of libIEC61850. - * - * libIEC61850 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * libIEC61850 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with libIEC61850. If not, see . - * - * See COPYING file for the complete license text. - */ - - -#ifndef LIBIEC61850_SRC_SAMPLED_VALUES_SV_PUBLISHER_DEPRECATED_H_ -#define LIBIEC61850_SRC_SAMPLED_VALUES_SV_PUBLISHER_DEPRECATED_H_ - -#include "libiec61850_platform_includes.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(__GNUC__) || defined(__clang__) - #define DEPRECATED __attribute__((deprecated)) -#else - #define DEPRECATED -#endif - -/** - * \addtogroup sv_publisher_deprecated_api_group Deprecated API - * \ingroup sv_publisher_api_group IEC 61850 Sampled Values (SV) publisher API - * \deprecated - * @{ - */ - -typedef DEPRECATED struct sSVPublisher* SampledValuesPublisher; - -typedef DEPRECATED struct sSV_ASDU* SV_ASDU; - -static DEPRECATED -SVPublisher -SampledValuesPublisher_create(CommParameters* parameters, const char* interfaceId) -{ - return SVPublisher_create(parameters, interfaceId); -} - -static DEPRECATED -SVPublisher_ASDU -SampledValuesPublisher_addASDU(SVPublisher self, char* svID, char* datset, uint32_t confRev) -{ - return SVPublisher_addASDU(self, svID, datset, confRev); -} - -static DEPRECATED -void -SampledValuesPublisher_setupComplete(SVPublisher self) -{ - SVPublisher_setupComplete(self); -} - -static DEPRECATED -void -SampledValuesPublisher_publish(SVPublisher self) -{ - SVPublisher_publish(self); -} - -static DEPRECATED -void -SampledValuesPublisher_destroy(SVPublisher self) -{ - SVPublisher_destroy(self); -} - -static DEPRECATED -void -SV_ASDU_resetBuffer(SVPublisher_ASDU self) -{ - SVPublisher_ASDU_resetBuffer(self); -} - -static DEPRECATED -int -SV_ASDU_addINT8(SVPublisher_ASDU self) -{ - return SVPublisher_ASDU_addINT8(self); -} - -static DEPRECATED -void -SV_ASDU_setINT8(SVPublisher_ASDU self, int index, int8_t value) -{ - SVPublisher_ASDU_setINT8(self, index, value); -} - -static DEPRECATED -int -SV_ASDU_addINT32(SVPublisher_ASDU self) -{ - return SVPublisher_ASDU_addINT32(self); -} - -static DEPRECATED -void -SV_ASDU_setINT32(SVPublisher_ASDU self, int index, int32_t value) -{ - SVPublisher_ASDU_setINT32(self, index, value); -} - -static DEPRECATED -int -SV_ASDU_addINT64(SVPublisher_ASDU self) -{ - return SVPublisher_ASDU_addINT64(self); -} - -static DEPRECATED -void -SV_ASDU_setINT64(SVPublisher_ASDU self, int index, int64_t value) -{ - SVPublisher_ASDU_setINT64(self, index, value); -} - -static DEPRECATED -int -SV_ASDU_addFLOAT(SVPublisher_ASDU self) -{ - return SVPublisher_ASDU_addFLOAT(self); -} - -static DEPRECATED -void -SV_ASDU_setFLOAT(SVPublisher_ASDU self, int index, float value) -{ - SVPublisher_ASDU_setFLOAT(self, index, value); -} - -static DEPRECATED -int -SV_ASDU_addFLOAT64(SVPublisher_ASDU self) -{ - return SVPublisher_ASDU_addFLOAT64(self); -} - -static DEPRECATED -void -SV_ASDU_setFLOAT64(SVPublisher_ASDU self, int index, double value) -{ - SVPublisher_ASDU_setFLOAT64(self, index, value); -} - -static DEPRECATED -void -SV_ASDU_setSmpCnt(SVPublisher_ASDU self, uint16_t value) -{ - SVPublisher_ASDU_setSmpCnt(self, value); -} - -static DEPRECATED -uint16_t -SV_ASDU_getSmpCnt(SVPublisher_ASDU self) -{ - return SVPublisher_ASDU_getSmpCnt(self); -} - -static DEPRECATED -void -SV_ASDU_increaseSmpCnt(SVPublisher_ASDU self) -{ - SVPublisher_ASDU_increaseSmpCnt(self); -} - -static DEPRECATED -void -SV_ASDU_setRefrTm(SVPublisher_ASDU self, uint64_t refrTm) -{ - SVPublisher_ASDU_setRefrTm(self, refrTm); -} - -static DEPRECATED -void -SV_ASDU_setSmpMod(SVPublisher_ASDU self, uint8_t smpMod) -{ - SVPublisher_ASDU_setSmpMod(self, smpMod); -} - -static DEPRECATED -void -SV_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate) -{ - SVPublisher_ASDU_setSmpRate(self, smpRate); -} - -/**@}*/ - -#ifdef __cplusplus -} -#endif - -#endif /* LIBIEC61850_SRC_SAMPLED_VALUES_SV_PUBLISHER_DEPRECATED_H_ */ diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index 573fc39ec..395540630 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -832,3 +832,99 @@ SVSubscriber_ASDU_getDataSize(SVSubscriber_ASDU self) return self->dataBufferLength; } +uint16_t +SVClientASDU_getSmpCnt(SVSubscriber_ASDU self) +{ + return SVSubscriber_ASDU_getSmpCnt(self); +} + +const char* +SVClientASDU_getSvId(SVSubscriber_ASDU self) +{ + return SVSubscriber_ASDU_getSvId(self); +} + +uint32_t +SVClientASDU_getConfRev(SVSubscriber_ASDU self) +{ + return SVSubscriber_ASDU_getConfRev(self); +} + +bool +SVClientASDU_hasRefrTm(SVSubscriber_ASDU self) +{ + return SVSubscriber_ASDU_hasRefrTm(self); +} + +uint64_t +SVClientASDU_getRefrTmAsMs(SVSubscriber_ASDU self) +{ + return SVSubscriber_ASDU_getRefrTmAsMs(self); +} + +int8_t +SVClientASDU_getINT8(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT8(self, index); +} + +int16_t +SVClientASDU_getINT16(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT16(self, index); +} + +int32_t +SVClientASDU_getINT32(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT32(self, index); +} + +int64_t +SVClientASDU_getINT64(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT64(self, index); +} + +uint8_t +SVClientASDU_getINT8U(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT8U(self, index); +} + +uint16_t +SVClientASDU_getINT16U(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT16U(self, index); +} + +uint32_t +SVClientASDU_getINT32U(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT32U(self, index); +} + +uint64_t +SVClientASDU_getINT64U(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getINT64U(self, index); +} + +float +SVClientASDU_getFLOAT32(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getFLOAT32(self, index); +} + +double +SVClientASDU_getFLOAT64(SVSubscriber_ASDU self, int index) +{ + return SVSubscriber_ASDU_getFLOAT64(self, index); +} + +int +SVClientASDU_getDataSize(SVSubscriber_ASDU self) +{ + return SVSubscriber_ASDU_getDataSize(self); +} + diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index ef5ab875f..879736a92 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -479,12 +479,75 @@ SVSubscriber_ASDU_getFLOAT64(SVSubscriber_ASDU self, int index); int SVSubscriber_ASDU_getDataSize(SVSubscriber_ASDU self); +#ifndef DEPRECATED +#if defined(__GNUC__) || defined(__clang__) + #define DEPRECATED __attribute__((deprecated)) +#else + #define DEPRECATED +#endif +#endif + +/** + * \addtogroup sv_subscriber_deprecated_api_group Deprecated API + * \ingroup sv_subscriber_api_group IEC 61850 Sampled Values (SV) publisher API + * \deprecated + * @{ + */ + +typedef struct sSVSubscriberASDU* SVClientASDU; + +DEPRECATED uint16_t +SVClientASDU_getSmpCnt(SVSubscriber_ASDU self); + +DEPRECATED const char* +SVClientASDU_getSvId(SVSubscriber_ASDU self); + +DEPRECATED uint32_t +SVClientASDU_getConfRev(SVSubscriber_ASDU self); + +DEPRECATED bool +SVClientASDU_hasRefrTm(SVSubscriber_ASDU self); + +DEPRECATED uint64_t +SVClientASDU_getRefrTmAsMs(SVSubscriber_ASDU self); + +DEPRECATED int8_t +SVClientASDU_getINT8(SVSubscriber_ASDU self, int index); + +DEPRECATED int16_t +SVClientASDU_getINT16(SVSubscriber_ASDU self, int index); + +DEPRECATED int32_t +SVClientASDU_getINT32(SVSubscriber_ASDU self, int index); + +DEPRECATED int64_t +SVClientASDU_getINT64(SVSubscriber_ASDU self, int index); + +DEPRECATED uint8_t +SVClientASDU_getINT8U(SVSubscriber_ASDU self, int index); + +DEPRECATED uint16_t +SVClientASDU_getINT16U(SVSubscriber_ASDU self, int index); + +DEPRECATED uint32_t +SVClientASDU_getINT32U(SVSubscriber_ASDU self, int index); + +DEPRECATED uint64_t +SVClientASDU_getINT64U(SVSubscriber_ASDU self, int index); + +DEPRECATED float +SVClientASDU_getFLOAT32(SVSubscriber_ASDU self, int index); + +DEPRECATED double +SVClientASDU_getFLOAT64(SVSubscriber_ASDU self, int index); + +DEPRECATED int +SVClientASDU_getDataSize(SVSubscriber_ASDU self); + /**@} @}*/ #ifdef __cplusplus } #endif -#include "sv_subscriber_deprecated.h" - #endif /* SAMPLED_VALUES_SV_SUBSCRIBER_ */ diff --git a/src/sampled_values/sv_subscriber_deprecated.h b/src/sampled_values/sv_subscriber_deprecated.h deleted file mode 100644 index 5d5741aa4..000000000 --- a/src/sampled_values/sv_subscriber_deprecated.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * sv_subscriber_deprecated.h - * - * Copyright 2015 Michael Zillgith - * - * This file is part of libIEC61850. - * - * libIEC61850 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * libIEC61850 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with libIEC61850. If not, see . - * - * See COPYING file for the complete license text. - */ - -#ifndef SAMPLED_VALUES_SV_SUBSCRIBER_DEPRECATED_H_ -#define SAMPLED_VALUES_SV_SUBSCRIBER_DEPRECATED_H_ - -#include "libiec61850_common_api.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(__GNUC__) || defined(__clang__) - #define DEPRECATED __attribute__((deprecated)) -#else - #define DEPRECATED -#endif - -/** - * \addtogroup sv_subscriber_deprecated_api_group Deprecated API - * \ingroup sv_subscriber_api_group IEC 61850 Sampled Values (SV) publisher API - * \deprecated - * @{ - */ - -typedef struct sSVSubscriberASDU* SVClientASDU; - -static DEPRECATED -uint16_t -SVClientASDU_getSmpCnt(SVSubscriber_ASDU self) -{ - return SVSubscriber_ASDU_getSmpCnt(self); -} - -static DEPRECATED -const char* -SVClientASDU_getSvId(SVSubscriber_ASDU self) -{ - return SVSubscriber_ASDU_getSvId(self); -} - -static DEPRECATED -uint32_t -SVClientASDU_getConfRev(SVSubscriber_ASDU self) -{ - return SVSubscriber_ASDU_getConfRev(self); -} - -static DEPRECATED -bool -SVClientASDU_hasRefrTm(SVSubscriber_ASDU self) -{ - return SVSubscriber_ASDU_hasRefrTm(self); -} - -static DEPRECATED -uint64_t -SVClientASDU_getRefrTmAsMs(SVSubscriber_ASDU self) -{ - return SVSubscriber_ASDU_getRefrTmAsMs(self); -} - -static DEPRECATED -int8_t -SVClientASDU_getINT8(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT8(self, index); -} - -static DEPRECATED -int16_t -SVClientASDU_getINT16(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT16(self, index); -} - -static DEPRECATED -int32_t -SVClientASDU_getINT32(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT32(self, index); -} - -static DEPRECATED -int64_t -SVClientASDU_getINT64(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT64(self, index); -} - -static DEPRECATED -uint8_t -SVClientASDU_getINT8U(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT8U(self, index); -} - -static DEPRECATED -uint16_t -SVClientASDU_getINT16U(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT16U(self, index); -} - -static DEPRECATED -uint32_t -SVClientASDU_getINT32U(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT32U(self, index); -} - -static DEPRECATED -uint64_t -SVClientASDU_getINT64U(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getINT64U(self, index); -} - -static DEPRECATED -float -SVClientASDU_getFLOAT32(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getFLOAT32(self, index); -} - -static DEPRECATED -double -SVClientASDU_getFLOAT64(SVSubscriber_ASDU self, int index) -{ - return SVSubscriber_ASDU_getFLOAT64(self, index); -} - -static DEPRECATED -int -SVClientASDU_getDataSize(SVSubscriber_ASDU self) -{ - return SVSubscriber_ASDU_getDataSize(self); -} - -/**@}*/ - -#ifdef __cplusplus -} -#endif - - -#endif /* SAMPLED_VALUES_SV_SUBSCRIBER_DEPRECATED_H_ */ From 10ed5af23035876387dc3fb80b9e79ce0a271b7f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 22:20:22 +0100 Subject: [PATCH 005/123] - .NET API: added support for GoCB handling - .NET API: Add PhyComAddress class --- dotnet/IEC61850forCSharp/GooseControlBlock.cs | 240 ++++++++++++++++++ dotnet/IEC61850forCSharp/IEC61850.NET.csproj | 1 + dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 24 +- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 11 + dotnet/IEC61850forCSharp/Reporting.cs | 13 - .../SampledValuesControlBlock.cs | 8 + 6 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 dotnet/IEC61850forCSharp/GooseControlBlock.cs diff --git a/dotnet/IEC61850forCSharp/GooseControlBlock.cs b/dotnet/IEC61850forCSharp/GooseControlBlock.cs new file mode 100644 index 000000000..ec27577e7 --- /dev/null +++ b/dotnet/IEC61850forCSharp/GooseControlBlock.cs @@ -0,0 +1,240 @@ +/* + * GooseControlBlock.cs + * + * Copyright 2017 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ +using System; +using System.Runtime.InteropServices; +using System.Diagnostics; + +using IEC61850.Common; + +namespace IEC61850 +{ + namespace Client + { + + public class GooseControlBlock { + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientGooseControlBlock_create (string dataAttributeReference); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void ClientGooseControlBlock_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedConnection_getGoCBValues (IntPtr connection, out int error, string rcbReference, IntPtr updateRcb); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_setGoCBValues (IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, bool singleRequest); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientGooseControlBlock_getGoEna (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void ClientGooseControlBlock_setGoEna(IntPtr self, bool rptEna); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientGooseControlBlock_getGoID (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void ClientGooseControlBlock_setGoID (IntPtr self, string goId); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientGooseControlBlock_getDatSet (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void ClientGooseControlBlock_setDatSet (IntPtr self, string datSet); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 ClientGooseControlBlock_getConfRev (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientGooseControlBlock_getNdsComm (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 ClientGooseControlBlock_getMinTime (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 ClientGooseControlBlock_getMaxTime (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ClientGooseControlBlock_getFixedOffs (IntPtr self); + + + private IntPtr self; + private IntPtr connection; + private string objectReference; + + private bool isDisposed = false; + + private bool flagGoEna = false; + private bool flagGoID = false; + private bool flagDatSet = false; + private bool flagDstAddress = false; + + internal GooseControlBlock(string objectReference, IntPtr connection) + { + self = ClientGooseControlBlock_create (objectReference); + this.connection = connection; + this.objectReference = objectReference; + } + + public string GetObjectReference () + { + return this.objectReference; + } + + /// + /// Read all GoCB values from the server + /// + /// This exception is thrown if there is a connection or service error + public void GetCBValues () + { + int error; + + IedConnection_getGoCBValues (connection, out error, objectReference, self); + + if (error != 0) + throw new IedConnectionException ("getGoCBValues service failed", error); + } + + private void + resetSendFlags() + { + flagGoEna = false; + flagGoID = false; + flagDatSet = false; + flagDstAddress = false; + } + + public void SetCBValues (bool singleRequest) + { + UInt32 parametersMask = 0; + + if (flagGoEna) + parametersMask += 1; + + if (flagGoID) + parametersMask += 2; + + if (flagDatSet) + parametersMask += 4; + + int error; + + IedConnection_setGoCBValues (connection, out error, self, parametersMask, singleRequest); + + resetSendFlags (); + + if (error != 0) + throw new IedConnectionException ("setGoCBValues service failed", error); + } + + public void SetCBValues () + { + SetCBValues (true); + } + + public bool GetGoEna() + { + return ClientGooseControlBlock_getGoEna (self); + } + + public void SetGoEna(bool value) + { + ClientGooseControlBlock_setGoEna (self, value); + + flagGoEna = true; + } + + public string GetGoID() + { + IntPtr goIdRef = ClientGooseControlBlock_getGoID (self); + + return Marshal.PtrToStringAnsi (goIdRef); + } + + public void SetGoID (string goID) + { + ClientGooseControlBlock_setGoID (self, goID); + + flagGoID = true; + } + + public string GetDatSet() + { + IntPtr datSetRef = ClientGooseControlBlock_getDatSet (self); + + return Marshal.PtrToStringAnsi (datSetRef); + } + + public void SetDataSet(string datSet) + { + ClientGooseControlBlock_setDatSet (self, datSet); + + flagDatSet = true; + } + + public UInt32 GetConfRev() + { + return ClientGooseControlBlock_getConfRev (self); + } + + public bool GetNdsComm() + { + return ClientGooseControlBlock_getNdsComm (self); + } + + public UInt32 GetMinTime() + { + return ClientGooseControlBlock_getMinTime (self); + } + + public UInt32 GetMaxTime() + { + return ClientGooseControlBlock_getMaxTime (self); + } + + public bool GetFixedOffs() + { + return ClientGooseControlBlock_getFixedOffs (self); + } + + public void Dispose() + { + if (isDisposed == false) { + isDisposed = true; + ClientGooseControlBlock_destroy (self); + self = IntPtr.Zero; + } + } + + ~GooseControlBlock() + { + Dispose (); + } + + } + } +} \ No newline at end of file diff --git a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj index 550a89b94..e752d2576 100644 --- a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj +++ b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj @@ -47,6 +47,7 @@ + \ No newline at end of file diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index e51c94c44..b17317c4c 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -531,10 +531,28 @@ public ControlObject CreateControlObject (string objectReference) return controlObject; } + /// + /// Creates a new SampledValuesControlBlock instance. + /// + /// > + /// This function will also read the SVCB values from the server. + /// + /// The new SVCB instance + /// The object reference of the SVCB + public SampledValuesControlBlock GetSvControlBlock (string svcbObjectReference) + { + return new SampledValuesControlBlock (connection, svcbObjectReference); + } - - - + /// + /// Creates a new SampledValuesControlBlock instance. + /// + /// The new GoCB instance + /// The object reference of the GoCB + public GooseControlBlock GetGooseControlBlock (string gocbObjectReference) + { + return new GooseControlBlock (gocbObjectReference, connection); + } /// /// Updates the device model by quering the server. diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 1e1d8617c..472054657 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -72,6 +72,17 @@ public static ulong DateTimeToMsTimestamp(DateTime dateTime) } } + [StructLayout(LayoutKind.Sequential)] + public class PhyComAddress + { + public byte vlanPriority; + public UInt16 vlanId; + public UInt16 appId; + + [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/IEC61850forCSharp/Reporting.cs b/dotnet/IEC61850forCSharp/Reporting.cs index 85bcd2495..dfbf6d6b3 100644 --- a/dotnet/IEC61850forCSharp/Reporting.cs +++ b/dotnet/IEC61850forCSharp/Reporting.cs @@ -46,19 +46,6 @@ private void cleanupRCBs() } - /// - /// Creates a new SampledValuesControlBlock instance. - /// - /// > - /// This function will also read the SVCB values from the server. - /// - /// The new SVCB instamce - /// The object reference of the SVCB - public SampledValuesControlBlock GetSvControlBlock (string svcbObjectReference) - { - return new SampledValuesControlBlock (connection, svcbObjectReference); - } - public ReportControlBlock GetReportControlBlock (string rcbObjectReference) { var newRCB = new ReportControlBlock (rcbObjectReference, this, connection); diff --git a/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs index bf4cd213d..e306fcafd 100644 --- a/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs +++ b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs @@ -88,6 +88,9 @@ public class SampledValuesControlBlock : IDisposable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int ClientSVControlBlock_getNoASDU (IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern PhyComAddress ClientSVControlBlock_getDstAddress (IntPtr self); + private IntPtr self; private string objectReference; @@ -174,6 +177,11 @@ public int GetNoASDU () return ClientSVControlBlock_getNoASDU (self); } + public PhyComAddress GetDstAddress() + { + return ClientSVControlBlock_getDstAddress (self); + } + public void Dispose() { if (isDisposed == false) { From a07f2ccedaa4fb9495d112f5455f396d978ca97e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 22:47:52 +0100 Subject: [PATCH 006/123] - IEC 61850 client: Added ClientGooseControlBlock_getDstAddress/ClientGooseControlBlock_setDstAddress; marked old access functions deprecated --- src/iec61850/client/client_goose_control.c | 91 ++++++++++++++++++++++ src/iec61850/inc/iec61850_client.h | 30 +++++-- src/vs/libiec61850-wo-goose.def | 4 +- src/vs/libiec61850.def | 4 +- 4 files changed, 119 insertions(+), 10 deletions(-) diff --git a/src/iec61850/client/client_goose_control.c b/src/iec61850/client/client_goose_control.c index 89c1a8a9d..c2a5d05d7 100644 --- a/src/iec61850/client/client_goose_control.c +++ b/src/iec61850/client/client_goose_control.c @@ -183,6 +183,97 @@ newEmptyPhyCommAddress(void) { return self; } +PhyComAddress +ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self) +{ + PhyComAddress retVal; + memset(&retVal, 0, sizeof(retVal)); + + if (self->dstAddress == NULL) goto exit_error; + + if (MmsValue_getType(self->dstAddress) != MMS_STRUCTURE) { + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); + goto exit_error; + } + + if (MmsValue_getArraySize(self->dstAddress) != 4) { + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); + goto exit_error; + } + + MmsValue* addr = MmsValue_getElement(self->dstAddress, 0); + + if (MmsValue_getType(addr) != MMS_OCTET_STRING) { + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); + goto exit_error; + } + + if (MmsValue_getOctetStringSize(addr) != 6) { + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong size\n"); + goto exit_error; + } + + uint8_t* addrBuf = MmsValue_getOctetStringBuffer(addr); + + memcpy(&(retVal.dstAddress), addrBuf, 6); + + MmsValue* prio = MmsValue_getElement(self->dstAddress, 1); + + if (MmsValue_getType(prio) != MMS_UNSIGNED) { + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - prio has wrong type\n"); + goto exit_error; + } + + retVal.vlanPriority = MmsValue_toUint32(prio); + + MmsValue* vid = MmsValue_getElement(self->dstAddress, 2); + + if (MmsValue_getType(vid) != MMS_UNSIGNED) { + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - vid has wrong type\n"); + goto exit_error; + } + + retVal.vlanId = MmsValue_toUint32(vid); + + MmsValue* appID = MmsValue_getElement(self->dstAddress, 3); + + if (MmsValue_getType(appID) != MMS_UNSIGNED) { + if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - appID has wrong type\n"); + goto exit_error; + } + + retVal.appId = MmsValue_toUint32(appID); + +exit_error: + return retVal; +} + +void +ClientGooseControlBlock_setDstAddress(ClientGooseControlBlock self, PhyComAddress value) +{ + if (self->dstAddress == NULL) + self->dstAddress = newEmptyPhyCommAddress(); + + if (self->dstAddress) { + + MmsValue* addr = MmsValue_getElement(self->dstAddress, 0); + + MmsValue_setOctetString(addr, value.dstAddress, 6); + + MmsValue* prio = MmsValue_getElement(self->dstAddress, 1); + + MmsValue_setUint8(prio, value.vlanPriority); + + MmsValue* vid = MmsValue_getElement(self->dstAddress, 2); + + MmsValue_setUint16(vid, value.vlanId); + + MmsValue* appID = MmsValue_getElement(self->dstAddress, 3); + + MmsValue_setUint16(appID, value.appId); + } +} + MmsValue* ClientGooseControlBlock_getDstAddress_addr(ClientGooseControlBlock self) { diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index e790cad1f..0cecd640d 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -34,6 +34,14 @@ extern "C" { #include "mms_client_connection.h" #include "linked_list.h" +#ifndef DEPRECATED +#if defined(__GNUC__) || defined(__clang__) + #define DEPRECATED __attribute__((deprecated)) +#else + #define DEPRECATED +#endif +#endif + /** * * \defgroup iec61850_client_api_group IEC 61850/MMS client API */ @@ -545,28 +553,34 @@ ClientGooseControlBlock_getMaxTime(ClientGooseControlBlock self); bool ClientGooseControlBlock_getFixedOffs(ClientGooseControlBlock self); -MmsValue* /* MMS_OCTET_STRING */ -ClientGooseControlBlock_getDstAddress_addr(ClientGooseControlBlock self); +PhyComAddress +ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self); void +ClientGooseControlBlock_setDstAddress(ClientGooseControlBlock self, PhyComAddress value); + +DEPRECATED MmsValue* /* MMS_OCTET_STRING */ +ClientGooseControlBlock_getDstAddress_addr(ClientGooseControlBlock self); + +DEPRECATED void ClientGooseControlBlock_setDstAddress_addr(ClientGooseControlBlock self, MmsValue* macAddr); -uint8_t +DEPRECATED uint8_t ClientGooseControlBlock_getDstAddress_priority(ClientGooseControlBlock self); -void +DEPRECATED void ClientGooseControlBlock_setDstAddress_priority(ClientGooseControlBlock self, uint8_t priorityValue); -uint16_t +DEPRECATED uint16_t ClientGooseControlBlock_getDstAddress_vid(ClientGooseControlBlock self); -void +DEPRECATED void ClientGooseControlBlock_setDstAddress_vid(ClientGooseControlBlock self, uint16_t vidValue); -uint16_t +DEPRECATED uint16_t ClientGooseControlBlock_getDstAddress_appid(ClientGooseControlBlock self); -void +DEPRECATED void ClientGooseControlBlock_setDstAddress_appid(ClientGooseControlBlock self, uint16_t appidValue); diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index d2defa15d..9b5cb3e88 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -574,4 +574,6 @@ EXPORTS MmsServer_setLocalIpAddress MmsServer_isRunning IedServer_createWithTlsSupport - IedConnection_createWithTlsSupport \ No newline at end of file + IedConnection_createWithTlsSupport + ClientGooseControlBlock_getDstAddress + ClientGooseControlBlock_setDstAddress \ No newline at end of file diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 909a35116..a5415dcbb 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -655,4 +655,6 @@ EXPORTS MmsServer_setLocalIpAddress MmsServer_isRunning IedServer_createWithTlsSupport - IedConnection_createWithTlsSupport \ No newline at end of file + IedConnection_createWithTlsSupport + ClientGooseControlBlock_getDstAddress + ClientGooseControlBlock_setDstAddress \ No newline at end of file From fb56e0bc865c50758f8a5ee7a0d53eef28387419 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 22:53:02 +0100 Subject: [PATCH 007/123] - .NET API: Added SetDstAddress/GetDstAddress to GooseControlBlock class --- dotnet/IEC61850forCSharp/GooseControlBlock.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dotnet/IEC61850forCSharp/GooseControlBlock.cs b/dotnet/IEC61850forCSharp/GooseControlBlock.cs index ec27577e7..f2b9c88fa 100644 --- a/dotnet/IEC61850forCSharp/GooseControlBlock.cs +++ b/dotnet/IEC61850forCSharp/GooseControlBlock.cs @@ -81,6 +81,12 @@ public class GooseControlBlock { [return: MarshalAs(UnmanagedType.I1)] static extern bool ClientGooseControlBlock_getFixedOffs (IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern PhyComAddress ClientGooseControlBlock_getDstAddress (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void ClientGooseControlBlock_setDstAddress (IntPtr self, PhyComAddress value); + private IntPtr self; private IntPtr connection; @@ -221,6 +227,18 @@ public bool GetFixedOffs() return ClientGooseControlBlock_getFixedOffs (self); } + public PhyComAddress GetDstAddress() + { + return ClientGooseControlBlock_getDstAddress (self); + } + + public void SetDstAddress(PhyComAddress value) + { + ClientGooseControlBlock_setDstAddress (self, value); + + flagDstAddress = true; + } + public void Dispose() { if (isDisposed == false) { From 3d9851f00cb54db6bd751f0b51385d32745c6c7d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Dec 2017 23:07:37 +0100 Subject: [PATCH 008/123] - added missing functions to windows .def files --- src/vs/libiec61850-wo-goose.def | 4 +++- src/vs/libiec61850.def | 42 ++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 9b5cb3e88..133e02078 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -576,4 +576,6 @@ EXPORTS IedServer_createWithTlsSupport IedConnection_createWithTlsSupport ClientGooseControlBlock_getDstAddress - ClientGooseControlBlock_setDstAddress \ No newline at end of file + ClientGooseControlBlock_setDstAddress + CDC_VSS_create + CDC_VSG_create \ No newline at end of file diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index a5415dcbb..83cee3bc6 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -657,4 +657,44 @@ EXPORTS IedServer_createWithTlsSupport IedConnection_createWithTlsSupport ClientGooseControlBlock_getDstAddress - ClientGooseControlBlock_setDstAddress \ No newline at end of file + ClientGooseControlBlock_setDstAddress + SVPublisher_create + SVPublisher_addASDU + SVPublisher_setupComplete + SVPublisher_publish + SVPublisher_destroy + SVPublisher_ASDU_resetBuffer + SVPublisher_ASDU_addINT8 + SVPublisher_ASDU_setINT8 + SVPublisher_ASDU_addINT32 + SVPublisher_ASDU_setINT32 + SVPublisher_ASDU_addINT64 + SVPublisher_ASDU_setINT64 + SVPublisher_ASDU_addFLOAT + SVPublisher_ASDU_setFLOAT + SVPublisher_ASDU_addFLOAT64 + SVPublisher_ASDU_setFLOAT64 + SVPublisher_ASDU_setSmpCnt + SVPublisher_ASDU_getSmpCnt + SVPublisher_ASDU_increaseSmpCnt + SVPublisher_ASDU_setRefrTm + SVPublisher_ASDU_setSmpMod + SVPublisher_ASDU_setSmpRate + SVSubscriber_ASDU_getSmpCnt + SVSubscriber_ASDU_getSvId + SVSubscriber_ASDU_getConfRev + SVSubscriber_ASDU_hasRefrTm + SVSubscriber_ASDU_getRefrTmAsMs + SVSubscriber_ASDU_getINT8 + SVSubscriber_ASDU_getINT16 + SVSubscriber_ASDU_getINT32 + SVSubscriber_ASDU_getINT64 + SVSubscriber_ASDU_getINT8U + SVSubscriber_ASDU_getINT16U + SVSubscriber_ASDU_getINT32U + SVSubscriber_ASDU_getINT64U + SVSubscriber_ASDU_getFLOAT32 + SVSubscriber_ASDU_getFLOAT64 + SVSubscriber_ASDU_getDataSize + CDC_VSS_create + CDC_VSG_create \ No newline at end of file From 2282741ef663abab3fca317496788590964c5074 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 29 Dec 2017 09:03:09 +0100 Subject: [PATCH 009/123] - updated GOOSE/SV example code --- examples/goose_publisher/goose_publisher_example.c | 10 +++++++++- examples/sv_publisher/sv_publisher_example.c | 5 +++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/goose_publisher/goose_publisher_example.c b/examples/goose_publisher/goose_publisher_example.c index 916be7006..7abf6f57a 100644 --- a/examples/goose_publisher/goose_publisher_example.c +++ b/examples/goose_publisher/goose_publisher_example.c @@ -16,6 +16,14 @@ int main(int argc, char** argv) { + char* interface; + + if (argc > 1) + interface = argv[1]; + else + interface = "eth0"; + + printf("Using interface %s\n", interface); LinkedList dataSetValues = LinkedList_create(); @@ -41,7 +49,7 @@ main(int argc, char** argv) * is NULL the interface name as defined with CONFIG_ETHERNET_INTERFACE_ID in * stack_config.h is used. */ - GoosePublisher publisher = GoosePublisher_create(&gooseCommParameters, NULL); + GoosePublisher publisher = GoosePublisher_create(&gooseCommParameters, interface); GoosePublisher_setGoCbRef(publisher, "simpleIOGenericIO/LLN0$GO$gcbAnalogValues"); GoosePublisher_setConfRev(publisher, 1); diff --git a/examples/sv_publisher/sv_publisher_example.c b/examples/sv_publisher/sv_publisher_example.c index 280dfee1b..1b589f40a 100644 --- a/examples/sv_publisher/sv_publisher_example.c +++ b/examples/sv_publisher/sv_publisher_example.c @@ -46,12 +46,13 @@ main(int argc, char** argv) float fVal1 = 1234.5678f; float fVal2 = 0.12345f; - int i; - while (running) { SVPublisher_ASDU_setFLOAT(asdu1, float1, fVal1); SVPublisher_ASDU_setFLOAT(asdu1, float2, fVal2); + SVPublisher_ASDU_setFLOAT(asdu2, float3, fVal1 * 2); + SVPublisher_ASDU_setFLOAT(asdu2, float4, fVal2 * 2); + SVPublisher_ASDU_increaseSmpCnt(asdu1); SVPublisher_ASDU_increaseSmpCnt(asdu2); From c20a5307615bac6cd5465accb8794334bd872e71 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 29 Dec 2017 11:55:41 +0100 Subject: [PATCH 010/123] - .NET API: Added GooseReceiver/GooseSubscriber --- dotnet/IEC61850forCSharp/GooseControlBlock.cs | 3 + dotnet/IEC61850forCSharp/GooseSubscriber.cs | 313 ++++++++++++++++++ dotnet/IEC61850forCSharp/IEC61850.NET.csproj | 1 + dotnet/dotnet.sln | 6 + dotnet/goose_subscriber/Program.cs | 68 ++++ .../Properties/AssemblyInfo.cs | 27 ++ src/goose/goose_receiver.c | 16 +- 7 files changed, 428 insertions(+), 6 deletions(-) create mode 100644 dotnet/IEC61850forCSharp/GooseSubscriber.cs create mode 100644 dotnet/goose_subscriber/Program.cs create mode 100644 dotnet/goose_subscriber/Properties/AssemblyInfo.cs diff --git a/dotnet/IEC61850forCSharp/GooseControlBlock.cs b/dotnet/IEC61850forCSharp/GooseControlBlock.cs index f2b9c88fa..03df57094 100644 --- a/dotnet/IEC61850forCSharp/GooseControlBlock.cs +++ b/dotnet/IEC61850forCSharp/GooseControlBlock.cs @@ -147,6 +147,9 @@ public void SetCBValues (bool singleRequest) if (flagDatSet) parametersMask += 4; + if (flagDstAddress) + parametersMask += 32; + int error; IedConnection_setGoCBValues (connection, out error, self, parametersMask, singleRequest); diff --git a/dotnet/IEC61850forCSharp/GooseSubscriber.cs b/dotnet/IEC61850forCSharp/GooseSubscriber.cs new file mode 100644 index 000000000..9b2c6e691 --- /dev/null +++ b/dotnet/IEC61850forCSharp/GooseSubscriber.cs @@ -0,0 +1,313 @@ +/* + * GooseSubscriber.cs + * + * Copyright 2017 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +using System; +using System.Runtime.InteropServices; +using IEC61850.Common; + +namespace IEC61850 +{ + namespace GOOSE + { + + namespace Subscriber + { + + /// + /// GOOSE listener. + /// + public delegate void GooseListener (GooseSubscriber report, object parameter); + + public class GooseReceiver : IDisposable + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GooseReceiver_create (); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseReceiver_addSubscriber(IntPtr self, IntPtr subscriber); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseReceiver_removeSubscriber(IntPtr self, IntPtr subscriber); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseReceiver_start(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseReceiver_stop(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool GooseReceiver_isRunning (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseReceiver_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseReceiver_setInterfaceId(IntPtr self, string interfaceId); + + private IntPtr self; + + private bool isDisposed = false; + + public GooseReceiver() + { + self = GooseReceiver_create (); + } + + public void SetInterfaceId(string interfaceId) + { + GooseReceiver_setInterfaceId (self, interfaceId); + } + + public void AddSubscriber(GooseSubscriber subscriber) + { + GooseReceiver_addSubscriber (self, subscriber.self); + } + + public void RemoveSubscriber(GooseSubscriber subscriber) + { + GooseReceiver_removeSubscriber (self, subscriber.self); + } + + public void Start() + { + GooseReceiver_start (self); + } + + public void Stop() + { + GooseReceiver_stop (self); + } + + public bool IsRunning() + { + return GooseReceiver_isRunning (self); + } + + public void Dispose() + { + if (isDisposed == false) { + isDisposed = true; + GooseReceiver_destroy (self); + self = IntPtr.Zero; + } + } + + ~GooseReceiver() + { + Dispose (); + } + } + + + /// + /// Representing a GOOSE subscriber + /// + /// + /// NOTE: After SetListener is called, do not call any function outside of + /// the callback handler! + /// + public class GooseSubscriber : IDisposable + { + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void InternalGooseListener (IntPtr subscriber, IntPtr parameter); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GooseSubscriber_create (string goCbRef, IntPtr dataSetValue); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseSubscriber_setAppId(IntPtr self, UInt16 appId); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool GooseSubscriber_isValid (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt32 GooseSubscriber_getStNum (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt32 GooseSubscriber_getSqNum (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool GooseSubscriber_isTest (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt32 GooseSubscriber_getConfRev (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool GooseSubscriber_needsCommission (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt32 GooseSubscriber_getTimeAllowedToLive (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt64 GooseSubscriber_getTimestamp (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GooseSubscriber_getDataSetValues(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseSubscriber_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void GooseSubscriber_setListener (IntPtr self, InternalGooseListener listener, IntPtr parameter); + + internal IntPtr self; + + private bool isDisposed = false; + + private GooseListener listener = null; + private object listenerParameter = null; + + private event InternalGooseListener internalListener = null; + + private void internalGooseListener (IntPtr subscriber, IntPtr parameter) + { + try { + + if (listener != null) { + listener(this, listenerParameter); + } + + } catch (Exception e) + { + // older versions of mono 2.10 (for linux?) cause this exception + Console.WriteLine(e.Message); + } + } + + public GooseSubscriber(string goCbRef) + { + self = GooseSubscriber_create (goCbRef, IntPtr.Zero); + } + + public void SetAppId(UInt16 appId) + { + GooseSubscriber_setAppId (self, appId); + } + + public bool IsValid () + { + return GooseSubscriber_isValid (self); + } + + + public void SetListener(GooseListener listener, object parameter) + { + this.listener = listener; + this.listenerParameter = parameter; + + if (internalListener == null) { + internalListener = new InternalGooseListener (internalGooseListener); + + GooseSubscriber_setListener (self, internalListener, IntPtr.Zero); + } + } + + public UInt32 GetStNum() + { + return GooseSubscriber_getStNum (self); + } + + public UInt32 GetSqNum() + { + return GooseSubscriber_getSqNum (self); + } + + public bool IsTest() + { + return GooseSubscriber_isTest (self); + } + + public UInt32 GetConfRev() + { + return GooseSubscriber_getConfRev (self); + } + + public bool NeedsCommission() + { + return GooseSubscriber_needsCommission (self); + } + + public UInt32 GetTimeAllowedToLive() + { + return GooseSubscriber_getTimeAllowedToLive (self); + } + + public UInt64 GetTimestamp () + { + return GooseSubscriber_getTimestamp (self); + } + + public DateTimeOffset GetTimestampsDateTimeOffset () + { + UInt64 entryTime = GetTimestamp (); + + DateTimeOffset retVal = new DateTimeOffset (1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + + return retVal.AddMilliseconds (entryTime); + } + + /// + /// Get the values of the GOOSE data set from the last received GOOSE message + /// + /// + /// The MmsValue instance is only valid in the context of the GooseLister callback. + /// Do not store for outside use! + /// + /// The data set values. + public MmsValue GetDataSetValues() + { + IntPtr mmsValueRef = GooseSubscriber_getDataSetValues (self); + + return (new MmsValue (mmsValueRef)); + } + + /// + /// Releases all resource used by the object. + /// + /// > + /// This function has only to be called when the + /// has not been added to the GooseReceiver or has been removed from the GooseReceiver. + /// When the GooseReceiver holds a reference it will take care for releasing the resources. + /// In this case Dispose MUST not be called! Otherwise the natice resources will be + /// released twice. + /// + public void Dispose() + { + if (isDisposed == false) { + isDisposed = true; + GooseSubscriber_destroy (self); + self = IntPtr.Zero; + } + } + + } + + } + + } +} \ No newline at end of file diff --git a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj index e752d2576..61829bc64 100644 --- a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj +++ b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj @@ -48,6 +48,7 @@ + \ No newline at end of file diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln index b199c1aa3..59941c71f 100644 --- a/dotnet/dotnet.sln +++ b/dotnet/dotnet.sln @@ -38,6 +38,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server1", "server1\server1. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_client_example", "tls_client_example\tls_client_example.csproj", "{6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "goose_subscriber", "goose_subscriber\goose_subscriber.csproj", "{1285372C-2E62-494A-A661-8D5D3873318C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +50,10 @@ Global {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 + {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 diff --git a/dotnet/goose_subscriber/Program.cs b/dotnet/goose_subscriber/Program.cs new file mode 100644 index 000000000..df84f0327 --- /dev/null +++ b/dotnet/goose_subscriber/Program.cs @@ -0,0 +1,68 @@ +using System; + +using IEC61850.GOOSE.Subscriber; +using System.Threading; +using IEC61850.Common; + +namespace goose_subscriber +{ + class MainClass + { + private static void gooseListener (GooseSubscriber subscriber, object parameter) + { + Console.WriteLine ("Received GOOSE message:\n-------------------------"); + + Console.WriteLine (" stNum: " + subscriber.GetStNum ()); + + Console.WriteLine (" sqNum: " + subscriber.GetSqNum ()); + + + MmsValue values = subscriber.GetDataSetValues (); + + Console.WriteLine (" values: " +values.Size ().ToString ()); + + foreach (MmsValue value in values) { + Console.WriteLine (" value: " + value.ToString ()); + } + } + + public static void Main (string[] args) + { + Console.WriteLine ("Starting GOOSE subscriber..."); + + GooseReceiver receiver = new GooseReceiver (); + + receiver.SetInterfaceId ("eth0"); + + GooseSubscriber subscriber = new GooseSubscriber ("simpleIOGenericIO/LLN0$GO$gcbAnalogValues"); + + subscriber.SetAppId(1000); + + subscriber.SetListener (gooseListener, null); + + receiver.AddSubscriber (subscriber); + + receiver.Start (); + + if (receiver.IsRunning ()) { + + bool running = true; + + /* run until Ctrl-C is pressed */ + Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { + e.Cancel = true; + running = false; + }; + + while (running) { + Thread.Sleep (100); + } + + receiver.Stop (); + } else + Console.WriteLine ("Failed to start GOOSE receiver. Running as root?"); + + receiver.Dispose (); + } + } +} diff --git a/dotnet/goose_subscriber/Properties/AssemblyInfo.cs b/dotnet/goose_subscriber/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d42b17b9e --- /dev/null +++ b/dotnet/goose_subscriber/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("goose_subscriber")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("mzillgit")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index d18dccd82..e7c70eb9c 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -722,13 +722,16 @@ gooseReceiverLoop(void* threadParameter) GooseReceiver_startThreadless(self); - while (self->running) { + if (self->running) { - if (GooseReceiver_tick(self) == false) - Thread_sleep(1); - } + while (self->running) { + + if (GooseReceiver_tick(self) == false) + Thread_sleep(1); + } - GooseReceiver_stopThreadless(self); + GooseReceiver_stopThreadless(self); + } self->stopped = true; } @@ -815,7 +818,8 @@ GooseReceiver_startThreadless(GooseReceiver self) void GooseReceiver_stopThreadless(GooseReceiver self) { - Ethernet_destroySocket(self->ethSocket); + if (self->ethSocket) + Ethernet_destroySocket(self->ethSocket); self->running = false; } From 99e7b9d9720fe11fb4e656ed5d0c066981c4eb7f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 29 Dec 2017 11:57:14 +0100 Subject: [PATCH 011/123] - added missing project file --- .../goose_subscriber/goose_subscriber.csproj | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 dotnet/goose_subscriber/goose_subscriber.csproj diff --git a/dotnet/goose_subscriber/goose_subscriber.csproj b/dotnet/goose_subscriber/goose_subscriber.csproj new file mode 100644 index 000000000..077b65dfb --- /dev/null +++ b/dotnet/goose_subscriber/goose_subscriber.csproj @@ -0,0 +1,44 @@ + + + + Debug + AnyCPU + {1285372C-2E62-494A-A661-8D5D3873318C} + Exe + goose_subscriber + goose_subscriber + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + full + true + bin\Release + prompt + 4 + true + + + + + + + + + + + + {C35D624E-5506-4560-8074-1728F1FA1A4D} + IEC61850.NET + + + \ No newline at end of file From 8f951cbcef847b9236a67e014130698f469ce379 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 30 Dec 2017 11:40:22 +0100 Subject: [PATCH 012/123] - SV subscriber: added SVReceiver_isRunning function - .NET API: Added SV subscriber --- dotnet/IEC61850forCSharp/IEC61850.NET.csproj | 1 + .../SampledValuesSubscriber.cs | 387 ++++++++++++++++++ src/goose/goose_receiver.h | 9 + src/hal/ethernet/win32/ethernet_win32.c | 6 +- src/sampled_values/sv_subscriber.c | 7 + src/sampled_values/sv_subscriber.h | 12 + src/vs/libiec61850.def | 3 +- 7 files changed, 421 insertions(+), 4 deletions(-) create mode 100644 dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs diff --git a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj index 61829bc64..4ce8fb16a 100644 --- a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj +++ b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj @@ -49,6 +49,7 @@ + \ No newline at end of file diff --git a/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs b/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs new file mode 100644 index 000000000..139bda67c --- /dev/null +++ b/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs @@ -0,0 +1,387 @@ +/* + * SampledValuedSubscriber.cs + * + * Copyright 2017 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +using System; +using System.Runtime.InteropServices; +using IEC61850.Common; + +namespace IEC61850 +{ + namespace SV + { + + namespace Subscriber + { + public class SVReceiver : IDisposable + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SVReceiver_create (); + + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVReceiver_disableDestAddrCheck(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVReceiver_addSubscriber(IntPtr self, IntPtr subscriber); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVReceiver_removeSubscriber(IntPtr self, IntPtr subscriber); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVReceiver_start(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVReceiver_stop(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool SVReceiver_isRunning (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVReceiver_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVReceiver_setInterfaceId(IntPtr self, string interfaceId); + + private IntPtr self; + + private bool isDisposed = false; + + public SVReceiver() + { + self = SVReceiver_create (); + } + + public void SetInterfaceId(string interfaceId) + { + SVReceiver_setInterfaceId (self, interfaceId); + } + + public void DisableDestAddrCheck() + { + SVReceiver_disableDestAddrCheck (self); + } + + public void AddSubscriber(SVSubscriber subscriber) + { + SVReceiver_addSubscriber (self, subscriber.self); + } + + public void RemoveSubscriber(SVSubscriber subscriber) + { + SVReceiver_removeSubscriber (self, subscriber.self); + } + + public void Start() + { + SVReceiver_start (self); + } + + public void Stop() + { + SVReceiver_stop (self); + } + + public bool IsRunning() + { + return SVReceiver_isRunning (self); + } + + public void Dispose() + { + if (isDisposed == false) { + isDisposed = true; + SVReceiver_destroy (self); + self = IntPtr.Zero; + } + } + + ~SVReceiver() + { + Dispose (); + } + } + + + /// + /// SV listener. + /// + public delegate void SVUpdateListener (SVSubscriber report, object parameter, SVSubscriberASDU asdu); + + public class SVSubscriber : IDisposable + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void InternalSVUpdateListener (IntPtr subscriber, IntPtr parameter, IntPtr asdu); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SVSubscriber_create([Out] byte[] ethAddr, UInt16 appID); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVSubscriber_setListener(IntPtr self, InternalSVUpdateListener listener, IntPtr parameter); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void SVSubscriber_destroy(IntPtr self); + + internal IntPtr self; + + private bool isDisposed = false; + + private SVUpdateListener listener; + private object listenerParameter = null; + + private event InternalSVUpdateListener internalListener = null; + + private void internalSVUpdateListener (IntPtr subscriber, IntPtr parameter, IntPtr asdu) + { + try { + + if (listener != null) { + listener(this, listenerParameter, new SVSubscriberASDU(asdu)); + } + + } + catch (Exception e) { + // older versions of mono 2.10 (for linux?) cause this exception + Console.WriteLine(e.Message); + } + } + + public SVSubscriber(byte[] ethAddr, UInt16 appID) + { + if (ethAddr.Length != 6) + throw new ArgumentException ("ethAddr argument has to be of 6 byte size"); + + self = SVSubscriber_create (ethAddr, appID); + } + + public void SetListener(SVUpdateListener listener, object parameter) + { + this.listener = listener; + this.listenerParameter = parameter; + + if (internalListener == null) { + internalListener = new InternalSVUpdateListener (internalSVUpdateListener); + + SVSubscriber_setListener (self, internalListener, IntPtr.Zero); + } + } + + public void Dispose() + { + if (isDisposed == false) { + isDisposed = true; + SVSubscriber_destroy (self); + self = IntPtr.Zero; + } + } + } + + + public class SVSubscriberASDU + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt16 SVSubscriber_ASDU_getSmpCnt(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SVSubscriber_ASDU_getSvId(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SVSubscriber_ASDU_getDatSet(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt32 SVSubscriber_ASDU_getConfRev(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern byte SVSubscriber_ASDU_getSmpMod(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt16 SVSubscriber_ASDU_getSmpRate(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool SVSubscriber_ASDU_hasDatSet(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool SVSubscriber_ASDU_hasRefrTm(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool SVSubscriber_ASDU_hasSmpMod(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private static extern bool SVSubscriber_ASDU_hasSmpRate(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt64 SVSubscriber_ASDU_getRefrTmAsMs(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern sbyte SVSubscriber_ASDU_getINT8(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern Int16 SVSubscriber_ASDU_getINT16(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern Int32 SVSubscriber_ASDU_getINT32(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern Int64 SVSubscriber_ASDU_getINT64(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern byte SVSubscriber_ASDU_getINT8U(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt16 SVSubscriber_ASDU_getINT16U(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt32 SVSubscriber_ASDU_getINT32U(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt64 SVSubscriber_ASDU_getINT64U(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern float SVSubscriber_ASDU_getFLOAT32(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern double SVSubscriber_ASDU_getFLOAT64(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern int SVSubscriber_ASDU_getDataSize(IntPtr self); + + private IntPtr self; + + internal SVSubscriberASDU (IntPtr self) + { + this.self = self; + } + + public UInt16 GetSmpCnt() + { + return SVSubscriber_ASDU_getSmpCnt (self); + } + + public string GetSvId() + { + return Marshal.PtrToStringAnsi (SVSubscriber_ASDU_getSvId(self)); + } + + public string GetDatSet() + { + return Marshal.PtrToStringAnsi (SVSubscriber_ASDU_getDatSet(self)); + } + + public UInt32 GetConfRev() + { + return SVSubscriber_ASDU_getConfRev (self); + } + + public SmpMod GetSmpMod() + { + return (SmpMod) SVSubscriber_ASDU_getSmpMod (self); + } + + public UInt16 GetSmpRate() + { + return (UInt16)SVSubscriber_ASDU_getSmpRate (self); + } + + public bool HasDatSet() + { + return SVSubscriber_ASDU_hasDatSet (self); + } + + public bool HasRefrRm() + { + return SVSubscriber_ASDU_hasRefrTm (self); + } + + public bool HasSmpMod() + { + return SVSubscriber_ASDU_hasSmpMod (self); + } + + public bool HasSmpRate() + { + return SVSubscriber_ASDU_hasSmpRate (self); + } + + public UInt64 GetRefrTmAsMs() + { + return SVSubscriber_ASDU_getRefrTmAsMs (self); + } + + public sbyte GetINT8(int index) + { + return SVSubscriber_ASDU_getINT8 (self, index); + } + + public Int16 GetINT16(int index) + { + return SVSubscriber_ASDU_getINT16 (self, index); + } + + public Int32 GetINT32(int index) + { + return SVSubscriber_ASDU_getINT32 (self, index); + } + + public Int64 GetINT64(int index) + { + return SVSubscriber_ASDU_getINT64 (self, index); + } + + public byte GetINT8U(int index) + { + return SVSubscriber_ASDU_getINT8U (self, index); + } + + public UInt16 GetINT16U(int index) + { + return SVSubscriber_ASDU_getINT16U (self, index); + } + + public UInt32 GetINT32U(int index) + { + return SVSubscriber_ASDU_getINT32U (self, index); + } + + public UInt64 GetINT64U(int index) + { + return SVSubscriber_ASDU_getINT64U (self, index); + } + + /// + /// Gets the size of the payload data in bytes. The payload comprises the data set data. + /// + /// The payload data size in byte + public int GetDataSize() + { + return SVSubscriber_ASDU_getDataSize (self); + } + } + } + + } +} + diff --git a/src/goose/goose_receiver.h b/src/goose/goose_receiver.h index 4995daeab..7ac661d87 100644 --- a/src/goose/goose_receiver.h +++ b/src/goose/goose_receiver.h @@ -103,6 +103,15 @@ GooseReceiver_start(GooseReceiver self); void GooseReceiver_stop(GooseReceiver self); +/** + * \brief Check if GOOSE receiver is running + * + * Can be used to check if \ref GooseReceiver_start has been successful. + * + * \param self the GooseReceiver instance + * + * \return true if GOOSE receiver is running, false otherwise + */ bool GooseReceiver_isRunning(GooseReceiver self); diff --git a/src/hal/ethernet/win32/ethernet_win32.c b/src/hal/ethernet/win32/ethernet_win32.c index 4b4f10ca9..8dca819ce 100644 --- a/src/hal/ethernet/win32/ethernet_win32.c +++ b/src/hal/ethernet/win32/ethernet_win32.c @@ -215,7 +215,7 @@ getInterfaceName(int interfaceIndex) interfaceName = (char*) malloc(strlen(device->name) + 1); strcpy(interfaceName, device->name); if (DEBUG_HAL_ETHERNET) - printf("Use interface (%s)\n", interfaceName); + printf("Use interface (%s)\n", interfaceName); ifaceFound = true; break; } @@ -226,7 +226,7 @@ getInterfaceName(int interfaceIndex) if (!ifaceFound) { if (DEBUG_HAL_ETHERNET) - printf("No ethernet interfaces found! Make sure WinPcap is installed.\n"); + printf("No ethernet interfaces found! Make sure WinPcap is installed.\n"); return NULL; } @@ -269,7 +269,7 @@ getAdapterMacAddress(char* pcapAdapterName, uint8_t* macAddress) if (strstr(pcapAdapterName, pAddress->AdapterName) != 0) { if (DEBUG_HAL_ETHERNET) - printf(" requested found!"); + printf(" requested found!"); for (i = 0; i < (int) addressLength; i++) { macAddress[i] = pAddress->PhysicalAddress[i]; diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index 395540630..8cefa423e 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -185,6 +185,13 @@ SVReceiver_start(SVReceiver self) } } +bool +SVReceiver_isRunning(SVReceiver self) +{ + return self->running; +} + + void SVReceiver_stop(SVReceiver self) { diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index 879736a92..04728cc10 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -194,6 +194,18 @@ SVReceiver_start(SVReceiver self); void SVReceiver_stop(SVReceiver self); +/** + * \brief Check if SV receiver is running + * + * Can be used to check if \ref SVReceiver_start has been successful. + * + * \param self the receiver instance reference + * + * \return true if SV receiver is running, false otherwise + */ +bool +SVReceiver_isRunning(SVReceiver self); + /** * \brief Destroy receiver instance (cleanup resources) * diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 83cee3bc6..98fab54b9 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -697,4 +697,5 @@ EXPORTS SVSubscriber_ASDU_getFLOAT64 SVSubscriber_ASDU_getDataSize CDC_VSS_create - CDC_VSG_create \ No newline at end of file + CDC_VSG_create + SVReceiver_isRunning \ No newline at end of file From 36b0c94eef04921e54b45513fd7e567456974f14 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 30 Dec 2017 14:00:01 +0100 Subject: [PATCH 013/123] - .NET API: added sv_subscriber example - .NET API: added missing functions for float types --- .../SampledValuesSubscriber.cs | 24 ++++++- dotnet/dotnet.sln | 6 ++ dotnet/sv_subscriber/Program.cs | 70 +++++++++++++++++++ .../sv_subscriber/Properties/AssemblyInfo.cs | 27 +++++++ dotnet/sv_subscriber/sv_subscriber.csproj | 44 ++++++++++++ 5 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 dotnet/sv_subscriber/Program.cs create mode 100644 dotnet/sv_subscriber/Properties/AssemblyInfo.cs create mode 100644 dotnet/sv_subscriber/sv_subscriber.csproj diff --git a/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs b/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs index 139bda67c..7375367d4 100644 --- a/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs +++ b/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs @@ -136,6 +136,9 @@ public class SVSubscriber : IDisposable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr SVSubscriber_create([Out] byte[] ethAddr, UInt16 appID); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SVSubscriber_create(IntPtr ethAddr, UInt16 appID); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void SVSubscriber_setListener(IntPtr self, InternalSVUpdateListener listener, IntPtr parameter); @@ -168,10 +171,15 @@ private void internalSVUpdateListener (IntPtr subscriber, IntPtr parameter, IntP public SVSubscriber(byte[] ethAddr, UInt16 appID) { - if (ethAddr.Length != 6) - throw new ArgumentException ("ethAddr argument has to be of 6 byte size"); + if (ethAddr == null) { + self = SVSubscriber_create (IntPtr.Zero, appID); + } else { + + if (ethAddr.Length != 6) + throw new ArgumentException ("ethAddr argument has to be of 6 byte size"); - self = SVSubscriber_create (ethAddr, appID); + self = SVSubscriber_create (ethAddr, appID); + } } public void SetListener(SVUpdateListener listener, object parameter) @@ -371,6 +379,16 @@ public UInt64 GetINT64U(int index) return SVSubscriber_ASDU_getINT64U (self, index); } + public float GetFLOAT32(int index) + { + return SVSubscriber_ASDU_getFLOAT32 (self, index); + } + + public double GetFLOAT64(int index) + { + return SVSubscriber_ASDU_getFLOAT64 (self, index); + } + /// /// Gets the size of the payload data in bytes. The payload comprises the data set data. /// diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln index 59941c71f..067ccaf99 100644 --- a/dotnet/dotnet.sln +++ b/dotnet/dotnet.sln @@ -40,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_client_example", "tls_c EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "goose_subscriber", "goose_subscriber\goose_subscriber.csproj", "{1285372C-2E62-494A-A661-8D5D3873318C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sv_subscriber", "sv_subscriber\sv_subscriber.csproj", "{44651D2D-3252-4FD5-8B8B-5552DBE1B499}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +64,10 @@ Global {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 {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 diff --git a/dotnet/sv_subscriber/Program.cs b/dotnet/sv_subscriber/Program.cs new file mode 100644 index 000000000..9865356da --- /dev/null +++ b/dotnet/sv_subscriber/Program.cs @@ -0,0 +1,70 @@ +using System; + +using IEC61850.SV.Subscriber; +using IEC61850.Common; +using System.Threading; + +namespace sv_subscriber +{ + class MainClass + { + private static void svUpdateListener(SVSubscriber subscriber, object parameter, SVSubscriberASDU asdu) + { + Console.WriteLine ("svUpdateListener called"); + + string svID = asdu.GetSvId (); + + if (svID != null) + Console.WriteLine (" svID=" + svID); + + Console.WriteLine (" smpCnt: " + asdu.GetSmpCnt ()); + Console.WriteLine (" confRev: " + asdu.GetConfRev ()); + + if (asdu.GetDataSize () >= 8) { + Console.WriteLine (" DATA[0]: " + asdu.GetFLOAT32(0)); + Console.WriteLine (" DATA[1]: " + asdu.GetFLOAT32(4)); + } + + } + + public static void Main (string[] args) + { + Console.WriteLine ("Starting SV subscriber"); + + SVReceiver receiver = new SVReceiver (); + + if (args.Length > 0) { + receiver.SetInterfaceId (args [0]); + } + + SVSubscriber subscriber = new SVSubscriber (null, 0x4000); + + subscriber.SetListener (svUpdateListener, null); + + receiver.AddSubscriber (subscriber); + + receiver.Start (); + + if (receiver.IsRunning ()) { + + bool running = true; + + /* run until Ctrl-C is pressed */ + Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { + e.Cancel = true; + running = false; + }; + + while (running) { + Thread.Sleep (100); + } + + receiver.Stop (); + } else + Console.WriteLine ("Failed to start SV receiver. Running as root?"); + + receiver.Dispose (); + + } + } +} diff --git a/dotnet/sv_subscriber/Properties/AssemblyInfo.cs b/dotnet/sv_subscriber/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ae4b1a2a4 --- /dev/null +++ b/dotnet/sv_subscriber/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("sv_subscriber")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("mzillgit")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/dotnet/sv_subscriber/sv_subscriber.csproj b/dotnet/sv_subscriber/sv_subscriber.csproj new file mode 100644 index 000000000..dc8f53fed --- /dev/null +++ b/dotnet/sv_subscriber/sv_subscriber.csproj @@ -0,0 +1,44 @@ + + + + Debug + AnyCPU + {44651D2D-3252-4FD5-8B8B-5552DBE1B499} + Exe + sv_subscriber + sv_subscriber + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + full + true + bin\Release + prompt + 4 + true + + + + + + + + + + + + {C35D624E-5506-4560-8074-1728F1FA1A4D} + IEC61850.NET + + + \ No newline at end of file From 70c311d4337f0854ba33e87e93f2b6be9144d0e0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 30 Dec 2017 14:31:10 +0100 Subject: [PATCH 014/123] - SV: added Timestamp type support --- examples/sv_publisher/sv_publisher_example.c | 8 ++++++ src/sampled_values/sv_publisher.c | 20 ++++++++++++++ src/sampled_values/sv_publisher.h | 28 +++++++++++++++++--- src/sampled_values/sv_subscriber.c | 9 +++++++ src/sampled_values/sv_subscriber.h | 12 +++++++++ src/vs/libiec61850.def | 5 +++- 6 files changed, 77 insertions(+), 5 deletions(-) diff --git a/examples/sv_publisher/sv_publisher_example.c b/examples/sv_publisher/sv_publisher_example.c index 1b589f40a..311b6ff6a 100644 --- a/examples/sv_publisher/sv_publisher_example.c +++ b/examples/sv_publisher/sv_publisher_example.c @@ -35,11 +35,13 @@ main(int argc, char** argv) int float1 = SVPublisher_ASDU_addFLOAT(asdu1); int float2 = SVPublisher_ASDU_addFLOAT(asdu1); + int ts1 = SVPublisher_ASDU_addTimestamp(asdu1); SVPublisher_ASDU asdu2 = SVPublisher_addASDU(svPublisher, "svpub2", NULL, 1); int float3 = SVPublisher_ASDU_addFLOAT(asdu2); int float4 = SVPublisher_ASDU_addFLOAT(asdu2); + int ts2 = SVPublisher_ASDU_addTimestamp(asdu2); SVPublisher_setupComplete(svPublisher); @@ -47,11 +49,17 @@ main(int argc, char** argv) float fVal2 = 0.12345f; while (running) { + Timestamp ts; + Timestamp_clearFlags(&ts); + Timestamp_setTimeInMilliseconds(&ts, Hal_getTimeInMs()); + SVPublisher_ASDU_setFLOAT(asdu1, float1, fVal1); SVPublisher_ASDU_setFLOAT(asdu1, float2, fVal2); + SVPublisher_ASDU_setTimestamp(asdu1, ts1, ts); SVPublisher_ASDU_setFLOAT(asdu2, float3, fVal1 * 2); SVPublisher_ASDU_setFLOAT(asdu2, float4, fVal2 * 2); + SVPublisher_ASDU_setTimestamp(asdu2, ts2, ts); SVPublisher_ASDU_increaseSmpCnt(asdu1); SVPublisher_ASDU_increaseSmpCnt(asdu2); diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index 7c681899e..315417cad 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -607,6 +607,26 @@ SVPublisher_ASDU_setFLOAT64(SVPublisher_ASDU self, int index, double value) } } +int +SVPublisher_ASDU_addTimestamp(SVPublisher_ASDU self) +{ + int index = self->dataSize; + self->dataSize += 8; + return index; +} + +void +SVPublisher_ASDU_setTimestamp(SVPublisher_ASDU self, int index, Timestamp value) +{ + int i; + + uint8_t* buffer = self->_dataBuffer + index; + + for (i = 0; i < 8; i++) { + buffer[i] = value.val[i]; + } +} + uint16_t SVPublisher_ASDU_getSmpCnt(SVPublisher_ASDU self) { diff --git a/src/sampled_values/sv_publisher.h b/src/sampled_values/sv_publisher.h index 82c0cdfcc..d4acc3991 100644 --- a/src/sampled_values/sv_publisher.h +++ b/src/sampled_values/sv_publisher.h @@ -26,6 +26,7 @@ #define LIBIEC61850_SRC_SAMPLED_VALUES_SV_PUBLISHER_H_ #include "libiec61850_platform_includes.h" +#include "iec61850_common.h" #ifdef __cplusplus extern "C" { @@ -187,7 +188,7 @@ void SVPublisher_ASDU_setINT64(SVPublisher_ASDU self, int index, int64_t value); /** - * \brief Reserve memory for a single precission floating point number in the ASDU. + * \brief Reserve memory for a single precision floating point number in the ASDU. * * \param[in] self the Sampled Values ASDU instance. * \return the offset in bytes of the new element within the ASDU data block. @@ -196,7 +197,7 @@ int SVPublisher_ASDU_addFLOAT(SVPublisher_ASDU self); /** - * \brief Set the value of a single precission floating point number in the ASDU. + * \brief Set the value of a single precision floating point number in the ASDU. * * \param[in] self the Sampled Values ASDU instance. * \param[in] index The offset within the data block of the ASDU in bytes. @@ -206,7 +207,7 @@ void SVPublisher_ASDU_setFLOAT(SVPublisher_ASDU self, int index, float value); /** - * \brief Reserve memory for a double precission floating point number in the ASDU. + * \brief Reserve memory for a double precision floating point number in the ASDU. * * \param[in] self the Sampled Values ASDU instance. * \return the offset in bytes of the new element within the ASDU data block. @@ -215,7 +216,7 @@ int SVPublisher_ASDU_addFLOAT64(SVPublisher_ASDU self); /** - * \brief Set the value of a double precission floating pointer number in the ASDU. + * \brief Set the value of a double precision floating pointer number in the ASDU. * * \param[in] self the Sampled Values ASDU instance. * \param[in] index The offset within the data block of the ASDU in bytes. @@ -224,6 +225,25 @@ SVPublisher_ASDU_addFLOAT64(SVPublisher_ASDU self); void SVPublisher_ASDU_setFLOAT64(SVPublisher_ASDU self, int index, double value); +/** + * \brief Reserve memory for a 64 bit time stamp in the ASDU + * + * \param[in] self the Sampled Values ASDU instance. + * \return the offset in bytes of the new element within the ASDU data block. + */ +int +SVPublisher_ASDU_addTimestamp(SVPublisher_ASDU self); + +/** + * \brief Set the value of a 64 bit time stamp in the ASDU. + * + * \param[in] self the Sampled Values ASDU instance. + * \param[in] index The offset within the data block of the ASDU in bytes. + * \param[in] value The value which should be set. + */ +void +SVPublisher_ASDU_setTimestamp(SVPublisher_ASDU self, int index, Timestamp value); + /** * \brief Set the sample count attribute of the ASDU. * diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index 8cefa423e..251c0497b 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -832,6 +832,15 @@ SVSubscriber_ASDU_getFLOAT64(SVSubscriber_ASDU self, int index) return retVal; } +Timestamp +SVSubscriber_ASDU_getTimestamp(SVSubscriber_ASDU self, int index) +{ + Timestamp retVal; + + memcpy(retVal.val, self->dataBuffer + index, sizeof(retVal.val)); + + return retVal; +} int SVSubscriber_ASDU_getDataSize(SVSubscriber_ASDU self) diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index 04728cc10..d6ad938d0 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -25,6 +25,7 @@ #define SAMPLED_VALUES_SV_SUBSCRIBER_H_ #include "libiec61850_common_api.h" +#include "iec61850_common.h" #ifdef __cplusplus extern "C" { @@ -481,6 +482,17 @@ SVSubscriber_ASDU_getFLOAT32(SVSubscriber_ASDU self, int index); double SVSubscriber_ASDU_getFLOAT64(SVSubscriber_ASDU self, int index); +/** + * \brief Get a timestamp data value in the data part of the ASDU + * + * \param self ASDU object instance + * \param index the index (byte position of the start) of the data in the data part + * + * \return SV data + */ +Timestamp +SVSubscriber_ASDU_getTimestamp(SVSubscriber_ASDU self, int index); + /** * \brief Returns the size of the data part of the ASDU * diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 98fab54b9..6df41f0ed 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -698,4 +698,7 @@ EXPORTS SVSubscriber_ASDU_getDataSize CDC_VSS_create CDC_VSG_create - SVReceiver_isRunning \ No newline at end of file + SVReceiver_isRunning + SVSubscriber_ASDU_getTimestamp + SVPublisher_ASDU_addTimestamp + SVPublisher_ASDU_setTimestamp \ No newline at end of file From b00fdfde3dee97d2ebc3a2d644db528c0ad66adf Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 2 Jan 2018 19:26:14 +0100 Subject: [PATCH 015/123] - .NET API: added properties for Quality flags --- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 146 +++++++++++++++++- dotnet/tests/Test.cs | 27 ++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 472054657..daed6660f 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -169,9 +169,26 @@ public enum Validity /// public class Quality { - private UInt16 value; + private const UInt16 QUALITY_DETAIL_OVERFLOW = 4; + private const UInt16 QUALITY_DETAIL_OUT_OF_RANGE = 8; + private const UInt16 QUALITY_DETAIL_BAD_REFERENCE = 16; + private const UInt16 QUALITY_DETAIL_OSCILLATORY = 32; + private const UInt16 QUALITY_DETAIL_FAILURE = 64; + private const UInt16 QUALITY_DETAIL_OLD_DATA = 128; + private const UInt16 QUALITY_DETAIL_INCONSISTENT = 256; + private const UInt16 QUALITY_DETAIL_INACCURATE = 512; + private const UInt16 QUALITY_SOURCE_SUBSTITUTED = 1024; + private const UInt16 QUALITY_TEST = 2048; + private const UInt16 QUALITY_OPERATOR_BLOCKED = 4096; + + public override string ToString () + { + return GetValidity ().ToString (); + + } + public Quality (int bitStringValue) { value = (UInt16)bitStringValue; @@ -195,6 +212,133 @@ public void SetValidity (Validity validity) value += (ushort)validity; } + + public Validity Validity + { + get {return GetValidity ();} + set { SetValidity (value); } + } + + public bool Overflow + { + get { return ((this.value & QUALITY_DETAIL_OVERFLOW) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_OVERFLOW; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_OVERFLOW)); + } + } + + public bool OutOfRange + { + get { return ((this.value & QUALITY_DETAIL_OUT_OF_RANGE) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_OUT_OF_RANGE; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_OUT_OF_RANGE)); + } + } + + public bool BadReference + { + get { return ((this.value & QUALITY_DETAIL_BAD_REFERENCE) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_BAD_REFERENCE; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_BAD_REFERENCE)); + } + } + + public bool Oscillatory + { + get { return ((this.value & QUALITY_DETAIL_OSCILLATORY) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_OSCILLATORY; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_OSCILLATORY)); + } + } + + public bool Failure + { + get { return ((this.value & QUALITY_DETAIL_FAILURE) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_FAILURE; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_FAILURE)); + } + } + + public bool OldData + { + get { return ((this.value & QUALITY_DETAIL_OLD_DATA) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_OLD_DATA; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_OLD_DATA)); + } + } + + public bool Inconsistent + { + get { return ((this.value & QUALITY_DETAIL_INCONSISTENT) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_INCONSISTENT; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_INCONSISTENT)); + } + } + + public bool Inaccurate + { + get { return ((this.value & QUALITY_DETAIL_INACCURATE) != 0);} + set { + if (value) + this.value |= QUALITY_DETAIL_INACCURATE; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DETAIL_INACCURATE)); + } + } + + public bool Substituted + { + get { return ((this.value & QUALITY_SOURCE_SUBSTITUTED) != 0);} + set { + if (value) + this.value |= QUALITY_SOURCE_SUBSTITUTED; + else + this.value = (ushort) ((int) this.value & (~QUALITY_SOURCE_SUBSTITUTED)); + } + } + + public bool Test + { + get { return ((this.value & QUALITY_TEST) != 0);} + set { + if (value) + this.value |= QUALITY_TEST; + else + this.value = (ushort) ((int) this.value & (~QUALITY_TEST)); + } + } + + public bool OperatorBlocked + { + get { return ((this.value & QUALITY_OPERATOR_BLOCKED) != 0);} + set { + if (value) + this.value |= QUALITY_OPERATOR_BLOCKED; + else + this.value = (ushort) ((int) this.value & (~QUALITY_OPERATOR_BLOCKED)); + } + } } /// diff --git a/dotnet/tests/Test.cs b/dotnet/tests/Test.cs index 8ec403eb1..ce22168f9 100644 --- a/dotnet/tests/Test.cs +++ b/dotnet/tests/Test.cs @@ -454,6 +454,33 @@ public void ConnectionHandler() iedServer.Stop (); } + + [Test()] + public void Quality() + { + Quality q = new Quality (); + + Assert.AreEqual (false, q.Overflow); + + q.Overflow = true; + + Assert.AreEqual (true, q.Overflow); + + q.Overflow = false; + + Assert.AreEqual (false, q.Overflow); + + Assert.AreEqual (Validity.GOOD, q.Validity); + + q.Substituted = true; + + Assert.AreEqual (true, q.Substituted); + Assert.AreEqual (false, q.Overflow); + + q.Validity = Validity.QUESTIONABLE; + + Assert.AreEqual (Validity.QUESTIONABLE, q.Validity); + } } } From e63abff71c61b22d33548b3f8eb64ecb8a5db0ea Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 2 Jan 2018 19:51:41 +0100 Subject: [PATCH 016/123] - SV subscriber/publisher: add support for Quality type --- src/sampled_values/sv_publisher.c | 18 ++++++++++++++++++ src/sampled_values/sv_publisher.h | 21 +++++++++++++++++++++ src/sampled_values/sv_subscriber.c | 13 +++++++++++++ src/sampled_values/sv_subscriber.h | 14 ++++++++++++++ src/vs/libiec61850.def | 5 ++++- 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index 315417cad..6bac2cb66 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -627,6 +627,24 @@ SVPublisher_ASDU_setTimestamp(SVPublisher_ASDU self, int index, Timestamp value) } } +int +SVPublisher_ASDU_addQuality(SVPublisher_ASDU self) +{ + int index = self->dataSize; + self->dataSize += 4; + return index; +} + +void +SVPublisher_ASDU_setQuality(SVPublisher_ASDU self, int index, Quality value) +{ + uint8_t* buffer = self->_dataBuffer + index; + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = value / 0x100; + buffer[3] = value % 0x100; +} + uint16_t SVPublisher_ASDU_getSmpCnt(SVPublisher_ASDU self) { diff --git a/src/sampled_values/sv_publisher.h b/src/sampled_values/sv_publisher.h index d4acc3991..d75f5f5e0 100644 --- a/src/sampled_values/sv_publisher.h +++ b/src/sampled_values/sv_publisher.h @@ -244,6 +244,27 @@ SVPublisher_ASDU_addTimestamp(SVPublisher_ASDU self); void SVPublisher_ASDU_setTimestamp(SVPublisher_ASDU self, int index, Timestamp value); +/** + * \brief Reserve memory for a quality value in the ASDU + * + * NOTE: Quality is encoded as BITSTRING (4 byte) + * + * \param[in] self the Sampled Values ASDU instance. + * \return the offset in bytes of the new element within the ASDU data block. + */ +int +SVPublisher_ASDU_addQuality(SVPublisher_ASDU self); + +/** + * \brief Set the value of a quality attribute in the ASDU. + * + * \param[in] self the Sampled Values ASDU instance. + * \param[in] index The offset within the data block of the ASDU in bytes. + * \param[in] value The value which should be set. + */ +void +SVPublisher_ASDU_setQuality(SVPublisher_ASDU self, int index, Quality value); + /** * \brief Set the sample count attribute of the ASDU. * diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index 251c0497b..a895d410b 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -842,6 +842,19 @@ SVSubscriber_ASDU_getTimestamp(SVSubscriber_ASDU self, int index) return retVal; } +Quality +SVSubscriber_ASDU_getQuality(SVSubscriber_ASDU self, int index) +{ + Quality retVal; + + uint8_t* buffer = self->dataBuffer + index; + + retVal = buffer[3]; + retVal += (buffer[2] * 0x100); + + return retVal; +} + int SVSubscriber_ASDU_getDataSize(SVSubscriber_ASDU self) { diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index d6ad938d0..e3760e998 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -74,6 +74,7 @@ typedef struct sEthernetSocket* EthernetSocket; * | TimeStamp | 8 byte | * | EntryTime | 6 byte | * | BITSTRING | 4 byte | + * | Quality | 4 byte | * * The SV subscriber API can be used independent of the IEC 61850 client API. In order to access the SVCB via MMS you * have to use the IEC 61850 client API. Please see \ref ClientSVControlBlock object in section \ref IEC61850_CLIENT_SV. @@ -493,6 +494,19 @@ SVSubscriber_ASDU_getFLOAT64(SVSubscriber_ASDU self, int index); Timestamp SVSubscriber_ASDU_getTimestamp(SVSubscriber_ASDU self, int index); +/** + * \brief Get a quality value in the data part of the ASDU + * + * NOTE: Quality is encoded as BITSTRING (4 byte) + * + * \param self ASDU object instance + * \param index the index (byte position of the start) of the data in the data part + * + * \return SV data + */ +Quality +SVSubscriber_ASDU_getQuality(SVSubscriber_ASDU self, int index); + /** * \brief Returns the size of the data part of the ASDU * diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 6df41f0ed..68309da56 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -701,4 +701,7 @@ EXPORTS SVReceiver_isRunning SVSubscriber_ASDU_getTimestamp SVPublisher_ASDU_addTimestamp - SVPublisher_ASDU_setTimestamp \ No newline at end of file + SVPublisher_ASDU_setTimestamp + SVSubscriber_ASDU_getQuality + SVPublisher_ASDU_addQuality + SVPublisher_ASDU_setQuality \ No newline at end of file From 33fb9206b3ea9034de673dbcdb5071e099ceb5bb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 2 Jan 2018 21:56:23 +0100 Subject: [PATCH 017/123] - .NET API: Added support for Timestamp and Quality to SV subscriber --- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 26 ++++++++++++++++--- .../SampledValuesSubscriber.cs | 26 +++++++++++++++++++ dotnet/sv_subscriber/Program.cs | 8 ++++-- src/iec61850/common/iec61850_common.c | 15 +++++++++++ src/iec61850/inc/iec61850_common.h | 3 +++ src/vs/libiec61850-wo-goose.def | 3 ++- src/vs/libiec61850.def | 3 ++- 7 files changed, 76 insertions(+), 8 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index daed6660f..3c4752f71 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -349,6 +349,9 @@ public class Timestamp [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr Timestamp_create (); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr Timestamp_createFromByteArray(byte[] byteArry); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void Timestamp_destroy (IntPtr self); @@ -399,12 +402,12 @@ public class Timestamp static extern void Timestamp_setByMmsUtcTime (IntPtr self, IntPtr mmsValue); internal IntPtr timestampRef = IntPtr.Zero; - private bool responsableForDeletion; + private bool responsibleForDeletion; internal Timestamp(IntPtr timestampRef, bool selfAllocated) { this.timestampRef = timestampRef; - this.responsableForDeletion = selfAllocated; + this.responsibleForDeletion = selfAllocated; } public Timestamp (DateTime timestamp) : this () @@ -421,12 +424,18 @@ public Timestamp() { timestampRef = Timestamp_create (); LeapSecondKnown = true; - responsableForDeletion = true; + responsibleForDeletion = true; + } + + public Timestamp(byte[] value) + { + timestampRef = Timestamp_createFromByteArray (value); + responsibleForDeletion = true; } ~Timestamp () { - if (responsableForDeletion) + if (responsibleForDeletion) Timestamp_destroy (timestampRef); } @@ -536,6 +545,15 @@ public void SetByMmsUtcTime(MmsValue mmsValue) Timestamp_setByMmsUtcTime (timestampRef, mmsValue.valueReference); } + public DateTime AsDateTime() + { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + DateTime retVal = epoch.AddMilliseconds ((double) GetTimeInMilliseconds ()); + + return retVal; + } + } public enum ACSIClass diff --git a/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs b/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs index 7375367d4..66a9091cf 100644 --- a/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs +++ b/dotnet/IEC61850forCSharp/SampledValuesSubscriber.cs @@ -274,6 +274,9 @@ public class SVSubscriberASDU [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern double SVSubscriber_ASDU_getFLOAT64(IntPtr self, int index); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt16 SVSubscriber_ASDU_getQuality(IntPtr self, int index); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern int SVSubscriber_ASDU_getDataSize(IntPtr self); @@ -389,6 +392,29 @@ public double GetFLOAT64(int index) return SVSubscriber_ASDU_getFLOAT64 (self, index); } + private struct PTimestamp + { + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 8)] + public byte[] val; + } + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern PTimestamp SVSubscriber_ASDU_getTimestamp(IntPtr self, int index); + + public Timestamp GetTimestamp(int index) + { + PTimestamp retVal = SVSubscriber_ASDU_getTimestamp (self, index); + + return new Timestamp (retVal.val); + } + + public Quality GetQuality(int index) + { + UInt16 qValue = SVSubscriber_ASDU_getQuality (self, index); + + return new Quality (qValue); + } + /// /// Gets the size of the payload data in bytes. The payload comprises the data set data. /// diff --git a/dotnet/sv_subscriber/Program.cs b/dotnet/sv_subscriber/Program.cs index 9865356da..17426e6f1 100644 --- a/dotnet/sv_subscriber/Program.cs +++ b/dotnet/sv_subscriber/Program.cs @@ -10,7 +10,7 @@ class MainClass { private static void svUpdateListener(SVSubscriber subscriber, object parameter, SVSubscriberASDU asdu) { - Console.WriteLine ("svUpdateListener called"); + Console.WriteLine ("RECV ASDU:"); string svID = asdu.GetSvId (); @@ -22,7 +22,11 @@ private static void svUpdateListener(SVSubscriber subscriber, object parameter, if (asdu.GetDataSize () >= 8) { Console.WriteLine (" DATA[0]: " + asdu.GetFLOAT32(0)); - Console.WriteLine (" DATA[1]: " + asdu.GetFLOAT32(4)); + Console.WriteLine (" DATA[1]: " + asdu.GetFLOAT32 (4)); + } + + if (asdu.GetDataSize () >= 16) { + Console.WriteLine (" DATA[2]: " + asdu.GetTimestamp (8).AsDateTime().ToString()); } } diff --git a/src/iec61850/common/iec61850_common.c b/src/iec61850/common/iec61850_common.c index d2a689886..8e17c05c6 100644 --- a/src/iec61850/common/iec61850_common.c +++ b/src/iec61850/common/iec61850_common.c @@ -232,6 +232,21 @@ Timestamp_create() return self; } +Timestamp* +Timestamp_createFromByteArray(uint8_t* byteArray) +{ + Timestamp* self = Timestamp_create(); + + if (self) { + int i; + + for (i = 0; i < 8; i++) + self->val[i] = byteArray[i]; + } + + return self; +} + void Timestamp_destroy(Timestamp* self) { diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index a48e8edab..5b5a14e4a 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -356,6 +356,9 @@ typedef union { Timestamp* Timestamp_create(void); +Timestamp* +Timestamp_createFromByteArray(uint8_t* byteArray); + void Timestamp_destroy(Timestamp* self); diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 133e02078..d296c09c0 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -578,4 +578,5 @@ EXPORTS ClientGooseControlBlock_getDstAddress ClientGooseControlBlock_setDstAddress CDC_VSS_create - CDC_VSG_create \ No newline at end of file + CDC_VSG_create + Timestamp_createFromByteArray \ No newline at end of file diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 68309da56..f6a6e8d2c 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -704,4 +704,5 @@ EXPORTS SVPublisher_ASDU_setTimestamp SVSubscriber_ASDU_getQuality SVPublisher_ASDU_addQuality - SVPublisher_ASDU_setQuality \ No newline at end of file + SVPublisher_ASDU_setQuality + Timestamp_createFromByteArray \ No newline at end of file From f9030a8b4f20000e9b0176c45d29fa63bd9cd174 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 11 Jan 2018 18:09:04 +0100 Subject: [PATCH 018/123] - MmsValue_update function now allows adjusting octet-string size of target object --- src/mms/iso_mms/common/mms_value.c | 131 +++++++++++++++-------------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index e0a27ee64..0d6e12c3c 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -223,67 +223,76 @@ MmsValue_equalTypes(const MmsValue* self, const MmsValue* otherValue) bool MmsValue_update(MmsValue* self, const MmsValue* update) { - if (self->type == update->type) { - switch (self->type) { - case MMS_STRUCTURE: - case MMS_ARRAY: - if (updateStructuredComponent(self, update) == false) - return false; - break; - case MMS_BOOLEAN: - self->value.boolean = update->value.boolean; - break; - case MMS_FLOAT: - if (self->value.floatingPoint.formatWidth == update->value.floatingPoint.formatWidth) { - self->value.floatingPoint.exponentWidth = update->value.floatingPoint.exponentWidth; - memcpy(self->value.floatingPoint.buf, update->value.floatingPoint.buf, - self->value.floatingPoint.formatWidth / 8); - } - else return false; - break; - case MMS_INTEGER: - case MMS_UNSIGNED: - if (BerInteger_setFromBerInteger(self->value.integer, update->value.integer)) - return true; - else - return false; - break; - case MMS_UTC_TIME: - memcpy(self->value.utcTime, update->value.utcTime, 8); - break; - case MMS_BIT_STRING: - if (self->value.bitString.size == update->value.bitString.size) - memcpy(self->value.bitString.buf, update->value.bitString.buf, bitStringByteSize(self)); - else return false; - break; - case MMS_OCTET_STRING: - if (self->value.octetString.maxSize == update->value.octetString.maxSize) { - memcpy(self->value.octetString.buf, update->value.octetString.buf, - update->value.octetString.size); - - self->value.octetString.size = update->value.octetString.size; - } - else return false; - break; - case MMS_VISIBLE_STRING: - MmsValue_setVisibleString(self, update->value.visibleString.buf); - break; - case MMS_STRING: - MmsValue_setMmsString(self, update->value.visibleString.buf); - break; - case MMS_BINARY_TIME: - self->value.binaryTime.size = update->value.binaryTime.size; - memcpy(self->value.binaryTime.buf, update->value.binaryTime.buf, - update->value.binaryTime.size); - break; - default: - return false; - break; - } - return true; - } - else - return false; + if (self->type == update->type) { + switch (self->type) { + case MMS_STRUCTURE: + case MMS_ARRAY: + if (updateStructuredComponent(self, update) == false) + return false; + break; + case MMS_BOOLEAN: + self->value.boolean = update->value.boolean; + break; + case MMS_FLOAT: + if (self->value.floatingPoint.formatWidth == update->value.floatingPoint.formatWidth) { + self->value.floatingPoint.exponentWidth = update->value.floatingPoint.exponentWidth; + memcpy(self->value.floatingPoint.buf, update->value.floatingPoint.buf, + self->value.floatingPoint.formatWidth / 8); + } + else + return false; + break; + case MMS_INTEGER: + case MMS_UNSIGNED: + if (BerInteger_setFromBerInteger(self->value.integer, update->value.integer)) + return true; + else + return false; + break; + case MMS_UTC_TIME: + memcpy(self->value.utcTime, update->value.utcTime, 8); + break; + case MMS_BIT_STRING: + if (self->value.bitString.size == update->value.bitString.size) + memcpy(self->value.bitString.buf, update->value.bitString.buf, bitStringByteSize(self)); + else + return false; + break; + case MMS_OCTET_STRING: + { + int size = update->value.octetString.size; + + if (size > self->value.octetString.maxSize) { + GLOBAL_FREEMEM(self->value.octetString.buf); + self->value.octetString.buf = GLOBAL_MALLOC(size); + self->value.octetString.maxSize = size; + } + size = self->value.octetString.maxSize; + + memcpy(self->value.octetString.buf, update->value.octetString.buf, size); + + self->value.octetString.size = size; + } + break; + case MMS_VISIBLE_STRING: + MmsValue_setVisibleString(self, update->value.visibleString.buf); + break; + case MMS_STRING: + MmsValue_setMmsString(self, update->value.visibleString.buf); + break; + case MMS_BINARY_TIME: + self->value.binaryTime.size = update->value.binaryTime.size; + memcpy(self->value.binaryTime.buf, update->value.binaryTime.buf, + update->value.binaryTime.size); + break; + default: + return false; + break; + } + return true; + } + else + return false; } MmsValue* From 5a0315b52b47692ede508fa0320e1b6160748263 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 14 Jan 2018 13:17:20 +0100 Subject: [PATCH 019/123] - MMS client: added mutex for state in IsoClientConnection --- .../client_example1.c | 2 - src/mms/iso_client/iso_client_connection.c | 42 ++++++++++++++++--- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/examples/iec61850_client_example1/client_example1.c b/examples/iec61850_client_example1/client_example1.c index 1142c768c..e20a23276 100644 --- a/examples/iec61850_client_example1/client_example1.c +++ b/examples/iec61850_client_example1/client_example1.c @@ -50,8 +50,6 @@ int main(int argc, char** argv) { if (error == IED_ERROR_OK) { - IedConnection_getServerDirectory(con, &error, false); - /* read an analog measurement value from server */ MmsValue* value = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX); diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c index 242eaa09c..8563270ce 100644 --- a/src/mms/iso_client/iso_client_connection.c +++ b/src/mms/iso_client/iso_client_connection.c @@ -57,7 +57,9 @@ struct sIsoClientConnection { IsoIndicationCallback callback; void* callbackParameter; + volatile int state; + Semaphore stateMutex; Socket socket; @@ -94,13 +96,32 @@ struct sIsoClientConnection Thread thread; }; +static void +setState(IsoClientConnection self, int newState) +{ + Semaphore_wait(self->stateMutex); + self->state = newState; + Semaphore_post(self->stateMutex); +} + +static int +getState(IsoClientConnection self) +{ + int stateVal; + + Semaphore_wait(self->stateMutex); + stateVal = self->state; + Semaphore_post(self->stateMutex); + + return stateVal; +} + static void connectionHandlingThread(IsoClientConnection self) { IsoSessionIndication sessionIndication; self->handlingThreadRunning = true; - self->stopHandlingThread = false; if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT_CONNECTION: new connection %p\n", self); @@ -165,7 +186,7 @@ connectionHandlingThread(IsoClientConnection self) self->callback(ISO_IND_CLOSED, self->callbackParameter, NULL);; - self->state = STATE_IDLE; + setState(self, STATE_IDLE); #if (CONFIG_MMS_SUPPORT_TLS == 1) if (self->cotpConnection->tlsSocket) @@ -215,7 +236,9 @@ IsoClientConnection_create(IsoIndicationCallback callback, void* callbackParamet self->callback = callback; self->callbackParameter = callbackParameter; + self->state = STATE_IDLE; + self->stateMutex = Semaphore_create(1); self->sendBuffer = (uint8_t*) GLOBAL_MALLOC(ISO_CLIENT_BUFFER_SIZE); @@ -248,6 +271,12 @@ IsoClientConnection_create(IsoIndicationCallback callback, void* callbackParamet self->cotpConnection = (CotpConnection*) GLOBAL_CALLOC(1, sizeof(CotpConnection)); + self->handlingThreadRunning = false; + + self->stopHandlingThread = false; + self->destroyHandlingThread = false; + self->startHandlingThread = false; + return self; } @@ -395,7 +424,7 @@ IsoClientConnection_associate(IsoClientConnection self, IsoConnectionParameters /* wait for upper layer to release buffer */ Semaphore_wait(self->receiveBufferMutex); - self->state = STATE_ASSOCIATED; + setState(self, STATE_ASSOCIATED); if (self->thread == NULL) { self->thread = Thread_create(connectionThreadFunction, self, false); @@ -412,7 +441,7 @@ IsoClientConnection_associate(IsoClientConnection self, IsoConnectionParameters returnError: self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); - self->state = STATE_ERROR; + setState(self, STATE_ERROR); Socket_destroy(self->socket); self->socket = NULL; @@ -466,7 +495,7 @@ IsoClientConnection_close(IsoClientConnection self) Thread_sleep(1); } - self->state = STATE_IDLE; + setState(self, STATE_IDLE); } @@ -476,7 +505,7 @@ IsoClientConnection_destroy(IsoClientConnection self) if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: IsoClientConnection_destroy\n"); - if (self->state == STATE_ASSOCIATED) { + if (getState(self) == STATE_ASSOCIATED) { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: call IsoClientConnection_close\n"); @@ -523,6 +552,7 @@ IsoClientConnection_destroy(IsoClientConnection self) Semaphore_destroy(self->receiveBufferMutex); Semaphore_destroy(self->transmitBufferMutex); + Semaphore_destroy(self->stateMutex); GLOBAL_FREEMEM(self->sendBuffer); GLOBAL_FREEMEM(self); From 260d97ae23c19e0f81b7cc35f06d1388f28cba11 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 14 Jan 2018 16:24:17 +0100 Subject: [PATCH 020/123] - MMS client: MmsConnection added mutexes for state variable --- src/mms/inc_private/mms_client_internal.h | 11 +- .../iso_mms/client/mms_client_connection.c | 163 ++++++++++++------ 2 files changed, 123 insertions(+), 51 deletions(-) diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 86253325e..45c64c258 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -79,8 +79,12 @@ struct sMmsConnection { uint32_t connectTimeout; IsoClientConnection isoClient; - AssociationState associationState; - ConnectionState connectionState; + + volatile AssociationState associationState; + Semaphore associationStateLock; + + volatile ConnectionState connectionState; + Semaphore connectionStateLock; MmsConnectionParameters parameters; IsoConnectionParameters isoParameters; @@ -97,7 +101,8 @@ struct sMmsConnection { #endif /* state of an active connection conclude/release process */ - int concludeState; + volatile int concludeState; + Semaphore concludeStateLock; #if (MMS_OBTAIN_FILE_SERVICE == 1) int32_t nextFrsmId; diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index dbab9435d..f088c64e8 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -41,6 +41,66 @@ #define CONFIG_MMS_CONNECTION_DEFAULT_CONNECT_TIMEOUT 10000 #define OUTSTANDING_CALLS 10 +static void +setAssociationState(MmsConnection self, AssociationState newState) +{ + Semaphore_wait(self->associationStateLock); + self->associationState = newState; + Semaphore_post(self->associationStateLock); +} + +static AssociationState +getAssociationState(MmsConnection self) +{ + AssociationState state; + + Semaphore_wait(self->associationStateLock); + state = self->associationState; + Semaphore_post(self->associationStateLock); + + return state; +} + +static void +setConnectionState(MmsConnection self, ConnectionState newState) +{ + Semaphore_wait(self->connectionStateLock); + self->connectionState = newState; + Semaphore_post(self->connectionStateLock); +} + +static ConnectionState +getConnectionState(MmsConnection self) +{ + ConnectionState state; + + Semaphore_wait(self->connectionStateLock); + state = self->connectionState; + Semaphore_post(self->connectionStateLock); + + return state; +} + +static void +setConcludeState(MmsConnection self, int newState) +{ + Semaphore_wait(self->concludeStateLock); + self->concludeState = newState; + Semaphore_post(self->concludeStateLock); +} + +static int +getConcludeState(MmsConnection self) +{ + int state; + + Semaphore_wait(self->concludeStateLock); + state = self->concludeState; + Semaphore_post(self->concludeStateLock); + + return state; +} + static void handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) { @@ -312,7 +372,7 @@ sendRequestAndWaitForResponse(MmsConnection self, uint32_t invokeId, ByteBuffer* while (currentTime < waitUntilTime) { uint32_t receivedInvokeId; - if (self->associationState == MMS_STATE_CLOSED) { + if (getAssociationState(self) == MMS_STATE_CLOSED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto connection_lost; } @@ -690,8 +750,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (indication == ISO_IND_CLOSED) { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: mmsIsoCallback: Connection lost or closed by client!\n"); - self->connectionState = MMS_CON_IDLE; - self->associationState = MMS_STATE_CLOSED; + setConnectionState(self, MMS_CON_IDLE); + setAssociationState(self, MMS_STATE_CLOSED); /* Call user provided callback function */ if (self->connectionLostHandler != NULL) @@ -703,8 +763,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (indication == ISO_IND_ASSOCIATION_FAILED) { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: mmsIsoCallback: association failed!\n"); - self->connectionState = MMS_CON_ASSOCIATION_FAILED; - self->associationState = MMS_STATE_CLOSED; + setConnectionState(self, MMS_CON_ASSOCIATION_FAILED); + setAssociationState(self, MMS_STATE_CLOSED); return; } @@ -730,12 +790,12 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) printf("MMS_CLIENT: MMS-PDU: %02x\n", tag); if (tag == 0xa9) { /* initiate response PDU */ - if (indication == ISO_IND_ASSOCIATION_SUCCESS) { - self->connectionState = MMS_CON_ASSOCIATED; - } - else { - self->connectionState = MMS_CON_ASSOCIATION_FAILED; - } + + if (indication == ISO_IND_ASSOCIATION_SUCCESS) + setConnectionState(self, MMS_CON_ASSOCIATED); + else + setConnectionState(self, MMS_CON_ASSOCIATION_FAILED); + self->lastResponse = payload; IsoClientConnection_releaseReceiveBuffer(self->isoClient); @@ -748,7 +808,7 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received conclude.request\n"); - self->concludeState = CONCLUDE_STATE_REQUESTED; + setConcludeState(self, CONCLUDE_STATE_REQUESTED); /* TODO block all new user requests? */ @@ -758,7 +818,7 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received conclude.reponse+\n"); - self->concludeState = CONCLUDE_STATE_ACCEPTED; + setConcludeState(self, CONCLUDE_STATE_ACCEPTED); IsoClientConnection_release(self->isoClient); @@ -768,7 +828,7 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received conclude.reponse-\n"); - self->concludeState = CONCLUDE_STATE_REJECTED; + setConcludeState(self, CONCLUDE_STATE_REJECTED); IsoClientConnection_releaseReceiveBuffer(self->isoClient); } @@ -1032,6 +1092,10 @@ MmsConnection_create() self->lastResponseLock = Semaphore_create(1); self->outstandingCallsLock = Semaphore_create(1); + self->connectionStateLock = Semaphore_create(1); + self->concludeStateLock = Semaphore_create(1); + self->associationStateLock = Semaphore_create(1); + self->lastResponseError = MMS_ERROR_NONE; self->outstandingCalls = (uint32_t*) GLOBAL_CALLOC(OUTSTANDING_CALLS, sizeof(uint32_t)); @@ -1086,6 +1150,10 @@ MmsConnection_destroy(MmsConnection self) Semaphore_destroy(self->lastResponseLock); Semaphore_destroy(self->outstandingCallsLock); + Semaphore_destroy(self->associationStateLock); + Semaphore_destroy(self->connectionStateLock); + Semaphore_destroy(self->concludeStateLock); + GLOBAL_FREEMEM(self->outstandingCalls); #if (MMS_OBTAIN_FILE_SERVICE == 1) @@ -1193,7 +1261,7 @@ waitForConnectResponse(MmsConnection self) uint64_t waitUntilTime = currentTime + self->requestTimeout; while (currentTime < waitUntilTime) { - if (self->connectionState != MMS_CON_WAITING) + if (getConnectionState(self) != MMS_CON_WAITING) break; Thread_sleep(10); @@ -1232,7 +1300,7 @@ MmsConnection_connect(MmsConnection self, MmsError* mmsError, const char* server } #endif /* (CONFIG_MMS_RAW_MESSAGE_LOGGING == 1) */ - self->connectionState = MMS_CON_WAITING; + setConnectionState(self, MMS_CON_WAITING); IsoClientConnection_associate(self->isoClient, self->isoParameters, payload, self->connectTimeout); @@ -1240,25 +1308,24 @@ MmsConnection_connect(MmsConnection self, MmsError* mmsError, const char* server waitForConnectResponse(self); if (DEBUG_MMS_CLIENT) - printf("MmsConnection_connect: received response conState: %i\n", self->connectionState); + printf("MmsConnection_connect: received response conState: %i\n", getConnectionState(self)); - if (self->connectionState == MMS_CON_ASSOCIATED) { + if (getConnectionState(self) == MMS_CON_ASSOCIATED) { mmsClient_parseInitiateResponse(self); releaseResponse(self); - self->associationState = MMS_STATE_CONNECTED; - } - else { - self->associationState = MMS_STATE_CLOSED; + setAssociationState(self, MMS_STATE_CONNECTED); } + else + setAssociationState(self, MMS_STATE_CLOSED); - self->connectionState = MMS_CON_IDLE; + setConnectionState(self, MMS_CON_IDLE); if (DEBUG_MMS_CLIENT) - printf("MmsConnection_connect: states: con %i ass %i\n", self->connectionState, self->associationState); + printf("MmsConnection_connect: states: con %i ass %i\n", getConnectionState(self), getAssociationState(self)); - if (self->associationState == MMS_STATE_CONNECTED) { + if (getAssociationState(self) == MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_NONE; return true; } @@ -1273,7 +1340,7 @@ MmsConnection_close(MmsConnection self) { self->connectionLostHandler = NULL; - if (self->associationState == MMS_STATE_CONNECTED) + if (getAssociationState(self) == MMS_STATE_CONNECTED) IsoClientConnection_close(self->isoClient); } @@ -1286,7 +1353,7 @@ MmsConnection_abort(MmsConnection self, MmsError* mmsError) bool success = true; - if (self->associationState == MMS_STATE_CONNECTED) + if (getAssociationState(self) == MMS_STATE_CONNECTED) success = IsoClientConnection_abort(self->isoClient); if (success == false) { @@ -1310,16 +1377,16 @@ sendConcludeRequestAndWaitForResponse(MmsConnection self) mmsClient_createConcludeRequest(self, concludeMessage); - self->concludeState = CONCLUDE_STATE_REQUESTED; + setConcludeState(self, CONCLUDE_STATE_REQUESTED); IsoClientConnection_sendMessage(self->isoClient, concludeMessage); while (currentTime < waitUntilTime) { - if (self->associationState == MMS_STATE_CLOSED) + if (getAssociationState(self) == MMS_STATE_CLOSED) goto exit_function; - if (self->concludeState != CONCLUDE_STATE_REQUESTED) { + if (getConcludeState(self) != CONCLUDE_STATE_REQUESTED) { success = true; break; } @@ -1342,7 +1409,7 @@ sendConcludeRequestAndWaitForResponse(MmsConnection self) void MmsConnection_conclude(MmsConnection self, MmsError* mmsError) { - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1356,12 +1423,12 @@ MmsConnection_conclude(MmsConnection self, MmsError* mmsError) releaseResponse(self); - if (self->concludeState != CONCLUDE_STATE_ACCEPTED) { + if (getConcludeState(self) != CONCLUDE_STATE_ACCEPTED) { - if (self->associationState == MMS_STATE_CLOSED) + if (getAssociationState(self) == MMS_STATE_CLOSED) *mmsError = MMS_ERROR_CONNECTION_LOST; - if (self->concludeState == CONCLUDE_STATE_REJECTED) + if (getConcludeState(self) == CONCLUDE_STATE_REJECTED) *mmsError = MMS_ERROR_CONCLUDE_REJECTED; } @@ -1391,7 +1458,7 @@ mmsClient_getNameListSingleRequest( { bool moreFollows = false; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1494,7 +1561,7 @@ MmsConnection_readVariable(MmsConnection self, MmsError* mmsError, { MmsValue* value = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1523,7 +1590,7 @@ MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError, { MmsValue* value = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1552,7 +1619,7 @@ MmsConnection_readMultipleVariables(MmsConnection self, MmsError* mmsError, { MmsValue* value = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1581,7 +1648,7 @@ MmsConnection_readNamedVariableListValues(MmsConnection self, MmsError* mmsError { MmsValue* value = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1612,7 +1679,7 @@ MmsConnection_readNamedVariableListValuesAssociationSpecific( { MmsValue* value = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1641,7 +1708,7 @@ MmsConnection_readNamedVariableListDirectory(MmsConnection self, MmsError* mmsEr { LinkedList attributes = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1671,7 +1738,7 @@ MmsConnection_readNamedVariableListDirectoryAssociationSpecific(MmsConnection se { LinkedList attributes = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1699,7 +1766,7 @@ void MmsConnection_defineNamedVariableList(MmsConnection self, MmsError* mmsError, const char* domainId, const char* listName, LinkedList variableSpecs) { - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1727,7 +1794,7 @@ void MmsConnection_defineNamedVariableListAssociationSpecific(MmsConnection self, MmsError* mmsError, const char* listName, LinkedList variableSpecs) { - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1758,7 +1825,7 @@ MmsConnection_deleteNamedVariableList(MmsConnection self, MmsError* mmsError, { bool isDeleted = false; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1787,7 +1854,7 @@ MmsConnection_deleteAssociationSpecificNamedVariableList(MmsConnection self, { bool isDeleted = false; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1817,7 +1884,7 @@ MmsConnection_getVariableAccessAttributes(MmsConnection self, MmsError* mmsError { MmsVariableSpecification* typeSpec = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -1844,7 +1911,7 @@ MmsConnection_identify(MmsConnection self, MmsError* mmsError) { MmsServerIdentity* identity = NULL; - if (self->associationState != MMS_STATE_CONNECTED) { + if (getAssociationState(self) != MMS_STATE_CONNECTED) { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } From 1b4675841bff0dec439bf356f7038a508d36bb00 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 14 Jan 2018 16:44:52 +0100 Subject: [PATCH 021/123] - rearranged goose_receiver.h and sv_subscriber.h files to avoid type redefinitions --- src/goose/goose_receiver.h | 5 ++--- src/sampled_values/sv_subscriber.h | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/goose/goose_receiver.h b/src/goose/goose_receiver.h index 7ac661d87..4638adbe3 100644 --- a/src/goose/goose_receiver.h +++ b/src/goose/goose_receiver.h @@ -30,15 +30,14 @@ extern "C" { #include -typedef struct sEthernetSocket* EthernetSocket; +#include "hal_ethernet.h" +#include "goose_subscriber.h" /** * \addtogroup goose_api_group */ /**@{*/ -typedef struct sGooseSubscriber* GooseSubscriber; - typedef struct sGooseReceiver* GooseReceiver; /** diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index e3760e998..8bfe10ed9 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -26,13 +26,12 @@ #include "libiec61850_common_api.h" #include "iec61850_common.h" +#include "hal_ethernet.h" #ifdef __cplusplus extern "C" { #endif -typedef struct sEthernetSocket* EthernetSocket; - /** * \defgroup sv_subscriber_api_group IEC 61850 Sampled Values (SV) subscriber API * From 7e43e265a74f73501d61ae233129d2fcd83e2d56 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 14 Jan 2018 17:27:52 +0100 Subject: [PATCH 022/123] - MMS server: added state mutex for IsoServer --- src/mms/iso_server/iso_server.c | 60 ++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index 6799af1c1..a2e0ba84f 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -1,7 +1,7 @@ /* * iso_server.c * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -51,6 +51,11 @@ struct sIsoServer { IsoServerState state; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore stateLock; +#endif + ConnectionIndicationHandler connectionHandler; void* connectionHandlerParameter; @@ -85,6 +90,34 @@ struct sIsoServer { int connectionCounter; }; +static void +setState(IsoServer self, IsoServerState newState) +{ +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->stateLock); +#endif + self->state = newState; +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->stateLock); +#endif +} + +static IsoServerState +getState(IsoServer self) +{ + IsoServerState state; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->stateLock); +#endif + state = self->state; +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->stateLock); +#endif + + return state; +} + #if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) static inline void lockClientConnections(IsoServer self) @@ -303,7 +336,7 @@ setupIsoServer(IsoServer self) self->serverSocket = (Socket) TcpServerSocket_create(self->localIpAddress, self->tcpPort); if (self->serverSocket == NULL) { - self->state = ISO_SVR_STATE_ERROR; + setState(self, ISO_SVR_STATE_ERROR); success = false; goto exit_function; @@ -313,7 +346,7 @@ setupIsoServer(IsoServer self) ServerSocket_listen((ServerSocket) self->serverSocket); - self->state = ISO_SVR_STATE_RUNNING; + setState(self, ISO_SVR_STATE_RUNNING); #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1) if (DEBUG_ISO_SERVER) @@ -453,6 +486,10 @@ IsoServer_create(TLSConfiguration tlsConfiguration) self->tlsConfiguration = tlsConfiguration; +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->stateLock = Semaphore_create(1); +#endif + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) self->openClientConnections = LinkedList_create(); #else @@ -485,7 +522,7 @@ IsoServer_setLocalIpAddress(IsoServer self, const char* ipAddress) IsoServerState IsoServer_getState(IsoServer self) { - return self->state; + return getState(self); } void @@ -552,7 +589,7 @@ IsoServer_startListeningThreadless(IsoServer self) self->serverSocket = NULL; } else { - self->state = ISO_SVR_STATE_RUNNING; + setState(self, ISO_SVR_STATE_RUNNING); if (DEBUG_ISO_SERVER) printf("ISO_SERVER: new iso server (threadless) started\n"); @@ -564,7 +601,7 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) { int result; - if (self->state == ISO_SVR_STATE_RUNNING) { + if (getState(self) == ISO_SVR_STATE_RUNNING) { HandleSet handles; handles = Handleset_new(); @@ -634,14 +671,15 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) void IsoServer_processIncomingMessages(IsoServer self) { - if (self->state == ISO_SVR_STATE_RUNNING) + if (getState(self) == ISO_SVR_STATE_RUNNING) handleIsoConnectionsThreadless(self); } static void stopListening(IsoServer self) { - self->state = ISO_SVR_STATE_STOPPED; + setState(self, ISO_SVR_STATE_STOPPED); + if (self->serverSocket != NULL) { ServerSocket_destroy((ServerSocket) self->serverSocket); self->serverSocket = NULL; @@ -682,7 +720,7 @@ IsoServer_stopListening(IsoServer self) void IsoServer_closeConnection(IsoServer self, IsoConnection isoConnection) { - if (self->state != ISO_SVR_STATE_IDLE) { + if (getState(self) != ISO_SVR_STATE_IDLE) { self->connectionHandler(ISO_CONNECTION_CLOSED, self->connectionHandlerParameter, isoConnection); } @@ -730,6 +768,10 @@ IsoServer_destroy(IsoServer self) Semaphore_destroy(self->openClientConnectionsMutex); #endif +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_destroy(self->stateLock); +#endif + GLOBAL_FREEMEM(self); } From 11f0fa9e2a409d24ba2a0ba049a11c084fc431d3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 19 Jan 2018 17:37:12 +0100 Subject: [PATCH 023/123] - added cast in MmsValue_update --- src/mms/iso_mms/common/mms_value.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 0d6e12c3c..41cf22ec7 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -264,7 +264,7 @@ MmsValue_update(MmsValue* self, const MmsValue* update) if (size > self->value.octetString.maxSize) { GLOBAL_FREEMEM(self->value.octetString.buf); - self->value.octetString.buf = GLOBAL_MALLOC(size); + self->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); self->value.octetString.maxSize = size; } size = self->value.octetString.maxSize; From d5900a9c52d5a7f61a1488abbff15b6b8f37eaa3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 25 Jan 2018 10:23:19 +0100 Subject: [PATCH 024/123] - cmake: fixed CMakeList.txt to allow to build the library as submodule --- CMakeLists.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 095e70063..6386e1014 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,17 +78,17 @@ option(DEBUG_HAL_ETHERNET "Enable Ethernet HAL printf debugging" ${DEBUG}) include_directories( ${CMAKE_CURRENT_BINARY_DIR}/config - src/common/inc - src/goose - src/sampled_values - src/hal/inc - src/iec61850/inc - src/iec61850/inc_private - src/mms/inc - src/mms/inc_private - src/mms/iso_mms/asn1c - src/logging - src/tls + ${CMAKE_CURRENT_LIST_DIR}/src/common/inc + ${CMAKE_CURRENT_LIST_DIR}/src/goose + ${CMAKE_CURRENT_LIST_DIR}/src/sampled_values + ${CMAKE_CURRENT_LIST_DIR}/src/hal/inc + ${CMAKE_CURRENT_LIST_DIR}/src/iec61850/inc + ${CMAKE_CURRENT_LIST_DIR}/src/iec61850/inc_private + ${CMAKE_CURRENT_LIST_DIR}/src/mms/inc + ${CMAKE_CURRENT_LIST_DIR}/src/mms/inc_private + ${CMAKE_CURRENT_LIST_DIR}/src/mms/iso_mms/asn1c + ${CMAKE_CURRENT_LIST_DIR}/src/logging + ${CMAKE_CURRENT_LIST_DIR}/src/tls ) set(API_HEADERS @@ -134,7 +134,7 @@ set(API_HEADERS if(MSVC) include_directories( - src/vs + ${CMAKE_CURRENT_LIST_DIR}/src/vs ) endif(MSVC) @@ -144,8 +144,8 @@ endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0) if(WITH_MBEDTLS) include_directories( - src/tls/mbedtls - third_party/mbedtls/mbedtls-2.6.0/include + ${CMAKE_CURRENT_LIST_DIR}/src/tls/mbedtls + ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0/include ) file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0/library/*.c) From 76cfa4637806792c05f8e95df50d15e841f1501a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 26 Jan 2018 07:57:10 +0100 Subject: [PATCH 025/123] - fixed cmake file - added function IedModel_getDeviceByIndex --- CMakeLists.txt | 8 ++++---- src/iec61850/inc/iec61850_model.h | 11 +++++++++++ src/iec61850/server/model/model.c | 27 ++++++++++++++++++++++++--- src/vs/libiec61850-wo-goose.def | 3 ++- src/vs/libiec61850.def | 3 ++- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6386e1014..690ac7bff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,10 +91,10 @@ include_directories( ${CMAKE_CURRENT_LIST_DIR}/src/tls ) -set(API_HEADERS - src/hal/inc/hal_time.h +set(API_HEADERS + src/hal/inc/hal_time.h src/hal/inc/hal_thread.h - src/hal/inc/hal_filesystem.h + src/hal/inc/hal_filesystem.h src/hal/inc/hal_ethernet.h src/hal/inc/platform_endian.h src/common/inc/libiec61850_common_api.h @@ -162,7 +162,7 @@ configure_file( ) if(BUILD_EXAMPLES) - add_subdirectory(examples) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/examples) endif(BUILD_EXAMPLES) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/src) diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index d4fbcb76e..1828db19a 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -468,6 +468,17 @@ IedModel_getModelNodeByShortAddress(IedModel* self, uint32_t shortAddress); LogicalDevice* IedModel_getDeviceByInst(IedModel* self, const char* ldInst); +/** + * \brief Lookup logical device (LD) instance by index + * + * \param self IedModel instance + * \param index the index of the LD in the range (0 .. number of LDs - 1) + * + * \return the corresponding LogicalDevice* object or NULL if the index is out of range + */ +LogicalDevice* +IedModel_getDeviceByIndex(IedModel* self, int index); + /** * \brief Lookup a logical node by name that is part of the given logical device diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 6c709b95b..0b5f84b7f 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -75,12 +75,12 @@ IedModel_setAttributeValuesToNull(IedModel* iedModel) int -IedModel_getLogicalDeviceCount(IedModel* iedModel) +IedModel_getLogicalDeviceCount(IedModel* self) { - if (iedModel->firstChild == NULL) + if (self->firstChild == NULL) return 0; - LogicalDevice* logicalDevice = iedModel->firstChild; + LogicalDevice* logicalDevice = self->firstChild; int ldCount = 1; @@ -165,6 +165,27 @@ IedModel_getDeviceByInst(IedModel* self, const char* ldInst) return NULL; } + +LogicalDevice* +IedModel_getDeviceByIndex(IedModel* self, int index) +{ + LogicalDevice* logicalDevice = self->firstChild; + + int currentIndex = 0; + + while (logicalDevice) { + + if (currentIndex == index) + return logicalDevice; + + currentIndex++; + + logicalDevice = (LogicalDevice*) logicalDevice->sibling; + } + + return NULL; +} + static DataAttribute* ModelNode_getDataAttributeByMmsValue(ModelNode* self, MmsValue* value) { diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index d296c09c0..f6c247ea4 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -579,4 +579,5 @@ EXPORTS ClientGooseControlBlock_setDstAddress CDC_VSS_create CDC_VSG_create - Timestamp_createFromByteArray \ No newline at end of file + Timestamp_createFromByteArray + IedModel_getDeviceByIndex \ No newline at end of file diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index f6a6e8d2c..b3289f793 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -705,4 +705,5 @@ EXPORTS SVSubscriber_ASDU_getQuality SVPublisher_ASDU_addQuality SVPublisher_ASDU_setQuality - Timestamp_createFromByteArray \ No newline at end of file + Timestamp_createFromByteArray + IedModel_getDeviceByIndex \ No newline at end of file From d45e729ecfffc450604d64a9c9c375fae7a01be0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 28 Jan 2018 20:34:43 +0100 Subject: [PATCH 026/123] - MMS client/server: added missing length field checks to increase decoder stability --- examples/server_example_basic_io/Makefile | 1 + src/mms/iso_acse/acse.c | 33 +++++++++++- .../iso_mms/client/mms_client_connection.c | 9 +++- src/mms/iso_mms/client/mms_client_files.c | 25 ++++++++- src/mms/iso_mms/client/mms_client_identify.c | 1 + src/mms/iso_mms/client/mms_client_journals.c | 18 ++++--- src/mms/iso_mms/client/mms_client_status.c | 1 + src/mms/iso_mms/client/mms_client_write.c | 6 +++ src/mms/iso_mms/server/mms_access_result.c | 2 +- src/mms/iso_mms/server/mms_file_service.c | 2 + .../iso_mms/server/mms_get_namelist_service.c | 5 ++ src/mms/iso_mms/server/mms_journal_service.c | 26 ++++++++- src/mms/iso_presentation/iso_presentation.c | 54 +++++++++++++++++-- 13 files changed, 163 insertions(+), 20 deletions(-) diff --git a/examples/server_example_basic_io/Makefile b/examples/server_example_basic_io/Makefile index 4e4e59560..ed1ce6a17 100644 --- a/examples/server_example_basic_io/Makefile +++ b/examples/server_example_basic_io/Makefile @@ -22,6 +22,7 @@ model: $(PROJECT_ICD_FILE) $(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + mkdir -p vmd-filestore $(CP) $(PROJECT_BINARY_NAME) vmd-filestore/IEDSERVER.BIN clean: diff --git a/src/mms/iso_acse/acse.c b/src/mms/iso_acse/acse.c index 92ba37a16..8e319592c 100644 --- a/src/mms/iso_acse/acse.c +++ b/src/mms/iso_acse/acse.c @@ -126,6 +126,11 @@ parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxB bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) { + *userInfoValid = false; + return -1; + } + switch (tag) { case 0x02: /* indirect-reference */ @@ -178,6 +183,8 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) int len; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) + return ACSE_ERROR; switch (tag) { case 0xa1: /* application context name */ @@ -188,6 +195,9 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) bufPos++; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) + return ACSE_ERROR; + result = BerDecoder_decodeUint32(buffer, len, bufPos); bufPos += len; @@ -206,8 +216,12 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) bufPos++; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) + return ACSE_ERROR; bufPos = parseUserInformation(self, buffer, bufPos, bufPos + len, &userInfoValid); + if (bufPos < 0) + return ACSE_ERROR; } break; @@ -247,8 +261,7 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); if (bufPos < 0) { - if (DEBUG_ACSE) - printf("ACSE: parseAarqPdu: user info invalid!\n"); + if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); return ACSE_ASSOCIATE_FAILED; } @@ -302,7 +315,13 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) case 0xac: /* authentication value */ bufPos++; + bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) { + if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); + return ACSE_ASSOCIATE_FAILED; + } + authValueLen = len; authValue = buffer + bufPos; bufPos += len; @@ -318,7 +337,17 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) { + if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); + return ACSE_ASSOCIATE_FAILED; + } + bufPos = parseUserInformation(self, buffer, bufPos, bufPos + len, &userInfoValid); + + if (bufPos < 0) { + if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); + return ACSE_ASSOCIATE_FAILED; + } } break; diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index f088c64e8..5dd45e581 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -651,8 +651,8 @@ mmsMsg_parseConfirmedErrorPDU(uint8_t* buffer, int bufPos, int maxBufPos, uint32 while (bufPos < endPos) { tag = buffer[bufPos++]; - bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); if (bufPos < 0) goto exit_error; @@ -709,8 +709,8 @@ mmsMsg_parseRejectPDU(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t* invo while (bufPos < endPos) { tag = buffer[bufPos++]; - bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); if (bufPos < 0) goto exit_error; @@ -949,7 +949,10 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) int bufPos = 1; int length; + bufPos = BerDecoder_decodeLength(buf, &length, bufPos, payload->size); + if (bufPos == -1) + goto exit_with_error; uint32_t invokeId; @@ -965,6 +968,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) } bufPos = BerDecoder_decodeLength(buf, &length, bufPos, payload->size); + if (bufPos == -1) + goto exit_with_error; if (extendedTag) { switch(nestedTag) { diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index 1eb79890e..e0886fcec 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -463,7 +463,13 @@ parseDirectoryEntry(uint8_t* buffer, int bufPos, int maxBufPos, MmsFileDirectory while (bufPos < maxBufPos) { uint8_t tag = buffer[bufPos++]; int length; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: message contains unknown tag!\n"); + return false; + } switch (tag) { case 0xa0: /* file-name */ @@ -471,7 +477,14 @@ parseDirectoryEntry(uint8_t* buffer, int bufPos, int maxBufPos, MmsFileDirectory filename = fileNameMemory; tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: message contains unknown tag!\n"); + return false; + } + memcpy(filename, buffer + bufPos, length); filename[length] = 0; @@ -485,7 +498,7 @@ parseDirectoryEntry(uint8_t* buffer, int bufPos, int maxBufPos, MmsFileDirectory default: bufPos += length; if (DEBUG_MMS_CLIENT) - printf("mmsClient_parseFileDirectoryResponse: message contains unknown tag!\n"); + printf("MMS_CLIENT: message contains unknown tag!\n"); return false; } @@ -511,7 +524,6 @@ parseListOfDirectoryEntries(uint8_t* buffer, int bufPos, int maxBufPos, int length; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if (bufPos < 0) return false; int endPos = bufPos + length; @@ -524,7 +536,9 @@ parseListOfDirectoryEntries(uint8_t* buffer, int bufPos, int maxBufPos, while (bufPos < endPos) { tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) return false; switch (tag) { case 0x30: /* Sequence */ @@ -583,7 +597,9 @@ mmsClient_parseFileDirectoryResponse(MmsConnection self, MmsFileDirectoryHandler while (bufPos < endPos) { tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) return false; switch (tag) { case 0xa0: /* listOfDirectoryEntries */ @@ -641,7 +657,10 @@ mmsMsg_parseFileOpenResponse(uint8_t* buffer, int bufPos, int maxBufPos, int32_t while (bufPos < endPos) { tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) + return false; switch (tag) { case 0x80: /* frsmId */ @@ -704,6 +723,8 @@ mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, int frs while (bufPos < endPos) { tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) + return false; switch (tag) { case 0x80: /* fileData */ diff --git a/src/mms/iso_mms/client/mms_client_identify.c b/src/mms/iso_mms/client/mms_client_identify.c index ea8519f06..dade11dc9 100644 --- a/src/mms/iso_mms/client/mms_client_identify.c +++ b/src/mms/iso_mms/client/mms_client_identify.c @@ -81,6 +81,7 @@ mmsClient_parseIdentifyResponse(MmsConnection self) tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) goto exit_error; switch (tag) { case 0x80: /* vendorName */ diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c index cf59685e3..89ffebba7 100644 --- a/src/mms/iso_mms/client/mms_client_journals.c +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -42,11 +42,13 @@ parseJournalVariable(uint8_t* buffer, int bufPos, int maxLength, MmsJournalVaria while (bufPos < maxBufPos) { - int length; uint8_t tag = buffer[bufPos++]; + + int length; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if ((bufPos < 0) || ((bufPos + length) > maxBufPos)) { /* check length field for validity */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); @@ -94,7 +96,7 @@ parseJournalVariables(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntr uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if ((bufPos < 0) || ((bufPos + length) > maxBufPos)) { /* check length field for validity */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); @@ -137,7 +139,7 @@ parseData(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEnt uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if ((bufPos < 0) || ((bufPos + length) > maxBufPos)) { /* check length field for validity */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); @@ -175,7 +177,7 @@ parseEntryContent(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry jo uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if ((bufPos < 0) ||((bufPos + length) > maxBufPos)) { /* check length field for validity */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); @@ -276,7 +278,7 @@ parseListOfJournalEntries(uint8_t* buffer, int bufPos, int maxLength, LinkedList uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if ((bufPos < 0) || ((bufPos + length) > maxBufPos)) { /* check length field for validity */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); @@ -330,8 +332,7 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked } bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if (bufPos < 0) - return false; + if (bufPos < 0) return false; int endPos = bufPos + length; @@ -346,6 +347,7 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked while (bufPos < endPos) { tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) return false; switch (tag) { case 0xa0: /* listOfJournalEntry */ diff --git a/src/mms/iso_mms/client/mms_client_status.c b/src/mms/iso_mms/client/mms_client_status.c index c693d2b49..808535874 100644 --- a/src/mms/iso_mms/client/mms_client_status.c +++ b/src/mms/iso_mms/client/mms_client_status.c @@ -82,6 +82,7 @@ mmsClient_parseStatusResponse(MmsConnection self, int* vmdLogicalStatus, int* vm while (bufPos < endPos) { tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) goto exit_error; switch (tag) { case 0x80: /* vmdLogicalStatus */ diff --git a/src/mms/iso_mms/client/mms_client_write.c b/src/mms/iso_mms/client/mms_client_write.c index ed0c6665c..3a6ecbb4b 100644 --- a/src/mms/iso_mms/client/mms_client_write.c +++ b/src/mms/iso_mms/client/mms_client_write.c @@ -169,6 +169,12 @@ mmsClient_parseWriteResponse(ByteBuffer* message, int32_t bufPos, MmsError* mmsE if (tag == 0x80) { bufPos = BerDecoder_decodeLength(buf, &length, bufPos, size); + if (bufPos == -1) { + *mmsError = MMS_ERROR_PARSING_RESPONSE; + retVal = DATA_ACCESS_ERROR_UNKNOWN; + goto exit_function; + } + uint32_t dataAccessErrorCode = BerDecoder_decodeUint32(buf, length, bufPos); diff --git a/src/mms/iso_mms/server/mms_access_result.c b/src/mms/iso_mms/server/mms_access_result.c index 0dafbf7d1..5ee4936c0 100644 --- a/src/mms/iso_mms/server/mms_access_result.c +++ b/src/mms/iso_mms/server/mms_access_result.c @@ -107,7 +107,7 @@ getNumberOfElements(uint8_t* buffer, int bufPos, int elementLength) bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, elementEndBufPos); - if (bufPos + elementLength > elementEndBufPos) { + if ((bufPos < 0) || (bufPos + elementLength > elementEndBufPos)) { goto exit_with_error; } diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index a9f9b7c5c..092e260c0 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -250,6 +250,8 @@ mmsServer_handleFileDeleteRequest( int length; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos == -1) + goto exit_reject_invalid_pdu; if (length > 255) { mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index 676a1964d..6f4f2e271 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -472,6 +472,11 @@ mmsServer_handleGetNameListRequest( uint8_t objectScopeTag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + switch (objectScopeTag) { case 0x80: /* vmd-specific */ objectScope = OBJECT_SCOPE_VMD; diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index 890adce85..6008f955a 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -271,6 +271,11 @@ mmsServer_handleReadJournalRequest( uint8_t objectIdTag = requestBuffer[bufPos++]; bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + if (bufPos < 0) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + switch (objectIdTag) { case 0xa1: /* domain-specific */ @@ -298,13 +303,18 @@ mmsServer_handleReadJournalRequest( case 0xa1: /* rangeStartSpecification */ { uint8_t subTag = requestBuffer[bufPos++]; - bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); if (subTag != 0x80) { mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); return; } + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (bufPos < 0) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } if ((length == 4) || (length == 6)) { @@ -328,13 +338,19 @@ mmsServer_handleReadJournalRequest( case 0xa2: /* rangeStopSpecification */ { uint8_t subTag = requestBuffer[bufPos++]; - bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); if (subTag != 0x80) { mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); return; } + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (bufPos < 0) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + if ((length == 4) || (length == 6)) { rangeStop.type = MMS_BINARY_TIME; rangeStop.value.binaryTime.size = length; @@ -359,8 +375,14 @@ mmsServer_handleReadJournalRequest( while (bufPos < maxSubBufPos) { uint8_t subTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + if (bufPos < 0) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + switch (subTag) { case 0x80: /* timeSpecification */ diff --git a/src/mms/iso_presentation/iso_presentation.c b/src/mms/iso_presentation/iso_presentation.c index 1aab91381..01f4bd5b6 100644 --- a/src/mms/iso_presentation/iso_presentation.c +++ b/src/mms/iso_presentation/iso_presentation.c @@ -210,14 +210,14 @@ parseFullyEncodedData(IsoPresentation* self, uint8_t* buffer, int len, int bufPo bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, endPos); - endPos = bufPos + len; - if (bufPos < 0) { if (DEBUG_PRES) printf("PRES: wrong parameter length\n"); return -1; } + endPos = bufPos + len; + while (bufPos < endPos) { uint8_t tag = buffer[bufPos++]; int length; @@ -285,6 +285,12 @@ parsePCDLEntry(IsoPresentation* self, uint8_t* buffer, int totalLength, int bufP bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, endPos); + if (bufPos < 0) { + if (DEBUG_PRES) + printf("PRES: Invalid PDU\n"); + return -1; + } + switch (tag) { case 0x02: /* presentation-context-identifier */ contextId = BerDecoder_decodeUint32(buffer, len, bufPos); @@ -357,6 +363,8 @@ parsePresentationContextDefinitionList(IsoPresentation* self, uint8_t* buffer, i int len; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, endPos); + if (bufPos < 0) + return -1; switch (tag) { case 0x30: @@ -452,6 +460,12 @@ IsoPresentation_parseAcceptMessage(IsoPresentation* self, ByteBuffer* byteBuffer bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) { + if (DEBUG_PRES) + printf("PRES: Invalid message\n"); + return 0; + } + while (bufPos < maxBufPos) { uint8_t tag = buffer[bufPos++]; @@ -572,11 +586,23 @@ IsoPresentation_parseUserData(IsoPresentation* self, ByteBuffer* readBuffer) bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, length); + if (bufPos < 0) { + if (DEBUG_PRES) + printf("PRES: invalid message!\n"); + return 0; + } + if (buffer[bufPos++] != 0x30) return 0; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, length); + if (bufPos < 0) { + if (DEBUG_PRES) + printf("PRES: invalid message!\n"); + return 0; + } + if (buffer[bufPos++] != 0x02) return 0; @@ -592,6 +618,12 @@ IsoPresentation_parseUserData(IsoPresentation* self, ByteBuffer* readBuffer) bufPos = BerDecoder_decodeLength(buffer, &userDataLength, bufPos, length); + if (bufPos < 0) { + if (DEBUG_PRES) + printf("PRES: invalid message!\n"); + return 0; + } + ByteBuffer_wrap(&(self->nextPayload), buffer + bufPos, userDataLength, userDataLength); return 1; @@ -617,6 +649,12 @@ IsoPresentation_parseConnect(IsoPresentation* self, ByteBuffer* byteBuffer) bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + if (bufPos < 0) { + if (DEBUG_PRES) + printf("PRES: invalid message!\n"); + return 0; + } + if (DEBUG_PRES) printf("PRES: CPType with len %i\n", len); @@ -627,7 +665,7 @@ IsoPresentation_parseConnect(IsoPresentation* self, ByteBuffer* byteBuffer) if (bufPos < 0) { if (DEBUG_PRES) - printf("PRES: wrong parameter length\n"); + printf("PRES: invalid message!\n"); return 0; } @@ -639,10 +677,20 @@ IsoPresentation_parseConnect(IsoPresentation* self, ByteBuffer* byteBuffer) printf("PRES: mode-value of wrong type!\n"); return 0; } + bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); + + if (bufPos < 0) { + if (DEBUG_PRES) + printf("PRES: invalid message!\n"); + return 0; + } + uint32_t modeSelector = BerDecoder_decodeUint32(buffer, len, bufPos); + if (DEBUG_PRES) printf("PRES: modesel %ui\n", modeSelector); + bufPos += len; } break; From 3a2ad2c4efefb2a3d3572bc4eca8c4efaf80622e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 29 Jan 2018 21:07:29 +0100 Subject: [PATCH 027/123] - refactored TLS API (moved TLSSocket in a separate private header file) --- src/mms/inc_private/acse.h | 2 +- src/mms/inc_private/cotp.h | 2 +- src/tls/tls_api.h | 60 +------------------------- src/tls/tls_socket.h | 88 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 61 deletions(-) create mode 100644 src/tls/tls_socket.h diff --git a/src/mms/inc_private/acse.h b/src/mms/inc_private/acse.h index 3f966d00d..6ad56342a 100644 --- a/src/mms/inc_private/acse.h +++ b/src/mms/inc_private/acse.h @@ -26,7 +26,7 @@ #include "byte_buffer.h" #include "buffer_chain.h" #include "iso_connection_parameters.h" -#include "tls_api.h" +#include "tls_socket.h" #ifndef ACSE_H_ #define ACSE_H_ diff --git a/src/mms/inc_private/cotp.h b/src/mms/inc_private/cotp.h index 4b6346b72..188d52826 100644 --- a/src/mms/inc_private/cotp.h +++ b/src/mms/inc_private/cotp.h @@ -29,7 +29,7 @@ #include "buffer_chain.h" #include "hal_socket.h" #include "iso_connection_parameters.h" -#include "tls_api.h" +#include "tls_socket.h" typedef struct { TSelector tSelSrc; diff --git a/src/tls/tls_api.h b/src/tls/tls_api.h index 4b0cde9c1..28bc7304e 100644 --- a/src/tls/tls_api.h +++ b/src/tls/tls_api.h @@ -15,7 +15,7 @@ #ifndef SRC_TLS_TLS_API_H_ #define SRC_TLS_TLS_API_H_ -#include "hal_socket.h" +#include #ifdef __cplusplus extern "C" { @@ -23,8 +23,6 @@ extern "C" { typedef struct sTLSConfiguration* TLSConfiguration; -typedef struct sTLSSocket* TLSSocket; - /** * \brief Create a new \ref TLSConfiguration object to represent TLS configuration and certificates * @@ -108,62 +106,6 @@ TLSConfiguration_setRenegotiationTime(TLSConfiguration self, int timeInMs); void TLSConfiguration_destroy(TLSConfiguration self); -TLSSocket -TLSSocket_create(Socket socket, TLSConfiguration configuration, bool storeClientCert); - -/** - * \brief Perform a new TLS handshake/session renegotiation - */ -bool -TLSSocket_performHandshake(TLSSocket self); - -/** - * \brief Access the certificate used by the peer - * - * \param[out] certSize the size of the certificate in bytes - * - * \return the certificate byte buffer - */ -uint8_t* -TLSSocket_getPeerCertificate(TLSSocket self, int* certSize); - -/** - * \brief read from socket to local buffer (non-blocking) - * - * The function shall return immediately if no data is available. In this case - * the function returns 0. If an error happens the function shall return -1. - * - * Implementation of this function is MANDATORY - * - * NOTE: The behaviour of this function changed with version 0.8! - * - * \param self the client, connection or server socket instance - * \param buf the buffer where the read bytes are copied to - * \param size the maximum number of bytes to read (size of the provided buffer) - * - * \return the number of bytes read or -1 if an error occurred - */ -int -TLSSocket_read(TLSSocket self, uint8_t* buf, int size); - -/** - * \brief send a message through the socket - * - * Implementation of this function is MANDATORY - * - * \param self client, connection or server socket instance - * - * \return number of bytes transmitted of -1 in case of an error - */ -int -TLSSocket_write(TLSSocket self, uint8_t* buf, int size); - -/** - * \brief Close the TLS socket and release all resources - */ -void -TLSSocket_close(TLSSocket self); - #ifdef __cplusplus } #endif diff --git a/src/tls/tls_socket.h b/src/tls/tls_socket.h new file mode 100644 index 000000000..009a9906e --- /dev/null +++ b/src/tls/tls_socket.h @@ -0,0 +1,88 @@ +/* + * tls_socket.h + * + * TLS API for TCP/IP protocol stacks + * + * Copyright 2017 MZ Automation GmbH + * + * Abstraction layer for different TLS implementations + * + * Implementation connects the TLS API layer with the socket API layer + * and performs all TLS tasks like handshake, encryption/decryption. + * + */ + +#ifndef SRC_TLS_TLS_SOCKET_H_ +#define SRC_TLS_TLS_SOCKET_H_ + +#include "tls_api.h" + +#include "hal_socket.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct sTLSSocket* TLSSocket; + +TLSSocket +TLSSocket_create(Socket socket, TLSConfiguration configuration, bool storeClientCert); + +/** + * \brief Perform a new TLS handshake/session renegotiation + */ +bool +TLSSocket_performHandshake(TLSSocket self); + +/** + * \brief Access the certificate used by the peer + * + * \param[out] certSize the size of the certificate in bytes + * + * \return the certificate byte buffer + */ +uint8_t* +TLSSocket_getPeerCertificate(TLSSocket self, int* certSize); + +/** + * \brief read from socket to local buffer (non-blocking) + * + * The function shall return immediately if no data is available. In this case + * the function returns 0. If an error happens the function shall return -1. + * + * Implementation of this function is MANDATORY + * + * NOTE: The behaviour of this function changed with version 0.8! + * + * \param self the client, connection or server socket instance + * \param buf the buffer where the read bytes are copied to + * \param size the maximum number of bytes to read (size of the provided buffer) + * + * \return the number of bytes read or -1 if an error occurred + */ +int +TLSSocket_read(TLSSocket self, uint8_t* buf, int size); + +/** + * \brief send a message through the socket + * + * Implementation of this function is MANDATORY + * + * \param self client, connection or server socket instance + * + * \return number of bytes transmitted of -1 in case of an error + */ +int +TLSSocket_write(TLSSocket self, uint8_t* buf, int size); + +/** + * \brief Close the TLS socket and release all resources + */ +void +TLSSocket_close(TLSSocket self); + +#ifdef __cplusplus +} +#endif + +#endif /* SRC_TLS_TLS_SOCKET_H_ */ From 6c218db778e2528616aa184da7329c909ee4dbc0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 12 Feb 2018 22:06:36 +0100 Subject: [PATCH 028/123] - .NET API: added TLSConfiguration destructor --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 4 ++-- dotnet/IEC61850forCSharp/MmsValue.cs | 8 ++++++-- dotnet/IEC61850forCSharp/TLS.cs | 12 +++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index fc08dd8f0..0e245f6d8 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -474,9 +474,9 @@ static extern IntPtr ReportControlBlock_create(string name, IntPtr parent, strin public IntPtr self = IntPtr.Zero; public ReportControlBlock(string name, LogicalNode parent, string rptId, bool isBuffered, - string dataSetName, uint confRef, byte trgOps, byte options, uint bufTm, uint intgPd) + string dataSetName, uint confRev, byte trgOps, byte options, uint bufTm, uint intgPd) { - self = ReportControlBlock_create(name, parent.self, rptId, isBuffered, dataSetName, confRef, trgOps, options, bufTm, intgPd); + self = ReportControlBlock_create(name, parent.self, rptId, isBuffered, dataSetName, confRev, trgOps, options, bufTm, intgPd); } } diff --git a/dotnet/IEC61850forCSharp/MmsValue.cs b/dotnet/IEC61850forCSharp/MmsValue.cs index ed9787ab7..ee9de699e 100644 --- a/dotnet/IEC61850forCSharp/MmsValue.cs +++ b/dotnet/IEC61850forCSharp/MmsValue.cs @@ -430,9 +430,13 @@ public MmsValue GetElement (int index) public MmsDataAccessError GetDataAccessError () { - int errorCode = MmsValue_getDataAccessError (valueReference); + if (GetType () == MmsType.MMS_DATA_ACCESS_ERROR) { + int errorCode = MmsValue_getDataAccessError (valueReference); - return (MmsDataAccessError)errorCode; + return (MmsDataAccessError)errorCode; + } + else + throw new MmsValueException ("Value is of wrong type"); } /// diff --git a/dotnet/IEC61850forCSharp/TLS.cs b/dotnet/IEC61850forCSharp/TLS.cs index a10d4c231..9beccc4b2 100644 --- a/dotnet/IEC61850forCSharp/TLS.cs +++ b/dotnet/IEC61850forCSharp/TLS.cs @@ -90,6 +90,11 @@ public TLSConfiguration() { self = TLSConfiguration_create (); } + ~TLSConfiguration() + { + Dispose (); + } + internal IntPtr GetNativeInstance() { return self; @@ -197,7 +202,12 @@ public void SetOwnKey (X509Certificate2 key, string password) public void Dispose() { - TLSConfiguration_destroy (self); + lock (self) { + if (self != IntPtr.Zero) { + TLSConfiguration_destroy (self); + self = IntPtr.Zero; + } + } } } From 49208df7f4941c76581c1842e3ae61463b26904d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 12 Feb 2018 22:08:19 +0100 Subject: [PATCH 029/123] - removed ATTRIBUTE_PACKED --- src/iec61850/inc/iec61850_client.h | 2 ++ src/iec61850/inc/iec61850_server.h | 2 ++ src/mms/inc/mms_common.h | 2 +- src/mms/inc/mms_types.h | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 0cecd640d..c033dea09 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1823,6 +1823,8 @@ IedConnection_getDataDirectoryByFC(IedConnection self, IedClientError* error, co * This function can be used to get the MMS variable type specification for an IEC 61850 data attribute. It is an extension * of the ACSI that may be required by generic client applications. * + * NOTE: API user is responsible to free the resources (see \ref MmsVariableSpecification_destroy) + * * \param self the connection object * \param error the error code if an error occurs * \param dataAttributeReference string that represents the DA reference diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 5a5c66d19..8fdd91eea 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -856,6 +856,8 @@ typedef enum { * a control operation has been invoked by the client. This callback function is * intended to perform the static tests. It should check if the interlock conditions * are met if the interlockCheck parameter is true. + * This handler can also be check if the client has the required permissions to execute the + * operation and allow or deny the operation accordingly. * * \param parameter the parameter that was specified when setting the control handler * \param ctlVal the control value of the control operation. diff --git a/src/mms/inc/mms_common.h b/src/mms/inc/mms_common.h index 59d172b4c..ed5a88365 100644 --- a/src/mms/inc/mms_common.h +++ b/src/mms/inc/mms_common.h @@ -102,7 +102,7 @@ typedef enum } MmsError; -typedef enum ATTRIBUTE_PACKED +typedef enum { /*! this represents all MMS array types (arrays contain uniform elements) */ MMS_ARRAY = 0, diff --git a/src/mms/inc/mms_types.h b/src/mms/inc/mms_types.h index 61fdab484..b26896205 100644 --- a/src/mms/inc/mms_types.h +++ b/src/mms/inc/mms_types.h @@ -26,7 +26,7 @@ #include "libiec61850_common_api.h" -typedef enum ATTRIBUTE_PACKED { +typedef enum { MMS_VALUE_NO_RESPONSE, MMS_VALUE_OK, MMS_VALUE_ACCESS_DENIED, From 17c66c20cdd7ee8a620887b9487179114fd0fe9e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 12 Feb 2018 22:25:16 +0100 Subject: [PATCH 030/123] - fixed some header inclusions --- src/tls/mbedtls/tls_mbedtls.c | 1 + src/tls/tls_api.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/tls/mbedtls/tls_mbedtls.c b/src/tls/mbedtls/tls_mbedtls.c index 3c40098a8..c026f2a44 100644 --- a/src/tls/mbedtls/tls_mbedtls.c +++ b/src/tls/mbedtls/tls_mbedtls.c @@ -12,6 +12,7 @@ #include #include "tls_api.h" +#include "tls_socket.h" #include "hal_thread.h" #include "lib_memory.h" #include "linked_list.h" diff --git a/src/tls/tls_api.h b/src/tls/tls_api.h index 28bc7304e..e23923695 100644 --- a/src/tls/tls_api.h +++ b/src/tls/tls_api.h @@ -16,6 +16,7 @@ #define SRC_TLS_TLS_API_H_ #include +#include #ifdef __cplusplus extern "C" { From 58057f89d2ec9863345688254cbebe031823de39 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 18 Feb 2018 20:55:50 +0100 Subject: [PATCH 031/123] - SV publisher: some code cleanup --- src/sampled_values/sv_publisher.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index 6bac2cb66..91bdeb30b 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -66,8 +66,6 @@ struct sSVPublisher_ASDU { SVPublisher_ASDU _next; }; - - struct sSVPublisher { uint8_t* buffer; uint16_t appId; @@ -79,9 +77,7 @@ struct sSVPublisher { int payloadLength; /* length of payload buffer */ int asduCount; /* number of ASDUs in the APDU */ - SVPublisher_ASDU asduLIst; - - + SVPublisher_ASDU asduList; }; @@ -288,7 +284,7 @@ SVPublisher_create(CommParameters* parameters, const char* interfaceId) { SVPublisher self = (SVPublisher) GLOBAL_CALLOC(1, sizeof(struct sSVPublisher)); - self->asduLIst = NULL; + self->asduList = NULL; preparePacketBuffer(self, parameters, interfaceId); @@ -307,10 +303,10 @@ SVPublisher_addASDU(SVPublisher self, const char* svID, const char* datset, uint newAsdu->_next = NULL; /* append new ASDU to list */ - if (self->asduLIst == NULL) - self->asduLIst = newAsdu; + if (self->asduList == NULL) + self->asduList = newAsdu; else { - SVPublisher_ASDU lastAsdu = self->asduLIst; + SVPublisher_ASDU lastAsdu = self->asduList; while (lastAsdu->_next != NULL) lastAsdu = lastAsdu->_next; @@ -376,8 +372,6 @@ SVPublisher_ASDU_encodeToBuffer(SVPublisher_ASDU self, uint8_t* buffer, int bufP if (self->datset != NULL) bufPos = BerEncoder_encodeStringWithTag(0x81, self->datset, buffer, bufPos); - //uint8_t octetString[4]; - /* SmpCnt */ bufPos = BerEncoder_encodeTL(0x82, 2, buffer, bufPos); self->smpCntBuf = buffer + bufPos; @@ -408,7 +402,7 @@ SVPublisher_ASDU_encodeToBuffer(SVPublisher_ASDU self, uint8_t* buffer, int bufP self->_dataBuffer = buffer + bufPos; - bufPos += self->dataSize; /* data has to inserted by user before sending message */ + bufPos += self->dataSize; /* data has to be inserted by user before sending message */ /* SmpMod */ if (self->hasSmpMod) { @@ -425,7 +419,7 @@ SVPublisher_setupComplete(SVPublisher self) int numberOfAsdu = 0; /* determine number of ASDUs and length of all ASDUs */ - SVPublisher_ASDU nextAsdu = self->asduLIst; + SVPublisher_ASDU nextAsdu = self->asduList; int totalASDULength = 0; while (nextAsdu != NULL) { @@ -456,7 +450,7 @@ SVPublisher_setupComplete(SVPublisher self) /* seqASDU */ bufPos = BerEncoder_encodeTL(0xa2, totalASDULength, buffer, bufPos); - nextAsdu = self->asduLIst; + nextAsdu = self->asduList; while (nextAsdu != NULL) { bufPos = SVPublisher_ASDU_encodeToBuffer(nextAsdu, buffer, bufPos); @@ -486,7 +480,6 @@ SVPublisher_publish(SVPublisher self) printf("SV_PUBLISHER: send SV message\n"); Ethernet_sendPacket(self->ethernetSocket, self->buffer, self->payloadStart + self->payloadLength); - } From 64b589ac314e97f84d62bcd457fc50ee1e5fe76d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 7 Mar 2018 14:18:43 +0100 Subject: [PATCH 032/123] - IEC 61850 server: added support for OptFlds and TrgOps basic data types (required for service tracking) --- src/iec61850/inc/iec61850_model.h | 6 +++++- src/iec61850/server/mms_mapping/mms_mapping.c | 9 +++++++++ src/iec61850/server/model/dynamic_model.c | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 1828db19a..86e640217 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -117,7 +117,9 @@ typedef enum { IEC61850_CONSTRUCTED = 27, IEC61850_ENTRY_TIME = 28, IEC61850_PHYCOMADDR = 29, - IEC61850_CURRENCY = 30 + IEC61850_CURRENCY = 30, + IEC61850_OPTFLDS = 31, /* bit-string(10) */ + IEC61850_TRGOPS = 32 /* bit-string(6) */ #if (CONFIG_IEC61850_USE_COMPAT_TYPE_DECLARATIONS == 1) @@ -153,6 +155,8 @@ typedef enum { ENTRY_TIME = 28, PHYCOMADDR = 29, CURRENCY = 30 + OPTFLDS = 31, + TRGOPS = 32 #endif } DataAttributeType; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 9a9e11cc3..13ad7057d 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -267,6 +267,15 @@ createNamedVariableFromDataAttribute(DataAttribute* attribute) case IEC61850_PHYCOMADDR: MmsMapping_createPhyComAddrStructure(namedVariable); break; + case IEC61850_OPTFLDS: + namedVariable->typeSpec.bitString = 10; + namedVariable->type = MMS_BIT_STRING; + break; + case IEC61850_TRGOPS: + namedVariable->typeSpec.bitString = 6; + namedVariable->type = MMS_BIT_STRING; + break; + default: if (DEBUG_IED_SERVER) printf("MMS-MAPPING: type cannot be mapped %i\n", attribute->type); diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 13185edf6..a1de52ede 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -681,6 +681,8 @@ DataSetEntry_create(DataSet* dataSet, const char* variable, int index, const cha self->sibling = NULL; + self->value = NULL; + DataSet_addEntry(dataSet, self); return self; From fe9d292d29cf7d5877f6ef55ffca7ed1f3b903fe Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 8 Mar 2018 09:13:05 +0100 Subject: [PATCH 033/123] - java-tools: added support for OptFlds and TrgOps basic data types --- src/iec61850/inc/iec61850_dynamic_model.h | 3 +++ src/iec61850/server/impl/ied_server.c | 2 +- .../server/model/config_file_parser.c | 10 ++++++++++ tools/model_generator/genmodel.jar | Bin 88749 -> 88824 bytes .../libiec61850/scl/model/AttributeType.java | 8 +++++--- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index ee011dbee..78c9ee76f 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -303,6 +303,9 @@ DataSetEntry_getNext(DataSetEntry* self); * that have to contain the LN name, the FC and subsequent path elements separated by "$" instead of ".". * This is due to efficiency reasons to avoid the creation of additional strings. * + * If the variable parameter does not contain a logical device name (separated from the remaining variable + * name by the "/" character) the logical device where the data set resides is used automatically. + * * \param dataSet the data set to which the new entry will be added * \param variable the name of the variable as MMS variable name including FC ("$" used as separator!) * \param index the index if the FCDA is an array element, otherwise -1 diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 72f07d45a..ff1d65046 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -1,7 +1,7 @@ /* * ied_server.c * - * Copyright 2013-2016 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index c63a802d3..a05d79dd8 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -392,6 +392,16 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) } break; + case IEC61850_OPTFLDS: + case IEC61850_TRGOPS: + { + int value; + if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newBitString(2); + MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value); + } + break; + default: break; diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index 6c45543a5955fce6e250f35cd2c183409d70b2e4..f7894e4e2d325cfe2619296276cba16ddc371573 100644 GIT binary patch delta 4834 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 Date: Fri, 9 Mar 2018 21:00:31 +0100 Subject: [PATCH 034/123] - client: fixed bug in parsing initiate response message --- src/mms/iso_mms/client/mms_client_initiate.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/mms/iso_mms/client/mms_client_initiate.c b/src/mms/iso_mms/client/mms_client_initiate.c index d6c61892f..10810ec50 100644 --- a/src/mms/iso_mms/client/mms_client_initiate.c +++ b/src/mms/iso_mms/client/mms_client_initiate.c @@ -169,20 +169,30 @@ mmsClient_parseInitiateResponse(MmsConnection self) self->parameters.maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED; self->parameters.maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING; - int bufPos = 0; + int bufPos = 1; /* ignore tag - already checked */ + int maxBufPos = ByteBuffer_getSize(self->lastResponse); uint8_t* buffer = ByteBuffer_getBuffer(self->lastResponse); + int length; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if (bufPos < 0) + return false; + + if (bufPos + length > maxBufPos) + return false; + while (bufPos < maxBufPos) { uint8_t tag = buffer[bufPos++]; - int length; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); - if (bufPos < 0) { - // TODO write initiate error PDU! + if (bufPos < 0) + return false; + + if (bufPos + length > maxBufPos) return false; - } switch (tag) { case 0x80: /* local-detail-calling */ From 02a330e4146141545acbe0c73d2a46b63b14614c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 18 Mar 2018 12:08:44 +0100 Subject: [PATCH 035/123] - IEC 61850 server: added memory alignement for buffered reporting --- make/target_system.mk | 4 +-- src/common/inc/simple_allocator.h | 3 ++ src/common/simple_allocator.c | 6 ++-- src/iec61850/server/mms_mapping/reporting.c | 29 ++++++++--------- src/mms/iso_mms/common/mms_value.c | 35 +++++++++++---------- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/make/target_system.mk b/make/target_system.mk index b01e98873..107f9b0bf 100644 --- a/make/target_system.mk +++ b/make/target_system.mk @@ -1,7 +1,7 @@ UNAME := $(shell uname) MIPSEL_TOOLCHAIN_PREFIX=mipsel-openwrt-linux- -ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabihf- +ARM_TOOLCHAIN_PREFIX=arm-linux- #ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi- #ARM_TOOLCHAIN_PREFIX=arm-poky-linux-gnueabi- #ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi- @@ -57,7 +57,7 @@ endif ifeq ($(TARGET), LINUX-ARM) TOOLCHAIN_PREFIX=$(ARM_TOOLCHAIN_PREFIX) -CFLAGS += -mno-unaligned-access +#CFLAGS += -mno-unaligned-access #CFLAGS += -mcpu=arm926ej-s endif diff --git a/src/common/inc/simple_allocator.h b/src/common/inc/simple_allocator.h index 7158ac7b4..59da7ce45 100644 --- a/src/common/inc/simple_allocator.h +++ b/src/common/inc/simple_allocator.h @@ -33,6 +33,9 @@ typedef struct { void MemoryAllocator_init(MemoryAllocator* self, char* memoryBlock, int size); +int +MemoryAllocator_getAlignedSize(int size); + char* MemoryAllocator_allocate(MemoryAllocator* self, int size); diff --git a/src/common/simple_allocator.c b/src/common/simple_allocator.c index 89c994033..2c4c5a248 100644 --- a/src/common/simple_allocator.c +++ b/src/common/simple_allocator.c @@ -32,8 +32,8 @@ MemoryAllocator_init(MemoryAllocator* self, char* memoryBlock, int size) self->size = size; } -static int -getAlignedSize(int size) +int inline +MemoryAllocator_getAlignedSize(int size) { if ((size % sizeof(void*)) > 0) return sizeof(void*) * ((size + sizeof(void*) - 1) / sizeof(void*)); @@ -44,7 +44,7 @@ getAlignedSize(int size) char* MemoryAllocator_allocate(MemoryAllocator* self, int size) { - size = getAlignedSize(size); + size = MemoryAllocator_getAlignedSize(size); if (((self->currentPtr - self->memoryBlock) + size) <= self->size) { char* ptr = self->currentPtr; diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index d5eae3e86..48ad2c7f6 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1688,9 +1688,9 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ ReportBuffer* buffer = reportControl->reportBuffer; /* calculate size of complete buffer entry */ - int bufferEntrySize = sizeof(ReportBufferEntry); + int bufferEntrySize = MemoryAllocator_getAlignedSize(sizeof(ReportBufferEntry)); - int inclusionFieldSize = MmsValue_getBitStringByteSize(reportControl->inclusionField); + int inclusionFieldSize = MemoryAllocator_getAlignedSize(MmsValue_getBitStringByteSize(reportControl->inclusionField)); MmsValue inclusionFieldStatic; @@ -1708,7 +1708,7 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) { assert(dataSetEntry != NULL); - bufferEntrySize += 1; /* reason-for-inclusion */ + bufferEntrySize += MemoryAllocator_getAlignedSize(1); /* reason-for-inclusion */ bufferEntrySize += MmsValue_getSizeInMemory(dataSetEntry->value); @@ -1723,7 +1723,7 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) { if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) { - bufferEntrySize += 1; /* reason-for-inclusion */ + bufferEntrySize += MemoryAllocator_getAlignedSize(1); /* reason-for-inclusion */ assert(reportControl->bufferedDataSetValues[i] != NULL); @@ -1946,12 +1946,9 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ else entry->flags = 0; - if ((bufferEntrySize % sizeof(void*)) > 0) - bufferEntrySize = sizeof(void*) * ((bufferEntrySize + sizeof(void*) - 1) / sizeof(void*)); + entry->entryLength = MemoryAllocator_getAlignedSize(bufferEntrySize); - entry->entryLength = bufferEntrySize; - - entryBufPos += sizeof(ReportBufferEntry); + entryBufPos += MemoryAllocator_getAlignedSize(sizeof(ReportBufferEntry)); if (isIntegrity || isGI) { DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas; @@ -1963,7 +1960,7 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ assert(dataSetEntry != NULL); *entryBufPos = (uint8_t) reportControl->inclusionFlags[i]; - entryBufPos++; + entryBufPos += MemoryAllocator_getAlignedSize(1); entryBufPos = MmsValue_cloneToBuffer(dataSetEntry->value, entryBufPos); @@ -1985,7 +1982,7 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ assert(reportControl->bufferedDataSetValues[i] != NULL); *entryBufPos = (uint8_t) reportControl->inclusionFlags[i]; - entryBufPos++; + entryBufPos += MemoryAllocator_getAlignedSize(1); entryBufPos = MmsValue_cloneToBuffer(reportControl->bufferedDataSetValues[i], entryBufPos); @@ -2078,7 +2075,7 @@ sendNextReportEntry(ReportControl* self) MmsValue* inclusionField = &inclusionFieldStack; if (report->flags == 0) - currentReportBufferPos += MmsValue_getBitStringByteSize(inclusionField); + currentReportBufferPos += MemoryAllocator_getAlignedSize(MmsValue_getBitStringByteSize(inclusionField)); else { inclusionFieldStack.value.bitString.buf = (uint8_t*) MemoryAllocator_allocate(&ma, MmsValue_getBitStringByteSize(inclusionField)); @@ -2244,7 +2241,7 @@ sendNextReportEntry(ReportControl* self) for (i = 0; i < self->dataSet->elementCount; i++) { if (report->flags > 0) { - currentReportBufferPos++; + currentReportBufferPos += MemoryAllocator_getAlignedSize(1);; if (MemAllocLinkedList_add(reportElements, currentReportBufferPos) == NULL) goto return_out_of_memory; @@ -2252,7 +2249,7 @@ sendNextReportEntry(ReportControl* self) } else { if (MmsValue_getBitStringBit(inclusionField, i)) { - currentReportBufferPos++; + currentReportBufferPos += MemoryAllocator_getAlignedSize(1);; if (MemAllocLinkedList_add(reportElements, currentReportBufferPos) == NULL) goto return_out_of_memory; currentReportBufferPos += MmsValue_getSizeInMemory((MmsValue*) currentReportBufferPos); @@ -2289,7 +2286,7 @@ sendNextReportEntry(ReportControl* self) if (MemAllocLinkedList_add(reportElements, reason) == NULL) goto return_out_of_memory; - currentReportBufferPos++; + currentReportBufferPos += MemoryAllocator_getAlignedSize(1); MmsValue* dataSetElement = (MmsValue*) currentReportBufferPos; @@ -2324,7 +2321,7 @@ sendNextReportEntry(ReportControl* self) break; } - currentReportBufferPos++; + currentReportBufferPos += MemoryAllocator_getAlignedSize(1); MmsValue* dataSetElement = (MmsValue*) currentReportBufferPos; diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 41cf22ec7..4bf62ca74 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -1,7 +1,7 @@ /* * mms_value.c * - * Copyright 2013-2016 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -32,6 +32,8 @@ #include "conversions.h" +#include "simple_allocator.h" + #include /* for ctime_r */ static inline int @@ -962,13 +964,13 @@ MmsValue_toUnixTimestamp(const MmsValue* self) int MmsValue_getSizeInMemory(const MmsValue* self) { - int memorySize = sizeof(MmsValue); + int memorySize = MemoryAllocator_getAlignedSize(sizeof(MmsValue)); switch(self->type) { case MMS_ARRAY: case MMS_STRUCTURE: { - memorySize += (sizeof(MmsValue*) * self->value.structure.size); + memorySize += (MemoryAllocator_getAlignedSize(sizeof(MmsValue*)) * self->value.structure.size); int i; for (i = 0; i < self->value.structure.size; i++) @@ -977,27 +979,26 @@ MmsValue_getSizeInMemory(const MmsValue* self) break; case MMS_BIT_STRING: - memorySize += bitStringByteSize(self); + memorySize += MemoryAllocator_getAlignedSize(bitStringByteSize(self)); break; case MMS_INTEGER: case MMS_UNSIGNED: - memorySize += sizeof(Asn1PrimitiveValue); - memorySize += self->value.integer->maxSize; + memorySize += MemoryAllocator_getAlignedSize(sizeof(Asn1PrimitiveValue)); + memorySize += MemoryAllocator_getAlignedSize(self->value.integer->maxSize); break; case MMS_FLOAT: - memorySize += (self->value.floatingPoint.formatWidth / 8); + memorySize += MemoryAllocator_getAlignedSize(self->value.floatingPoint.formatWidth / 8); break; case MMS_OCTET_STRING: - memorySize += self->value.octetString.maxSize; + memorySize += MemoryAllocator_getAlignedSize(self->value.octetString.maxSize); break; case MMS_STRING: case MMS_VISIBLE_STRING: - memorySize += strlen(self->value.visibleString.buf); - memorySize += 1; /* add space for 0 character */ + memorySize += MemoryAllocator_getAlignedSize(strlen(self->value.visibleString.buf) + 1); /* add space for 0 character */ break; default: @@ -1013,7 +1014,7 @@ MmsValue_cloneToBuffer(const MmsValue* self, uint8_t* destinationAddress) MmsValue* newValue = (MmsValue*) destinationAddress; memcpy(destinationAddress, self, sizeof(MmsValue)); - destinationAddress += sizeof(MmsValue); + destinationAddress += MemoryAllocator_getAlignedSize(sizeof(MmsValue)); switch (self->type) { case MMS_ARRAY: @@ -1033,7 +1034,7 @@ MmsValue_cloneToBuffer(const MmsValue* self, uint8_t* destinationAddress) case MMS_BIT_STRING: memcpy(destinationAddress, self->value.bitString.buf, bitStringByteSize(self)); newValue->value.bitString.buf = destinationAddress; - destinationAddress += bitStringByteSize(self); + destinationAddress += MemoryAllocator_getAlignedSize(bitStringByteSize(self)); break; case MMS_INTEGER: @@ -1042,10 +1043,10 @@ MmsValue_cloneToBuffer(const MmsValue* self, uint8_t* destinationAddress) newValue->value.integer = (Asn1PrimitiveValue*) destinationAddress; Asn1PrimitiveValue* newAsn1Value = (Asn1PrimitiveValue*) destinationAddress; memcpy(destinationAddress, self->value.integer, sizeof(Asn1PrimitiveValue)); - destinationAddress += sizeof(Asn1PrimitiveValue); + destinationAddress += MemoryAllocator_getAlignedSize(sizeof(Asn1PrimitiveValue)); newAsn1Value->octets = destinationAddress; memcpy(destinationAddress, self->value.integer->octets, self->value.integer->maxSize); - destinationAddress += self->value.integer->maxSize; + destinationAddress += MemoryAllocator_getAlignedSize(self->value.integer->maxSize); } break; @@ -1055,14 +1056,14 @@ MmsValue_cloneToBuffer(const MmsValue* self, uint8_t* destinationAddress) newValue->value.floatingPoint.buf = destinationAddress; memcpy(destinationAddress, self->value.floatingPoint.buf, floatSizeInBytes); - destinationAddress += floatSizeInBytes; + destinationAddress += MemoryAllocator_getAlignedSize(floatSizeInBytes); } break; case MMS_OCTET_STRING: newValue->value.octetString.buf = destinationAddress; memcpy(destinationAddress, self->value.octetString.buf, self->value.octetString.maxSize); - destinationAddress += self->value.octetString.maxSize; + destinationAddress += MemoryAllocator_getAlignedSize(self->value.octetString.maxSize); break; case MMS_STRING: @@ -1070,7 +1071,7 @@ MmsValue_cloneToBuffer(const MmsValue* self, uint8_t* destinationAddress) newValue->value.visibleString.buf = (char*) destinationAddress; newValue->value.visibleString.size = self->value.visibleString.size; strcpy((char*) destinationAddress, self->value.visibleString.buf); - destinationAddress += (strlen(self->value.visibleString.buf) + 1); + destinationAddress += MemoryAllocator_getAlignedSize(strlen(self->value.visibleString.buf) + 1); break; default: From 68d56d947ecf011dea38d8a55691f62302ec8318 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 18 Mar 2018 12:44:26 +0100 Subject: [PATCH 036/123] - made memory alignment configurable --- config/stack_config.h | 3 +++ config/stack_config.h.cmake | 3 +++ src/common/simple_allocator.c | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/config/stack_config.h b/config/stack_config.h index 770353c27..aec739c31 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -157,6 +157,9 @@ /* include support for IEC 61850 log services */ #define CONFIG_IEC61850_LOG_SERVICE 1 +/* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ +#define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 + /* overwrite default results for MMS identify service */ //#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" //#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index c36f782da..6e9197f7b 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -151,6 +151,9 @@ /* include support for IEC 61850 log services */ #cmakedefine01 CONFIG_IEC61850_LOG_SERVICE +/* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ +#define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 + /* default results for MMS identify service */ #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" #define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" diff --git a/src/common/simple_allocator.c b/src/common/simple_allocator.c index 2c4c5a248..d9bcd223e 100644 --- a/src/common/simple_allocator.c +++ b/src/common/simple_allocator.c @@ -23,6 +23,7 @@ #include "libiec61850_platform_includes.h" #include "simple_allocator.h" +#include "stack_config.h" void MemoryAllocator_init(MemoryAllocator* self, char* memoryBlock, int size) @@ -35,10 +36,14 @@ MemoryAllocator_init(MemoryAllocator* self, char* memoryBlock, int size) int inline MemoryAllocator_getAlignedSize(int size) { +#if (CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT == 1) if ((size % sizeof(void*)) > 0) return sizeof(void*) * ((size + sizeof(void*) - 1) / sizeof(void*)); else return size; +#else + return size; +#endif } char* From 31e9886b6d39d2ead7a7c8e39f7a93ec76c77a30 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 20 Mar 2018 07:06:46 +0100 Subject: [PATCH 037/123] - .NET API: fixed bug in TLS wrapper --- dotnet/IEC61850forCSharp/TLS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/IEC61850forCSharp/TLS.cs b/dotnet/IEC61850forCSharp/TLS.cs index 9beccc4b2..2b3b49d84 100644 --- a/dotnet/IEC61850forCSharp/TLS.cs +++ b/dotnet/IEC61850forCSharp/TLS.cs @@ -202,7 +202,7 @@ public void SetOwnKey (X509Certificate2 key, string password) public void Dispose() { - lock (self) { + lock (this) { if (self != IntPtr.Zero) { TLSConfiguration_destroy (self); self = IntPtr.Zero; From a6b8b7767ea2f110c5ec636324ba66a33819cdd1 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 20 Mar 2018 09:01:30 +0100 Subject: [PATCH 038/123] - updated README.md --- README.md | 4 ++-- dotnet/IEC61850forCSharp/IEC61850.NET.csproj | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69dfb2ab6..bb7605637 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ If the build succeeds you can find a few binary files in the projects root direc Run the sample applications in the example folders. E.g.: ``` -cd examples/server_example1 -sudo ./server_example1 +cd examples/server_example_basic_io +sudo ./server_example_basic_io ``` on the Linux command line. diff --git a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj index 4ce8fb16a..750d6a4b5 100644 --- a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj +++ b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj @@ -7,6 +7,8 @@ Library iec61850dotnet iec61850dotnet + 8.0.30703 + 2.0 true From b195acd1285efa3ca815e64088403fc82d9f6141 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 20 Mar 2018 21:15:55 +0100 Subject: [PATCH 039/123] - .NET API: Added SEGMENTATION to ReportOptions --- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 3c4752f71..925cb797d 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -153,7 +153,9 @@ public enum ReportOptions { BUFFER_OVERFLOW = 32, ENTRY_ID = 64, CONF_REV = 128, - ALL = 255 + SEGMENTATION = 256, + ALL = SEQ_NUM | TIME_STAMP | REASON_FOR_INCLUSION | DATA_SET | DATA_REFERENCE | + BUFFER_OVERFLOW | ENTRY_ID | CONF_REV | SEGMENTATION } public enum Validity From 60b7b673f44ea40d882301c3594aa7e7ccfedca6 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 28 Mar 2018 19:40:24 +0200 Subject: [PATCH 040/123] - Java SCL parser: allow parse float "Val" elements using "," as decimal separator --- src/iec61850/inc_private/ied_server_private.h | 4 ++ tools/model_generator/genconfig.jar | Bin 88162 -> 89306 bytes tools/model_generator/gendyncode.jar | Bin 88158 -> 89306 bytes tools/model_generator/genmodel.jar | Bin 88824 -> 89305 bytes tools/model_generator/modelviewer.jar | Bin 88152 -> 89298 bytes .../libiec61850/scl/model/DataModelValue.java | 58 ++++++++++-------- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index 32bf68d2e..a63082677 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -43,6 +43,10 @@ struct sIedServer LinkedList clientConnections; uint8_t writeAccessPolicies; +#if (CONFIG_IEC61850_REPORT_SERVICE == 1) + int reportBufferSize; +#endif + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore dataModelLock; #endif diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index f50b47c88425ff377bc040cd04c3bbea5d59b8d6..9aba4fc859d603a06bfc795ee12c2030cf23dd3f 100644 GIT binary patch delta 81868 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 EA5QXXHvj+t delta 80652 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 HrJ(*F5-HJL diff --git a/tools/model_generator/gendyncode.jar b/tools/model_generator/gendyncode.jar index ba2bd0fafd303f4ae6d4315a0dba8e736b5875c8..748799e029393ee0277a95fd11af5bcf4422d00f 100644 GIT binary patch delta 81931 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

q?Ii$mW!hA9W9l;S=W+a5NLWr8#3eT1E;Zd9 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ěqc8?=MpbM!j>2r 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

public delegate void SVUpdateListener (SVSubscriber report, object parameter, SVSubscriberASDU asdu); + /// + /// Sampled Values (SV) Subscriber + /// + /// A subscriber is an instance associated with a single stream of measurement data. It is identified + /// by the Ethernet destination address, the appID value (both are on SV message level) and the svID value + /// that is part of each ASDU. + /// public class SVSubscriber : IDisposable { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] diff --git a/examples/iec61850_client_example1/client_example1.c b/examples/iec61850_client_example1/client_example1.c index e20a23276..f5b23575d 100644 --- a/examples/iec61850_client_example1/client_example1.c +++ b/examples/iec61850_client_example1/client_example1.c @@ -1,7 +1,7 @@ /* * client_example1.c * - * This example is intended to be used with server_example3 or server_example_goose. + * This example is intended to be used with server_example_basic_io or server_example_goose. */ #include "iec61850_client.h" diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index 89b62a54c..807e62087 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -118,6 +118,12 @@ SVReceiver_disableDestAddrCheck(SVReceiver self) self->checkDestAddr = false; } +void +SVReceiver_enableDestAddrCheck(SVReceiver self) +{ + self->checkDestAddr = false; +} + void SVReceiver_addSubscriber(SVReceiver self, SVSubscriber subscriber) { diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index 8bfe10ed9..7379ac0b7 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -1,7 +1,7 @@ /* * sv_subscriber.h * - * Copyright 2015 Michael Zillgith + * Copyright 2015-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -98,7 +98,7 @@ typedef struct sSVSubscriber_ASDU* SVSubscriber_ASDU; /** * \brief opaque handle to a SV subscriber instance * - * A subscriber is an instance associated with a single stream of measurement data. It is identified + * A subscriber is an instance associated with a single stream of measurement data. It is identified * by the Ethernet destination address, the appID value (both are on SV message level) and the svID value * that is part of each ASDU (SVSubscriber_ASDU object). */ @@ -135,15 +135,24 @@ SVReceiver_create(void); /** * \brief Disable check for destination address of the received SV messages * - * Per default both the appID and the destination address are checked to identify - * relevant SV messages. Destination address check can be disabled for performance - * reason when the appIDs are unique in the local system. - * * \param self the receiver instance reference */ void SVReceiver_disableDestAddrCheck(SVReceiver self); +/** + * \brief Enable check for destination address of the received SV messages + * + * Per default only the appID is checked to identify relevant SV messages and the + * destination address is ignored for performance reasons. This only works when the + * appIDs are unique in the local system. Otherwise the destination address check + * has to be enabled. + * + * \param self the receiver instance reference + */ +void +SVReceiver_enableDestAddrCheck(SVReceiver self); + /** * \brief Set the Ethernet interface ID for the receiver instance * diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index b3289f793..6ba5d3030 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -706,4 +706,5 @@ EXPORTS SVPublisher_ASDU_addQuality SVPublisher_ASDU_setQuality Timestamp_createFromByteArray - IedModel_getDeviceByIndex \ No newline at end of file + IedModel_getDeviceByIndex + SVReceiver_enableDestAddrCheck \ No newline at end of file From a696e6e35e05954057897905ed50472b915282d9 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 25 Apr 2018 18:52:54 +0200 Subject: [PATCH 048/123] - updated README and CHANGELOG --- CHANGELOG | 39 +++++++++++++++++++++++++++++++++++++++ README.md | 26 +++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 6fc41699b..dc24b4be8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,45 @@ Changes to version 1.2.0 - IEC 61850/MMS server: removed deprecated AttributeChangedHandler - Added pkg-config file - The Sampled Values APIs have been renamed. The old version of the API is deprecated but still supported and will be removed in the next major version of the library. +- SV Publisher/Subscriber: a lot of small fixed and improvements +- .NET API: Added support for sampled values (SV) subscriber +- .NET API: Added support for GOOSE subscriber +- SV subscriber: added function SVReceiver_enableDestAddrCheck +- IEC 61850 server: fixed bug in buffered report module - report can be lost under some circumstances when BRCB is enabled +- SV subscriber: replaced code that caused unaligned memory access +- IEC 61850 server: added memory alignement for buffered reporting + + +Changes to version 1.1.2 +------------------------ + +- MMS client: fixed parsing initiate response message +- SV publisher: conditional encoding for SmpRate +- MmsValue_update function now allows adjusting octet-string size of target object +- .NET API: Added DeleteFile +- CDC helper functions: added helper functions for VSS and VSG CDC +- added additional locks in client and server + +Changes to version 1.1.1 +------------------------ + +- IEC 61850 client: fixed bug in APC control handling +- IEC 61850 client: ClientReportControlBlock now accepts "$" and "." as seperator for RCB object reference +- MMS client: fixed bug in MmsConnection_connect (COTP payload buffer was not reset in case of an error during connect -> connection failed in case of reuse of MmsConnection object +- MMS client: delete named variable list service supports VMD specific lists +- SV subscriber/publisher: additional features and bug fixes +- SV: fixed data type for smpRate +- SV: fixed encoding of optional smpMod attribute +- SV receiver: Added semaphore to make subscriber list thread-safe +- .NET API: ControlObject implements IDisposable interface +- IED server: added new function IedServer_udpateDbposValue +- fixed problem with cmake include folders +- MMS client: file services -fixed encoding problem with long file names +- MMS server: ACSE authenticator passes application reference (ap-title and ae-qualifier) +- example directory cleanup +- MMS: fixed potential memory leak in asn1 code that can be caused by malformed MMS messages +- MMS client: MmsConnection_getVariableAccessAttributes support for VMD specific variables +- Java SCL parser: added support for "Val" elements for Octet64 types Changes to version 1.1.0 ------------------------ diff --git a/README.md b/README.md index bb7605637..b45cb7ade 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,33 @@ libiec61850 is an open-source (GPLv3) implementation of an IEC 61850 client and For commercial projects licenses and support is provided by MZ Automation GmbH. Please contact info@mz-automation.de for more details on licensing options. + +## Features + +The library support the following IEC 61850 protocol features: + +* MMS client/server, GOOSE (IEC 61850-8-1) +* Sampled Values (SV - IEC 61850-9-2) +* Support for buffered and unbuffered reports +* Online report control block configuration +* Data access service (get data, set data) +* online data model discovery and browsing +* all data set services (get values, set values, browse) +* dynamic data set services (create and delete) +* log service +** flexible API to connect custom data bases +** comes with sqlite implementation +* MMS file services (browse, get file, set file, delete/rename file) +** required to download COMTRADE files +* Setting group handling +* GOOSE and SV control block handling +* TLS support +* C and C#/.NET API + + ## Building and running the examples with the provided makefiles -In the project root directoy type +In the project root directory type ``` make examples From 76ab1ec9f68e0c08bdba33cda55b66254828324c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 27 Apr 2018 10:45:33 +0200 Subject: [PATCH 049/123] - .NET API: Added destructor and Dispose method to ReportControlBlock - .NET API: Changed ReportControlBlock access to IedConnection to improve stability when connection closes unexpectedly --- dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 54 ++++++++++++++-- .../IEC61850forCSharp/ReportControlBlock.cs | 63 ++++++++++--------- dotnet/IEC61850forCSharp/Reporting.cs | 11 ---- 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index b17317c4c..236439fca 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -371,6 +371,20 @@ static extern IntPtr IedConnection_queryLogAfter(IntPtr self, out int error, str static extern IntPtr IedConnection_queryLogByTime (IntPtr self, out int error, string logReference, ulong startTime, ulong endTime, out bool moreFollows); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedConnection_getRCBValues (IntPtr connection, out int error, string rcbReference, IntPtr updateRcb); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_setRCBValues (IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, bool singleRequest); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_installReportHandler (IntPtr connection, string rcbReference, string rptId, InternalReportHandler handler, + IntPtr handlerParameter); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_uninstallReportHandler(IntPtr connection, string rcbReference); + /******************** * FileDirectoryEntry @@ -436,7 +450,6 @@ public IedConnection (TLSConfiguration tlsConfig) public void Dispose() { if (connection != IntPtr.Zero) { - cleanupRCBs (); IedConnection_destroy (connection); @@ -446,11 +459,7 @@ public void Dispose() ~IedConnection () { - if (connection != IntPtr.Zero) { - cleanupRCBs (); - - IedConnection_destroy (connection); - } + Dispose (); } private IsoConnectionParameters isoConnectionParameters = null; @@ -1436,6 +1445,39 @@ public List GetDataSetDirectory (string dataSetReference, out bool isDel return newList; } + + internal void UninstallReportHandler (string objectReference) + { + if (connection != IntPtr.Zero) { + IedConnection_uninstallReportHandler (connection, objectReference); + } + } + + internal void InstallReportHandler (string objectReference, string reportId, InternalReportHandler internalHandler) + { + if (connection != IntPtr.Zero) { + IedConnection_installReportHandler (connection, objectReference, reportId, internalHandler, IntPtr.Zero); + } + } + + internal void GetRCBValues(out int error, string objectReference, IntPtr updateRcb) + { + if (connection != IntPtr.Zero) { + IedConnection_getRCBValues (connection, out error, objectReference, updateRcb); + } else { + error = 1; /* not connected */ + } + } + + internal void SetRCBValues(out int error, IntPtr rcb, UInt32 parametersMask, bool singleRequest) + { + if (connection != IntPtr.Zero) { + IedConnection_setRCBValues (connection, out error, rcb, parametersMask, singleRequest); + } else { + error = 1; /* not connected */ + } + } + } public class IedConnectionException : Exception diff --git a/dotnet/IEC61850forCSharp/ReportControlBlock.cs b/dotnet/IEC61850forCSharp/ReportControlBlock.cs index ad9f5da3c..7d13a6746 100644 --- a/dotnet/IEC61850forCSharp/ReportControlBlock.cs +++ b/dotnet/IEC61850forCSharp/ReportControlBlock.cs @@ -1,7 +1,7 @@ /* * ReportControlBlock.cs * - * Copyright 2014 Michael Zillgith + * Copyright 2014-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -36,6 +36,9 @@ namespace Client ///
public delegate void ReportHandler (Report report, object parameter); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void InternalReportHandler (IntPtr parameter, IntPtr report); + /// /// Report control block (RCB) representation. /// @@ -44,16 +47,13 @@ namespace Client /// Values from the server will only be read when the GetRCBValues method is called. /// Values at the server are only affected when the SetRCBValues method is called. /// - public class ReportControlBlock + public class ReportControlBlock : IDisposable { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ClientReportControlBlock_create (string dataAttributeReference); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr IedConnection_getRCBValues (IntPtr connection, out int error, string rcbReference, IntPtr updateRcb); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_setRCBValues (IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, bool singleRequest); + static extern void ClientReportControlBlock_destroy (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] @@ -147,18 +147,7 @@ public class ReportControlBlock [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ClientReportControlBlock_getOwner (IntPtr self); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_installReportHandler (IntPtr connection, string rcbReference, string rptId, InternalReportHandler handler, - IntPtr handlerParameter); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_uninstallReportHandler(IntPtr connection, string rcbReference); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void InternalReportHandler (IntPtr parameter, IntPtr report); - private IntPtr self; - private IntPtr connection; private IedConnection iedConnection = null; private string objectReference; private bool flagRptId = false; @@ -221,14 +210,11 @@ private void internalReportHandler (IntPtr parameter, IntPtr report) internal ReportControlBlock (string objectReference, IedConnection iedConnection, IntPtr connection) { self = ClientReportControlBlock_create (objectReference); - this.iedConnection = iedConnection; - this.connection = connection; - this.objectReference = objectReference; - } - internal void DisposeInternal() - { - IedConnection_uninstallReportHandler(connection, objectReference); + if (self != IntPtr.Zero) { + this.iedConnection = iedConnection; + this.objectReference = objectReference; + } } /// @@ -239,11 +225,25 @@ internal void DisposeInternal() /// After calling , you must release all references to the /// so the garbage collector can reclaim the memory that the /// was occupying. - public void Dispose() + public void Dispose() { - DisposeInternal (); + lock (this) { + if (self != IntPtr.Zero) { + + iedConnection.UninstallReportHandler (objectReference); + + iedConnection.RemoveRCB (this); + + ClientReportControlBlock_destroy (self); - iedConnection.RemoveRCB (this); + self = IntPtr.Zero; + } + } + } + + ~ReportControlBlock() + { + Dispose (); } public string GetObjectReference () @@ -279,9 +279,10 @@ public void InstallReportHandler (ReportHandler reportHandler, object parameter) { internalHandler = new InternalReportHandler(internalReportHandler); } + + iedConnection.InstallReportHandler (objectReference, reportId, internalHandler); - IedConnection_installReportHandler(this.connection, objectReference, reportId, internalHandler, IntPtr.Zero); - reportHandlerInstalled = true; + reportHandlerInstalled = true; } } @@ -293,7 +294,7 @@ public void GetRCBValues () { int error; - IedConnection_getRCBValues (connection, out error, objectReference, self); + iedConnection.GetRCBValues (out error, objectReference, self); if (error != 0) throw new IedConnectionException ("getRCBValues service failed", error); @@ -370,7 +371,7 @@ public void SetRCBValues (bool singleRequest) int error; - IedConnection_setRCBValues (connection, out error, self, parametersMask, singleRequest); + iedConnection.SetRCBValues (out error, self, parametersMask, singleRequest); resetSendFlags(); diff --git a/dotnet/IEC61850forCSharp/Reporting.cs b/dotnet/IEC61850forCSharp/Reporting.cs index dfbf6d6b3..90ddba2d1 100644 --- a/dotnet/IEC61850forCSharp/Reporting.cs +++ b/dotnet/IEC61850forCSharp/Reporting.cs @@ -35,17 +35,6 @@ public partial class IedConnection private List activeRCBs = null; - private void cleanupRCBs() - { - if (activeRCBs != null) { - - foreach (ReportControlBlock rcb in activeRCBs) { - rcb.DisposeInternal (); - } - } - - } - public ReportControlBlock GetReportControlBlock (string rcbObjectReference) { var newRCB = new ReportControlBlock (rcbObjectReference, this, connection); From cf049071ccda3f597fb6dd543a197f6c55bee7bc Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 27 Apr 2018 12:12:27 +0200 Subject: [PATCH 050/123] - IEC 61850 server: fixed bug in report module when RCB was enabled multiple times (new in 1.2.0) --- src/iec61850/server/mms_mapping/reporting.c | 29 +++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 253dc1e93..c3cf4bc53 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -290,6 +290,14 @@ ReportControl_getRCBValue(ReportControl* rc, char* elementName) return NULL ; } +static inline void +clearInclusionFlags(ReportControl* reportControl) +{ + int i; + for (i = 0; i < reportControl->dataSet->elementCount; i++) + reportControl->inclusionFlags[i] = REPORT_CONTROL_NONE; +} + static void updateTimeOfEntry(ReportControl* self, uint64_t currentTime) { @@ -562,8 +570,10 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, } } - if (dataSetChanged) - purgeBuf(rc); + if (rc->buffered) { + if (dataSetChanged) + purgeBuf(rc); + } } @@ -1396,8 +1406,8 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme rc->isResync = false; } else { - GLOBAL_FREEMEM(rc->inclusionFlags); - rc->inclusionFlags = NULL; + if (rc->dataSet) + clearInclusionFlags(rc); MmsValue* resv = ReportControl_getRCBValue(rc, "Resv"); MmsValue_setBoolean(resv, false); @@ -1618,11 +1628,6 @@ Reporting_deactivateReportsForConnection(MmsMapping* self, MmsServerConnection c if (rc->buffered == false) { - if (rc->inclusionField != NULL) { - MmsValue_delete(rc->inclusionField); - rc->inclusionField = NULL; - } - MmsValue* resv = ReportControl_getRCBValue(rc, "Resv"); MmsValue_setBoolean(resv, false); @@ -2042,11 +2047,7 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ } } - /* clear inclusion flags */ - int i; - - for (i = 0; i < reportControl->dataSet->elementCount; i++) - reportControl->inclusionFlags[i] = REPORT_CONTROL_NONE; + clearInclusionFlags(reportControl); if (DEBUG_IED_SERVER) printf("IED_SERVER: enqueueReport: encoded %i bytes for report (estimated %i) at buffer offset %i\n", From da17f8210a13bb4e3ef51def84007c34d044fe76 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 27 Apr 2018 12:38:14 +0200 Subject: [PATCH 051/123] - updated version to 1.2.1 --- CHANGELOG | 8 +++++++- CMakeLists.txt | 2 +- src/common/inc/libiec61850_platform_includes.h | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dc24b4be8..d33905156 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +Changes to version 1.2.1 +------------------------ + +- IEC 61850 server: fixed bug in report module when RCB was enabled multiple times (was new in 1.2.0) +- .NET API: Added destructor and Dispose method to ReportControlBlock (fixed memory leak) +- .NET API: Changed ReportControlBlock access to IedConnection to improve stability when connection closes unexpectedly + Changes to version 1.2.0 ------------------------ @@ -13,7 +20,6 @@ Changes to version 1.2.0 - SV subscriber: replaced code that caused unaligned memory access - IEC 61850 server: added memory alignement for buffered reporting - Changes to version 1.1.2 ------------------------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 690ac7bff..221297a3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ ENABLE_TESTING() set(LIB_VERSION_MAJOR "1") set(LIB_VERSION_MINOR "2") -set(LIB_VERSION_PATCH "0") +set(LIB_VERSION_PATCH "1") set(LIB_VERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/") diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index d8e8611db..5ca0f5234 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -15,7 +15,7 @@ #include "platform_endian.h" -#define LIBIEC61850_VERSION "1.2.0" +#define LIBIEC61850_VERSION "1.2.1" #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" From 731608e5b04461f07d51e50861aaa94f7ef4add6 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 27 Apr 2018 21:07:42 +0200 Subject: [PATCH 052/123] - removed inline qualifier from MemoryAllocator_getAlignedSize --- src/common/simple_allocator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/simple_allocator.c b/src/common/simple_allocator.c index d9bcd223e..e93398fc4 100644 --- a/src/common/simple_allocator.c +++ b/src/common/simple_allocator.c @@ -33,7 +33,7 @@ MemoryAllocator_init(MemoryAllocator* self, char* memoryBlock, int size) self->size = size; } -int inline +int MemoryAllocator_getAlignedSize(int size) { #if (CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT == 1) From eb97d64ae2eb9eeb45722894e04f0fa374b22d6e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 2 May 2018 09:21:37 +0200 Subject: [PATCH 053/123] - .NET API: updated marshaling of bool types in pinvoke code --- dotnet/IEC61850forCSharp/Control.cs | 6 +++--- dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 9 +++++---- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 6 +++--- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 17 +++++++++-------- dotnet/IEC61850forCSharp/MmsValue.cs | 6 +++--- dotnet/IEC61850forCSharp/ReportControlBlock.cs | 8 ++++---- .../SampledValuesControlBlock.cs | 4 ++-- dotnet/IEC61850forCSharp/TLS.cs | 12 ++++++++++-- 8 files changed, 39 insertions(+), 29 deletions(-) diff --git a/dotnet/IEC61850forCSharp/Control.cs b/dotnet/IEC61850forCSharp/Control.cs index 711542c7f..44826f478 100644 --- a/dotnet/IEC61850forCSharp/Control.cs +++ b/dotnet/IEC61850forCSharp/Control.cs @@ -138,13 +138,13 @@ public class ControlObject : IDisposable private static extern void ControlObjectClient_setOrigin(IntPtr self, string orIdent, int orCat); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - private static extern void ControlObjectClient_setInterlockCheck(IntPtr self, bool value); + private static extern void ControlObjectClient_setInterlockCheck(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - private static extern void ControlObjectClient_setSynchroCheck(IntPtr self, bool value); + private static extern void ControlObjectClient_setSynchroCheck(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - private static extern void ControlObjectClient_setTestMode(IntPtr self, bool value); + private static extern void ControlObjectClient_setTestMode(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void InternalCommandTerminationHandler(IntPtr parameter,IntPtr controlClient); diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 236439fca..8fc72a1db 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -317,7 +317,7 @@ public partial class IedConnection static extern IntPtr IedConnection_getLogicalNodeDirectory (IntPtr self, out int error, string logicalNodeReference, int acsiClass); [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] - static extern IntPtr IedConnection_getServerDirectory (IntPtr self, out int error, bool getFileNames); + static extern IntPtr IedConnection_getServerDirectory (IntPtr self, out int error, [MarshalAs(UnmanagedType.I1)] bool getFileNames); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_getDeviceModelFromServer(IntPtr self, out int error); @@ -346,6 +346,7 @@ public partial class IedConnection static extern IntPtr IedConnection_createDataSet (IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.Bool)] static extern bool IedConnection_deleteDataSet (IntPtr self, out int error, string dataSetReference); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -365,18 +366,18 @@ public partial class IedConnection [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_queryLogAfter(IntPtr self, out int error, string logReference, - IntPtr entryID, ulong timeStamp, out bool moreFollows); + IntPtr entryID, ulong timeStamp, [MarshalAs(UnmanagedType.I1)] out bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_queryLogByTime (IntPtr self, out int error, string logReference, - ulong startTime, ulong endTime, out bool moreFollows); + ulong startTime, ulong endTime, [MarshalAs(UnmanagedType.I1)] out bool moreFollows); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getRCBValues (IntPtr connection, out int error, string rcbReference, IntPtr updateRcb); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_setRCBValues (IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, bool singleRequest); + static extern void IedConnection_setRCBValues (IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, [MarshalAs(UnmanagedType.I1)] bool singleRequest); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_installReportHandler (IntPtr connection, string rcbReference, string rptId, InternalReportHandler handler, diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 925cb797d..cda16ae9a 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -365,21 +365,21 @@ public class Timestamp static extern bool Timestamp_isLeapSecondKnown (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void Timestamp_setLeapSecondKnown (IntPtr self, bool value); + static extern void Timestamp_setLeapSecondKnown (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] static extern bool Timestamp_hasClockFailure (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void Timestamp_setClockFailure (IntPtr self, bool value); + static extern void Timestamp_setClockFailure (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] static extern bool Timestamp_isClockNotSynchronized (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void Timestamp_setClockNotSynchronized (IntPtr self, bool value); + static extern void Timestamp_setClockNotSynchronized (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int Timestamp_getSubsecondPrecision (IntPtr self); diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 0e245f6d8..e8d90bc38 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -43,7 +43,7 @@ public class ConfigFileParser { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr FileSystem_openFile(string filePath, bool readWrite); + static extern IntPtr FileSystem_openFile(string filePath, [MarshalAs(UnmanagedType.I1)] bool readWrite); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -260,7 +260,7 @@ public class CDC 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, bool isIntegerNotFloat); + 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); @@ -468,7 +468,7 @@ public DataSetEntry(DataSet dataSet, string variable, int index, string componen public class ReportControlBlock { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ReportControlBlock_create(string name, IntPtr parent, string rptId, bool isBuffered, + 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; @@ -574,6 +574,7 @@ public class IedServer 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)] @@ -586,7 +587,7 @@ public class IedServer 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, bool value); + 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); @@ -613,13 +614,13 @@ public class IedServer static extern IntPtr IedServer_getAttributeValue(IntPtr self, IntPtr dataAttribute); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int InternalControlPerformCheckHandler (IntPtr parameter, IntPtr ctlVal, bool test, bool interlockCheck, IntPtr connection); + private delegate int InternalControlPerformCheckHandler (IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test, [MarshalAs(UnmanagedType.I1)] bool interlockCheck, IntPtr connection); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int InternalControlWaitForExecutionHandler (IntPtr parameter, IntPtr ctlVal, bool test, bool synchoCheck); + private delegate int InternalControlWaitForExecutionHandler (IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test, [MarshalAs(UnmanagedType.I1)] bool synchoCheck); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int InternalControlHandler (IntPtr parameter, IntPtr ctlVal, bool test); + private delegate int InternalControlHandler (IntPtr parameter, IntPtr ctlVal, [MarshalAs(UnmanagedType.I1)] bool test); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServer_setWaitForExecutionHandler(IntPtr self, IntPtr node, InternalControlWaitForExecutionHandler handler, IntPtr parameter); @@ -652,7 +653,7 @@ public void SetConnectionIndicationHandler(ConnectionIndicationHandler handler, } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void InternalConnectionHandler (IntPtr iedServer, IntPtr clientConnection, bool connected, IntPtr parameter); + 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); diff --git a/dotnet/IEC61850forCSharp/MmsValue.cs b/dotnet/IEC61850forCSharp/MmsValue.cs index ee9de699e..889126f63 100644 --- a/dotnet/IEC61850forCSharp/MmsValue.cs +++ b/dotnet/IEC61850forCSharp/MmsValue.cs @@ -67,7 +67,7 @@ public class MmsValue : IEnumerable static extern int MmsValue_getBitStringSize(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void MmsValue_setBitStringBit(IntPtr self, int bitPos, bool value); + static extern void MmsValue_setBitStringBit(IntPtr self, int bitPos, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] @@ -113,7 +113,7 @@ public class MmsValue : IEnumerable static extern UInt32 MmsValue_toUnixTimestamp (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr MmsValue_newBoolean (bool value); + static extern IntPtr MmsValue_newBoolean ([MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_newFloat (float value); @@ -156,7 +156,7 @@ public class MmsValue : IEnumerable static extern bool MmsValue_equals(IntPtr self, IntPtr otherValue); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr MmsValue_newBinaryTime (bool timeOfDay); + static extern IntPtr MmsValue_newBinaryTime ([MarshalAs(UnmanagedType.I1)] bool timeOfDay); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void MmsValue_setBinaryTime (IntPtr self, UInt64 timestamp); diff --git a/dotnet/IEC61850forCSharp/ReportControlBlock.cs b/dotnet/IEC61850forCSharp/ReportControlBlock.cs index 7d13a6746..20ee9146b 100644 --- a/dotnet/IEC61850forCSharp/ReportControlBlock.cs +++ b/dotnet/IEC61850forCSharp/ReportControlBlock.cs @@ -70,14 +70,14 @@ public class ReportControlBlock : IDisposable static extern bool ClientReportControlBlock_getRptEna (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void ClientReportControlBlock_setRptEna(IntPtr self, bool rptEna); + static extern void ClientReportControlBlock_setRptEna(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool rptEna); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] static extern bool ClientReportControlBlock_getResv (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void ClientReportControlBlock_setResv (IntPtr self, bool resv); + static extern void ClientReportControlBlock_setResv (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool resv); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ClientReportControlBlock_getDataSetReference (IntPtr self); @@ -120,14 +120,14 @@ public class ReportControlBlock : IDisposable static extern bool ClientReportControlBlock_getGI (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void ClientReportControlBlock_setGI (IntPtr self, bool gi); + static extern void ClientReportControlBlock_setGI (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool gi); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] static extern bool ClientReportControlBlock_getPurgeBuf (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void ClientReportControlBlock_setPurgeBuf (IntPtr self, bool purgeBuf); + static extern void ClientReportControlBlock_setPurgeBuf (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool purgeBuf); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern Int32 ClientReportControlBlock_getResvTms (IntPtr self); diff --git a/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs index e306fcafd..dc481140d 100644 --- a/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs +++ b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs @@ -53,11 +53,11 @@ public class SampledValuesControlBlock : IDisposable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] - static extern bool ClientSVControlBlock_setSvEna (IntPtr self, bool value); + static extern bool ClientSVControlBlock_setSvEna (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] - static extern bool ClientSVControlBlock_setResv (IntPtr self, bool value); + static extern bool ClientSVControlBlock_setResv (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] diff --git a/dotnet/IEC61850forCSharp/TLS.cs b/dotnet/IEC61850forCSharp/TLS.cs index 2b3b49d84..b7d67dc03 100644 --- a/dotnet/IEC61850forCSharp/TLS.cs +++ b/dotnet/IEC61850forCSharp/TLS.cs @@ -54,36 +54,44 @@ public class TLSConfiguration : IDisposable static extern void TLSConfiguration_destroy(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void TLSConfiguration_setAllowOnlyKnownCertificates(IntPtr self, bool value); + static extern void TLSConfiguration_setAllowOnlyKnownCertificates(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void TLSConfiguration_setChainValidation (IntPtr self, bool value); + static extern void TLSConfiguration_setChainValidation (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void TLSConfiguration_setClientMode(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_setOwnCertificate(IntPtr self, byte[] certificate, int certLen); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_setOwnCertificateFromFile(IntPtr self, string filename); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_setOwnKey(IntPtr self, byte[] key, int keyLen, string keyPassword); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_setOwnKeyFromFile (IntPtr self, string filename, string keyPassword); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_addAllowedCertificate(IntPtr self, byte[] certificate, int certLen); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_addAllowedCertificateFromFile(IntPtr self, string filename); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_addCACertificate(IntPtr self, byte[] certificate, int certLen); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] static extern bool TLSConfiguration_addCACertificateFromFile(IntPtr self, string filename); public TLSConfiguration() { From cab1f783fd636519f9e3c8f1e5d12ecfa5016ccf Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 2 May 2018 10:31:13 +0200 Subject: [PATCH 054/123] - .NET API: added project files for .NET core 2.0 --- dotnet/core/2.0/IEC61850.NET.core.2.0.sln | 109 ++++++++++++++++++ .../IEC61850.NET.core.2.0.csproj | 25 ++++ .../client_example1/client_example1.csproj | 16 +++ .../client_example2/client_example2.csproj | 16 +++ .../client_example3/client_example3.csproj | 16 +++ .../client_example_authenticate.csproj | 16 +++ .../client_example_control.csproj | 16 +++ .../client_example_datasets.csproj | 16 +++ .../client_example_files.csproj | 16 +++ .../client_example_log.csproj | 16 +++ .../client_example_model_browsing.csproj | 16 +++ .../client_example_reporting.csproj | 16 +++ .../client_example_tls.csproj | 28 +++++ .../goose_subscriber_example.csproj | 16 +++ .../server_example1/server_example1.csproj | 22 ++++ .../sv_subscriber_example.csproj | 16 +++ dotnet/reporting/ReportingExample.cs | 8 +- dotnet/server1/Program.cs | 2 +- dotnet/server1/server1.csproj | 7 +- 19 files changed, 385 insertions(+), 8 deletions(-) create mode 100644 dotnet/core/2.0/IEC61850.NET.core.2.0.sln create mode 100644 dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj create mode 100644 dotnet/core/2.0/client_example1/client_example1.csproj create mode 100644 dotnet/core/2.0/client_example2/client_example2.csproj create mode 100644 dotnet/core/2.0/client_example3/client_example3.csproj create mode 100644 dotnet/core/2.0/client_example_authenticate/client_example_authenticate.csproj create mode 100644 dotnet/core/2.0/client_example_control/client_example_control.csproj create mode 100644 dotnet/core/2.0/client_example_datasets/client_example_datasets.csproj create mode 100644 dotnet/core/2.0/client_example_files/client_example_files.csproj create mode 100644 dotnet/core/2.0/client_example_log/client_example_log.csproj create mode 100644 dotnet/core/2.0/client_example_model_browsing/client_example_model_browsing.csproj create mode 100644 dotnet/core/2.0/client_example_reporting/client_example_reporting.csproj create mode 100644 dotnet/core/2.0/client_example_tls/client_example_tls.csproj create mode 100644 dotnet/core/2.0/goose_subscriber_example/goose_subscriber_example.csproj create mode 100644 dotnet/core/2.0/server_example1/server_example1.csproj create mode 100644 dotnet/core/2.0/sv_subscriber_example/sv_subscriber_example.csproj diff --git a/dotnet/core/2.0/IEC61850.NET.core.2.0.sln b/dotnet/core/2.0/IEC61850.NET.core.2.0.sln new file mode 100644 index 000000000..0d5924286 --- /dev/null +++ b/dotnet/core/2.0/IEC61850.NET.core.2.0.sln @@ -0,0 +1,109 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IEC61850.NET.core.2.0", "IEC61850.NET.core.2.0\IEC61850.NET.core.2.0.csproj", "{16C58017-94CC-4C92-AFDC-84AC24C393AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example1", "client_example1\client_example1.csproj", "{AFDC261C-B293-4650-8D90-A862E27CEC15}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example2", "client_example2\client_example2.csproj", "{4FD69E0A-2548-4BFF-BD15-7ED0612C2FC1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example3", "client_example3\client_example3.csproj", "{11B5EE5D-36AC-4DAD-89E5-251E46A6269E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_files", "client_example_files\client_example_files.csproj", "{3B0970E6-77A3-4D97-A998-50758BE7C4DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_datasets", "client_example_datasets\client_example_datasets.csproj", "{F61DD20F-4CF4-49FD-836A-AA233B4659B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_control", "client_example_control\client_example_control.csproj", "{53FF9ABD-300B-49F2-80A6-15B98121066D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_authenticate", "client_example_authenticate\client_example_authenticate.csproj", "{6E85A44B-E29D-4819-8AF6-2CEAD7851C1B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_reporting", "client_example_reporting\client_example_reporting.csproj", "{BA077BC8-0B98-4071-8F1F-C97865954477}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_tls", "client_example_tls\client_example_tls.csproj", "{954CCB43-3640-48F1-9A8E-4D736A5EF575}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_model_browsing", "client_example_model_browsing\client_example_model_browsing.csproj", "{A9890431-ABE4-4D80-8D36-132E4096E163}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server_example1", "server_example1\server_example1.csproj", "{18A97E35-2FF9-49F7-934F-F1E9C50DC054}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sv_subscriber_example", "sv_subscriber_example\sv_subscriber_example.csproj", "{D9D8B1B7-B4CB-473D-9C6C-3B2F3C4DB2BD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "goose_subscriber_example", "goose_subscriber_example\goose_subscriber_example.csproj", "{ABBF5A82-4F5E-41DA-A727-C2298E8AF650}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_log", "client_example_log\client_example_log.csproj", "{160586BB-3601-4D0C-86D7-414F585041B4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {16C58017-94CC-4C92-AFDC-84AC24C393AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16C58017-94CC-4C92-AFDC-84AC24C393AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16C58017-94CC-4C92-AFDC-84AC24C393AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16C58017-94CC-4C92-AFDC-84AC24C393AD}.Release|Any CPU.Build.0 = Release|Any CPU + {AFDC261C-B293-4650-8D90-A862E27CEC15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFDC261C-B293-4650-8D90-A862E27CEC15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFDC261C-B293-4650-8D90-A862E27CEC15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFDC261C-B293-4650-8D90-A862E27CEC15}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD69E0A-2548-4BFF-BD15-7ED0612C2FC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD69E0A-2548-4BFF-BD15-7ED0612C2FC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD69E0A-2548-4BFF-BD15-7ED0612C2FC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD69E0A-2548-4BFF-BD15-7ED0612C2FC1}.Release|Any CPU.Build.0 = Release|Any CPU + {11B5EE5D-36AC-4DAD-89E5-251E46A6269E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11B5EE5D-36AC-4DAD-89E5-251E46A6269E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11B5EE5D-36AC-4DAD-89E5-251E46A6269E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11B5EE5D-36AC-4DAD-89E5-251E46A6269E}.Release|Any CPU.Build.0 = Release|Any CPU + {3B0970E6-77A3-4D97-A998-50758BE7C4DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B0970E6-77A3-4D97-A998-50758BE7C4DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B0970E6-77A3-4D97-A998-50758BE7C4DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B0970E6-77A3-4D97-A998-50758BE7C4DC}.Release|Any CPU.Build.0 = Release|Any CPU + {F61DD20F-4CF4-49FD-836A-AA233B4659B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F61DD20F-4CF4-49FD-836A-AA233B4659B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F61DD20F-4CF4-49FD-836A-AA233B4659B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F61DD20F-4CF4-49FD-836A-AA233B4659B8}.Release|Any CPU.Build.0 = Release|Any CPU + {53FF9ABD-300B-49F2-80A6-15B98121066D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53FF9ABD-300B-49F2-80A6-15B98121066D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53FF9ABD-300B-49F2-80A6-15B98121066D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53FF9ABD-300B-49F2-80A6-15B98121066D}.Release|Any CPU.Build.0 = Release|Any CPU + {6E85A44B-E29D-4819-8AF6-2CEAD7851C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E85A44B-E29D-4819-8AF6-2CEAD7851C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E85A44B-E29D-4819-8AF6-2CEAD7851C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E85A44B-E29D-4819-8AF6-2CEAD7851C1B}.Release|Any CPU.Build.0 = Release|Any CPU + {BA077BC8-0B98-4071-8F1F-C97865954477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA077BC8-0B98-4071-8F1F-C97865954477}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA077BC8-0B98-4071-8F1F-C97865954477}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA077BC8-0B98-4071-8F1F-C97865954477}.Release|Any CPU.Build.0 = Release|Any CPU + {954CCB43-3640-48F1-9A8E-4D736A5EF575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {954CCB43-3640-48F1-9A8E-4D736A5EF575}.Debug|Any CPU.Build.0 = Debug|Any CPU + {954CCB43-3640-48F1-9A8E-4D736A5EF575}.Release|Any CPU.ActiveCfg = Release|Any CPU + {954CCB43-3640-48F1-9A8E-4D736A5EF575}.Release|Any CPU.Build.0 = Release|Any CPU + {A9890431-ABE4-4D80-8D36-132E4096E163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9890431-ABE4-4D80-8D36-132E4096E163}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9890431-ABE4-4D80-8D36-132E4096E163}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9890431-ABE4-4D80-8D36-132E4096E163}.Release|Any CPU.Build.0 = Release|Any CPU + {18A97E35-2FF9-49F7-934F-F1E9C50DC054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18A97E35-2FF9-49F7-934F-F1E9C50DC054}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18A97E35-2FF9-49F7-934F-F1E9C50DC054}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18A97E35-2FF9-49F7-934F-F1E9C50DC054}.Release|Any CPU.Build.0 = Release|Any CPU + {D9D8B1B7-B4CB-473D-9C6C-3B2F3C4DB2BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9D8B1B7-B4CB-473D-9C6C-3B2F3C4DB2BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9D8B1B7-B4CB-473D-9C6C-3B2F3C4DB2BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9D8B1B7-B4CB-473D-9C6C-3B2F3C4DB2BD}.Release|Any CPU.Build.0 = Release|Any CPU + {ABBF5A82-4F5E-41DA-A727-C2298E8AF650}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABBF5A82-4F5E-41DA-A727-C2298E8AF650}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABBF5A82-4F5E-41DA-A727-C2298E8AF650}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABBF5A82-4F5E-41DA-A727-C2298E8AF650}.Release|Any CPU.Build.0 = Release|Any CPU + {160586BB-3601-4D0C-86D7-414F585041B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {160586BB-3601-4D0C-86D7-414F585041B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {160586BB-3601-4D0C-86D7-414F585041B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {160586BB-3601-4D0C-86D7-414F585041B4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE4487ED-D36A-438B-B1EF-BC3CA409D4BF} + EndGlobalSection +EndGlobal diff --git a/dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj b/dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj new file mode 100644 index 000000000..4894edb79 --- /dev/null +++ b/dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example1/client_example1.csproj b/dotnet/core/2.0/client_example1/client_example1.csproj new file mode 100644 index 000000000..1226b7915 --- /dev/null +++ b/dotnet/core/2.0/client_example1/client_example1.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example2/client_example2.csproj b/dotnet/core/2.0/client_example2/client_example2.csproj new file mode 100644 index 000000000..61db2c7b4 --- /dev/null +++ b/dotnet/core/2.0/client_example2/client_example2.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example3/client_example3.csproj b/dotnet/core/2.0/client_example3/client_example3.csproj new file mode 100644 index 000000000..bf9d703c4 --- /dev/null +++ b/dotnet/core/2.0/client_example3/client_example3.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_authenticate/client_example_authenticate.csproj b/dotnet/core/2.0/client_example_authenticate/client_example_authenticate.csproj new file mode 100644 index 000000000..c964a84bb --- /dev/null +++ b/dotnet/core/2.0/client_example_authenticate/client_example_authenticate.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_control/client_example_control.csproj b/dotnet/core/2.0/client_example_control/client_example_control.csproj new file mode 100644 index 000000000..8ce176f23 --- /dev/null +++ b/dotnet/core/2.0/client_example_control/client_example_control.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_datasets/client_example_datasets.csproj b/dotnet/core/2.0/client_example_datasets/client_example_datasets.csproj new file mode 100644 index 000000000..f09936c09 --- /dev/null +++ b/dotnet/core/2.0/client_example_datasets/client_example_datasets.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_files/client_example_files.csproj b/dotnet/core/2.0/client_example_files/client_example_files.csproj new file mode 100644 index 000000000..b50d8be89 --- /dev/null +++ b/dotnet/core/2.0/client_example_files/client_example_files.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_log/client_example_log.csproj b/dotnet/core/2.0/client_example_log/client_example_log.csproj new file mode 100644 index 000000000..db171ba0c --- /dev/null +++ b/dotnet/core/2.0/client_example_log/client_example_log.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_model_browsing/client_example_model_browsing.csproj b/dotnet/core/2.0/client_example_model_browsing/client_example_model_browsing.csproj new file mode 100644 index 000000000..04d01c322 --- /dev/null +++ b/dotnet/core/2.0/client_example_model_browsing/client_example_model_browsing.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_reporting/client_example_reporting.csproj b/dotnet/core/2.0/client_example_reporting/client_example_reporting.csproj new file mode 100644 index 000000000..e61df4ce0 --- /dev/null +++ b/dotnet/core/2.0/client_example_reporting/client_example_reporting.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/client_example_tls/client_example_tls.csproj b/dotnet/core/2.0/client_example_tls/client_example_tls.csproj new file mode 100644 index 000000000..c230b6a17 --- /dev/null +++ b/dotnet/core/2.0/client_example_tls/client_example_tls.csproj @@ -0,0 +1,28 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + diff --git a/dotnet/core/2.0/goose_subscriber_example/goose_subscriber_example.csproj b/dotnet/core/2.0/goose_subscriber_example/goose_subscriber_example.csproj new file mode 100644 index 000000000..bf8ca7c9b --- /dev/null +++ b/dotnet/core/2.0/goose_subscriber_example/goose_subscriber_example.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/core/2.0/server_example1/server_example1.csproj b/dotnet/core/2.0/server_example1/server_example1.csproj new file mode 100644 index 000000000..d7e90c277 --- /dev/null +++ b/dotnet/core/2.0/server_example1/server_example1.csproj @@ -0,0 +1,22 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + PreserveNewest + + + + + + + + diff --git a/dotnet/core/2.0/sv_subscriber_example/sv_subscriber_example.csproj b/dotnet/core/2.0/sv_subscriber_example/sv_subscriber_example.csproj new file mode 100644 index 000000000..817353b65 --- /dev/null +++ b/dotnet/core/2.0/sv_subscriber_example/sv_subscriber_example.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + diff --git a/dotnet/reporting/ReportingExample.cs b/dotnet/reporting/ReportingExample.cs index 454f4376a..4edb29b89 100644 --- a/dotnet/reporting/ReportingExample.cs +++ b/dotnet/reporting/ReportingExample.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; using System.Threading; using IEC61850.Client; using IEC61850.Common; -using System.Runtime.Remoting.Metadata.W3cXsd2001; namespace reporting { - class ReportingExample + class ReportingExample { private static void reportHandler (Report report, object parameter) @@ -25,9 +23,7 @@ private static void reportHandler (Report report, object parameter) byte[] entryId = report.GetEntryId (); if (entryId != null) { - SoapHexBinary shb = new SoapHexBinary(entryId); - - Console.WriteLine (" entryID: " + shb.ToString ()); + Console.WriteLine (" entryID: " + BitConverter.ToString(entryId)); } if (report.HasDataSetName ()) { diff --git a/dotnet/server1/Program.cs b/dotnet/server1/Program.cs index 197787906..b4d1c66fb 100644 --- a/dotnet/server1/Program.cs +++ b/dotnet/server1/Program.cs @@ -17,7 +17,7 @@ public static void Main (string[] args) running = false; }; - IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("../../model.cfg"); + IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("model.cfg"); if (iedModel == null) { Console.WriteLine ("No valid data model found!"); diff --git a/dotnet/server1/server1.csproj b/dotnet/server1/server1.csproj index 5d820daf7..a66e2c304 100644 --- a/dotnet/server1/server1.csproj +++ b/dotnet/server1/server1.csproj @@ -1,4 +1,4 @@ - + Debug @@ -41,4 +41,9 @@ IEC61850.NET + + + PreserveNewest + + \ No newline at end of file From 1ac2a7390fb7230fb6fe2ed841bef03e16ca4f3a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 19 May 2018 10:51:09 +0200 Subject: [PATCH 055/123] - .NET API: updated marshalling for GooseControlBlock --- dotnet/IEC61850forCSharp/GooseControlBlock.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/IEC61850forCSharp/GooseControlBlock.cs b/dotnet/IEC61850forCSharp/GooseControlBlock.cs index 03df57094..e0bfbe1cd 100644 --- a/dotnet/IEC61850forCSharp/GooseControlBlock.cs +++ b/dotnet/IEC61850forCSharp/GooseControlBlock.cs @@ -43,14 +43,14 @@ public class GooseControlBlock { static extern IntPtr IedConnection_getGoCBValues (IntPtr connection, out int error, string rcbReference, IntPtr updateRcb); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_setGoCBValues (IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, bool singleRequest); + static extern void IedConnection_setGoCBValues (IntPtr connection, out int error, IntPtr rcb, UInt32 parametersMask, [MarshalAs(UnmanagedType.I1)] bool singleRequest); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] static extern bool ClientGooseControlBlock_getGoEna (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void ClientGooseControlBlock_setGoEna(IntPtr self, bool rptEna); + static extern void ClientGooseControlBlock_setGoEna(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool rptEna); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ClientGooseControlBlock_getGoID (IntPtr self); From 2e434d8d477a3ea9688377bb176cf235a29954ca Mon Sep 17 00:00:00 2001 From: Arthur Zopellaro Date: Tue, 22 May 2018 19:22:45 -0300 Subject: [PATCH 056/123] Update content list Add missing items from content list Add anchor link to each section --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b45cb7ade..4fa3d56af 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,19 @@ This file is part of the documentation of **libIEC61850**. More documentation ca Content: -* Overview -* Building and running the examples -* Installing the library and the API headers -* Building on Windows with GOOSE support -* Building with the cmake build script -* Using the log service with sqlite -* C# API -* Licensing -* Contributing -* Third-party contributions +* [Overview](#overview) +* [Features](#features) +* [Building and running the examples](#building-and-running-the-examples-with-the-provided-makefiles) +* [Building the library with TLS support](#building-the-library-with-tls-support) +* [Installing the library and the API headers](#installing-the-library-and-the-api-headers) +* [Building on Windows with GOOSE support](#building-on-windows-with-goose-support) +* [Building with the cmake build script](#building-with-the-cmake-build-script) +* [Using the log service with sqlite](#using-the-log-service-with-sqlite) +* [C# API](#c-api) +* [Experimental Python bindings](#experimental-python-bindings) +* [Licensing](#commercial-licenses-and-support) +* [Contributing](#contributing) +* [Third-party contributions](#third-party-contributions) ## Overview From a451731454f362e048419b60c421b659bffbb596 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 19 May 2018 15:55:29 +0200 Subject: [PATCH 057/123] - IEC 61850 server: removed unnecessary dynamic string allocation --- src/iec61850/server/impl/ied_server.c | 10 +++------- src/iec61850/server/mms_mapping/mms_mapping.c | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index ff1d65046..1c47e0859 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -204,23 +204,19 @@ createMmsServerCache(IedServer self) ) { - char* variableName = StringUtils_createString(3, lnName, "$", fcName); + char variableName[65]; - if (variableName == NULL) goto exit_function; + StringUtils_createStringInBuffer(variableName, 3, lnName, "$", fcName); MmsValue* defaultValue = MmsValue_newDefaultValue(fcSpec); - if (defaultValue == NULL) { - GLOBAL_FREEMEM(variableName); + if (defaultValue == NULL) goto exit_function; - } if (DEBUG_IED_SERVER) printf("ied_server.c: Insert into cache %s - %s\n", logicalDevice->domainName, variableName); MmsServer_insertIntoCache(self->mmsServer, logicalDevice, variableName, defaultValue); - - GLOBAL_FREEMEM(variableName); } } } diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 13ad7057d..a99bc384d 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1303,6 +1303,7 @@ MmsMapping_create(IedModel* model) self->attributeAccessHandlers = LinkedList_create(); + /* create data model specification */ self->mmsDevice = createMmsModelFromIedModel(self, model); return self; From 605913b0c152d8bb8e6b16434420abef988b949f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 23 May 2018 08:06:12 +0200 Subject: [PATCH 058/123] - fixed some configuration issues --- config/stack_config.h | 47 ++++++++++++++--- config/stack_config.h.cmake | 40 ++++++++++++-- src/goose/goose_receiver.c | 2 + src/iec61850/server/mms_mapping/mms_mapping.c | 8 ++- .../iso_mms/client/mms_client_connection.c | 52 +++++++++++++++++++ src/mms/iso_mms/client/mms_client_files.c | 4 +- src/mms/iso_mms/common/mms_common_msg.c | 8 ++- src/mms/iso_mms/server/mms_file_service.c | 2 +- src/mms/iso_server/iso_connection.c | 2 +- 9 files changed, 145 insertions(+), 20 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index aec739c31..b63e45e72 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -28,7 +28,7 @@ #define DEBUG_HAL_ETHERNET 0 /* Maximum MMS PDU SIZE - default is 65000 */ -#define CONFIG_MMS_MAXIMUM_PDU_SIZE 120000 +#define CONFIG_MMS_MAXIMUM_PDU_SIZE 65000 /* * Enable single threaded mode @@ -201,13 +201,9 @@ #define MMS_STATUS_SERVICE 1 #define MMS_IDENTIFY_SERVICE 1 #define MMS_FILE_SERVICE 1 -#define MMS_OBTAIN_FILE_SERVICE 1 +#define MMS_OBTAIN_FILE_SERVICE 1 /* requires MMS_FILE_SERVICE */ #endif /* MMS_DEFAULT_PROFILE */ -#if (MMS_WRITE_SERVICE != 1) -#undef CONFIG_IEC61850_CONTROL_SERVICE -#define CONFIG_IEC61850_CONTROL_SERVICE 0 -#endif /* support flat named variable name space required by IEC 61850-8-1 MMS mapping */ #define CONFIG_MMS_SUPPORT_FLATTED_NAME_SPACE 1 @@ -227,9 +223,44 @@ /* Support user access to raw messages */ #define CONFIG_MMS_RAW_MESSAGE_LOGGING 1 -/* Allow to set the virtual filestore basepath for MMS file services at runtime with the - * MmsServer_setFilestoreBasepath function +/* Allow to set the virtual file store base path for MMS file services at runtime with the + * MmsServer_setFilestoreBasepath function. */ #define CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME 1 +/************************************************************************************ + * Check configuration for consistency - DO NOT MODIFY THIS PART! + ************************************************************************************/ + +#if (MMS_JOURNAL_SERVICE != 1) + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) +#warning "Invalid configuration: CONFIG_IEC61850_LOG_SERVICE requires MMS_JOURNAL_SERVICE!" +#endif + +#undef CONFIG_IEC61850_LOG_SERVICE +#define CONFIG_IEC61850_LOG_SERVICE 0 + +#endif + +#if (MMS_WRITE_SERVICE != 1) + +#if (CONFIG_IEC61850_CONTROL_SERVICE == 1) +#warning "Invalid configuration: CONFIG_IEC61850_CONTROL_SERVICE requires MMS_WRITE_SERVICE!" +#endif + +#undef CONFIG_IEC61850_CONTROL_SERVICE +#define CONFIG_IEC61850_CONTROL_SERVICE 0 +#endif + +#if (MMS_FILE_SERVICE != 1) + +#if (MMS_OBTAIN_FILE_SERVICE == 1) +#warning "Invalid configuration: MMS_OBTAIN_FILE_SERVICE requires MMS_FILE_SERVICE!" +#endif + +#undef MMS_OBTAIN_FILE_SERVICE +#define MMS_OBTAIN_FILE_SERVICE 0 +#endif + #endif /* STACK_CONFIG_H_ */ diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 6e9197f7b..e6766b47c 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -198,11 +198,6 @@ #define MMS_OBTAIN_FILE_SERVICE 1 #endif /* MMS_DEFAULT_PROFILE */ -#if (MMS_WRITE_SERVICE != 1) -#undef CONFIG_IEC61850_CONTROL_SERVICE -#define CONFIG_IEC61850_CONTROL_SERVICE 0 -#endif - /* Sort getNameList response according to the MMS specified collation order - this is required by the standard * Set to 0 only for performance reasons and when no certification is required! */ #define CONFIG_MMS_SORT_NAME_LIST 1 @@ -220,4 +215,39 @@ */ #define CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME 1 +/************************************************************************************ + * Check configuration for consistency - DO NOT MODIFY THIS PART! + ************************************************************************************/ + +#if (MMS_JOURNAL_SERVICE != 1) + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) +#warning "Invalid configuration: CONFIG_IEC61850_LOG_SERVICE requires MMS_JOURNAL_SERVICE!" +#endif + +#undef CONFIG_IEC61850_LOG_SERVICE +#define CONFIG_IEC61850_LOG_SERVICE 0 + +#endif + +#if (MMS_WRITE_SERVICE != 1) + +#if (CONFIG_IEC61850_CONTROL_SERVICE == 1) +#warning "Invalid configuration: CONFIG_IEC61850_CONTROL_SERVICE requires MMS_WRITE_SERVICE!" +#endif + +#undef CONFIG_IEC61850_CONTROL_SERVICE +#define CONFIG_IEC61850_CONTROL_SERVICE 0 +#endif + +#if (MMS_FILE_SERVICE != 1) + +#if (MMS_OBTAIN_FILE_SERVICE == 1) +#warning "Invalid configuration: MMS_OBTAIN_FILE_SERVICE requires MMS_FILE_SERVICE!" +#endif + +#undef MMS_OBTAIN_FILE_SERVICE +#define MMS_OBTAIN_FILE_SERVICE 0 +#endif + #endif /* STACK_CONFIG_H_ */ diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index 45d8f8427..2a7280938 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -766,6 +766,7 @@ parseGooseMessage(GooseReceiver self, int numbytes) } } +#if (CONFIG_MMS_THREADLESS_STACK == 0) static void gooseReceiverLoop(void* threadParameter) { @@ -789,6 +790,7 @@ gooseReceiverLoop(void* threadParameter) self->stopped = true; } +#endif // start GOOSE receiver in a separate thread void diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index a99bc384d..5e4b2fddb 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -823,6 +823,8 @@ countSVControlBlocksForLogicalNode(MmsMapping* self, LogicalNode* logicalNode, b #endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ +#if (CONFIG_IEC61850_SETTING_GROUPS == 1) + static SettingGroupControlBlock* checkForSgcb(MmsMapping* self, LogicalNode* logicalNode) { @@ -838,6 +840,9 @@ checkForSgcb(MmsMapping* self, LogicalNode* logicalNode) return NULL; } +#endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */ + + static MmsVariableSpecification* createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode) @@ -851,9 +856,8 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, int componentCount = determineLogicalNodeComponentCount(logicalNode); - SettingGroupControlBlock* sgControlBlock = NULL; - #if (CONFIG_IEC61850_SETTING_GROUPS == 1) + SettingGroupControlBlock* sgControlBlock = NULL; sgControlBlock = checkForSgcb(self, logicalNode); diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index ce8f6de4c..0fc531229 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -2074,6 +2074,7 @@ int32_t MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filename, uint32_t initialPosition, uint32_t* fileSize, uint64_t* lastModified) { +#if (MMS_FILE_SERVICE == 1) ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); uint32_t invokeId = getNextInvokeId(self); @@ -2097,11 +2098,19 @@ MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filen releaseResponse(self); return frsmId; +#else + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: service not supported\n"); + + *mmsError = MMS_ERROR_OTHER; + return 0; +#endif } void MmsConnection_fileClose(MmsConnection self, MmsError* mmsError, int32_t frsmId) { +#if (MMS_FILE_SERVICE == 1) ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); uint32_t invokeId = getNextInvokeId(self); @@ -2113,11 +2122,18 @@ MmsConnection_fileClose(MmsConnection self, MmsError* mmsError, int32_t frsmId) /* nothing to do - response contains no data to evaluate */ releaseResponse(self); +#else + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: service not supported\n"); + + *mmsError = MMS_ERROR_OTHER; +#endif } void MmsConnection_fileDelete(MmsConnection self, MmsError* mmsError, const char* fileName) { +#if (MMS_FILE_SERVICE == 1) ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); uint32_t invokeId = getNextInvokeId(self); @@ -2129,12 +2145,19 @@ MmsConnection_fileDelete(MmsConnection self, MmsError* mmsError, const char* fil /* nothing to do - response contains no data to evaluate */ releaseResponse(self); +#else + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: service not supported\n"); + + *mmsError = MMS_ERROR_OTHER; +#endif } bool MmsConnection_fileRead(MmsConnection self, MmsError* mmsError, int32_t frsmId, MmsFileReadHandler handler, void* handlerParameter) { +#if (MMS_FILE_SERVICE == 1) ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); uint32_t invokeId = getNextInvokeId(self); @@ -2156,12 +2179,20 @@ MmsConnection_fileRead(MmsConnection self, MmsError* mmsError, int32_t frsmId, M releaseResponse(self); return moreFollows; +#else + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: service not supported\n"); + + *mmsError = MMS_ERROR_OTHER; + return false; +#endif } bool MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const char* fileSpecification, const char* continueAfter, MmsFileDirectoryHandler handler, void* handlerParameter) { +#if (MMS_FILE_SERVICE == 1) ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); uint32_t invokeId = getNextInvokeId(self); @@ -2180,11 +2211,19 @@ MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const cha releaseResponse(self); return moreFollows; +#else + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: service not supported\n"); + + *mmsError = MMS_ERROR_OTHER; + return false; +#endif } void MmsConnection_fileRename(MmsConnection self, MmsError* mmsError, const char* currentFileName, const char* newFileName) { +#if (MMS_FILE_SERVICE == 1) ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); uint32_t invokeId = getNextInvokeId(self); @@ -2196,11 +2235,18 @@ MmsConnection_fileRename(MmsConnection self, MmsError* mmsError, const char* cur /* nothing to do - response contains no data to evaluate */ releaseResponse(self); +#else + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: service not supported\n"); + + *mmsError = MMS_ERROR_OTHER; +#endif } void MmsConnection_obtainFile(MmsConnection self, MmsError* mmsError, const char* sourceFile, const char* destinationFile) { +#if ((MMS_FILE_SERVICE == 1) && (MMS_OBTAIN_FILE_SERVICE == 1)) ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); uint32_t invokeId = getNextInvokeId(self); @@ -2212,6 +2258,12 @@ MmsConnection_obtainFile(MmsConnection self, MmsError* mmsError, const char* sou /* nothing to do - response contains no data to evaluate */ releaseResponse(self); +#else + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: service not supported\n"); + + *mmsError = MMS_ERROR_OTHER; +#endif } MmsDataAccessError diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index e0886fcec..4310da052 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -198,6 +198,8 @@ mmsClient_handleFileCloseRequest( #endif /* (MMS_OBTAIN_FILE_SERVICE == 1) */ +#if (MMS_FILE_SERVICE == 1) + void mmsClient_createFileOpenRequest(uint32_t invokeId, ByteBuffer* request, const char* fileName, uint32_t initialPosition) { @@ -777,4 +779,4 @@ mmsClient_createFileCloseRequest(uint32_t invokeId, ByteBuffer* request, int32_t request->size = bufPos; } - +#endif /* (MMS_FILE_SERVICE == 1) */ diff --git a/src/mms/iso_mms/common/mms_common_msg.c b/src/mms/iso_mms/common/mms_common_msg.c index 75443e726..47673a48b 100644 --- a/src/mms/iso_mms/common/mms_common_msg.c +++ b/src/mms/iso_mms/common/mms_common_msg.c @@ -1,7 +1,7 @@ /* * mms_common_msg.c * - * Copyright 2013 - 2017 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -344,6 +344,8 @@ mmsMsg_copyAsn1IdentifierToStringBuffer(Identifier_t identifier, char* buffer, i } } +#if (MMS_FILE_SERVICE == 1) + void mmsMsg_createExtendedFilename(const char* basepath, char* extendedFileName, char* fileName) { @@ -358,7 +360,6 @@ mmsMsg_createExtendedFilename(const char* basepath, char* extendedFileName, char } - FileHandle mmsMsg_openFile(const char* basepath, char* fileName, bool readWrite) { @@ -416,3 +417,6 @@ mmsMsg_parseFileName(char* filename, uint8_t* buffer, int* bufPos, int maxBufPos return true; } + +#endif /* (MMS_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 092e260c0..132039e01 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -1,7 +1,7 @@ /* * mms_file_service.c * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/src/mms/iso_server/iso_connection.c b/src/mms/iso_server/iso_connection.c index eb7fe48e4..254cde263 100644 --- a/src/mms/iso_server/iso_connection.c +++ b/src/mms/iso_server/iso_connection.c @@ -432,7 +432,7 @@ IsoConnection_handleTcpConnection(IsoConnection self) return; } -#if (CONFIG_MMS_SINGLE_THREADED == 0) +#if ((CONFIG_MMS_SINGLE_THREADED == 0) && (CONFIG_MMS_THREADLESS_STACK == 0)) static void handleTcpConnection(void* parameter) { From 9e15185c74fd6ce8cd45430d3888f37466a63535 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 24 May 2018 20:50:30 +0200 Subject: [PATCH 059/123] - ISO connection: fixed race condition that can cause corrupted messages --- src/iec61850/server/mms_mapping/reporting.c | 2 +- src/mms/iso_server/iso_connection.c | 39 +++++++++------------ 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index c3cf4bc53..21d2e91ac 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1,7 +1,7 @@ /* * reporting.c * - * Copyright 2013 - 2016 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/src/mms/iso_server/iso_connection.c b/src/mms/iso_server/iso_connection.c index 254cde263..a7705a253 100644 --- a/src/mms/iso_server/iso_connection.c +++ b/src/mms/iso_server/iso_connection.c @@ -1,7 +1,7 @@ /* * iso_connection.c * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -86,8 +86,6 @@ struct sIsoConnection #if (CONFIG_MMS_THREADLESS_STACK != 1) Thread thread; Semaphore conMutex; - - bool isInsideCallback; #endif }; @@ -211,17 +209,8 @@ IsoConnection_handleTcpConnection(IsoConnection self) ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); if (self->msgRcvdHandler != NULL) { - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - self->isInsideCallback = true; -#endif - self->msgRcvdHandler(self->msgRcvdHandlerParameter, &mmsRequest, &mmsResponseBuffer); - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - self->isInsideCallback = false; -#endif } struct sBufferChain mmsBufferPartStruct; @@ -306,16 +295,9 @@ IsoConnection_handleTcpConnection(IsoConnection self) ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); if (self->msgRcvdHandler != NULL) { -#if (CONFIG_MMS_THREADLESS_STACK != 1) - self->isInsideCallback = true; -#endif self->msgRcvdHandler(self->msgRcvdHandlerParameter, mmsRequest, &mmsResponseBuffer); - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - self->isInsideCallback = false; -#endif } /* send a response if required */ @@ -365,6 +347,11 @@ IsoConnection_handleTcpConnection(IsoConnection self) if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: presentation ok\n"); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + IsoServer_userLock(self->isoServer); + Semaphore_wait(self->conMutex); +#endif + struct sBufferChain acseBufferPartStruct; BufferChain acseBufferPart = &acseBufferPartStruct; acseBufferPart->buffer = self->sendBuffer; @@ -387,6 +374,11 @@ IsoConnection_handleTcpConnection(IsoConnection self) IsoSession_createDisconnectSpdu(self->session, sessionBufferPart, presentationBufferPart); CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->conMutex); + IsoServer_userUnlock(self->isoServer); +#endif } self->state = ISO_CON_STATE_STOPPED; @@ -476,7 +468,6 @@ IsoConnection_create(Socket socket, IsoServer isoServer) self->clientAddress = Socket_getPeerAddress(self->socket); #if (CONFIG_MMS_THREADLESS_STACK != 1) - self->isInsideCallback = false; self->conMutex = Semaphore_create(1); #endif @@ -550,9 +541,13 @@ IsoConnection_sendMessage(IsoConnection self, ByteBuffer* message, bool handlerM goto exit_error; } + bool locked = false; + #if (CONFIG_MMS_THREADLESS_STACK != 1) - if (self->isInsideCallback == false) + if (handlerMode == false) { Semaphore_wait(self->conMutex); + locked = true; + } #endif struct sBufferChain payloadBufferStruct; @@ -589,7 +584,7 @@ IsoConnection_sendMessage(IsoConnection self, ByteBuffer* message, bool handlerM } #if (CONFIG_MMS_THREADLESS_STACK != 1) - if (self->isInsideCallback == false) + if (locked) Semaphore_post(self->conMutex); #endif From 2c9a4bb088f76ec7ea32dfce42322963861d8feb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 24 May 2018 22:59:15 +0200 Subject: [PATCH 060/123] - TLS client: fixed problem with high CPU load --- config/stack_config.h | 4 ++++ src/mms/iso_mms/client/mms_client_connection.c | 4 ++-- src/tls/mbedtls/tls_mbedtls.c | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index b63e45e72..5332b048f 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -39,6 +39,10 @@ */ #define CONFIG_MMS_SINGLE_THREADED 0 +#if (WITH_MBEDTLS == 1) +#define CONFIG_MMS_SUPPORT_TLS 1 +#endif + /* * Optimize stack for threadless operation - don't use semaphores * diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 0fc531229..93fb0d18a 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1283,9 +1283,9 @@ MmsConnection_connect(MmsConnection self, MmsError* mmsError, const char* server if (serverPort == -1) { #if (CONFIG_MMS_SUPPORT_TLS == 1) if (self->isoParameters->tlsConfiguration) - serverPort = 3782; + serverPort = 3782; else - serverPort = 102; + serverPort = 102; #else serverPort = 102; #endif diff --git a/src/tls/mbedtls/tls_mbedtls.c b/src/tls/mbedtls/tls_mbedtls.c index 8ee8ace88..f0dc1da27 100644 --- a/src/tls/mbedtls/tls_mbedtls.c +++ b/src/tls/mbedtls/tls_mbedtls.c @@ -343,8 +343,10 @@ readFunction(void* ctx, unsigned char* buf, size_t len) { int ret = Socket_read((Socket) ctx, buf, len); - if ((ret == 0) && (len > 0)) + if ((ret == 0) && (len > 0)) { + Thread_sleep(1); return MBEDTLS_ERR_SSL_WANT_READ; + } return ret; } From 9a8415b3e64017037886a257308c7c9dcfba83a7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 26 May 2018 14:29:10 +0200 Subject: [PATCH 061/123] - IEC 61850 server: prevent sending reports when data model is locked (updated) --- .../server_example_basic_io.c | 4 +- src/iec61850/inc/iec61850_server.h | 6 +- src/iec61850/inc_private/mms_mapping.h | 9 ++- .../inc_private/mms_mapping_internal.h | 5 +- src/iec61850/inc_private/reporting.h | 7 +- src/iec61850/server/impl/ied_server.c | 7 ++ src/iec61850/server/mms_mapping/mms_mapping.c | 5 +- src/iec61850/server/mms_mapping/reporting.c | 75 +++++++++++++++++-- 8 files changed, 99 insertions(+), 19 deletions(-) diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index 30faf6e40..14c44e321 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -133,8 +133,6 @@ main(int argc, char** argv) float an3 = sinf(t + 2.f); float an4 = sinf(t + 3.f); - IedServer_lockDataModel(iedServer); - Timestamp iecTimestamp; Timestamp_clearFlags(&iecTimestamp); @@ -145,6 +143,8 @@ main(int argc, char** argv) if (((int) t % 2) == 0) Timestamp_setClockNotSynchronized(&iecTimestamp, true); + IedServer_lockDataModel(iedServer); + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_t, &iecTimestamp); IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f, an1); diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 8fdd91eea..eadafaef0 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -338,8 +338,10 @@ IedServer_setConnectionIndicationHandler(IedServer self, IedConnectionIndication /** - * \brief Lock the MMS server data model. + * \brief Lock the data model for data update. * + * This function should be called before the data model is updated. + * After updating the data model the function \ref IedServer_unlockDataModel should be called. * Client requests will be postponed until the lock is removed. * * NOTE: This method should never be called inside of a library callback function. In the context of @@ -352,7 +354,7 @@ void IedServer_lockDataModel(IedServer self); /** - * \brief Unlock the MMS server data model and process pending client requests. + * \brief Unlock the data model and process pending client requests. * * NOTE: 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! diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index cff0969ff..ec6240ad2 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -29,10 +29,11 @@ #include "control.h" typedef enum { - REPORT_CONTROL_NONE, - REPORT_CONTROL_VALUE_UPDATE, - REPORT_CONTROL_VALUE_CHANGED, - REPORT_CONTROL_QUALITY_CHANGED + REPORT_CONTROL_NONE = 0, + REPORT_CONTROL_VALUE_UPDATE = 1, + REPORT_CONTROL_VALUE_CHANGED = 2, + REPORT_CONTROL_QUALITY_CHANGED = 4, + REPORT_CONTROL_NOT_UPDATED = 8 } ReportInclusionFlag; typedef enum { diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index 52b7cf2df..f136a39ac 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -1,7 +1,7 @@ /* * mms_mapping_internal.h * - * Copyright 2013-2016 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -64,6 +64,9 @@ struct sMmsMapping { Thread reportWorkerThread; #endif + /* flag indicates if data model is locked --> prevents reports to be sent */ + bool isModelLocked; + IedServer iedServer; IedConnectionIndicationHandler connectionIndicationHandler; diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index e484ad1ea..dce535a44 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -103,7 +103,7 @@ void ReportControl_destroy(ReportControl* self); void -ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, ReportInclusionFlag flag); +ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, ReportInclusionFlag flag, bool modelLocked); MmsValue* ReportControl_getRCBValue(ReportControl* rc, char* elementName); @@ -123,9 +123,14 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme void Reporting_activateBufferedReports(MmsMapping* self); +/* periodic check if reports have to be sent */ void Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs); +/* check if report have to be sent after data model update */ +void +Reporting_processReportEventsAfterUnlock(MmsMapping* self); + void Reporting_deactivateReportsForConnection(MmsMapping* self, MmsServerConnection connection); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 1c47e0859..c219b9a06 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -649,11 +649,18 @@ void IedServer_lockDataModel(IedServer self) { MmsServer_lockModel(self->mmsServer); + + self->mmsMapping->isModelLocked = true; } void IedServer_unlockDataModel(IedServer self) { + /* check if reports have to be sent! */ + Reporting_processReportEventsAfterUnlock(self->mmsMapping); + + self->mmsMapping->isModelLocked = false; + MmsServer_unlockModel(self->mmsServer); } diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 5e4b2fddb..7cf2225bd 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2757,7 +2757,10 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclu } if (DataSet_isMemberValue(rc->dataSet, value, &index)) { - ReportControl_valueUpdated(rc, index, flag); + + bool modelLocked = self->isModelLocked; + + ReportControl_valueUpdated(rc, index, flag, modelLocked); } } } diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 21d2e91ac..642d06fcb 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -2513,37 +2513,96 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) void Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs) +{ + if (self->isModelLocked == false) { + + LinkedList element = self->reportControls; + + while ((element = LinkedList_getNext(element)) != NULL ) { + ReportControl* rc = (ReportControl*) element->data; + + ReportControl_lockNotify(rc); + + processEventsForReport(rc, currentTimeInMs); + + ReportControl_unlockNotify(rc); + } + } +} + +static inline void +copySingleValueToReportBuffer(ReportControl* self, int dataSetEntryIndex) +{ + if (self->bufferedDataSetValues[dataSetEntryIndex] == NULL) + self->bufferedDataSetValues[dataSetEntryIndex] = MmsValue_clone(self->valueReferences[dataSetEntryIndex]); + else + MmsValue_update(self->bufferedDataSetValues[dataSetEntryIndex], self->valueReferences[dataSetEntryIndex]); +} + +static void +copyValuesToReportBuffer(ReportControl* self) +{ + int i; + for (i = 0; i < self->dataSet->elementCount; i++) { + if (self->inclusionFlags[i] & REPORT_CONTROL_NOT_UPDATED) { + copySingleValueToReportBuffer(self, i); + + /* clear not-updated flag */ + self->inclusionFlags[i] &= (~REPORT_CONTROL_NOT_UPDATED); + } + } +} + +/* check if report have to be sent after data model update */ +void +Reporting_processReportEventsAfterUnlock(MmsMapping* self) { LinkedList element = self->reportControls; + uint64_t currentTime = Hal_getTimeInMs(); + while ((element = LinkedList_getNext(element)) != NULL ) { ReportControl* rc = (ReportControl*) element->data; ReportControl_lockNotify(rc); - processEventsForReport(rc, currentTimeInMs); + if ((rc->enabled) || (rc->isBuffering)) { + copyValuesToReportBuffer(rc); + + processEventsForReport(rc, currentTime); + } ReportControl_unlockNotify(rc); } } void -ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, ReportInclusionFlag flag) +ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, ReportInclusionFlag flag, bool modelLocked) { ReportControl_lockNotify(self); if (self->inclusionFlags[dataSetEntryIndex] != 0) { /* report for this data set entry is already pending (bypass BufTm) */ self->reportTime = Hal_getTimeInMs(); + + if (modelLocked) { + /* buffer all relevant values */ + copyValuesToReportBuffer(self); + } + processEventsForReport(self, self->reportTime); } - self->inclusionFlags[dataSetEntryIndex] = flag; + if (modelLocked) { + /* set flag to update values when report is to be sent or data model unlocked */ + self->inclusionFlags[dataSetEntryIndex] = flag | REPORT_CONTROL_NOT_UPDATED; - /* buffer value for report */ - if (self->bufferedDataSetValues[dataSetEntryIndex] == NULL) - self->bufferedDataSetValues[dataSetEntryIndex] = MmsValue_clone(self->valueReferences[dataSetEntryIndex]); - else - MmsValue_update(self->bufferedDataSetValues[dataSetEntryIndex], self->valueReferences[dataSetEntryIndex]); + } + else { + self->inclusionFlags[dataSetEntryIndex] = flag; + + /* buffer value for report */ + copySingleValueToReportBuffer(self, dataSetEntryIndex); + } if (self->triggered == false) { uint64_t currentTime = Hal_getTimeInMs(); From 7cb5ff670a4a55af11faea5809d8cd810590030a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 26 May 2018 19:13:47 +0200 Subject: [PATCH 062/123] - IEC 61850 server: added support to configure report buffer at runtime - IEC 61850 server: new IedServerConfig type and new IedServer constructor --- CHANGELOG | 6 ++ .../server_example_basic_io.c | 14 ++++- src/CMakeLists.txt | 1 + .../inc/libiec61850_platform_includes.h | 2 +- src/iec61850/inc/iec61850_common.h | 3 +- src/iec61850/inc/iec61850_server.h | 55 ++++++++++++++++++- src/iec61850/inc_private/ied_server_private.h | 2 +- src/iec61850/inc_private/mms_mapping.h | 7 +-- src/iec61850/inc_private/reporting.h | 2 +- src/iec61850/server/impl/ied_server.c | 22 ++++++-- src/iec61850/server/impl/ied_server_config.c | 55 +++++++++++++++++++ src/iec61850/server/mms_mapping/mms_mapping.c | 9 +-- src/iec61850/server/mms_mapping/reporting.c | 34 ++++++++---- src/vs/libiec61850-wo-goose.def | 7 ++- src/vs/libiec61850.def | 7 ++- 15 files changed, 187 insertions(+), 39 deletions(-) create mode 100644 src/iec61850/server/impl/ied_server_config.c diff --git a/CHANGELOG b/CHANGELOG index d33905156..d152b05a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +Changes to version 1.2.2 +------------------------ + +- IEC 61850 server: added support to configure report buffer at runtime +- IEC 61850 server: new IedServerConfig type and new IedServer constructor + Changes to version 1.2.1 ------------------------ diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index 14c44e321..218a7466b 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -83,7 +83,17 @@ main(int argc, char** argv) { printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); - iedServer = IedServer_create(&iedModel); + /* Create new server configuration object */ + IedServerConfig config = IedServerConfig_create(); + + /* Set buffer size for buffered report control blocks to 200000 bytes */ + IedServerConfig_setReportBufferSize(config, 200000); + + /* Create a new IEC 61850 server instance */ + iedServer = IedServer_createWithConfig(&iedModel, NULL, config); + + /* configuration object is no longer required */ + IedServerConfig_destroy(config); /* Set the base path for the MMS file services */ MmsServer mmsServer = IedServer_getMmsServer(iedServer); @@ -108,7 +118,7 @@ main(int argc, char** argv) IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); - /* MMS server will be instructed to start listening to client connections. */ + /* MMS server will be instructed to start listening for client connections. */ IedServer_start(iedServer, 102); if (!IedServer_isRunning(iedServer)) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81fbd3cc8..03d77fabf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,6 +66,7 @@ set (lib_common_SRCS ./iec61850/client/ied_connection.c ./iec61850/common/iec61850_common.c ./iec61850/server/impl/ied_server.c +./iec61850/server/impl/ied_server_config.c ./iec61850/server/impl/client_connection.c ./iec61850/server/model/model.c ./iec61850/server/model/dynamic_model.c diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index 5ca0f5234..d25ac59cb 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -15,7 +15,7 @@ #include "platform_endian.h" -#define LIBIEC61850_VERSION "1.2.1" +#define LIBIEC61850_VERSION "1.2.2" #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index 5b5a14e4a..d8de6a3e9 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -421,12 +421,11 @@ Timestamp_toMmsValue(Timestamp* self, MmsValue* mmsValue); /** * \brief Get the version of the library as string * - * \return the version of the library (e.g. "0.8.3") + * \return the version of the library (e.g. "1.2.2") */ char* LibIEC61850_getVersionString(void); - /** @} */ /**@}*/ diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index eadafaef0..30f572017 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -3,7 +3,7 @@ * * IEC 61850 server API for libiec61850. * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -41,6 +41,49 @@ extern "C" { #include "hal_filesystem.h" #include "iec61850_config_file_parser.h" +/** + * \brief Configuration object to configure IEC 61850 stack features + */ +typedef struct sIedServerConfig* IedServerConfig; + +struct sIedServerConfig +{ + /** size of the report buffer associated with a buffered report control block */ + int reportBufferSize; +}; + +/** + * \brief Create a new configuration object + * + * \return a new configuration object with default configuration values + */ +IedServerConfig +IedServerConfig_create(void); + +/** + * \brief Destroy the configuration object + */ +void +IedServerConfig_destroy(IedServerConfig self); + +/** + * \brief Set the report buffer size for buffered reporting + * + * \param reportBufferSize the buffer size for each buffered report control block + */ +void +IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize); + +/** + * \brief Gets the report buffer size for buffered reporting + * + * \return the buffer size for each buffered report control block + */ +int +IedServerConfig_getReportBufferSize(IedServerConfig self); + + + /** * An opaque handle for an IED server instance */ @@ -79,6 +122,16 @@ IedServer_create(IedModel* dataModel); IedServer IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfiguration); +/** + * \brief Create new new IedServer with extended configurations parameters + * + * \param dataModel reference to the IedModel data structure to be used as IEC 61850 data model of the device + * \param tlsConfiguration TLS configuration object, or NULL to not use TLS + * \param serverConfiguration IED server configuration object for advanced server configuration + */ +IedServer +IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguration, IedServerConfig serverConfiguration); + /** * \brief Destroy an IedServer instance and release all resources (memory, TCP sockets) * diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index a63082677..d78702ccc 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -3,7 +3,7 @@ * * Library private function definitions for IedServer. * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index ec6240ad2..5c0335352 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -1,7 +1,7 @@ /* * mms_mapping.h * - * Copyright 2013-2016 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -46,7 +46,7 @@ typedef enum { typedef struct sMmsMapping MmsMapping; MmsMapping* -MmsMapping_create(IedModel* model); +MmsMapping_create(IedModel* model, IedServer iedServer); MmsDevice* MmsMapping_getMmsDeviceModel(MmsMapping* mapping); @@ -138,9 +138,6 @@ MmsMapping_ObjectReferenceToVariableAccessSpec(char* objectReference); char* MmsMapping_varAccessSpecToObjectReference(MmsVariableAccessSpecification* varAccessSpec); -void -MmsMapping_setIedServer(MmsMapping* self, IedServer iedServer); - void MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicationHandler handler, void* parameter); diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index dce535a44..2dddb257d 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -97,7 +97,7 @@ typedef struct { } ReportControl; ReportControl* -ReportControl_create(bool buffered, LogicalNode* parentLN); +ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize); void ReportControl_destroy(ReportControl* self); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index c219b9a06..280819801 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -391,8 +391,9 @@ updateDataSetsWithCachedValues(IedServer self) } } + IedServer -IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfiguration) +IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguration, IedServerConfig serverConfiguration) { IedServer self = (IedServer) GLOBAL_CALLOC(1, sizeof(struct sIedServer)); @@ -407,7 +408,14 @@ IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfigur self->dataModelLock = Semaphore_create(1); #endif - self->mmsMapping = MmsMapping_create(dataModel); +#if (CONFIG_IEC61850_REPORT_SERVICE == 1) + if (serverConfiguration) + self->reportBufferSize = serverConfiguration->reportBufferSize; + else + self->reportBufferSize = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; +#endif + + self->mmsMapping = MmsMapping_create(dataModel, self); self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping); @@ -417,8 +425,6 @@ IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfigur MmsMapping_installHandlers(self->mmsMapping); - MmsMapping_setIedServer(self->mmsMapping, self); - createMmsServerCache(self); dataModel->initializer(); @@ -447,7 +453,13 @@ IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfigur IedServer IedServer_create(IedModel* dataModel) { - return IedServer_createWithTlsSupport(dataModel, NULL); + return IedServer_createWithConfig(dataModel, NULL, NULL); +} + +IedServer +IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfiguration) +{ + return IedServer_createWithConfig(dataModel, tlsConfiguration, NULL); } void diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c new file mode 100644 index 000000000..4f0f50721 --- /dev/null +++ b/src/iec61850/server/impl/ied_server_config.c @@ -0,0 +1,55 @@ +/* + * ied_server_config.c + * + * Copyright 2018 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "iec61850_server.h" +#include "libiec61850_platform_includes.h" + +IedServerConfig +IedServerConfig_create() +{ + IedServerConfig self = (IedServerConfig) GLOBAL_MALLOC(sizeof(struct sIedServerConfig)); + + if (self) { + self->reportBufferSize = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; + } + + return self; +} + +void +IedServerConfig_destroy(IedServerConfig self) +{ + GLOBAL_FREEMEM(self); +} + +void +IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize) +{ + self->reportBufferSize = reportBufferSize; +} + +int +IedServerConfig_getReportBufferSize(IedServerConfig self) +{ + return self->reportBufferSize; +} diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 7cf2225bd..957c34291 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1272,11 +1272,12 @@ createMmsModelFromIedModel(MmsMapping* self, IedModel* iedModel) } MmsMapping* -MmsMapping_create(IedModel* model) +MmsMapping_create(IedModel* model, IedServer iedServer) { MmsMapping* self = (MmsMapping*) GLOBAL_CALLOC(1, sizeof(struct sMmsMapping)); self->model = model; + self->iedServer = iedServer; #if (CONFIG_IEC61850_REPORT_SERVICE == 1) self->reportControls = LinkedList_create(); @@ -2564,12 +2565,6 @@ MmsMapping_installHandlers(MmsMapping* self) MmsServer_installVariableListChangedHandler(self->mmsServer, variableListChangedHandler, (void*) self); } -void -MmsMapping_setIedServer(MmsMapping* self, IedServer iedServer) -{ - self->iedServer = iedServer; -} - void MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicationHandler handler, void* parameter) { diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 642d06fcb..5edf15045 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -35,6 +35,7 @@ #include "mms_value_internal.h" #include "conversions.h" #include "reporting.h" +#include "ied_server_private.h" #include #ifndef DEBUG_IED_SERVER @@ -62,16 +63,25 @@ static ReportBuffer* -ReportBuffer_create(void) +ReportBuffer_create(int bufferSize) { ReportBuffer* self = (ReportBuffer*) GLOBAL_MALLOC(sizeof(ReportBuffer)); - self->lastEnqueuedReport = NULL; - self->oldestReport = NULL; - self->nextToTransmit = NULL; - self->memoryBlockSize = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; - self->memoryBlock = (uint8_t*) GLOBAL_MALLOC(self->memoryBlockSize); - self->reportsCount = 0; - self->isOverflow = true; + + if (self) { + self->lastEnqueuedReport = NULL; + self->oldestReport = NULL; + self->nextToTransmit = NULL; + self->reportsCount = 0; + self->isOverflow = true; + + self->memoryBlockSize = bufferSize; + self->memoryBlock = (uint8_t*) GLOBAL_MALLOC(self->memoryBlockSize); + + if (self->memoryBlock == NULL) { + GLOBAL_FREEMEM(self); + self = NULL; + } + } return self; } @@ -84,7 +94,7 @@ ReportBuffer_destroy(ReportBuffer* self) } ReportControl* -ReportControl_create(bool buffered, LogicalNode* parentLN) +ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize) { ReportControl* self = (ReportControl*) GLOBAL_MALLOC(sizeof(ReportControl)); self->name = NULL; @@ -120,7 +130,7 @@ ReportControl_create(bool buffered, LogicalNode* parentLN) self->lastEntryId = 0; if (buffered) { - self->reportBuffer = ReportBuffer_create(); + self->reportBuffer = ReportBuffer_create(reportBufferSize); } return self; @@ -1156,7 +1166,7 @@ Reporting_createMmsBufferedRCBs(MmsMapping* self, MmsDomain* domain, int currentReport = 0; while (currentReport < reportsCount) { - ReportControl* rc = ReportControl_create(true, logicalNode); + ReportControl* rc = ReportControl_create(true, logicalNode, self->iedServer->reportBufferSize); rc->domain = domain; @@ -1193,7 +1203,7 @@ Reporting_createMmsUnbufferedRCBs(MmsMapping* self, MmsDomain* domain, int currentReport = 0; while (currentReport < reportsCount) { - ReportControl* rc = ReportControl_create(false, logicalNode); + ReportControl* rc = ReportControl_create(false, logicalNode, self->iedServer->reportBufferSize); rc->domain = domain; diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index f6c247ea4..7c866d421 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -580,4 +580,9 @@ EXPORTS CDC_VSS_create CDC_VSG_create Timestamp_createFromByteArray - IedModel_getDeviceByIndex \ No newline at end of file + IedModel_getDeviceByIndex + IedServerConfig_create + IedServerConfig_destroy + IedServerConfig_setReportBufferSize + IedServerConfig_getReportBufferSize + IedServer_createWithConfig \ No newline at end of file diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 6ba5d3030..c9190c50d 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -707,4 +707,9 @@ EXPORTS SVPublisher_ASDU_setQuality Timestamp_createFromByteArray IedModel_getDeviceByIndex - SVReceiver_enableDestAddrCheck \ No newline at end of file + SVReceiver_enableDestAddrCheck + IedServerConfig_create + IedServerConfig_destroy + IedServerConfig_setReportBufferSize + IedServerConfig_getReportBufferSize + IedServer_createWithConfig \ No newline at end of file From c28d06f4d89c7ab02599083151771d86e3fb4461 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 26 May 2018 19:34:55 +0200 Subject: [PATCH 063/123] - IEC 61850 server: added support to set file service base path with server configuration object --- .../server_example_basic_io.c | 8 ++++---- src/iec61850/inc/iec61850_server.h | 17 ++++++++++++++++ src/iec61850/server/impl/ied_server.c | 3 +++ src/iec61850/server/impl/ied_server_config.c | 20 +++++++++++++++++++ src/vs/libiec61850-wo-goose.def | 4 +++- src/vs/libiec61850.def | 4 +++- 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index 218a7466b..b77b814c7 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -3,6 +3,7 @@ * * - How to use simple control models * - How to serve analog measurement data + * - Using the IedServerConfig object to configure stack features */ #include "iec61850_server.h" @@ -89,16 +90,15 @@ main(int argc, char** argv) /* Set buffer size for buffered report control blocks to 200000 bytes */ IedServerConfig_setReportBufferSize(config, 200000); + /* Set the base path for the MMS file services */ + IedServerConfig_setFileServiceBasePath(config, "./vmd-filestore/"); + /* Create a new IEC 61850 server instance */ iedServer = IedServer_createWithConfig(&iedModel, NULL, config); /* configuration object is no longer required */ IedServerConfig_destroy(config); - /* Set the base path for the MMS file services */ - MmsServer mmsServer = IedServer_getMmsServer(iedServer); - MmsServer_setFilestoreBasepath(mmsServer, "./vmd-filestore/"); - /* Install handler for operate command */ IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1, (ControlHandler) controlHandlerForBinaryOutput, diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 30f572017..39589a64f 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -50,6 +50,9 @@ struct sIedServerConfig { /** size of the report buffer associated with a buffered report control block */ int reportBufferSize; + + /** Base path (directory where the file service serves files */ + char* fileServiceBasepath; }; /** @@ -82,6 +85,20 @@ IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize); int IedServerConfig_getReportBufferSize(IedServerConfig self); +/** + * \brief Set the basepath of the file services + * + * \param basepath new file service base path + */ +void +IedServerConfig_setFileServiceBasePath(IedServerConfig self, const char* basepath); + +/** + * \brief Get the basepath of the file services + */ +const char* +IedServerConfig_getFileServiceBasePath(IedServerConfig self); + /** diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 280819801..8fa94f6ef 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -421,6 +421,9 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->mmsServer = MmsServer_create(self->mmsDevice, tlsConfiguration); + if (serverConfiguration) + MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); + MmsMapping_setMmsServer(self->mmsMapping, self->mmsServer); MmsMapping_installHandlers(self->mmsMapping); diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index 4f0f50721..2fa5065a2 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -31,6 +31,7 @@ IedServerConfig_create() if (self) { self->reportBufferSize = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; + self->fileServiceBasepath = StringUtils_copyString(CONFIG_VIRTUAL_FILESTORE_BASEPATH); } return self; @@ -39,6 +40,7 @@ IedServerConfig_create() void IedServerConfig_destroy(IedServerConfig self) { + GLOBAL_FREEMEM(self->fileServiceBasepath); GLOBAL_FREEMEM(self); } @@ -53,3 +55,21 @@ IedServerConfig_getReportBufferSize(IedServerConfig self) { return self->reportBufferSize; } + +void +IedServerConfig_setFileServiceBasePath(IedServerConfig self, const char* basepath) +{ +#if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) + GLOBAL_FREEMEM(self->fileServiceBasepath); + self->fileServiceBasepath = StringUtils_copyString(basepath); +#else + if (DEBUG_IED_SERVER) + printf("IED_SERVER_CONFIG: Cannot set file service basepath (enable CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME)!\n"); +#endif +} + +const char* +IedServerConfig_getFileServiceBasePath(IedServerConfig self) +{ + return self->fileServiceBasepath; +} diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 7c866d421..3df65ab06 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -585,4 +585,6 @@ EXPORTS IedServerConfig_destroy IedServerConfig_setReportBufferSize IedServerConfig_getReportBufferSize - IedServer_createWithConfig \ No newline at end of file + IedServer_createWithConfig + IedServerConfig_setFileServiceBasePath + IedServerConfig_getFileServiceBasePath diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index c9190c50d..1f7fe8ce0 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -712,4 +712,6 @@ EXPORTS IedServerConfig_destroy IedServerConfig_setReportBufferSize IedServerConfig_getReportBufferSize - IedServer_createWithConfig \ No newline at end of file + IedServer_createWithConfig + IedServerConfig_setFileServiceBasePath + IedServerConfig_getFileServiceBasePath From cc24c864847aa0107457bf430ca70a7ac2201f4c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 26 May 2018 20:04:07 +0200 Subject: [PATCH 064/123] - Fixed to compile with C++ --- src/iec61850/inc_private/mms_mapping.h | 14 ++++++-------- src/iec61850/inc_private/reporting.h | 4 ++-- src/iec61850/server/mms_mapping/mms_mapping.c | 2 +- src/iec61850/server/mms_mapping/reporting.c | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index 5c0335352..a97e51d69 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -28,13 +28,11 @@ #include "mms_device_model.h" #include "control.h" -typedef enum { - REPORT_CONTROL_NONE = 0, - REPORT_CONTROL_VALUE_UPDATE = 1, - REPORT_CONTROL_VALUE_CHANGED = 2, - REPORT_CONTROL_QUALITY_CHANGED = 4, - REPORT_CONTROL_NOT_UPDATED = 8 -} ReportInclusionFlag; +#define REPORT_CONTROL_NONE 0U +#define REPORT_CONTROL_VALUE_UPDATE 1U +#define REPORT_CONTROL_VALUE_CHANGED 2U +#define REPORT_CONTROL_QUALITY_CHANGED 4U +#define REPORT_CONTROL_NOT_UPDATED 8U typedef enum { LOG_CONTROL_NONE, @@ -91,7 +89,7 @@ DataSet* MmsMapping_createDataSetByNamedVariableList(MmsMapping* self, MmsNamedVariableList variableList); void -MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag); +MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag); void MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag); diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index 2dddb257d..b603efd42 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -80,7 +80,7 @@ typedef struct { Semaphore createNotificationsMutex; /* { covered by mutex } */ #endif - ReportInclusionFlag* inclusionFlags; /* { covered by mutex } */ + uint8_t* inclusionFlags; /* { covered by mutex } */ bool triggered; /* { covered by mutex } */ uint64_t reportTime; /* { covered by mutex } */ @@ -103,7 +103,7 @@ void ReportControl_destroy(ReportControl* self); void -ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, ReportInclusionFlag flag, bool modelLocked); +ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag, bool modelLocked); MmsValue* ReportControl_getRCBValue(ReportControl* rc, char* elementName); diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 957c34291..edb53e4f3 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2723,7 +2723,7 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl #if (CONFIG_IEC61850_REPORT_SERVICE == 1) void -MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag) +MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) { LinkedList element = self->reportControls; diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 5edf15045..ce2509c37 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -662,7 +662,7 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, if (rc->inclusionFlags != NULL) GLOBAL_FREEMEM(rc->inclusionFlags); - rc->inclusionFlags = (ReportInclusionFlag*) GLOBAL_CALLOC(dataSet->elementCount, sizeof(ReportInclusionFlag)); + rc->inclusionFlags = (uint8_t*) GLOBAL_CALLOC(dataSet->elementCount, sizeof(uint8_t)); success = true; @@ -2368,7 +2368,7 @@ sendNextReportEntry(ReportControl* self) MmsValue_deleteAllBitStringBits(reason); - switch((ReportInclusionFlag) *currentReportBufferPos) { + switch((int) *currentReportBufferPos) { case REPORT_CONTROL_QUALITY_CHANGED: MmsValue_setBitStringBit(reason, 2, true); break; @@ -2587,7 +2587,7 @@ Reporting_processReportEventsAfterUnlock(MmsMapping* self) } void -ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, ReportInclusionFlag flag, bool modelLocked) +ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag, bool modelLocked) { ReportControl_lockNotify(self); From 48b14619a3b681dccb6a32489b791fd5fe4cd04b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 26 May 2018 20:38:19 +0200 Subject: [PATCH 065/123] - .NET API: Added IedServerConfig class --- dotnet/IEC61850forCSharp/IEC61850.NET.csproj | 3 +- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 13 ++ dotnet/IEC61850forCSharp/IedServerConfig.cs | 111 ++++++++++++++++++ dotnet/server1/Program.cs | 5 +- 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 dotnet/IEC61850forCSharp/IedServerConfig.cs diff --git a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj index 750d6a4b5..78b3cbcd1 100644 --- a/dotnet/IEC61850forCSharp/IEC61850.NET.csproj +++ b/dotnet/IEC61850forCSharp/IEC61850.NET.csproj @@ -7,8 +7,6 @@ Library iec61850dotnet iec61850dotnet - 8.0.30703 - 2.0 true @@ -52,6 +50,7 @@ + \ No newline at end of file diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index e8d90bc38..4b95c19ed 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -561,6 +561,9 @@ public class IedServer [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr IedServer_create(IntPtr modelRef); + [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); @@ -791,6 +794,16 @@ public IedServer(IedModel iedModel) self = IedServer_create(iedModel.self); } + public IedServer(IedModel iedModel, IedServerConfig config) + { + IntPtr nativeConfig = IntPtr.Zero; + + if (config != null) + nativeConfig = config.self; + + self = IedServer_createWithConfig (iedModel.self, IntPtr.Zero, nativeConfig); + } + // causes undefined behavior //~IedServer() //{ diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs new file mode 100644 index 000000000..550c3a26d --- /dev/null +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -0,0 +1,111 @@ +/* + * IedServerConfig.cs + * + * Copyright 2018 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +using System; +using System.Runtime.InteropServices; + +namespace IEC61850.Server +{ + /// + /// 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 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_setFileServiceBasePath(IntPtr self, string basepath); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedServerConfig_getFileServiceBasePath(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 file service base path. + /// + /// The file service base path. + public string FileServiceBasePath + { + get { + return Marshal.PtrToStringAnsi (IedServerConfig_getFileServiceBasePath (self)); + } + set { + IedServerConfig_setFileServiceBasePath (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/dotnet/server1/Program.cs b/dotnet/server1/Program.cs index b4d1c66fb..62c7c5e36 100644 --- a/dotnet/server1/Program.cs +++ b/dotnet/server1/Program.cs @@ -26,7 +26,10 @@ public static void Main (string[] args) DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.SPCSO1"); - IedServer iedServer = new IedServer (iedModel); + IedServerConfig config = new IedServerConfig (); + config.ReportBufferSize = 100000; + + IedServer iedServer = new IedServer (iedModel, config); iedServer.SetControlHandler (spcso1, delegate(DataObject controlObject, object parameter, MmsValue ctlVal, bool test) { bool val = ctlVal.GetBoolean(); From 56046beebe75abdb736eab8865f6f8796e22dc92 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 26 May 2018 20:42:16 +0200 Subject: [PATCH 066/123] - updated project file for .NET core --- .../core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj b/dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj index 4894edb79..7e30a5439 100644 --- a/dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj +++ b/dotnet/core/2.0/IEC61850.NET.core.2.0/IEC61850.NET.core.2.0.csproj @@ -12,6 +12,7 @@ + From 38ee7b43ef91c191f1101aeee7cd4421004dcc7a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 26 May 2018 20:48:57 +0200 Subject: [PATCH 067/123] - updated version to 1.2.2 --- CHANGELOG | 7 ++++++- CMakeLists.txt | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d152b05a6..7fc9c7444 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,13 @@ Changes to version 1.2.2 ------------------------ -- IEC 61850 server: added support to configure report buffer at runtime +- IEC 61850 server: added support to configure report buffer size at runtime - IEC 61850 server: new IedServerConfig type and new IedServer constructor +- .NET API: added support for IedServerConfig +- IEC 61850 server: prevent sending reports when data model is locked (updated) +- TLS client: fixed problem with high CPU load +- ISO connection: fixed race condition that can cause corrupted messages +- .NET API: added project files for .NET core 2.0 Changes to version 1.2.1 ------------------------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 221297a3b..55b76d0d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ ENABLE_TESTING() set(LIB_VERSION_MAJOR "1") set(LIB_VERSION_MINOR "2") -set(LIB_VERSION_PATCH "1") +set(LIB_VERSION_PATCH "2") set(LIB_VERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/") From 49d06cc9d37888c633057f894fd4a81e7f078dbd Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 27 May 2018 20:16:27 +0200 Subject: [PATCH 068/123] - NET API: added TLS support for server side --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 25 +- dotnet/IEC61850forCSharp/TLS.cs | 8 - dotnet/dotnet.sln | 6 + dotnet/tls_server_example/Program.cs | 85 +++++++ .../Properties/AssemblyInfo.cs | 27 ++ dotnet/tls_server_example/model.cfg | 237 ++++++++++++++++++ dotnet/tls_server_example/root.cer | Bin 0 -> 785 bytes dotnet/tls_server_example/server-key.pem | 27 ++ dotnet/tls_server_example/server.cer | Bin 0 -> 767 bytes .../tls_server_example.csproj | 58 +++++ 10 files changed, 457 insertions(+), 16 deletions(-) create mode 100644 dotnet/tls_server_example/Program.cs create mode 100644 dotnet/tls_server_example/Properties/AssemblyInfo.cs create mode 100644 dotnet/tls_server_example/model.cfg create mode 100644 dotnet/tls_server_example/root.cer create mode 100644 dotnet/tls_server_example/server-key.pem create mode 100644 dotnet/tls_server_example/server.cer create mode 100644 dotnet/tls_server_example/tls_server_example.csproj diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 4b95c19ed..efff22eff 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -27,6 +27,7 @@ using System.Collections; using IEC61850.Common; +using IEC61850.TLS; /// /// IEC 61850 API for the libiec61850 .NET wrapper library @@ -558,9 +559,6 @@ public delegate CheckHandlerResult CheckHandler (DataObject controlObject, objec /// public class IedServer { - [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] - static extern IntPtr IedServer_create(IntPtr modelRef); - [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr IedServer_createWithConfig(IntPtr modelRef, IntPtr tlsConfiguration, IntPtr serverConfiguratio); @@ -789,19 +787,30 @@ private void connectionIndicationHandler (IntPtr iedServer, IntPtr clientConnect private Dictionary clientConnections = new Dictionary (); - public IedServer(IedModel iedModel) + + + public IedServer(IedModel iedModel, IedServerConfig config = null) { - self = IedServer_create(iedModel.self); + IntPtr nativeConfig = IntPtr.Zero; + + if (config != null) + nativeConfig = config.self; + + self = IedServer_createWithConfig (iedModel.self, IntPtr.Zero, nativeConfig); } - public IedServer(IedModel iedModel, IedServerConfig config) + public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null) { IntPtr nativeConfig = IntPtr.Zero; + IntPtr nativeTLSConfig = IntPtr.Zero; if (config != null) nativeConfig = config.self; - self = IedServer_createWithConfig (iedModel.self, IntPtr.Zero, nativeConfig); + if (tlsConfig != null) + nativeTLSConfig = tlsConfig.GetNativeInstance (); + + self = IedServer_createWithConfig (iedModel.self, nativeTLSConfig, nativeConfig); } // causes undefined behavior @@ -850,7 +859,7 @@ public void Start(int tcpPort) /// Start MMS server public void Start () { - Start(102); + Start(-1); } /// diff --git a/dotnet/IEC61850forCSharp/TLS.cs b/dotnet/IEC61850forCSharp/TLS.cs index b7d67dc03..69a273b05 100644 --- a/dotnet/IEC61850forCSharp/TLS.cs +++ b/dotnet/IEC61850forCSharp/TLS.cs @@ -139,7 +139,6 @@ public void SetClientMode() public void SetOwnCertificate(string filename) { if (TLSConfiguration_setOwnCertificateFromFile (self, filename) == false) { - Console.WriteLine ("Failed to read certificate from file!"); throw new CryptographicException ("Failed to read certificate from file"); } } @@ -149,7 +148,6 @@ public void SetOwnCertificate(X509Certificate2 cert) byte[] certBytes = cert.GetRawCertData (); if (TLSConfiguration_setOwnCertificate (self, certBytes, certBytes.Length) == false) { - Console.WriteLine ("Failed to set certificate!"); throw new CryptographicException ("Failed to set certificate"); } } @@ -157,7 +155,6 @@ public void SetOwnCertificate(X509Certificate2 cert) public void AddAllowedCertificate(string filename) { if (TLSConfiguration_addAllowedCertificateFromFile (self, filename) == false) { - Console.WriteLine ("Failed to read allowed certificate from file!"); throw new CryptographicException ("Failed to read allowed certificate from file"); } } @@ -167,7 +164,6 @@ public void AddAllowedCertificate(X509Certificate2 cert) byte[] certBytes = cert.GetRawCertData (); if (TLSConfiguration_addAllowedCertificate (self, certBytes, certBytes.Length) == false) { - Console.WriteLine ("Failed to add allowed certificate!"); throw new CryptographicException ("Failed to add allowed certificate"); } } @@ -175,7 +171,6 @@ public void AddAllowedCertificate(X509Certificate2 cert) public void AddCACertificate(string filename) { if (TLSConfiguration_addCACertificateFromFile (self, filename) == false) { - Console.WriteLine ("Failed to read CA certificate from file!"); throw new CryptographicException ("Failed to read CA certificate from file"); } } @@ -185,7 +180,6 @@ public void AddCACertificate(X509Certificate2 cert) byte[] certBytes = cert.GetRawCertData (); if (TLSConfiguration_addCACertificate (self, certBytes, certBytes.Length) == false) { - Console.WriteLine ("Failed to add CA certificate!"); throw new CryptographicException ("Failed to add CA certificate"); } } @@ -193,7 +187,6 @@ public void AddCACertificate(X509Certificate2 cert) public void SetOwnKey (string filename, string password) { if (TLSConfiguration_setOwnKeyFromFile (self, filename, password) == false) { - Console.WriteLine ("Failed to read own key from file!"); throw new CryptographicException ("Failed to read own key from file"); } } @@ -203,7 +196,6 @@ public void SetOwnKey (X509Certificate2 key, string password) byte[] certBytes = key.Export (X509ContentType.Pkcs12); if (TLSConfiguration_setOwnKey (self, certBytes, certBytes.Length, password) == false) { - Console.WriteLine ("Failed to set own key!"); throw new CryptographicException ("Failed to set own key"); } } diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln index 067ccaf99..ba1be6f49 100644 --- a/dotnet/dotnet.sln +++ b/dotnet/dotnet.sln @@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "goose_subscriber", "goose_s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sv_subscriber", "sv_subscriber\sv_subscriber.csproj", "{44651D2D-3252-4FD5-8B8B-5552DBE1B499}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_server_example", "tls_server_example\tls_server_example.csproj", "{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -96,6 +98,10 @@ Global {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 + {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 diff --git a/dotnet/tls_server_example/Program.cs b/dotnet/tls_server_example/Program.cs new file mode 100644 index 000000000..e7aa0cfce --- /dev/null +++ b/dotnet/tls_server_example/Program.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading; +using System.Security.Cryptography.X509Certificates; + +using IEC61850.Server; +using IEC61850.Common; +using IEC61850.TLS; + +namespace tls_server_example +{ + 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; + }; + + IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("model.cfg"); + + if (iedModel == null) { + Console.WriteLine ("No valid data model found!"); + return; + } + + DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.SPCSO1"); + + TLSConfiguration tlsConfig = new TLSConfiguration (); + + tlsConfig.SetOwnCertificate (new X509Certificate2 ("server.cer")); + + tlsConfig.SetOwnKey ("server-key.pem", null); + + // Add a CA certificate to check the certificate provided by the server - not required when ChainValidation == false + tlsConfig.AddCACertificate (new X509Certificate2 ("root.cer")); + + // Check if the certificate is signed by a provided CA + tlsConfig.ChainValidation = true; + + // Check that the shown server certificate is in the list of allowed certificates + tlsConfig.AllowOnlyKnownCertificates = false; + + IedServer iedServer = new IedServer (iedModel, tlsConfig); + + iedServer.SetControlHandler (spcso1, delegate(DataObject controlObject, object parameter, MmsValue ctlVal, bool test) { + bool val = ctlVal.GetBoolean(); + + if (val) + Console.WriteLine("received binary control command: on"); + else + Console.WriteLine("received binary control command: off"); + + return ControlHandlerResult.OK; + }, null); + + iedServer.Start (); + 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"); + + iedServer.Destroy (); + } + } +} diff --git a/dotnet/tls_server_example/Properties/AssemblyInfo.cs b/dotnet/tls_server_example/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..638df6352 --- /dev/null +++ b/dotnet/tls_server_example/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("tls_server_example")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("mzillgit")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/dotnet/tls_server_example/model.cfg b/dotnet/tls_server_example/model.cfg new file mode 100644 index 000000000..6f332425a --- /dev/null +++ b/dotnet/tls_server_example/model.cfg @@ -0,0 +1,237 @@ +MODEL(simpleIO){ +LD(GenericIO){ +LN(LLN0){ +DO(Mod 0){ +DA(stVal 0 12 0 1 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 12 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(AnalogValues){ +DE(GGIO1$MX$AnIn1); +DE(GGIO1$MX$AnIn2); +DE(GGIO1$MX$AnIn3); +DE(GGIO1$MX$AnIn4); +} +RC(EventsRCB01 Events 0 Events 1 24 111 50 1000); +RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 24 111 50 1000); +LC(EventLog Events GenericIO/LLN0$EventLog 19 0 0 1); +LC(GeneralLog - - 19 0 0 1); +LOG(GeneralLog); +LOG(EventLog); +GC(gcbEvents events Events 2 0 -1 -1 ){ +PA(4 273 4096 010ccd010001); +} +GC(gcbAnalogValues analog AnalogValues 2 0 -1 -1 ){ +PA(4 273 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(stVal 0 12 0 1 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 12 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 101){ +DA(f 0 10 1 1 0); +} +DA(q 0 23 1 2 0); +DA(t 0 22 1 0 102); +} +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); +} +} +LN(PDUP1){ +DO(Beh 0){ +DA(stVal 0 12 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Mod 0){ +DA(stVal 0 12 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +DA(ctlModel 0 12 4 0 0)=0; +} +DO(Str 0){ +DA(general 0 0 0 1 0); +DA(dirGeneral 0 12 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(Op 0){ +DA(general 0 0 0 1 0); +DA(q 0 23 0 2 0); +DA(t 0 22 0 0 0); +} +DO(OpDlTmms 0){ +DA(setVal 0 3 2 1 0); +} +DO(RsDlTmms 0){ +DA(setVal 0 3 2 1 0); +} +} +} +} diff --git a/dotnet/tls_server_example/root.cer b/dotnet/tls_server_example/root.cer new file mode 100644 index 0000000000000000000000000000000000000000..87683444999a84b86230a7a7af3e8ae5f43de456 GIT binary patch literal 785 zcmXqLV&*kyV*I&)nTe5!Nuct==g24KTiYGuKQ`@snRCd1lZ{oIkC{o9mBB#VP}D$} zjX9KsS(v9JwYWr~C_leMAvw`NPMp`!+`!V%z`)eh!pJa6oY&aW(8w5wOf5|` z(wWYiKOLyEJDq62&(eRT@%!?cffsk(2{qzp?3i|LPg0^I z|3lW;x$){3^KEY}i!BO|$}-AMs4^C4cP!ZW=&9nDPhvj~U474^mv+d=U_zFY{BEl+ zCtTf5O|W#ZeB=Ig_opwfx_%y=H2sM%o6x!Rl+|8NL3KNVKlBQOmi@J4*c>@^bCAou zI};2iACBrQKasWOf>D}@*!o)MlEjb6PoFP&b|r4DxevR9zxKk}lIA92)4Qjqe6!Y; zcls_?@a<`Pn`P|u1?TthN?3_0?wA$wfKB2B+o7L<6$d^>%UJf;GBGnUFfNWYh&JE{ zhP$jVBjbM-Rs&`rW#9o4U}SMLux}8USd{(u$~})AH-eYjB`xX`SSX1z$eZxRFfi~L z84mxQHa%&11#VjF7vG}U-PY%L*=*YJ^S(M?MLC@M_bm_ z?CkRJ{Hieb&DO0EY#cW$EaQb*yT8AkZE?uXic68-skyfG@}|xxp~lSC#6M)USQ3{1a@a{C}tQ&+P(d!mVw;%cg~dZP1!8#g(e7899&ZS6)q4 z*UIzX+OJgGIUKt8;nQB8lWT4j40$|3PQ~ literal 0 HcmV?d00001 diff --git a/dotnet/tls_server_example/server-key.pem b/dotnet/tls_server_example/server-key.pem new file mode 100644 index 000000000..6fe35295d --- /dev/null +++ b/dotnet/tls_server_example/server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAu3Fjxb904UdyV/dDfvs8SFi6zJeNoMYjmpXm/LBcFSH3zGoK +wdcMovrUTED3Cc6Ww84AYpJ5MRMPTct7DfKJkWfSnkzOPmLldTSTv3RvzGVb4NzK +QqA5aSVDqAzPiP5RnFT6Q4KWRe69TMFxpw7zMXCJx9jDggqN1oojGGkmSgYGXnFd +Nc20Mujejh5pihgwnN4Y/bPFyxJwvIMj+D8qr9klhEmXKPTiX9UFd8oLkn9JCB6+ +SiHhNyFIo+Llossn1Q2hxCGty36fAhEWzpfBTaY510VLjie9y4q9GPTwxITDqSQd +xcX8IuvrbxX0DyEK507SMmTJmB9448eF9ZCWFQIDAQABAoIBAC80BuQtqslwrKLq +adz4d93gOmp7X/c07pJnXZwU7ZuEylp3+e2Gsm/4qq3pTkzx8ZWtsvsf19U774av +z3VbtrkfZDLpNKcRUKeLbgmw0NawT8r4zxaoMsz/zWHsl/bv1K2B2ORXZnCGBrXl +oTFo2mWA6bGiLNn6vm1grCXhlPreywyG/kFK3pi2VvkpvG3XZSI7mmZ0Dq/MD3nO +03oOZBqwwnMObfQQdhKE7646/+KgeuF/JsXaUH4bkHmtzYWyocWYMqpC0hjpNWlQ +cKuQ7t1kfmpsGD9aNW4+ND2ok9BdxIiC+rPXS9NDqZxoWLp+a8seU++uqk1l8RPq +tPE3LqECgYEAz1NmemNLiUsKvyemUvjp8+dJupzWtdV7fsnCbYhj/5gDA2UhFKCf +dP9xiHCdNe0797oAqHY7c3JhS4ug8haDy9aDIu5GG2DNYzjX/oYm4ywbCdRx+uEN +RcTw69FjSYVGkObmxWYszwsFybRasV6PYamg65qYR3FlvW2Td4Fndy8CgYEA53L/ +zHtBRQiNGJU9jfMHeX0bTtXIAt622Qn78jw0it/rhXWi2RwG2Cw5Q2aPRJ6uMt9F +yk1+GAPZcwYqwjq/nKRrl71Tn+KDWIk5rz1fNYRkaXtnMLs2MOogqoDTBshW0QBq +tnPrFNsaLKX6V92Az69wHjd2uwvLQLTvS/EuNfsCgYEAr3to/uhytAd3VirKRep3 +o0E+D5zWw1upxrwhPDK4aUuSKVp8sIfvz8iyoQiomE9vdZPTIMPKOEI1BgtuM9pI +vcyYfIVvg5bg4T3o3H9SBPB9BknyG6ZHZKl4PjGht0X+X4GBDM4Z2Tj8Mijcpsph +1AkOsrzMbZQWyEoqCnnWSHMCgYAFEHUcak4BTrCXqxxPsNOnCt/AF9lqhqkFkrxa +joqvxzqGDw7jJUPZEw6ltObJn5c8Mbp7NLrfl6X4aFgjK9npeYeJKHFd/DzXgRks +BnHA4Aa6cCLP5CjJZTYVxP/ZFCUiKZosJ9kq+ahW9cLGjWg2IyaW4qvMZ/OolMzv +onVaZQKBgQCir8u1vDsyA4JQXMytPHBJe27XaLRGULvteNydVB59Vt21a99o5gt1 +5B9gwWArZdZby3/KZiliNmzp8lMCrLJYjTL5WK6dbWdq92X5hCOofKPIjEcgHjhk +mvnAos3HeC83bJQtADXhw9jR7Vr6GJLM9HDcIgeIMzX7+BuqlMgaHA== +-----END RSA PRIVATE KEY----- diff --git a/dotnet/tls_server_example/server.cer b/dotnet/tls_server_example/server.cer new file mode 100644 index 0000000000000000000000000000000000000000..957bdc30f978d52f7ef9b5aa73375a9a21b7d6de GIT binary patch literal 767 zcmXqLV)|{+#Q1yxGZP~dlfcF$T&n{mk2X1Hu@+U&Yh7l*$;PV9$IK+l%3vUFC~6?g z#vIDREX-4qT3n(~l%HRskep~BC(dhVZeVF>U|?ooY-AoK&TDLGXk-jTrk19bQ3hg$ zA_hVbjocuOy2YtQWvN9#Yn2Rm**LY@JlekVGBPr1y6jofFe9&GR{Dm-MvMWb*!!{4=T15AK|DT40%}>b!#Ie8<1QIU&ECo2I$G z+v{_%a5>*+!-CG^Hx4&(^;34=NJB>rwbdRnMp zPqXq5d#&|1Ra-o#YkYYWf0ecT6!)ZhPY$_#UWyOR6+IR|db;Se`c>YAM-A`gOkO7k)*q=YE%rQclj0uXudC z_3MObqD;(;42+9?47?0HfT1VL$l_>V-ykrtDEse~dmcM(1TVKsTGS`7P!ea%GznB* z_#FAfd~3U7{KuxfFLMqd#V0VV85z_X5Aq+fxo*FBLA!nRf40v@?yqIdGWfc4*JM!> zt$wo&YR2=0t!-!RILa5m?eeIE+kc)37srHKV!8XST{P|D&E3atlJWDngFNGwPYwk} zae8^{g~9@oBAN~v)mHHzf7_V)qQAQ)l}FApI%hS@ysH`>QnME=GoABm@;9OB@xsUd zY|B@igY~1rOhO&iJ=7*nRRg`ByzZ^d_=> zIr44Av<_+G164WU*JW?^@5ts${Al;^-O(+cW-`V*rf3@ + + + Debug + AnyCPU + {B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307} + Exe + tls_server_example + tls_server_example + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + full + true + bin\Release + prompt + 4 + true + + + + + + + + + + + + {C35D624E-5506-4560-8074-1728F1FA1A4D} + IEC61850.NET + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file From 9fce1a342a6127db9a9cf442c999b548f726868a Mon Sep 17 00:00:00 2001 From: Robert Middleton Date: Wed, 6 Jun 2018 09:20:25 -0400 Subject: [PATCH 069/123] Use GNUInstallDirs for install location on Linux --- src/CMakeLists.txt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 03d77fabf..3b5424e18 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -377,7 +377,14 @@ if(MSVC) endif() ENDIF(WITH_WPCAP) +set(BINDIR "bin") +set(LIBDIR "lib") if(UNIX) + # GNUInstallDirs is required for Debian multiarch + include(GNUInstallDirs) + set(LIBDIR ${CMAKE_INSTALL_LIBDIR}) + set(BINDIR ${CMAKE_INSTALL_BINDIR}) + configure_file( ${CMAKE_CURRENT_LIST_DIR}/libiec61850.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libiec61850.pc @ONLY @@ -392,7 +399,7 @@ if(DOXYGEN_FOUND) endif(DOXYGEN_FOUND) install (TARGETS iec61850 iec61850-shared - RUNTIME DESTINATION bin COMPONENT Applications - ARCHIVE DESTINATION lib COMPONENT Libraries - LIBRARY DESTINATION lib COMPONENT Libraries + RUNTIME DESTINATION ${BINDIR} COMPONENT Applications + ARCHIVE DESTINATION ${LIBDIR} COMPONENT Libraries + LIBRARY DESTINATION ${LIBDIR} COMPONENT Libraries ) From 9cb032138be16cb1914a15f3282c4a6d0aace814 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 8 Jun 2018 14:56:07 +0200 Subject: [PATCH 070/123] - Java SCL parser: parser is more tolerant. Added access to IED list. --- CHANGELOG | 1 + .../src/com/libiec61850/scl/SclParser.java | 12 +++++++++--- .../src/com/libiec61850/scl/model/AccessPoint.java | 6 +++--- .../src/com/libiec61850/scl/model/IED.java | 3 --- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7fc9c7444..24eae2960 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ Changes to version 1.2.2 - TLS client: fixed problem with high CPU load - ISO connection: fixed race condition that can cause corrupted messages - .NET API: added project files for .NET core 2.0 +- .NET API: added server side support for TLS Changes to version 1.2.1 ------------------------ diff --git a/tools/model_generator/src/com/libiec61850/scl/SclParser.java b/tools/model_generator/src/com/libiec61850/scl/SclParser.java index 4ed22e511..fc73748d0 100644 --- a/tools/model_generator/src/com/libiec61850/scl/SclParser.java +++ b/tools/model_generator/src/com/libiec61850/scl/SclParser.java @@ -27,6 +27,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Stack; @@ -57,8 +58,8 @@ import com.libiec61850.scl.types.LogicalNodeType; import com.libiec61850.scl.types.TypeDeclarations; -public class SclParser { - +public class SclParser +{ private List ieds; private Communication communication; private TypeDeclarations typeDeclarations; @@ -111,6 +112,11 @@ public IED getIedByName(String iedName) { return null; } + public Collection getIeds() + { + return ieds; + } + public IED getFirstIed() { return ieds.get(0); } @@ -314,4 +320,4 @@ public ConnectedAP getConnectedAP(IED ied, String accessPointName) { return null; } -} \ No newline at end of file +} diff --git a/tools/model_generator/src/com/libiec61850/scl/model/AccessPoint.java b/tools/model_generator/src/com/libiec61850/scl/model/AccessPoint.java index 91fec8134..f506caa61 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/AccessPoint.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/AccessPoint.java @@ -43,9 +43,9 @@ public AccessPoint(Node apNode, TypeDeclarations typeDeclarations) throws SclPar Node serverNode = ParserUtils.getChildNodeWithTag(apNode, "Server"); if (serverNode == null) - throw new SclParserException(apNode, "AccessPoint has no server defined!"); - - this.server = new Server(serverNode, typeDeclarations); + this.server = null; + else + this.server = new Server(serverNode, typeDeclarations); } diff --git a/tools/model_generator/src/com/libiec61850/scl/model/IED.java b/tools/model_generator/src/com/libiec61850/scl/model/IED.java index fb381aeb2..45936a049 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/IED.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/IED.java @@ -41,9 +41,6 @@ public IED(Node iedNode, TypeDeclarations typeDeclarations) List accessPointNodes = ParserUtils.getChildNodesWithTag(iedNode, "AccessPoint"); - if (accessPointNodes.size() == 0) - throw new SclParserException(iedNode, "no AccessPoint defined in IED " + name); - this.accessPoints = new LinkedList(); for (Node accessPointNode : accessPointNodes) { From 5064c9c31c4e912b0f6a229a27e6246ca715c91d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 8 Jun 2018 15:00:22 +0200 Subject: [PATCH 071/123] - updated java binaries --- tools/model_generator/genconfig.jar | Bin 89306 -> 89204 bytes tools/model_generator/gendyncode.jar | Bin 89306 -> 89204 bytes tools/model_generator/genmodel.jar | Bin 89305 -> 89203 bytes tools/model_generator/modelviewer.jar | Bin 89298 -> 89196 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 9aba4fc859d603a06bfc795ee12c2030cf23dd3f..7fbf3f9647c7f8ba001497015125442e379f4e1b 100644 GIT binary patch delta 9188 zcmZu%1yodPwC2#=Af3`(BHhy6Fd&`MEiuw6%^*W6AxH}hotFkFkq|_X4rx(95qvX) z*LQvEu@>u`wd33S-+%10_dg$6F}}875a?;5p_8LvV`HN@1P!MVNTMMg8eCvhA^b@d@^HqWV`bklLcp#Uudp#3A=x4xX zJXgPI+wGuW?3p-zSeUuZ{&Qr&u2 zy_x+uk)9?NHr7riZU71jN(2fK3JStAToe>+6cqcQVF&;)q9LBF01v`Nh&~{Me7YSn zLj^SfOb7!G1;h&j7d8qwp&=!u!hUnCB*|f37_n#wdDuBN^DP7$n$HXT(Zqs&(g4U{ zASQYOgb-Ag@`iT{*1!kGN0R=C!>lAN?;uULdkY|u3grPd=#?da1EKZ71$+3B8Iwoc zQHL>GX>c_Vzhr4t%rQV^q2MfHBOujA`x>iAvKQ#*8lAh;vLJtgp{Fnqly`y_p%*{Z zLwyy6c7Pt1J4H=#1^lEOYW549zVp)6@c~EB(euzliS3Yy*6qXUZm%$`6@{=`?DOLI z_+aAddDit4rm*?G^iJv97vOJCn8u%1sgQO8FCF$P2O{XO6&3FDnH0%A0y6RE)|ldg z@AmiETGJA)VLxA6ElYfbMuoFN9bvfefpb#Sgw|2ql>uFVZhavu(T947jaNO_f|&#EBOy>BMM?+k?n3PaAB`&)k?|49gArTM7BKp|g2pEG0&Yg0h8A^74g}I`CQ8ITOEX z;V=Gj#^2{DORCJQSm=GDncAmn_XIs*PgJw4iHfttB1(MUDYx?cXA z+g7ZuC8z!Uaa)wQH&~%3_wMTzzK-e@`i@cWPlm^`{2FG(X=pMcIV2X_TOO9;DyKBY zr=p*#bIS?I;+U(lYPL?JhF!T{zw#;E<}93I@X_MiIkTQs+lv})1C@7QzSYuKcd>q3 z7}F^6obJtMCF;@11l zR?bpD{HNVQk~_2$#&R{HAA z;zi|V)H?I3n}JQi1W^)Z22)Z`@<7bG*Y9JuqspI7=8E`bjC#+iNI+7Y{m!^JHv5!Y zb4K+!5|XA*g2FPUN0MH#z>*)^lSV0?wzuy#w1m4yx^vp()|3f1P@m0RIh*^4Y*c7Z z(+^bLiNE#biMIcX=66_+h!_rCL{Gg#(LV3BGD_;YJ-K~84StPA%gn3+(+S>XG^23s zCgiGLm6T573T72wm6DFl**m^pV*F$uO`#qKq)ab+d&KF-ythX~YM$xO2TCym#%qfkU_hc7 zMpabuW1*yEQxm+b11^@^;3-n*b!>=nQ|!72CK;9X<`|XXESgI2i`ac5*F5rw!${;w zX<;Ry6`gNP6sBz9iD6U-b>%U2)0*E8^0p|Fut=PT&j+D1y4@@iwN%S(-EUco4Sz)6 zk=u8W@4fTGV1v;@t1OTbw!Pnzcn)Ovtmr*Em~ZQ4;FjrPxj+eJzprU4?iDmTi!(ARA1atrt}6 zSxGVFcv70#_+%E0G8Fgby4pfW-Q&1Vf3doq7*dNcbUAB|(eW97np=1T!5?7z+{H5Y z@a z`4MDZVLaOA_2J>(Ao08WS*Ai0}uQ=oC;x>=BtY43J@)dhPoEs>pwkkR9$u;!xzah<}+E4|1X)P0p z2&!2zi1rP#`+YB)GeCE_I`m9tLi@_m{846dBZW`FzQZ%;1;-NgGxUvL2NVRc5=J+ zTD@X8hNLxUpVA6TqG-QL=zz$+F8A}LN4YbK7oI=u|b*409+XQ zT;k2X{C7P=x)w;lL$@4g2Z-ZS-(|dONU~21w?k!FHATjQ)cBXN0|NKn) z;)dZDjF1u$<`}2a>Fo&l7nk(eI=hm>!2OKu;{w)$>VUcB(1Y-o-Pe~_L@1^whUn2* zxT_#^aY6b)^Os6lX@kzV!X%to=@Yq~7jz5OAA74nTjXOrV6(?ePX#t|7fE59;{vP~ zxDvaT2C1grs!V#b&03LWhmqF_HO61v%z5qxR9fiZm9^PsHFbhu70G;WKlO{!RTgdX zE;6%#YTpaFb_WbiITFKM}$6ZVnq zEfiMa)KzCJy2tadPI0n4s+g7t`qkropxDcqQQR&QZ&N1WU&DgURty*zks^-MyZB6k zX;d=i9=NVwJaaKyW{dU1(H#~aQiKvbI524&4M}GpXaHlierw4#ketd-3kqLq(2k|7 zYFm24EBFI5wu6c#)}q)m3omnD*TpBoQ?azz4xIXj7xyK|sbRy>1gZLjFuc6AVM*rlvScUYBalFVgz|ISL@o9aaZq1J%G1tA;9HS(X2%#0zl=cj=FW*fs04>Gl0@$Qqi4g|iKvtyz6o86{Q-Vm{w~=6rAe^=~@y4%Iv9;aqTUM#@Fxe zcoQ&^1Tb`=^+w^!nt1CgfO^h!O?j?-jL@3{DR*XYXrG}{$f$&x@S$ocZ^fR7?;$3hxnQ8 z)g{&O$lZLd?G~N3G}$Xg#9=X_6?cCD4FyFFUM~J`Nee$KwBY6B%^$?QjTz>#%#KW& zi1P2|ut4NZT-fAFJMzSUC2c@&QXxD`3P9D30V0Uw?j`bk$&k58j?e;QfD~5ndFV#N z?GS|2fWR{?A`jm;0q|kyr_PuNC79=T<{Pe5*iB~byaFRR5O~^dc0&g+k$DzTaw7T7 z&oO{Kq>IgP4+61~l_pIl+*fk|6Dmjo;36o^G9BO8EBT`nn>h0ZpeP*wmdt|NNVgqDB1f@IjR7_|cE-CdDk6NnWy@tf z+IRn?jp~}c)XVI+G^)GOEMYsg-;(mDQjeT*12*0-gJUm>>a}kk7{ByaNnamrtab;# zk2)vsc)cdwQL%!y`X%1(E=%6!i>P95Ngu5qun&b5)p}$Bad}H>ei3g(`iP>_$(RXe zd_f~7>cg^;Bb@kef<61wp%?laA9h=3))ViAMZ6lll{`b{yBOXH=-#Gzhm-Jbn1J7_ z_S5cpz@`7u7Zn~TpMP9ugfqBS^<2vGz1aKLPT#|vc2h9JtabZu;bYThI}8d*q}XG~0KWRjn)?nhS1O5PR*EYTkv%_-48_ml&VxE^DKrOl#SmXPS) zf4e|qGx>p)d1?B7cjISNLd($p^`+S@>Qt~|o=+@!EN@48KxoGg`LXiyY%y#Mn%70f zN9OJC4|bi4HSO{ubPL{(Jm`}53g5zDn|`IoUp)MlHOaVSqnm5zd~ew-Y9i;6za+|t zJsMw^o|zeQhA#2eOZ>h_ww%FYw-IzZjv?dkk2uKZn!jF3QPYn|H(~~&D_seVHsRv}Lv^7(h(oo3YbkjT8TO0sc12LG@RC~m?kW`4cAMmB5o_VAk*sXh)Lag*qN zKR*G<$Eu!`2mmK2ymOa5uy6ZFq;m0K0QncdjmP>nk`{2VB zbW*9PDS6#iGX*0hBL9i@djm$j17-SL+Oxy=rssb_2Ca&BMWca2ed_TU7s>?+?)NTt z#;08P#|_IAwYJ=i)Qxc^L@P>ZJbhRmh319+xYB7Y)th%!&OEK_drvF2lDQ)n<2thM znT*NO6(OUrfuYA2)a_uJN|4m|Yy^CwOt{TY8bt>-X64|U@#d>pPtGg*CrKTxK}{K4 zAzy!dckFqyxTpFFKN$3ktHG?8ts!GYy6+ydw8fwb=XzV7332I5&*(_2hbi;;t-jNh z8frB&JG?m~jln;1v1|=Q)(yfA9~Y>lhA;=qygHt8+ zt~|MA)tyG{Eq9X1^a4$FA$7;E_I`~Re#O~-cW;bP3DEW3*Y}bdGMn4BlHwdT`_p@8 zla{0wWTM4hwN3?>%eHFS$Ixm7iISd<*Ygt}$d%+qsf+hMIL^6?-|^aQMtMA};&HPY z|Ac(ZrS1GOBfgK{&NSMYjXYEVoS;&lr$;kX;-bRU-iN-Kt(IAKI4TAj509MF$6J&U zyS-SvLIgX=)G{GVgga6Vw1FY@4l9H-1&TZzQZg_)Trhk4Dk(2nke+6 z#>CG{-G4;W8k1i1l9V;H+%bpdDOYwA)17Bkg>#S9)Kd?1!fX>SNWVT@f8>;RGAPoY=&P^` zRhGH-&e0VLDg4w=gWf1se7ykX7*S6c8s$IQ^i!4pv_ylgDh&uo8IbsGY#3Bo+Sg^E zPfE7Z;Tb(wOIG5>T#L~ZqkiVN)zw^<6H5&iO#T%LhI7P zVgd4xd^ISfGGz2SAlJnzhl$J@VjAHWHdf_Q&MyubQ?2);r1ilL(Hw8V>)X$8#A4Af zd(Aymhgsj{`rwHh_{+8v->K2l4)5^R&sg(bT3uxUnTM>kicAASeX9+3qszQKH7W|d zd*6MTZt(B*XcmI3P=hXGblG)@?mK)L3JJ99T|dwO)n(6?U_E3FuV1qq2mm!PnWs}1 zG3$I^o8=6RJ)q7tSJ_$zx2*CU$wu*zGzCOh*>H3O3dE1S-53s5&-=&Shg!{3qeIc% zG42s%a<{Nxt{aGxAi-`o32iVm+1W;(=2l5l&E&iEg*`oi4O*$^vWKfs%AstZ^A@~@NdCWff=X@{g ziwSe1TG=685xG#hzF}YgiC`3V)o+4ho~j474vdNuiQ&WTj8A8UV?bCU1#gxw{Qqw(Zy~`-J)35lT zN8^DC8h<|xD28Bu0t99#^|z<(__-LZX|9PgX3y(jQft}6U{HH23v`zNDl|UkUJcpn zlx|v<5ImtRxLYyam*p==LT4OtxXJV_iL4XVbg<}iLhPFnO<1yxaACC*x*_(Z-k`uh zjH>=x4ZWi4H{#MmrND#8o;p*9pGn40Z@Zpdp7jl5-yd{&9Ts4nT(elrY5g)>S;;+Y zKQh_4lp$#y?NTwB>?tb6nzaRv4nuYb0`TtYUz^m3~A{eF#&se2x?@j~4m#42_a z#?iHpodd=Jr?Dv16C&f21`P~b>Uj{ui`50#&nw5RV8Re^aHL1~7)ZcQ*jkaxI_+93 z+BK0{3zHv%mpWIx#?%`kfnj65_K@DMD!R^`>t+}H;f1D|rk0ptj8#T#E_sYmJY&~V6Ep9-C*rKF5Hok6nlWM?DXF6S} z)+8l9NQDl(tht1$U(C5%K~e2UHF~dB8ZSTEev(1!bl>A_pI+xGsduF z95t@&q?%mRXP=3s-YZQ>)qtPcwr>jct87wC+FnAOedIs5F{oa1er~J!*5?u&b7i68 zotv4PFy6>&rrm;PI!l?W>K#alPOe%T_iTIKw<@U~%+6t&V&?(n3#HOla$$(c<=P|7 zpj0;(;L^;lXI5~1mf?5Xk;ZW1Sq4jK%k7dRF4J6B@BRAE4o@d+wNO-Tdh{ctCL*b$ z^RZ7yVjLX?7c!y=9D1DIsEC&z8#2@|5xh5$^(;aCs1Vzd62iz-iN8$8Ib>scj4@Fa z^Zk~@IGD#F$E$HcR(XUZIZU{y;-XuhcPYQFNqo{aAjQU>5;Q9e+FG<<{dgOGK&3}& z*8IaQsK_>4QjU*J`^Kg1Yd@;wQ{h0S0T4ghCXbNcw+A`}l{2;$`!oX~sGYfs_okMO zMG^^v>i1|coqDu3hhIr>7PdZVyQ#nvyMxs#$!#}H(=KMLC3hq=9v}uX}gA2^X%}s%6VS{ z(X$R{(wZXXrpjDGd~Lq#Vm0GxPwIj!I+2(E9lW4xDXgKhm!iKU2W4Gkd5vNfeG4+& zsL;WUE=(LvQ;Pd92U;>Rg;6qBbu1#Uj8YUVVnbb$@Ix?slWjBgC5D7_dprg&^&c2| zOL@^SGVLifIrh+lHb$QFN7RpZRC7doe*Mbs)MeNfbnB?Z4(;~xy@8a?Ux*TNpo9W# z2p@joNc;blkj>dL00y$AYKGtUeY~l?9;pD6sED!&YM=((#fHCVA+9i?>1u!^vSgf8 z18(jJ0d+tMDJZ87C?H>=)d6dy%!WE(g`_BJ03al#SOc&`QUFcB0Qq8~`J2+93CQ6f z%35U48v0Wc&_JrFYXP*V3xN>ZOiE-Q`l%Lxk7Q&c7}P-s#-9#0Et2yC!4U}it-=7+ z)c&nvp^d--|G|Ryw2^nO&`t!SBlce`J|qm24xIS&@o#A&i1ZU|sGH7js1OA6AmLvq z2yY?|ubw7=N{;fUnWyF6F%Ox+L_sMfL_rbyw^&C$7n6~S_$l=8+hG6|7v;}&FjPnv z4osK{=hH#mbpN%6Al^d|1)$m%06rrkP}<>{K1T31A}zd&$p05S06o?Pko|qApdMVa zt_T75yOmCd99$xT4yuBl=mHGTAw7T+NnJruwQG^a;jMK#CWIYSa$L4Ucn1Om??CYV zWe2|rA&Ka?Lo}N3p=QPaK2k$v!@J26?v({xgZnQH<2EGr7#tgEa8Dm@5W5=*BiD@s z8aG4fvC_F+MZ6r>gLv{~eKGXr00Dh@2R}O@egAZ07J1o$3r#Ts$dR^bG3e_>;UTz-1ONX^<9HpR z0sUkIa3dp$Zwxp1bmw0fcz=f$iJ;wa-@O`clLBs&_b-IOT_gg$=fi+R=s_TiA0U>UXhR5?M{D%vF&n54de~rN#LsUqJhbC}{sf&L-BZm%|0HjFHM+E2F zPe|dFD3s6icf_Sl;lv^|r~(L}gkR2JLEX^-GN_y7?{J-OQ?&NO7q}xl033h04sS;N z*1?3r+e!q`<$oa$5RgY$NH(Hn&W1c?kekYd_%{{aZ~AM#5XW4q8CGfNbyIR5gC`KAojv#cwFh>Yy9uI1X2AF#YA+iSdcChn8Q)B=>EfkH?Jt5hvx8D zARM?t@ZFei`0)M}7t%J!0#5zR_J@k>YSA$v&f`({_x^$K=orDH^S1*iy#KKR9|j)u zrv-2e>Gj7}QK5J6P~rn9C}MwwbcX*wO1hQ+2U7mOspIdpk0W>^kN9~Aw9)eSG{3Qg z`^zlzhjp6~;c7KnI#nXv&2f0F*#5H050wW2qDWJxAUAq{_;?Tp$KRWmK;;In0t9el KE~&y@NBJM91FW6^ delta 9422 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 diff --git a/tools/model_generator/gendyncode.jar b/tools/model_generator/gendyncode.jar index 748799e029393ee0277a95fd11af5bcf4422d00f..a0d686f45806544350202d2f4480cf11a8cb2ff4 100644 GIT binary patch delta 9175 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%0AA=e2Fc-XQ7{B;-$i%JJ+lkVR{sC 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 delta 9409 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 R1+%sU5Me5)BIX&&{{hWH1!4dI diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index a9bc584cd11bcb69336d25fb89755a9e2636ab82..5554c798f0e95d77f0815a6d86c4914bafd796e2 100644 GIT binary patch delta 9249 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 AqyPW_ delta 9415 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 Date: Fri, 8 Jun 2018 15:13:00 +0200 Subject: [PATCH 072/123] - Visual Studio support: added missing export definitions for client side GoCB handling --- src/vs/libiec61850-wo-goose.def | 15 +++++++++++++++ src/vs/libiec61850.def | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 3df65ab06..9c5467f25 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -588,3 +588,18 @@ EXPORTS IedServer_createWithConfig IedServerConfig_setFileServiceBasePath IedServerConfig_getFileServiceBasePath + ClientGooseControlBlock_create + ClientGooseControlBlock_destroy + IedConnection_getGoCBValues + IedConnection_setGoCBValues + ClientGooseControlBlock_getGoEna + ClientGooseControlBlock_setGoEna + ClientGooseControlBlock_getGoID + ClientGooseControlBlock_setGoID + ClientGooseControlBlock_getDatSet + ClientGooseControlBlock_setDatSet + ClientGooseControlBlock_getConfRev + ClientGooseControlBlock_getNdsComm + ClientGooseControlBlock_getMinTime + ClientGooseControlBlock_getMaxTime + ClientGooseControlBlock_getFixedOffs diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 1f7fe8ce0..085c7e9d9 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -715,3 +715,18 @@ EXPORTS IedServer_createWithConfig IedServerConfig_setFileServiceBasePath IedServerConfig_getFileServiceBasePath + ClientGooseControlBlock_create + ClientGooseControlBlock_destroy + IedConnection_getGoCBValues + IedConnection_setGoCBValues + ClientGooseControlBlock_getGoEna + ClientGooseControlBlock_setGoEna + ClientGooseControlBlock_getGoID + ClientGooseControlBlock_setGoID + ClientGooseControlBlock_getDatSet + ClientGooseControlBlock_setDatSet + ClientGooseControlBlock_getConfRev + ClientGooseControlBlock_getNdsComm + ClientGooseControlBlock_getMinTime + ClientGooseControlBlock_getMaxTime + ClientGooseControlBlock_getFixedOffs From 4605c60a3b6f409c6b8406cf28d8e227c610411e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 15 Jun 2018 07:47:16 +0200 Subject: [PATCH 073/123] - fixed bug in MmsValue_update --- src/mms/iso_mms/common/mms_value.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index b8b5a282d..1d8485e2b 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -270,7 +270,6 @@ MmsValue_update(MmsValue* self, const MmsValue* update) self->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); self->value.octetString.maxSize = size; } - size = self->value.octetString.maxSize; memcpy(self->value.octetString.buf, update->value.octetString.buf, size); From a7cb12f5b08721a5efc4a6fdf3b64995f0fbcf62 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 22 Jun 2018 13:47:18 +0200 Subject: [PATCH 074/123] - IEC 61850 client: added new function ControlObjectClient_getCtlValType to simplify control handling --- dotnet/IEC61850forCSharp/Control.cs | 14 ++++++++ dotnet/control/ControlExample.cs | 1 + src/iec61850/client/client_control.c | 9 +++++ src/iec61850/inc/iec61850_client.h | 52 ++++++++++++++++++++++++++++ src/vs/libiec61850-wo-goose.def | 1 + src/vs/libiec61850.def | 2 ++ 6 files changed, 79 insertions(+) diff --git a/dotnet/IEC61850forCSharp/Control.cs b/dotnet/IEC61850forCSharp/Control.cs index 44826f478..c629ad90b 100644 --- a/dotnet/IEC61850forCSharp/Control.cs +++ b/dotnet/IEC61850forCSharp/Control.cs @@ -118,6 +118,9 @@ public class ControlObject : IDisposable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern int ControlObjectClient_getControlModel(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern int ControlObjectClient_getCtlValType(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] private static extern bool ControlObjectClient_operate(IntPtr self, IntPtr ctlVal, UInt64 operTime); @@ -196,6 +199,17 @@ public ControlModel GetControlModel () return controlModel; } + /// + /// Get the type of ctlVal. + /// + /// MmsType required for the ctlVal value. + public MmsType GetCtlValType () + { + MmsType ctlValType = (MmsType) ControlObjectClient_getCtlValType (controlObject); + + return ctlValType; + } + /// /// Sets the origin parameter used by control commands. /// diff --git a/dotnet/control/ControlExample.cs b/dotnet/control/ControlExample.cs index 7bce2eec5..a48d013c9 100644 --- a/dotnet/control/ControlExample.cs +++ b/dotnet/control/ControlExample.cs @@ -40,6 +40,7 @@ public static void Main (string[] args) ControlModel controlModel = control.GetControlModel(); Console.WriteLine(objectReference + " has control model " + controlModel.ToString()); + Console.WriteLine(" type of ctlVal: " + control.GetCtlValType().ToString()); switch (controlModel) { diff --git a/src/iec61850/client/client_control.c b/src/iec61850/client/client_control.c index 907f5ab52..6bc3add74 100644 --- a/src/iec61850/client/client_control.c +++ b/src/iec61850/client/client_control.c @@ -270,6 +270,15 @@ ControlObjectClient_getControlModel(ControlObjectClient self) return self->ctlModel; } +MmsType +ControlObjectClient_getCtlValType(ControlObjectClient self) +{ + if (self->analogValue != NULL) + return MmsValue_getType(self->analogValue); + else + return MmsValue_getType(self->ctlVal); +} + void ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat) { diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 09c4852dc..5f74bb391 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1595,6 +1595,19 @@ ControlObjectClient_getObjectReference(ControlObjectClient self); ControlModel ControlObjectClient_getControlModel(ControlObjectClient self); +/** + * \brief Get the type of ctlVal. + * + * This type is required for the ctlVal parameter of the \ref ControlObjectClient_operate + * and \ref ControlObjectClient_selectWithValue functions. + * + * \param self the control object instance to use + * + * \return MmsType required for the ctlVal value. + */ +MmsType +ControlObjectClient_getCtlValType(ControlObjectClient self); + /** * \brief Send an operate command to the server * @@ -1607,12 +1620,25 @@ ControlObjectClient_getControlModel(ControlObjectClient self); bool ControlObjectClient_operate(ControlObjectClient self, MmsValue* ctlVal, uint64_t operTime); +/** + * \brief Send a select command to the server + * + * The select command is only used for the control model "select-before-operate with normal security" + * (CONTROL_MODEL_SBO_NORMAL). The select command has to be sent before the operate command can be used. + * + * \param self the control object instance to use + * + * \return true if operation has been successful, false otherwise. + */ bool ControlObjectClient_select(ControlObjectClient self); /** * \brief Send an select with value command to the server * + * The select-with-value command is only used for the control model "select-before-operate with enhanced security" + * (CONTROL_MODEL_SBO_ENHANCED). The select-with-value command has to be sent before the operate command can be used. + * * \param self the control object instance to use * \param ctlVal the control value (for APC the value may be either AnalogueValue (MMS_STRUCT) or MMS_FLOAT/MMS_INTEGER * @@ -1621,6 +1647,16 @@ ControlObjectClient_select(ControlObjectClient self); bool ControlObjectClient_selectWithValue(ControlObjectClient self, MmsValue* ctlVal); +/** + * \brief Send a cancel command to the server + * + * The cancel command can be used to stop an ongoing operation (when the server and application + * support this) and to cancel a former select command. + * + * \param self the control object instance to use + * + * \return true if operation has been successful, false otherwise. + */ bool ControlObjectClient_cancel(ControlObjectClient self); @@ -1630,9 +1666,25 @@ ControlObjectClient_setLastApplError(ControlObjectClient self, LastApplError las LastApplError ControlObjectClient_getLastApplError(ControlObjectClient self); +/** + * \brief Send commands in test mode. + * + * When the server supports test mode the commands that are sent with the test flag set + * are not executed (will have no effect on the attached physical process). + * + * \param self the control object instance to use + * \param value value if the test flag (true = test mode). + */ void ControlObjectClient_setTestMode(ControlObjectClient self, bool value); +/** + * \brief Set the origin parameter for control commands + * + * The origin parameter is used to identify the client/application that sent a control + * command. It is intended for later analysis. + * + */ void ControlObjectClient_setOrigin(ControlObjectClient self, const char* orIdent, int orCat); diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 9c5467f25..af416b2cb 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -603,3 +603,4 @@ EXPORTS ClientGooseControlBlock_getMinTime ClientGooseControlBlock_getMaxTime ClientGooseControlBlock_getFixedOffs + ControlObjectClient_getCtlValType diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 085c7e9d9..c19ac03cc 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -730,3 +730,5 @@ EXPORTS ClientGooseControlBlock_getMinTime ClientGooseControlBlock_getMaxTime ClientGooseControlBlock_getFixedOffs + ControlObjectClient_getCtlValType + From ab51835377212e9cadb1b429d079c877498ae17e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 26 Jun 2018 19:28:24 +0200 Subject: [PATCH 075/123] - MMS server: fixed wrong preprocessor defines that can cause problems in some configurations (unlimited number of client connections/ multi-threaded server) --- src/mms/iso_server/iso_server.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index a2e0ba84f..1b5628b9b 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -623,7 +623,9 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) IsoConnection_addHandleSet(isoConnection, handles); openConnection = LinkedList_getNext(openConnection); } else { +#if ((CONFIG_MMS_SINGLE_THREADED == 1) || (CONFIG_MMS_THREADLESS_STACK == 1)) IsoConnection_destroy(isoConnection); +#endif lastConnection->next = openConnection->next; GLOBAL_FREEMEM(openConnection); openConnection = lastConnection->next; @@ -649,11 +651,12 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) } } +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ + #if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) unlockClientConnections(self); #endif -#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ Handleset_addSocket(handles, self->serverSocket); result = Handleset_waitReady(handles, timeoutMs); Handleset_destroy(handles); From 5de644b3bc1e776a88b156566a7b807fd847b54c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 6 Jul 2018 08:47:27 +0200 Subject: [PATCH 076/123] - .NET API: added check for maximum size of s-selector --- dotnet/IEC61850forCSharp/IsoConnectionParameters.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs b/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs index 9192eff38..759e7f532 100644 --- a/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs +++ b/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs @@ -142,6 +142,9 @@ public void SetRemoteAddresses (UInt32 pSelector, byte[] sSelector, byte[] tSele for (int i = 0; i < tSelector.Length; i++) nativeTSelector.value[i] = tSelector[i]; + if (sSelector.Length > 16) + throw new ArgumentOutOfRangeException("sSelector", "maximum size (16) exceeded"); + NativeSSelector nativeSSelector; nativeSSelector.size = (byte) sSelector.Length; nativeSSelector.value = new byte[16]; @@ -190,6 +193,9 @@ public void SetLocalAddresses (UInt32 pSelector, byte[] sSelector, byte[] tSelec for (int i = 0; i < tSelector.Length; i++) nativeTSelector.value[i] = tSelector[i]; + if (sSelector.Length > 16) + throw new ArgumentOutOfRangeException("sSelector", "maximum size (16) exceeded"); + NativeSSelector nativeSSelector; nativeSSelector.size = (byte) sSelector.Length; nativeSSelector.value = new byte[16]; From 894ea2e726bb8b721239e45fe4ef3e1759a45580 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 6 Jul 2018 09:33:00 +0200 Subject: [PATCH 077/123] - SV: added function SVPublisher_ASDU_setSmpCntWrap - added quality flag "derived" - updated 9-2LE publisher example --- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 12 ++++++ .../iec61850_9_2_LE_example.c | 40 ++++++++++++++++++- src/iec61850/inc/iec61850_common.h | 2 + src/sampled_values/sv_publisher.c | 10 ++++- src/sampled_values/sv_publisher.h | 10 +++++ src/sampled_values/sv_subscriber.c | 8 ++-- src/vs/libiec61850.def | 2 +- 7 files changed, 78 insertions(+), 6 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index cda16ae9a..516929016 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -184,6 +184,7 @@ public class Quality private const UInt16 QUALITY_SOURCE_SUBSTITUTED = 1024; private const UInt16 QUALITY_TEST = 2048; private const UInt16 QUALITY_OPERATOR_BLOCKED = 4096; + private const UInt16 QUALITY_DERIVED = 8192; public override string ToString () { @@ -341,6 +342,17 @@ public bool OperatorBlocked this.value = (ushort) ((int) this.value & (~QUALITY_OPERATOR_BLOCKED)); } } + + public bool Derived + { + get { return ((this.value & QUALITY_DERIVED) != 0); } + set { + if (value) + this.value |= QUALITY_DERIVED; + else + this.value = (ushort) ((int) this.value & (~QUALITY_DERIVED)); + } + } } /// diff --git a/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c b/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c index c49347b19..d29a095d7 100644 --- a/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c +++ b/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c @@ -48,11 +48,21 @@ static int amp2; static int amp3; static int amp4; +static int amp1q; +static int amp2q; +static int amp3q; +static int amp4q; + static int vol1; static int vol2; static int vol3; static int vol4; +static int vol1q; +static int vol2q; +static int vol3q; +static int vol4q; + static SVPublisher svPublisher; static SVPublisher_ASDU asdu; @@ -64,14 +74,24 @@ setupSVPublisher(const char* svInterface) asdu = SVPublisher_addASDU(svPublisher, "xxxxMUnn01", NULL, 1); amp1 = SVPublisher_ASDU_addINT32(asdu); + amp1q = SVPublisher_ASDU_addQuality(asdu); amp2 = SVPublisher_ASDU_addINT32(asdu); + amp2q = SVPublisher_ASDU_addQuality(asdu); amp3 = SVPublisher_ASDU_addINT32(asdu); + amp3q = SVPublisher_ASDU_addQuality(asdu); amp4 = SVPublisher_ASDU_addINT32(asdu); + amp4q = SVPublisher_ASDU_addQuality(asdu); vol1 = SVPublisher_ASDU_addINT32(asdu); + vol1q = SVPublisher_ASDU_addQuality(asdu); vol2 = SVPublisher_ASDU_addINT32(asdu); + vol2q = SVPublisher_ASDU_addQuality(asdu); vol3 = SVPublisher_ASDU_addINT32(asdu); + vol3q = SVPublisher_ASDU_addQuality(asdu); vol4 = SVPublisher_ASDU_addINT32(asdu); + vol4q = SVPublisher_ASDU_addQuality(asdu); + + SVPublisher_ASDU_setSmpCntWrap(asdu, 4000); SVPublisher_setupComplete(svPublisher); } @@ -123,6 +143,8 @@ main(int argc, char** argv) IedServer_setSVCBHandler(iedServer, svcb, sVCBEventHandler, NULL); + Quality q = QUALITY_VALIDITY_GOOD; + while (running) { uint64_t timeval = Hal_getTimeInMs(); @@ -130,28 +152,44 @@ main(int argc, char** argv) IedServer_lockDataModel(iedServer); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR1_Amp_instMag_i, current); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR1_Amp_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR2_Amp_instMag_i, current); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR2_Amp_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR3_Amp_instMag_i, current); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR3_Amp_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR3_Amp_instMag_i, current); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR4_Amp_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR1_Vol_instMag_i, voltage); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR1_Vol_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR2_Vol_instMag_i, voltage); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR2_Vol_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR3_Vol_instMag_i, voltage); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR3_Vol_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR4_Vol_instMag_i, voltage); + IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR4_Vol_q, q); IedServer_unlockDataModel(iedServer); if (svcbEnabled) { SVPublisher_ASDU_setINT32(asdu, amp1, current); + SVPublisher_ASDU_setQuality(asdu, amp1q, q); SVPublisher_ASDU_setINT32(asdu, amp2, current); + SVPublisher_ASDU_setQuality(asdu, amp2q, q); SVPublisher_ASDU_setINT32(asdu, amp3, current); + SVPublisher_ASDU_setQuality(asdu, amp3q, q); SVPublisher_ASDU_setINT32(asdu, amp4, current); + SVPublisher_ASDU_setQuality(asdu, amp4q, q); SVPublisher_ASDU_setINT32(asdu, vol1, voltage); + SVPublisher_ASDU_setQuality(asdu, vol1q, q); SVPublisher_ASDU_setINT32(asdu, vol2, voltage); + SVPublisher_ASDU_setQuality(asdu, vol2q, q); SVPublisher_ASDU_setINT32(asdu, vol3, voltage); + SVPublisher_ASDU_setQuality(asdu, vol3q, q); SVPublisher_ASDU_setINT32(asdu, vol4, voltage); + SVPublisher_ASDU_setQuality(asdu, vol4q, q); SVPublisher_ASDU_increaseSmpCnt(asdu); @@ -161,7 +199,7 @@ main(int argc, char** argv) voltage++; current++; - Thread_sleep(500); + Thread_sleep(1); } /* stop MMS server - close TCP server socket and all client sockets */ diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index d8de6a3e9..f0a67246a 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -286,6 +286,8 @@ typedef uint16_t Validity; #define QUALITY_OPERATOR_BLOCKED 4096 +#define QUALITY_DERIVED 8192 + Validity Quality_getValidity(Quality* self); diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index 91bdeb30b..b703ad469 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -55,6 +55,7 @@ struct sSVPublisher_ASDU { uint8_t smpSynch; uint16_t smpCnt; + uint16_t smpCntLimit; uint32_t confRev; uint64_t refrTm; @@ -299,6 +300,7 @@ SVPublisher_addASDU(SVPublisher self, const char* svID, const char* datset, uint newAsdu->svID = svID; newAsdu->datset = datset; newAsdu->confRev = confRev; + newAsdu->smpCntLimit = UINT16_MAX; newAsdu->_next = NULL; @@ -652,10 +654,16 @@ SVPublisher_ASDU_setSmpCnt(SVPublisher_ASDU self, uint16_t value) encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0); } +void +SVPublisher_ASDU_setSmpCntWrap(SVPublisher_ASDU self, uint16_t value) +{ + self->smpCntLimit = value; +} + void SVPublisher_ASDU_increaseSmpCnt(SVPublisher_ASDU self) { - self->smpCnt++; + self->smpCnt = ((self->smpCnt + 1) % self->smpCntLimit); encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0); } diff --git a/src/sampled_values/sv_publisher.h b/src/sampled_values/sv_publisher.h index d75f5f5e0..957e12a89 100644 --- a/src/sampled_values/sv_publisher.h +++ b/src/sampled_values/sv_publisher.h @@ -294,6 +294,16 @@ SVPublisher_ASDU_getSmpCnt(SVPublisher_ASDU self); void SVPublisher_ASDU_increaseSmpCnt(SVPublisher_ASDU self); +/** + * \brief Set the roll-over (wrap) limit for the sample counter. When reaching the limit the + * sample counter will be reset to 0 (default is no limit) + * + * \param[in] self the Sampled Values ASDU instance. + * \param[in] value the new sample counter limit + */ +void +SVPublisher_ASDU_setSmpCntWrap(SVPublisher_ASDU self, uint16_t value); + /** * \brief Set the refresh time attribute of the ASDU. * diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index 807e62087..9e03607c2 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -1,7 +1,7 @@ /* * sv_receiver.c * - * Copyright 2015 Michael Zillgith + * Copyright 2015-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -337,8 +337,10 @@ parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length) } /* Call callback handler */ - if (subscriber->listener != NULL) - subscriber->listener(subscriber, subscriber->listenerParameter, &asdu); + if (subscriber) { + if (subscriber->listener != NULL) + subscriber->listener(subscriber, subscriber->listenerParameter, &asdu); + } } static void diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index c19ac03cc..7faa74db6 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -731,4 +731,4 @@ EXPORTS ClientGooseControlBlock_getMaxTime ClientGooseControlBlock_getFixedOffs ControlObjectClient_getCtlValType - + SVPublisher_ASDU_setSmpCntWrap From 77c4d3ae03b61881d6fda87682a91e440943bb23 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 6 Jul 2018 09:46:39 +0200 Subject: [PATCH 078/123] - 9-2LE example: updated data set --- .../iec61850_9_2_LE_example.c | 2 +- examples/iec61850_9_2_LE_example/static_model.c | 16 ++++++++-------- examples/iec61850_9_2_LE_example/sv.icd | 16 ++++++++-------- .../server_example_basic_io.c | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c b/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c index d29a095d7..640fff3c3 100644 --- a/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c +++ b/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c @@ -157,7 +157,7 @@ main(int argc, char** argv) IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR2_Amp_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR3_Amp_instMag_i, current); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR3_Amp_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR3_Amp_instMag_i, current); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR4_Amp_instMag_i, current); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR4_Amp_q, q); IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR1_Vol_instMag_i, voltage); diff --git a/examples/iec61850_9_2_LE_example/static_model.c b/examples/iec61850_9_2_LE_example/static_model.c index ada63aad9..5b3043f53 100644 --- a/examples/iec61850_9_2_LE_example/static_model.c +++ b/examples/iec61850_9_2_LE_example/static_model.c @@ -22,7 +22,7 @@ extern DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda7; DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda0 = { "MUnn", false, - "TCTR1$MX$Amp$instMag$i", + "TCTR1$MX$Amp", -1, NULL, NULL, @@ -32,7 +32,7 @@ DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda0 = { DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda1 = { "MUnn", false, - "TCTR2$MX$Amp$instMag$i", + "TCTR2$MX$Amp", -1, NULL, NULL, @@ -42,7 +42,7 @@ DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda1 = { DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda2 = { "MUnn", false, - "TCTR3$MX$Amp$instMag$i", + "TCTR3$MX$Amp", -1, NULL, NULL, @@ -52,7 +52,7 @@ DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda2 = { DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda3 = { "MUnn", false, - "TCTR4$MX$Amp$instMag$i", + "TCTR4$MX$Amp", -1, NULL, NULL, @@ -62,7 +62,7 @@ DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda3 = { DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda4 = { "MUnn", false, - "TVTR1$MX$Vol$instMag$i", + "TVTR1$MX$Vol", -1, NULL, NULL, @@ -72,7 +72,7 @@ DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda4 = { DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda5 = { "MUnn", false, - "TVTR2$MX$Vol$instMag$i", + "TVTR2$MX$Vol", -1, NULL, NULL, @@ -82,7 +82,7 @@ DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda5 = { DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda6 = { "MUnn", false, - "TVTR3$MX$Vol$instMag$i", + "TVTR3$MX$Vol", -1, NULL, NULL, @@ -92,7 +92,7 @@ DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda6 = { DataSetEntry iedModelds_MUnn_LLN0_PhsMeas1_fcda7 = { "MUnn", false, - "TVTR4$MX$Vol$instMag$i", + "TVTR4$MX$Vol", -1, NULL, NULL, diff --git a/examples/iec61850_9_2_LE_example/sv.icd b/examples/iec61850_9_2_LE_example/sv.icd index 9ef2d1a11..b52f8d92f 100644 --- a/examples/iec61850_9_2_LE_example/sv.icd +++ b/examples/iec61850_9_2_LE_example/sv.icd @@ -74,21 +74,21 @@ SCL.xsd"> + doName="Amp" /> + doName="Amp" /> + doName="Amp" /> + doName="Amp" /> + doName="Vol" /> + doName="Vol" /> + doName="Vol" /> + doName="Vol" /> diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index b77b814c7..b77aa0868 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -13,7 +13,7 @@ #include #include -#include "../server_example_basic_io/static_model.h" +#include "static_model.h" /* import IEC 61850 device model created from SCL-File */ extern IedModel iedModel; From 2e160d627997ea38883d7420e729a27873ea56d0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 12 Jul 2018 16:01:56 +0200 Subject: [PATCH 079/123] - added server side example for the substitution service --- examples/CMakeLists.txt | 1 + examples/Makefile | 1 + .../CMakeLists.txt | 21 + examples/server_example_substitution/Makefile | 30 + .../server_example_substitution.c | 220 ++++ .../static_model.c | 998 ++++++++++++++++++ .../static_model.h | 177 ++++ .../substitution_example.icd | 229 ++++ 8 files changed, 1677 insertions(+) create mode 100644 examples/server_example_substitution/CMakeLists.txt create mode 100644 examples/server_example_substitution/Makefile create mode 100644 examples/server_example_substitution/server_example_substitution.c create mode 100644 examples/server_example_substitution/static_model.c create mode 100644 examples/server_example_substitution/static_model.h create mode 100644 examples/server_example_substitution/substitution_example.icd diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2d802c9b1..abd77fb74 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(server_example_61400_25) add_subdirectory(server_example_setting_groups) add_subdirectory(server_example_logging) add_subdirectory(server_example_files) +add_subdirectory(server_example_substitution) add_subdirectory(iec61850_client_example1) add_subdirectory(iec61850_client_example2) diff --git a/examples/Makefile b/examples/Makefile index 543cb03b3..6bf1c8f91 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_logging EXAMPLE_DIRS += server_example_files +EXAMPLE_DIRS += server_example_substitution EXAMPLE_DIRS += goose_subscriber EXAMPLE_DIRS += goose_publisher EXAMPLE_DIRS += sv_subscriber diff --git a/examples/server_example_substitution/CMakeLists.txt b/examples/server_example_substitution/CMakeLists.txt new file mode 100644 index 000000000..d9ae35129 --- /dev/null +++ b/examples/server_example_substitution/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories( + . +) + +set(server_example_SRCS + server_example_substitution.c + static_model.c +) + +IF(WIN32) +set_source_files_properties(${server_example_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(server_example_substitution + ${server_example_SRCS} +) + +target_link_libraries(server_example_substitution + iec61850 +) diff --git a/examples/server_example_substitution/Makefile b/examples/server_example_substitution/Makefile new file mode 100644 index 000000000..f3b779f43 --- /dev/null +++ b/examples/server_example_substitution/Makefile @@ -0,0 +1,30 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_substitution +PROJECT_SOURCES = server_example_substitution.c +PROJECT_SOURCES += static_model.c + +PROJECT_ICD_FILE = substitution_example.icd + +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_substitution/server_example_substitution.c b/examples/server_example_substitution/server_example_substitution.c new file mode 100644 index 000000000..043280a3b --- /dev/null +++ b/examples/server_example_substitution/server_example_substitution.c @@ -0,0 +1,220 @@ +/* + * server_example_substitution.c + * + * - How to use the IEC 61850 substitution service + * - Two data objects can be substituted: + * -- GGIO1.AnIn1 + * -- GGIO1.Ind1 + */ + +#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; + +static bool subsAnIn1 = false; +static bool subsInd1 = false; + +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"); +} + +static MmsDataAccessError +writeAccessHandler (DataAttribute* dataAttribute, MmsValue* value, ClientConnection connection, void* parameter) +{ + if (dataAttribute == IEDMODEL_LD1_GGIO1_AnIn1_subEna) { + + printf("Received GGIO1.AnIn1.subEna: %i\n", MmsValue_getBoolean(value)); + + if (MmsValue_getBoolean(value)) { + subsAnIn1 = true; + + /* Update values with substituted values */ + + Quality quality = + Quality_fromMmsValue(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_subQ)); + + Quality_setFlag(&quality, QUALITY_SOURCE_SUBSTITUTED); + + IedServer_updateQuality(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_q, quality); + + IedServer_updateAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_mag_f, + IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_subMag_f)); + } + else { + subsAnIn1 = false; + } + + } + else if (dataAttribute == IEDMODEL_LD1_GGIO1_AnIn1_subMag_f) { + + if (subsAnIn1) { + IedServer_updateAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_mag_f, value); + } + + } + else if (dataAttribute == IEDMODEL_LD1_GGIO1_AnIn1_subQ) { + + if (subsAnIn1) { + Quality quality = Quality_fromMmsValue(value); + + Quality_setFlag(&quality, QUALITY_SOURCE_SUBSTITUTED); + + IedServer_updateQuality(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_q, quality); + } + + } + else if (dataAttribute == IEDMODEL_LD1_GGIO1_Ind1_subEna) { + printf("Received GGIO1.Ind1.subEna: %i\n", MmsValue_getBoolean(value)); + + if (MmsValue_getBoolean(value)) { + subsInd1 = true; + + /* Update values with substituted values */ + + Quality quality = + Quality_fromMmsValue(IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_Ind1_subQ)); + + Quality_setFlag(&quality, QUALITY_SOURCE_SUBSTITUTED); + + IedServer_updateQuality(iedServer, IEDMODEL_LD1_GGIO1_Ind1_q, quality); + + IedServer_updateAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_Ind1_stVal, + IedServer_getAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_Ind1_subVal)); + } + else { + subsInd1 = false; + } + } + else if (dataAttribute == IEDMODEL_LD1_GGIO1_Ind1_subVal) { + + if (subsInd1) { + IedServer_updateAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_Ind1_stVal, value); + } + + } + else if (dataAttribute == IEDMODEL_LD1_GGIO1_Ind1_subQ) { + + if (subsInd1) { + Quality quality = Quality_fromMmsValue(value); + + Quality_setFlag(&quality, QUALITY_SOURCE_SUBSTITUTED); + + IedServer_updateQuality(iedServer, IEDMODEL_LD1_GGIO1_Ind1_q, quality); + } + + } + + return DATA_ACCESS_ERROR_SUCCESS; +} + +int +main(int argc, char** argv) +{ + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); + + + + + /* Create a new IEC 61850 server instance */ + iedServer = IedServer_create(&iedModel); + + IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); + + /* Install write callback handler for substitution variables */ + + IedServer_handleWriteAccess(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_subEna, writeAccessHandler, NULL); + IedServer_handleWriteAccess(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_subMag_f, writeAccessHandler, NULL); + IedServer_handleWriteAccess(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_subQ, writeAccessHandler, NULL); + + IedServer_handleWriteAccess(iedServer, IEDMODEL_LD1_GGIO1_Ind1_subEna, writeAccessHandler, NULL); + IedServer_handleWriteAccess(iedServer, IEDMODEL_LD1_GGIO1_Ind1_subVal, writeAccessHandler, NULL); + IedServer_handleWriteAccess(iedServer, IEDMODEL_LD1_GGIO1_Ind1_subQ, writeAccessHandler, NULL); + + /* MMS server will be instructed to start listening for client connections. */ + IedServer_start(iedServer, 102); + + if (!IedServer_isRunning(iedServer)) { + printf("Starting server failed! Exit.\n"); + IedServer_destroy(iedServer); + exit(-1); + } + + running = 1; + + signal(SIGINT, sigint_handler); + + float t = 0.f; + bool ind1 = true; + + while (running) { + uint64_t timestamp = Hal_getTimeInMs(); + + t += 0.1f; + + float an1 = sinf(t); + + if (ind1) + ind1 = false; + else + ind1 = true; + + Timestamp iecTimestamp; + + Timestamp_clearFlags(&iecTimestamp); + Timestamp_setTimeInMilliseconds(&iecTimestamp, timestamp); + Timestamp_setLeapSecondKnown(&iecTimestamp, true); + + + /* toggle clock-not-synchronized flag in timestamp */ + if (((int) t % 2) == 0) + Timestamp_setClockNotSynchronized(&iecTimestamp, true); + + IedServer_lockDataModel(iedServer); + + if (subsAnIn1 == false) { + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_t, &iecTimestamp); + IedServer_updateQuality(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_q, QUALITY_VALIDITY_GOOD); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_AnIn1_mag_f, an1); + } + + if (subsInd1 == false) { + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_Ind1_t, &iecTimestamp); + IedServer_updateQuality(iedServer, IEDMODEL_LD1_GGIO1_Ind1_q, QUALITY_VALIDITY_GOOD); + IedServer_updateBooleanAttributeValue(iedServer, IEDMODEL_LD1_GGIO1_Ind1_stVal, ind1); + } + + IedServer_unlockDataModel(iedServer); + + Thread_sleep(100); + } + + /* stop MMS server - close TCP server socket and all client sockets */ + IedServer_stop(iedServer); + + /* Cleanup - free all resources */ + IedServer_destroy(iedServer); + +} /* main() */ diff --git a/examples/server_example_substitution/static_model.c b/examples/server_example_substitution/static_model.c new file mode 100644 index 000000000..bf53c61a7 --- /dev/null +++ b/examples/server_example_substitution/static_model.c @@ -0,0 +1,998 @@ +/* + * static_model.c + * + * automatically generated from substitution_example.icd + */ +#include "static_model.h" + +static void initializeValues(); + + + +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_Beh, +}; + +DataObject iedModel_LD1_LLN0_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_LD1_LLN0, + NULL, + (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}; + +LogicalNode iedModel_LD1_LPHD1 = { + LogicalNodeModelType, + "LPHD1", + (ModelNode*) &iedModel_LD1, + (ModelNode*) &iedModel_LD1_MMDC1, + (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, + 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, + 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, + 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_subEna, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_subEna = { + DataAttributeModelType, + "subEna", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy_subVal, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_subVal = { + DataAttributeModelType, + "subVal", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy_subQ, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_subQ = { + DataAttributeModelType, + "subQ", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + (ModelNode*) &iedModel_LD1_LPHD1_Proxy_subID, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_LPHD1_Proxy_subID = { + DataAttributeModelType, + "subID", + (ModelNode*) &iedModel_LD1_LPHD1_Proxy, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_VISIBLE_STRING_64, + 0, + NULL, + 0}; + +LogicalNode iedModel_LD1_MMDC1 = { + LogicalNodeModelType, + "MMDC1", + (ModelNode*) &iedModel_LD1, + (ModelNode*) &iedModel_LD1_GGIO1, + (ModelNode*) &iedModel_LD1_MMDC1_Beh, +}; + +DataObject iedModel_LD1_MMDC1_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_LD1_MMDC1, + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + (ModelNode*) &iedModel_LD1_MMDC1_Beh_stVal, + 0 +}; + +DataAttribute iedModel_LD1_MMDC1_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_MMDC1_Beh, + (ModelNode*) &iedModel_LD1_MMDC1_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_MMDC1_Beh, + (ModelNode*) &iedModel_LD1_MMDC1_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_MMDC1_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_MMDC1_Watt = { + DataObjectModelType, + "Watt", + (ModelNode*) &iedModel_LD1_MMDC1, + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_mag, + 0 +}; + +DataAttribute iedModel_LD1_MMDC1_Watt_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_q, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_MMDC1_Watt_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_subEna, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_subEna = { + DataAttributeModelType, + "subEna", + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_subMag, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_subMag = { + DataAttributeModelType, + "subMag", + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_subQ, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_subMag_f, + 0, + IEC61850_FC_SV, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_subMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_MMDC1_Watt_subMag, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_subQ = { + DataAttributeModelType, + "subQ", + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + (ModelNode*) &iedModel_LD1_MMDC1_Watt_subID, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Watt_subID = { + DataAttributeModelType, + "subID", + (ModelNode*) &iedModel_LD1_MMDC1_Watt, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_VISIBLE_STRING_64, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_MMDC1_Amp = { + DataObjectModelType, + "Amp", + (ModelNode*) &iedModel_LD1_MMDC1, + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_mag, + 0 +}; + +DataAttribute iedModel_LD1_MMDC1_Amp_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_q, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_MMDC1_Amp_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_subEna, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_subEna = { + DataAttributeModelType, + "subEna", + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_subMag, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_subMag = { + DataAttributeModelType, + "subMag", + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_subQ, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_subMag_f, + 0, + IEC61850_FC_SV, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_subMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_MMDC1_Amp_subMag, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_subQ = { + DataAttributeModelType, + "subQ", + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + (ModelNode*) &iedModel_LD1_MMDC1_Amp_subID, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Amp_subID = { + DataAttributeModelType, + "subID", + (ModelNode*) &iedModel_LD1_MMDC1_Amp, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_VISIBLE_STRING_64, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_MMDC1_Vol = { + DataObjectModelType, + "Vol", + (ModelNode*) &iedModel_LD1_MMDC1, + NULL, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_mag, + 0 +}; + +DataAttribute iedModel_LD1_MMDC1_Vol_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_q, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_MMDC1_Vol_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_subEna, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_subEna = { + DataAttributeModelType, + "subEna", + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_subMag, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_subMag = { + DataAttributeModelType, + "subMag", + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_subQ, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_subMag_f, + 0, + IEC61850_FC_SV, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_subMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_MMDC1_Vol_subMag, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_subQ = { + DataAttributeModelType, + "subQ", + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + (ModelNode*) &iedModel_LD1_MMDC1_Vol_subID, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_MMDC1_Vol_subID = { + DataAttributeModelType, + "subID", + (ModelNode*) &iedModel_LD1_MMDC1_Vol, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_VISIBLE_STRING_64, + 0, + NULL, + 0}; + +LogicalNode iedModel_LD1_GGIO1 = { + LogicalNodeModelType, + "GGIO1", + (ModelNode*) &iedModel_LD1, + NULL, + (ModelNode*) &iedModel_LD1_GGIO1_Beh, +}; + +DataObject iedModel_LD1_GGIO1_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_LD1_GGIO1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + (ModelNode*) &iedModel_LD1_GGIO1_Beh_stVal, + 0 +}; + +DataAttribute iedModel_LD1_GGIO1_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_GGIO1_Beh, + (ModelNode*) &iedModel_LD1_GGIO1_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_GGIO1_Beh, + (ModelNode*) &iedModel_LD1_GGIO1_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_GGIO1_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_GGIO1_Ind1 = { + DataObjectModelType, + "Ind1", + (ModelNode*) &iedModel_LD1_GGIO1, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1_stVal, + 0 +}; + +DataAttribute iedModel_LD1_GGIO1_Ind1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Ind1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Ind1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1_subEna, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Ind1_subEna = { + DataAttributeModelType, + "subEna", + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1_subVal, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Ind1_subVal = { + DataAttributeModelType, + "subVal", + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1_subQ, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Ind1_subQ = { + DataAttributeModelType, + "subQ", + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + (ModelNode*) &iedModel_LD1_GGIO1_Ind1_subID, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_Ind1_subID = { + DataAttributeModelType, + "subID", + (ModelNode*) &iedModel_LD1_GGIO1_Ind1, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_VISIBLE_STRING_64, + 0, + NULL, + 0}; + +DataObject iedModel_LD1_GGIO1_AnIn1 = { + DataObjectModelType, + "AnIn1", + (ModelNode*) &iedModel_LD1_GGIO1, + NULL, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_mag, + 0 +}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_q, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_subEna, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_subEna = { + DataAttributeModelType, + "subEna", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_subMag, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_subMag = { + DataAttributeModelType, + "subMag", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_subQ, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_subMag_f, + 0, + IEC61850_FC_SV, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_subMag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_subMag, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_FLOAT32, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_subQ = { + DataAttributeModelType, + "subQ", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1_subID, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_QUALITY, + 0, + NULL, + 0}; + +DataAttribute iedModel_LD1_GGIO1_AnIn1_subID = { + DataAttributeModelType, + "subID", + (ModelNode*) &iedModel_LD1_GGIO1_AnIn1, + NULL, + NULL, + 0, + IEC61850_FC_SV, + IEC61850_VISIBLE_STRING_64, + 0, + NULL, + 0}; + +extern ReportControlBlock iedModel_LD1_LLN0_report0; +extern ReportControlBlock iedModel_LD1_LLN0_report1; + +ReportControlBlock iedModel_LD1_LLN0_report0 = {&iedModel_LD1_LLN0, "urcb01", "13e08c78", false, "", 1, 23, 247, 3000, 5000, &iedModel_LD1_LLN0_report1}; +ReportControlBlock iedModel_LD1_LLN0_report1 = {&iedModel_LD1_LLN0, "urcb02", "13e08c78", false, "", 1, 23, 247, 3000, 5000, NULL}; + + + + + + + +IedModel iedModel = { + "IED1", + &iedModel_LD1, + NULL, + &iedModel_LD1_LLN0_report0, + NULL, + NULL, + NULL, + NULL, + NULL, + initializeValues +}; + +static void +initializeValues() +{ +} diff --git a/examples/server_example_substitution/static_model.h b/examples/server_example_substitution/static_model.h new file mode 100644 index 000000000..1ad6a5e49 --- /dev/null +++ b/examples/server_example_substitution/static_model.h @@ -0,0 +1,177 @@ +/* + * static_model.h + * + * automatically generated from substitution_example.icd + */ + +#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_Beh; +extern DataAttribute iedModel_LD1_LLN0_Beh_stVal; +extern DataAttribute iedModel_LD1_LLN0_Beh_q; +extern DataAttribute iedModel_LD1_LLN0_Beh_t; +extern LogicalNode iedModel_LD1_LPHD1; +extern DataObject iedModel_LD1_LPHD1_PhyNam; +extern DataAttribute iedModel_LD1_LPHD1_PhyNam_vendor; +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_subEna; +extern DataAttribute iedModel_LD1_LPHD1_Proxy_subVal; +extern DataAttribute iedModel_LD1_LPHD1_Proxy_subQ; +extern DataAttribute iedModel_LD1_LPHD1_Proxy_subID; +extern LogicalNode iedModel_LD1_MMDC1; +extern DataObject iedModel_LD1_MMDC1_Beh; +extern DataAttribute iedModel_LD1_MMDC1_Beh_stVal; +extern DataAttribute iedModel_LD1_MMDC1_Beh_q; +extern DataAttribute iedModel_LD1_MMDC1_Beh_t; +extern DataObject iedModel_LD1_MMDC1_Watt; +extern DataAttribute iedModel_LD1_MMDC1_Watt_mag; +extern DataAttribute iedModel_LD1_MMDC1_Watt_mag_f; +extern DataAttribute iedModel_LD1_MMDC1_Watt_q; +extern DataAttribute iedModel_LD1_MMDC1_Watt_t; +extern DataAttribute iedModel_LD1_MMDC1_Watt_subEna; +extern DataAttribute iedModel_LD1_MMDC1_Watt_subMag; +extern DataAttribute iedModel_LD1_MMDC1_Watt_subMag_f; +extern DataAttribute iedModel_LD1_MMDC1_Watt_subQ; +extern DataAttribute iedModel_LD1_MMDC1_Watt_subID; +extern DataObject iedModel_LD1_MMDC1_Amp; +extern DataAttribute iedModel_LD1_MMDC1_Amp_mag; +extern DataAttribute iedModel_LD1_MMDC1_Amp_mag_f; +extern DataAttribute iedModel_LD1_MMDC1_Amp_q; +extern DataAttribute iedModel_LD1_MMDC1_Amp_t; +extern DataAttribute iedModel_LD1_MMDC1_Amp_subEna; +extern DataAttribute iedModel_LD1_MMDC1_Amp_subMag; +extern DataAttribute iedModel_LD1_MMDC1_Amp_subMag_f; +extern DataAttribute iedModel_LD1_MMDC1_Amp_subQ; +extern DataAttribute iedModel_LD1_MMDC1_Amp_subID; +extern DataObject iedModel_LD1_MMDC1_Vol; +extern DataAttribute iedModel_LD1_MMDC1_Vol_mag; +extern DataAttribute iedModel_LD1_MMDC1_Vol_mag_f; +extern DataAttribute iedModel_LD1_MMDC1_Vol_q; +extern DataAttribute iedModel_LD1_MMDC1_Vol_t; +extern DataAttribute iedModel_LD1_MMDC1_Vol_subEna; +extern DataAttribute iedModel_LD1_MMDC1_Vol_subMag; +extern DataAttribute iedModel_LD1_MMDC1_Vol_subMag_f; +extern DataAttribute iedModel_LD1_MMDC1_Vol_subQ; +extern DataAttribute iedModel_LD1_MMDC1_Vol_subID; +extern LogicalNode iedModel_LD1_GGIO1; +extern DataObject iedModel_LD1_GGIO1_Beh; +extern DataAttribute iedModel_LD1_GGIO1_Beh_stVal; +extern DataAttribute iedModel_LD1_GGIO1_Beh_q; +extern DataAttribute iedModel_LD1_GGIO1_Beh_t; +extern DataObject iedModel_LD1_GGIO1_Ind1; +extern DataAttribute iedModel_LD1_GGIO1_Ind1_stVal; +extern DataAttribute iedModel_LD1_GGIO1_Ind1_q; +extern DataAttribute iedModel_LD1_GGIO1_Ind1_t; +extern DataAttribute iedModel_LD1_GGIO1_Ind1_subEna; +extern DataAttribute iedModel_LD1_GGIO1_Ind1_subVal; +extern DataAttribute iedModel_LD1_GGIO1_Ind1_subQ; +extern DataAttribute iedModel_LD1_GGIO1_Ind1_subID; +extern DataObject iedModel_LD1_GGIO1_AnIn1; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_mag; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_mag_f; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_q; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_t; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_subEna; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_subMag; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_subMag_f; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_subQ; +extern DataAttribute iedModel_LD1_GGIO1_AnIn1_subID; + + + +#define IEDMODEL_LD1 (&iedModel_LD1) +#define IEDMODEL_LD1_LLN0 (&iedModel_LD1_LLN0) +#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_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_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_subEna (&iedModel_LD1_LPHD1_Proxy_subEna) +#define IEDMODEL_LD1_LPHD1_Proxy_subVal (&iedModel_LD1_LPHD1_Proxy_subVal) +#define IEDMODEL_LD1_LPHD1_Proxy_subQ (&iedModel_LD1_LPHD1_Proxy_subQ) +#define IEDMODEL_LD1_LPHD1_Proxy_subID (&iedModel_LD1_LPHD1_Proxy_subID) +#define IEDMODEL_LD1_MMDC1 (&iedModel_LD1_MMDC1) +#define IEDMODEL_LD1_MMDC1_Beh (&iedModel_LD1_MMDC1_Beh) +#define IEDMODEL_LD1_MMDC1_Beh_stVal (&iedModel_LD1_MMDC1_Beh_stVal) +#define IEDMODEL_LD1_MMDC1_Beh_q (&iedModel_LD1_MMDC1_Beh_q) +#define IEDMODEL_LD1_MMDC1_Beh_t (&iedModel_LD1_MMDC1_Beh_t) +#define IEDMODEL_LD1_MMDC1_Watt (&iedModel_LD1_MMDC1_Watt) +#define IEDMODEL_LD1_MMDC1_Watt_mag (&iedModel_LD1_MMDC1_Watt_mag) +#define IEDMODEL_LD1_MMDC1_Watt_mag_f (&iedModel_LD1_MMDC1_Watt_mag_f) +#define IEDMODEL_LD1_MMDC1_Watt_q (&iedModel_LD1_MMDC1_Watt_q) +#define IEDMODEL_LD1_MMDC1_Watt_t (&iedModel_LD1_MMDC1_Watt_t) +#define IEDMODEL_LD1_MMDC1_Watt_subEna (&iedModel_LD1_MMDC1_Watt_subEna) +#define IEDMODEL_LD1_MMDC1_Watt_subMag (&iedModel_LD1_MMDC1_Watt_subMag) +#define IEDMODEL_LD1_MMDC1_Watt_subMag_f (&iedModel_LD1_MMDC1_Watt_subMag_f) +#define IEDMODEL_LD1_MMDC1_Watt_subQ (&iedModel_LD1_MMDC1_Watt_subQ) +#define IEDMODEL_LD1_MMDC1_Watt_subID (&iedModel_LD1_MMDC1_Watt_subID) +#define IEDMODEL_LD1_MMDC1_Amp (&iedModel_LD1_MMDC1_Amp) +#define IEDMODEL_LD1_MMDC1_Amp_mag (&iedModel_LD1_MMDC1_Amp_mag) +#define IEDMODEL_LD1_MMDC1_Amp_mag_f (&iedModel_LD1_MMDC1_Amp_mag_f) +#define IEDMODEL_LD1_MMDC1_Amp_q (&iedModel_LD1_MMDC1_Amp_q) +#define IEDMODEL_LD1_MMDC1_Amp_t (&iedModel_LD1_MMDC1_Amp_t) +#define IEDMODEL_LD1_MMDC1_Amp_subEna (&iedModel_LD1_MMDC1_Amp_subEna) +#define IEDMODEL_LD1_MMDC1_Amp_subMag (&iedModel_LD1_MMDC1_Amp_subMag) +#define IEDMODEL_LD1_MMDC1_Amp_subMag_f (&iedModel_LD1_MMDC1_Amp_subMag_f) +#define IEDMODEL_LD1_MMDC1_Amp_subQ (&iedModel_LD1_MMDC1_Amp_subQ) +#define IEDMODEL_LD1_MMDC1_Amp_subID (&iedModel_LD1_MMDC1_Amp_subID) +#define IEDMODEL_LD1_MMDC1_Vol (&iedModel_LD1_MMDC1_Vol) +#define IEDMODEL_LD1_MMDC1_Vol_mag (&iedModel_LD1_MMDC1_Vol_mag) +#define IEDMODEL_LD1_MMDC1_Vol_mag_f (&iedModel_LD1_MMDC1_Vol_mag_f) +#define IEDMODEL_LD1_MMDC1_Vol_q (&iedModel_LD1_MMDC1_Vol_q) +#define IEDMODEL_LD1_MMDC1_Vol_t (&iedModel_LD1_MMDC1_Vol_t) +#define IEDMODEL_LD1_MMDC1_Vol_subEna (&iedModel_LD1_MMDC1_Vol_subEna) +#define IEDMODEL_LD1_MMDC1_Vol_subMag (&iedModel_LD1_MMDC1_Vol_subMag) +#define IEDMODEL_LD1_MMDC1_Vol_subMag_f (&iedModel_LD1_MMDC1_Vol_subMag_f) +#define IEDMODEL_LD1_MMDC1_Vol_subQ (&iedModel_LD1_MMDC1_Vol_subQ) +#define IEDMODEL_LD1_MMDC1_Vol_subID (&iedModel_LD1_MMDC1_Vol_subID) +#define IEDMODEL_LD1_GGIO1 (&iedModel_LD1_GGIO1) +#define IEDMODEL_LD1_GGIO1_Beh (&iedModel_LD1_GGIO1_Beh) +#define IEDMODEL_LD1_GGIO1_Beh_stVal (&iedModel_LD1_GGIO1_Beh_stVal) +#define IEDMODEL_LD1_GGIO1_Beh_q (&iedModel_LD1_GGIO1_Beh_q) +#define IEDMODEL_LD1_GGIO1_Beh_t (&iedModel_LD1_GGIO1_Beh_t) +#define IEDMODEL_LD1_GGIO1_Ind1 (&iedModel_LD1_GGIO1_Ind1) +#define IEDMODEL_LD1_GGIO1_Ind1_stVal (&iedModel_LD1_GGIO1_Ind1_stVal) +#define IEDMODEL_LD1_GGIO1_Ind1_q (&iedModel_LD1_GGIO1_Ind1_q) +#define IEDMODEL_LD1_GGIO1_Ind1_t (&iedModel_LD1_GGIO1_Ind1_t) +#define IEDMODEL_LD1_GGIO1_Ind1_subEna (&iedModel_LD1_GGIO1_Ind1_subEna) +#define IEDMODEL_LD1_GGIO1_Ind1_subVal (&iedModel_LD1_GGIO1_Ind1_subVal) +#define IEDMODEL_LD1_GGIO1_Ind1_subQ (&iedModel_LD1_GGIO1_Ind1_subQ) +#define IEDMODEL_LD1_GGIO1_Ind1_subID (&iedModel_LD1_GGIO1_Ind1_subID) +#define IEDMODEL_LD1_GGIO1_AnIn1 (&iedModel_LD1_GGIO1_AnIn1) +#define IEDMODEL_LD1_GGIO1_AnIn1_mag (&iedModel_LD1_GGIO1_AnIn1_mag) +#define IEDMODEL_LD1_GGIO1_AnIn1_mag_f (&iedModel_LD1_GGIO1_AnIn1_mag_f) +#define IEDMODEL_LD1_GGIO1_AnIn1_q (&iedModel_LD1_GGIO1_AnIn1_q) +#define IEDMODEL_LD1_GGIO1_AnIn1_t (&iedModel_LD1_GGIO1_AnIn1_t) +#define IEDMODEL_LD1_GGIO1_AnIn1_subEna (&iedModel_LD1_GGIO1_AnIn1_subEna) +#define IEDMODEL_LD1_GGIO1_AnIn1_subMag (&iedModel_LD1_GGIO1_AnIn1_subMag) +#define IEDMODEL_LD1_GGIO1_AnIn1_subMag_f (&iedModel_LD1_GGIO1_AnIn1_subMag_f) +#define IEDMODEL_LD1_GGIO1_AnIn1_subQ (&iedModel_LD1_GGIO1_AnIn1_subQ) +#define IEDMODEL_LD1_GGIO1_AnIn1_subID (&iedModel_LD1_GGIO1_AnIn1_subID) + +#endif /* STATIC_MODEL_H_ */ + diff --git a/examples/server_example_substitution/substitution_example.icd b/examples/server_example_substitution/substitution_example.icd new file mode 100644 index 000000000..32d62c33c --- /dev/null +++ b/examples/server_example_substitution/substitution_example.icd @@ -0,0 +1,229 @@ + + +
+ + + +
+ + + +
+

1 3 9999 33

+

33

+

00 00 00 01

+

00 01

+

00 01

+

102

+

192.168.62.248

+

255.255.255.0

+

192.168.62.254

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + e1 + e2 + e3 + e4 + e5 + + + normal + high + low + high-high + low-low + + + 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/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 + + As + + 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 + + +
From 47d34702b1488b9852b63b43c19456be42d1fa17 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 12 Jul 2018 21:11:16 +0200 Subject: [PATCH 080/123] - updated IEC 61850-9-2 LE example to be more realistic --- examples/iec61850_9_2_LE_example/Makefile | 2 + .../iec61850_9_2_LE_example.c | 88 +++++++++++++------ make/target_system.mk | 4 +- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/examples/iec61850_9_2_LE_example/Makefile b/examples/iec61850_9_2_LE_example/Makefile index 0e3703ace..2ce35b732 100644 --- a/examples/iec61850_9_2_LE_example/Makefile +++ b/examples/iec61850_9_2_LE_example/Makefile @@ -9,6 +9,8 @@ PROJECT_ICD_FILE = sv.icd include $(LIBIEC_HOME)/make/target_system.mk include $(LIBIEC_HOME)/make/stack_includes.mk +LDFLAGS += -lm + all: $(PROJECT_BINARY_NAME) include $(LIBIEC_HOME)/make/common_targets.mk diff --git a/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c b/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c index 640fff3c3..d63554d86 100644 --- a/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c +++ b/examples/iec61850_9_2_LE_example/iec61850_9_2_LE_example.c @@ -27,7 +27,7 @@ #include #include #include - +#include /* Include the generated header with the model access handles */ #include "static_model.h" @@ -131,9 +131,6 @@ main(int argc, char** argv) setupSVPublisher(svInterface); - int voltage = 1; - int current = 1; - SVControlBlock* svcb = IedModel_getSVControlBlock(&iedModel, IEDMODEL_MUnn_LLN0, "MSVCB01"); if (svcb == NULL) { @@ -145,61 +142,102 @@ main(int argc, char** argv) Quality q = QUALITY_VALIDITY_GOOD; + int vol = (int) (6350.f * sqrt(2)); + int amp = 0; + float phaseAngle = 0.f; + + int voltageA; + int voltageB; + int voltageC; + int voltageN; + int currentA; + int currentB; + int currentC; + int currentN; + + int sampleCount = 0; + + uint64_t nextCycleStart = Hal_getTimeInMs() + 1000; + while (running) { - uint64_t timeval = Hal_getTimeInMs(); + /* update measurement values */ + int samplePoint = sampleCount % 80; + + double angleA = (2 * M_PI / 80) * samplePoint; + double angleB = (2 * M_PI / 80) * samplePoint - ( 2 * M_PI / 3); + double angleC = (2 * M_PI / 80) * samplePoint - ( 4 * M_PI / 3); + + voltageA = (vol * sin(angleA)) * 100; + voltageB = (vol * sin(angleB)) * 100; + voltageC = (vol * sin(angleC)) * 100; + voltageN = voltageA + voltageB + voltageC; + + currentA = (amp * sin(angleA - phaseAngle)) * 1000; + currentB = (amp * sin(angleB - phaseAngle)) * 1000; + currentC = (amp * sin(angleC - phaseAngle)) * 1000; + currentN = currentA + currentB + currentC; IedServer_lockDataModel(iedServer); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR1_Amp_instMag_i, current); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR1_Amp_instMag_i, currentA); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR1_Amp_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR2_Amp_instMag_i, current); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR2_Amp_instMag_i, currentB); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR2_Amp_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR3_Amp_instMag_i, current); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR3_Amp_instMag_i, currentC); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR3_Amp_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR4_Amp_instMag_i, current); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TCTR4_Amp_instMag_i, currentN); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TCTR4_Amp_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR1_Vol_instMag_i, voltage); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR1_Vol_instMag_i, voltageA); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR1_Vol_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR2_Vol_instMag_i, voltage); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR2_Vol_instMag_i, voltageB); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR2_Vol_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR3_Vol_instMag_i, voltage); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR3_Vol_instMag_i, voltageC); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR3_Vol_q, q); - IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR4_Vol_instMag_i, voltage); + IedServer_updateInt32AttributeValue(iedServer, IEDMODEL_MUnn_TVTR4_Vol_instMag_i, voltageN); IedServer_updateQuality(iedServer, IEDMODEL_MUnn_TVTR4_Vol_q, q); IedServer_unlockDataModel(iedServer); if (svcbEnabled) { - SVPublisher_ASDU_setINT32(asdu, amp1, current); + SVPublisher_ASDU_setINT32(asdu, amp1, currentA); SVPublisher_ASDU_setQuality(asdu, amp1q, q); - SVPublisher_ASDU_setINT32(asdu, amp2, current); + SVPublisher_ASDU_setINT32(asdu, amp2, currentB); SVPublisher_ASDU_setQuality(asdu, amp2q, q); - SVPublisher_ASDU_setINT32(asdu, amp3, current); + SVPublisher_ASDU_setINT32(asdu, amp3, currentC); SVPublisher_ASDU_setQuality(asdu, amp3q, q); - SVPublisher_ASDU_setINT32(asdu, amp4, current); + SVPublisher_ASDU_setINT32(asdu, amp4, currentN); SVPublisher_ASDU_setQuality(asdu, amp4q, q); - SVPublisher_ASDU_setINT32(asdu, vol1, voltage); + SVPublisher_ASDU_setINT32(asdu, vol1, voltageA); SVPublisher_ASDU_setQuality(asdu, vol1q, q); - SVPublisher_ASDU_setINT32(asdu, vol2, voltage); + SVPublisher_ASDU_setINT32(asdu, vol2, voltageB); SVPublisher_ASDU_setQuality(asdu, vol2q, q); - SVPublisher_ASDU_setINT32(asdu, vol3, voltage); + SVPublisher_ASDU_setINT32(asdu, vol3, voltageC); SVPublisher_ASDU_setQuality(asdu, vol3q, q); - SVPublisher_ASDU_setINT32(asdu, vol4, voltage); + SVPublisher_ASDU_setINT32(asdu, vol4, voltageN); SVPublisher_ASDU_setQuality(asdu, vol4q, q); - SVPublisher_ASDU_increaseSmpCnt(asdu); + SVPublisher_ASDU_setSmpCnt(asdu, (uint16_t) sampleCount); SVPublisher_publish(svPublisher); } - voltage++; - current++; + sampleCount = ((sampleCount + 1) % 4000); + + if ((sampleCount % 400) == 0) { + uint64_t timeval = Hal_getTimeInMs(); - Thread_sleep(1); + while (timeval < nextCycleStart + 100) { + Thread_sleep(1); + + timeval = Hal_getTimeInMs(); + } + + nextCycleStart = nextCycleStart + 100; + } } /* stop MMS server - close TCP server socket and all client sockets */ diff --git a/make/target_system.mk b/make/target_system.mk index 107f9b0bf..b03a0ec7e 100644 --- a/make/target_system.mk +++ b/make/target_system.mk @@ -1,10 +1,10 @@ UNAME := $(shell uname) MIPSEL_TOOLCHAIN_PREFIX=mipsel-openwrt-linux- -ARM_TOOLCHAIN_PREFIX=arm-linux- +#ARM_TOOLCHAIN_PREFIX=arm-linux- #ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi- #ARM_TOOLCHAIN_PREFIX=arm-poky-linux-gnueabi- -#ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi- +ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabihf- UCLINUX_ARM_TOOLCHAIN_PREFIX=arm-uclinux-elf- UCLINUX_XPORT_TOOLCHAIN_PREFIX=m68k-uclinux- MINGW_TOOLCHAIN_PREFIX=i586-mingw32msvc- From eacdfa953dd94197d318157089beceeb48933a1a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 18 Jul 2018 21:48:04 +0200 Subject: [PATCH 081/123] - IED SERVER: fixed bug when calling write access handler (wrong pointer for ClientConnection object) --- demos/beaglebone/beagle_demo.icd | 2 +- .../server_example_password_auth.c | 17 +++++++++++ .../simpleIO_direct_control.icd | 4 +++ .../static_model.c | 28 +++++++++++++++++-- .../static_model.h | 4 +++ src/iec61850/server/mms_mapping/mms_mapping.c | 6 +++- 6 files changed, 56 insertions(+), 5 deletions(-) diff --git a/demos/beaglebone/beagle_demo.icd b/demos/beaglebone/beagle_demo.icd index b76febab1..57cdbb999 100644 --- a/demos/beaglebone/beagle_demo.icd +++ b/demos/beaglebone/beagle_demo.icd @@ -270,7 +270,7 @@ - + diff --git a/examples/server_example_password_auth/server_example_password_auth.c b/examples/server_example_password_auth/server_example_password_auth.c index 40f3d8048..b66b469e9 100644 --- a/examples/server_example_password_auth/server_example_password_auth.c +++ b/examples/server_example_password_auth/server_example_password_auth.c @@ -126,6 +126,20 @@ controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test) MmsValue_delete(timeStamp); } + +static MmsDataAccessError +writeAccessHandler (DataAttribute* dataAttribute, MmsValue* value, ClientConnection connection, void* parameter) +{ + void* securityToken = ClientConnection_getSecurityToken(connection); + + if (securityToken != password2) + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + return DATA_ACCESS_ERROR_SUCCESS; +} + + + int main(int argc, char** argv) { iedServer = IedServer_create(&iedModel); @@ -153,6 +167,9 @@ int main(int argc, char** argv) { IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4, (ControlHandler) controlHandlerForBinaryOutput, IEDMODEL_GenericIO_GGIO1_SPCSO4); + /* Set write access handler */ + IedServer_handleWriteAccess(iedServer, IEDMODEL_GenericIO_LLN0_ModAuto_setVal, writeAccessHandler, NULL); + /* MMS server will be instructed to start listening to client connections. */ IedServer_start(iedServer, 102); diff --git a/examples/server_example_password_auth/simpleIO_direct_control.icd b/examples/server_example_password_auth/simpleIO_direct_control.icd index 3081dffec..7cb91a232 100644 --- a/examples/server_example_password_auth/simpleIO_direct_control.icd +++ b/examples/server_example_password_auth/simpleIO_direct_control.icd @@ -94,6 +94,7 @@ + @@ -186,6 +187,9 @@ + + + diff --git a/examples/server_example_password_auth/static_model.c b/examples/server_example_password_auth/static_model.c index 1db7d7786..4cc943c7b 100644 --- a/examples/server_example_password_auth/static_model.c +++ b/examples/server_example_password_auth/static_model.c @@ -3,7 +3,7 @@ * * automatically generated from simpleIO_direct_control.icd */ -#include "../server_example_password_auth/static_model.h" +#include "static_model.h" static void initializeValues(); @@ -227,7 +227,7 @@ DataObject iedModel_GenericIO_LLN0_NamPlt = { DataObjectModelType, "NamPlt", (ModelNode*) &iedModel_GenericIO_LLN0, - NULL, + (ModelNode*) &iedModel_GenericIO_LLN0_ModAuto, (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_vendor, 0 }; @@ -297,6 +297,28 @@ DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs = { NULL, 0}; +DataObject iedModel_GenericIO_LLN0_ModAuto = { + DataObjectModelType, + "ModAuto", + (ModelNode*) &iedModel_GenericIO_LLN0, + NULL, + (ModelNode*) &iedModel_GenericIO_LLN0_ModAuto_setVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_ModAuto_setVal = { + DataAttributeModelType, + "setVal", + (ModelNode*) &iedModel_GenericIO_LLN0_ModAuto, + NULL, + NULL, + 0, + IEC61850_FC_SP, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + LogicalNode iedModel_GenericIO_LPHD1 = { LogicalNodeModelType, "LPHD1", @@ -1773,7 +1795,7 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { extern ReportControlBlock iedModel_GenericIO_LLN0_report0; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 24, 111, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 24, 239, 50, 1000, NULL}; diff --git a/examples/server_example_password_auth/static_model.h b/examples/server_example_password_auth/static_model.h index 58dd28b5f..372674e73 100644 --- a/examples/server_example_password_auth/static_model.h +++ b/examples/server_example_password_auth/static_model.h @@ -31,6 +31,8 @@ extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_swRev; extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_d; extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_configRev; extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs; +extern DataObject iedModel_GenericIO_LLN0_ModAuto; +extern DataAttribute iedModel_GenericIO_LLN0_ModAuto_setVal; extern LogicalNode iedModel_GenericIO_LPHD1; extern DataObject iedModel_GenericIO_LPHD1_PhyNam; extern DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor; @@ -174,6 +176,8 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_LLN0_NamPlt_d (&iedModel_GenericIO_LLN0_NamPlt_d) #define IEDMODEL_GenericIO_LLN0_NamPlt_configRev (&iedModel_GenericIO_LLN0_NamPlt_configRev) #define IEDMODEL_GenericIO_LLN0_NamPlt_ldNs (&iedModel_GenericIO_LLN0_NamPlt_ldNs) +#define IEDMODEL_GenericIO_LLN0_ModAuto (&iedModel_GenericIO_LLN0_ModAuto) +#define IEDMODEL_GenericIO_LLN0_ModAuto_setVal (&iedModel_GenericIO_LLN0_ModAuto_setVal) #define IEDMODEL_GenericIO_LPHD1 (&iedModel_GenericIO_LPHD1) #define IEDMODEL_GenericIO_LPHD1_PhyNam (&iedModel_GenericIO_LPHD1_PhyNam) #define IEDMODEL_GenericIO_LPHD1_PhyNam_vendor (&iedModel_GenericIO_LPHD1_PhyNam_vendor) diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index edb53e4f3..04163c712 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2062,8 +2062,12 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, MmsValue* matchingValue = checkIfValueBelongsToModelNode(dataAttribute, cachedValue, value); if (matchingValue != NULL) { + + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, + connection); + MmsDataAccessError handlerResult = - accessHandler->handler(dataAttribute, matchingValue, (ClientConnection) connection, + accessHandler->handler(dataAttribute, matchingValue, clientConnection, accessHandler->parameter); if (handlerResult == DATA_ACCESS_ERROR_SUCCESS) From b5cb80868c6557b88bc7203868a8e3d390ef0ae9 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 19 Jul 2018 07:24:49 +0200 Subject: [PATCH 082/123] - IED SERVER: fixed bug when calling write access handler (when access policy DENY) --- .../server_example_password_auth.c | 10 +++++++++- src/iec61850/server/mms_mapping/mms_mapping.c | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/examples/server_example_password_auth/server_example_password_auth.c b/examples/server_example_password_auth/server_example_password_auth.c index b66b469e9..46715b1bc 100644 --- a/examples/server_example_password_auth/server_example_password_auth.c +++ b/examples/server_example_password_auth/server_example_password_auth.c @@ -132,8 +132,13 @@ writeAccessHandler (DataAttribute* dataAttribute, MmsValue* value, ClientConnect { void* securityToken = ClientConnection_getSecurityToken(connection); - if (securityToken != password2) + if (dataAttribute == IEDMODEL_GenericIO_LLN0_ModAuto_setVal) + printf("Write access to LLN0.ModAuto.setVal: %i\n", MmsValue_getBoolean(value)); + + if (securityToken != password2) { + printf(" Access denied\n"); return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + } return DATA_ACCESS_ERROR_SUCCESS; } @@ -167,6 +172,9 @@ int main(int argc, char** argv) { IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4, (ControlHandler) controlHandlerForBinaryOutput, IEDMODEL_GenericIO_GGIO1_SPCSO4); + /* Allow only write access to settings that have a handler */ + IedServer_setWriteAccessPolicy(iedServer, IEC61850_FC_SP, ACCESS_POLICY_DENY); + /* Set write access handler */ IedServer_handleWriteAccess(iedServer, IEDMODEL_GenericIO_LLN0_ModAuto_setVal, writeAccessHandler, NULL); diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 04163c712..5931a1c9f 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2079,8 +2079,12 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, } 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) connection, + accessHandler->handler(dataAttribute, value, clientConnection, accessHandler->parameter); if (handlerResult == DATA_ACCESS_ERROR_SUCCESS) { From c36050f455572d8dd866c2f25ff2894a12224580 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 23 Jul 2018 17:38:18 +0200 Subject: [PATCH 083/123] - renamed iec61850_client_example3 to iec61850_client_example_control - removed outcommented code --- examples/CMakeLists.txt | 2 +- .../iec61850_client_example3/CMakeLists.txt | 17 ----------------- .../CMakeLists.txt | 17 +++++++++++++++++ .../Makefile | 4 ++-- .../client_example_control.c} | 0 src/iec61850/server/mms_mapping/control.c | 6 ------ 6 files changed, 20 insertions(+), 26 deletions(-) delete mode 100644 examples/iec61850_client_example3/CMakeLists.txt create mode 100644 examples/iec61850_client_example_control/CMakeLists.txt rename examples/{iec61850_client_example3 => iec61850_client_example_control}/Makefile (81%) rename examples/{iec61850_client_example3/client_example3.c => iec61850_client_example_control/client_example_control.c} (100%) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index abd77fb74..153ee6060 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,7 +15,7 @@ add_subdirectory(server_example_substitution) add_subdirectory(iec61850_client_example1) add_subdirectory(iec61850_client_example2) -add_subdirectory(iec61850_client_example3) +add_subdirectory(iec61850_client_example_control) add_subdirectory(iec61850_client_example4) add_subdirectory(iec61850_client_example5) add_subdirectory(iec61850_client_example_reporting) diff --git a/examples/iec61850_client_example3/CMakeLists.txt b/examples/iec61850_client_example3/CMakeLists.txt deleted file mode 100644 index 37b61ff80..000000000 --- a/examples/iec61850_client_example3/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ - -set(iec61850_client_example3_SRCS - client_example3.c -) - -IF(WIN32) -set_source_files_properties(${iec61850_client_example3_SRCS} - PROPERTIES LANGUAGE CXX) -ENDIF(WIN32) - -add_executable(iec61850_client_example3 - ${iec61850_client_example3_SRCS} -) - -target_link_libraries(iec61850_client_example3 - iec61850 -) diff --git a/examples/iec61850_client_example_control/CMakeLists.txt b/examples/iec61850_client_example_control/CMakeLists.txt new file mode 100644 index 000000000..bb612b44d --- /dev/null +++ b/examples/iec61850_client_example_control/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_example_control_SRCS + client_example_control.c +) + +IF(WIN32) +set_source_files_properties(${iec61850_client_example_control_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(client_example_control + ${iec61850_client_example_control_SRCS} +) + +target_link_libraries(client_example_control + iec61850 +) diff --git a/examples/iec61850_client_example3/Makefile b/examples/iec61850_client_example_control/Makefile similarity index 81% rename from examples/iec61850_client_example3/Makefile rename to examples/iec61850_client_example_control/Makefile index 138d49700..1bb8cd1c8 100644 --- a/examples/iec61850_client_example3/Makefile +++ b/examples/iec61850_client_example_control/Makefile @@ -1,7 +1,7 @@ LIBIEC_HOME=../.. -PROJECT_BINARY_NAME = client_example3 -PROJECT_SOURCES = client_example3.c +PROJECT_BINARY_NAME = client_example_control +PROJECT_SOURCES = client_example_control.c include $(LIBIEC_HOME)/make/target_system.mk include $(LIBIEC_HOME)/make/stack_includes.mk diff --git a/examples/iec61850_client_example3/client_example3.c b/examples/iec61850_client_example_control/client_example_control.c similarity index 100% rename from examples/iec61850_client_example3/client_example3.c rename to examples/iec61850_client_example_control/client_example_control.c diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 71b3e5ace..b761e3ce5 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -1494,12 +1494,6 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari setState(controlObject, STATE_WAIT_FOR_EXECUTION); initiateControlTask(controlObject); - -#if (CONFIG_MMS_THREADLESS_STACK == 1) - //TODO call this in single threaded version to increase response time!? - //executeControlTask(controlObject); -#endif - } else { indication = (MmsDataAccessError) checkResult; From dc4fbd76adbd63ce153f8fd490f6adcfda9cf4a2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 23 Jul 2018 20:56:28 +0200 Subject: [PATCH 084/123] - unification of HAL with lib60870 --- src/hal/inc/hal_socket.h | 50 +++++++++++++++++++++++---- src/hal/inc/hal_thread.h | 4 +++ src/hal/inc/hal_time.h | 7 +++- src/hal/socket/bsd/socket_bsd.c | 48 ++++++++++++++++++++++++-- src/hal/socket/linux/socket_linux.c | 52 ++++++++++++++++++++++++++--- src/hal/socket/win32/socket_win32.c | 48 ++++++++++++++++++++++++++ 6 files changed, 194 insertions(+), 15 deletions(-) diff --git a/src/hal/inc/hal_socket.h b/src/hal/inc/hal_socket.h index 3fddd3f24..41ec55de9 100644 --- a/src/hal/inc/hal_socket.h +++ b/src/hal/inc/hal_socket.h @@ -1,7 +1,7 @@ /* * socket_hal.h * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -27,11 +27,21 @@ #include #include +/** + * \file hal_socket.h + * \brief Abstraction layer TCP/IP sockets + * Has to be implemented for CS 104 TCP/IP. + */ + #ifdef __cplusplus extern "C" { #endif -/*! \addtogroup hal Platform (Hardware/OS) abstraction layer +/*! \defgroup hal Platform (Hardware/OS) abstraction layer + * + * Platform abstraction layer. These functions have to be implemented when the library is + * to be ported to new platforms. It might not be required to implement all interfaces + * depending on the required library features. * * @{ */ @@ -40,7 +50,7 @@ extern "C" { * @defgroup HAL_SOCKET Interface to the TCP/IP stack (abstract socket layer) * * Thread and Socket abstraction layer. This functions have to be implemented to - * port libIEC61850 to a new hardware/OS platform. + * port lib60870 to a new hardware/OS platform when TCP/IP is required. * * @{ */ @@ -63,7 +73,13 @@ HandleSet Handleset_new(void); /** - * \brief add a socket to an existing handle set + * \brief Reset the handle set for reuse + */ +void +Handleset_reset(HandleSet self); + +/** + * \brief add a soecket to an existing handle set * * \param self the HandleSet instance * \param sock the socket to add @@ -75,7 +91,10 @@ Handleset_addSocket(HandleSet self, const Socket sock); * \brief wait for a socket to become ready * * This function is corresponding to the BSD socket select function. - * The function will return after \p timeoutMs ms if no data is pending. + * It returns the number of sockets on which data is pending or 0 if no data is pending + * on any of the monitored connections. The function will return after "timeout" ms if no + * data is pending. + * The function shall return -1 if a socket error occures. * * \param self the HandleSet instance * \param timeout in milliseconds (ms) @@ -188,7 +207,7 @@ Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs); * \param address the IP address or hostname as C string * \param port the TCP port of the application to connect to * - * \return a new client socket instance. + * \return true if the connection was established successfully, false otherwise */ bool Socket_connect(Socket self, const char* address, int port); @@ -229,7 +248,7 @@ Socket_write(Socket self, uint8_t* buf, int size); * * The peer address has to be returned as * - * Implementation of this function is MANDATORY + * Implementation of this function is MANDATORY (libiec61850) * * \param self the client, connection or server socket instance * @@ -238,6 +257,23 @@ Socket_write(Socket self, uint8_t* buf, int size); char* Socket_getPeerAddress(Socket self); +/** + * \brief Get the address of the peer application (IP address and port number) + * + * The peer address has to be returned as + * + * Implementation of this function is MANDATORY (lib60870) + * + * \param self the client, connection or server socket instance + * \param peerAddressString a string to store the peer address (the string should have space + * for at least 60 characters) + * + * \return the IP address and port number as strings separated by the ':' character. If the + * address is an IPv6 address the IP part is encapsulated in square brackets. + */ +char* +Socket_getPeerAddressStatic(Socket self, char* peerAddressString); + /** * \brief destroy a socket (close the socket if a connection is established) * diff --git a/src/hal/inc/hal_thread.h b/src/hal/inc/hal_thread.h index 7d719f95a..7dbb390af 100644 --- a/src/hal/inc/hal_thread.h +++ b/src/hal/inc/hal_thread.h @@ -32,6 +32,10 @@ extern "C" { #endif +/** + * \file hal_thread.h + * \brief Abstraction layer for threading and synchronization + */ /*! \addtogroup hal * diff --git a/src/hal/inc/hal_time.h b/src/hal/inc/hal_time.h index 50fcf505e..121649014 100644 --- a/src/hal/inc/hal_time.h +++ b/src/hal/inc/hal_time.h @@ -28,6 +28,10 @@ extern "C" { #endif +/** + * \file hal_time.h + * \brief Abstraction layer for system time access + */ /*! \addtogroup hal * @@ -48,7 +52,8 @@ extern "C" { * * \return the system time with millisecond resolution. */ -uint64_t Hal_getTimeInMs(void); +uint64_t +Hal_getTimeInMs(void); /*! @} */ diff --git a/src/hal/socket/bsd/socket_bsd.c b/src/hal/socket/bsd/socket_bsd.c index 800ee4269..6207c71a6 100644 --- a/src/hal/socket/bsd/socket_bsd.c +++ b/src/hal/socket/bsd/socket_bsd.c @@ -70,6 +70,14 @@ Handleset_new(void) return result; } +void +Handleset_reset(HandleSet self) +{ + FD_ZERO(&self->handles); + self->maxHandle = -1; +} + + void Handleset_addSocket(HandleSet self, const Socket sock) { @@ -105,6 +113,7 @@ Handleset_destroy(HandleSet self) GLOBAL_FREEMEM(self); } +#if (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) static void activateKeepAlive(int sd) { @@ -127,6 +136,7 @@ activateKeepAlive(int sd) #endif /* SO_KEEPALIVE */ } +#endif /* (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) */ static bool prepareServerAddress(const char* address, int port, struct sockaddr_in* sockaddr) @@ -348,11 +358,45 @@ Socket_getPeerAddress(Socket self) return clientConnection; } +char* +Socket_getPeerAddressStatic(Socket self, char* peerAddressString) +{ + struct sockaddr_storage addr; + socklen_t addrLen = sizeof(addr); + + getpeername(self->fd, (struct sockaddr*) &addr, &addrLen); + + char addrString[INET6_ADDRSTRLEN + 7]; + int port; + + bool isIPv6; + + if (addr.ss_family == AF_INET) { + struct sockaddr_in* ipv4Addr = (struct sockaddr_in*) &addr; + port = ntohs(ipv4Addr->sin_port); + inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN); + isIPv6 = false; + } + else if (addr.ss_family == AF_INET6) { + struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*) &addr; + port = ntohs(ipv6Addr->sin6_port); + inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN); + isIPv6 = true; + } + else + return NULL ; + + if (isIPv6) + sprintf(peerAddressString, "[%s]:%i", addrString, port); + else + sprintf(peerAddressString, "%s:%i", addrString, port); + + return peerAddressString; +} + int Socket_read(Socket self, uint8_t* buf, int size) { - assert(self != NULL); - if (self->fd == -1) return -1; diff --git a/src/hal/socket/linux/socket_linux.c b/src/hal/socket/linux/socket_linux.c index cbf6c25d6..e1761d472 100644 --- a/src/hal/socket/linux/socket_linux.c +++ b/src/hal/socket/linux/socket_linux.c @@ -24,6 +24,7 @@ #include "hal_socket.h" #include #include +#include #include #include #include @@ -70,6 +71,13 @@ Handleset_new(void) return result; } +void +Handleset_reset(HandleSet self) +{ + FD_ZERO(&self->handles); + self->maxHandle = -1; +} + void Handleset_addSocket(HandleSet self, const Socket sock) { @@ -197,7 +205,7 @@ TcpServerSocket_create(const char* address, int port) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optionReuseAddr, sizeof(int)); if (bind(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) >= 0) { - serverSocket = GLOBAL_MALLOC(sizeof(struct sServerSocket)); + serverSocket = (ServerSocket) GLOBAL_MALLOC(sizeof(struct sServerSocket)); serverSocket->fd = fd; serverSocket->backLog = 0; @@ -281,7 +289,7 @@ ServerSocket_destroy(ServerSocket self) Socket TcpSocket_create() { - Socket self = GLOBAL_MALLOC(sizeof(struct sSocket)); + Socket self = (Socket) GLOBAL_MALLOC(sizeof(struct sSocket)); self->fd = -1; self->connectTimeout = 5000; @@ -374,7 +382,7 @@ Socket_getPeerAddress(Socket self) else return NULL ; - char* clientConnection = GLOBAL_MALLOC(strlen(addrString) + 9); + char* clientConnection = (char*) GLOBAL_MALLOC(strlen(addrString) + 9); if (isIPv6) @@ -385,11 +393,45 @@ Socket_getPeerAddress(Socket self) return clientConnection; } +char* +Socket_getPeerAddressStatic(Socket self, char* peerAddressString) +{ + struct sockaddr_storage addr; + socklen_t addrLen = sizeof(addr); + + getpeername(self->fd, (struct sockaddr*) &addr, &addrLen); + + char addrString[INET6_ADDRSTRLEN + 7]; + int port; + + bool isIPv6; + + if (addr.ss_family == AF_INET) { + struct sockaddr_in* ipv4Addr = (struct sockaddr_in*) &addr; + port = ntohs(ipv4Addr->sin_port); + inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN); + isIPv6 = false; + } + else if (addr.ss_family == AF_INET6) { + struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*) &addr; + port = ntohs(ipv6Addr->sin6_port); + inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN); + isIPv6 = true; + } + else + return NULL ; + + if (isIPv6) + sprintf(peerAddressString, "[%s]:%i", addrString, port); + else + sprintf(peerAddressString, "%s:%i", addrString, port); + + return peerAddressString; +} + int Socket_read(Socket self, uint8_t* buf, int size) { - assert(self != NULL); - if (self->fd == -1) return -1; diff --git a/src/hal/socket/win32/socket_win32.c b/src/hal/socket/win32/socket_win32.c index 777d17c87..b699816e2 100644 --- a/src/hal/socket/win32/socket_win32.c +++ b/src/hal/socket/win32/socket_win32.c @@ -70,6 +70,13 @@ Handleset_new(void) return result; } +void +Handleset_reset(HandleSet self) +{ + FD_ZERO(&self->handles); + self->maxHandle = INVALID_SOCKET; +} + void Handleset_addSocket(HandleSet self, const Socket sock) { @@ -396,6 +403,47 @@ Socket_getPeerAddress(Socket self) return clientConnection; } +char* +Socket_getPeerAddressStatic(Socket self, char* peerAddressString) +{ + struct sockaddr_storage addr; + int addrLen = sizeof(addr); + + getpeername(self->fd, (struct sockaddr*) &addr, &addrLen); + + char addrString[INET6_ADDRSTRLEN + 7]; + int addrStringLen = INET6_ADDRSTRLEN + 7; + int port; + + bool isIPv6; + + if (addr.ss_family == AF_INET) { + struct sockaddr_in* ipv4Addr = (struct sockaddr_in*) &addr; + port = ntohs(ipv4Addr->sin_port); + ipv4Addr->sin_port = 0; + WSAAddressToString((LPSOCKADDR) ipv4Addr, sizeof(struct sockaddr_storage), NULL, + (LPSTR) addrString, (LPDWORD) & addrStringLen); + isIPv6 = false; + } + else if (addr.ss_family == AF_INET6) { + struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*) &addr; + port = ntohs(ipv6Addr->sin6_port); + ipv6Addr->sin6_port = 0; + WSAAddressToString((LPSOCKADDR) ipv6Addr, sizeof(struct sockaddr_storage), NULL, + (LPSTR) addrString, (LPDWORD) & addrStringLen); + isIPv6 = true; + } + else + return NULL; + + if (isIPv6) + sprintf(peerAddressString, "[%s]:%i", addrString, port); + else + sprintf(peerAddressString, "%s:%i", addrString, port); + + return peerAddressString; +} + int Socket_read(Socket self, uint8_t* buf, int size) { From 2f7174407949b8a45d7295d68bdee9231a28a5a3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 24 Jul 2018 06:31:39 +0200 Subject: [PATCH 085/123] - added serial port hal - moved hal to separate directory - added new hal cmake project --- CMakeLists.txt | 12 +- hal/CMakeLists.txt | 107 +++++++ {src/hal => hal}/ethernet/bsd/ethernet_bsd.c | 0 .../ethernet/linux/ethernet_linux.c | 0 .../ethernet/win32/ethernet_win32.c | 0 .../filesystem/linux/file_provider_linux.c | 0 .../filesystem/win32/file_provider_win32.c | 0 {src/hal => hal}/inc/hal_ethernet.h | 0 {src/hal => hal}/inc/hal_filesystem.h | 0 {src/hal => hal}/inc/hal_socket.h | 0 {src/hal => hal}/inc/hal_thread.h | 0 {src/hal => hal}/inc/hal_time.h | 0 {src/hal => hal}/inc/platform_endian.h | 0 hal/serial/linux/serial_port_linux.c | 277 +++++++++++++++++ hal/serial/win32/serial_port_win32.c | 283 ++++++++++++++++++ {src/hal => hal}/socket/bsd/socket_bsd.c | 0 {src/hal => hal}/socket/linux/socket_linux.c | 0 {src/hal => hal}/socket/win32/socket_win32.c | 0 {src/hal => hal}/thread/bsd/thread_bsd.c | 0 {src/hal => hal}/thread/linux/thread_linux.c | 0 {src/hal => hal}/thread/win32/thread_win32.c | 0 {src/hal => hal}/time/unix/time.c | 0 {src/hal => hal}/time/win32/time.c | 0 src/CMakeLists.txt | 22 +- 24 files changed, 681 insertions(+), 20 deletions(-) create mode 100644 hal/CMakeLists.txt rename {src/hal => hal}/ethernet/bsd/ethernet_bsd.c (100%) rename {src/hal => hal}/ethernet/linux/ethernet_linux.c (100%) rename {src/hal => hal}/ethernet/win32/ethernet_win32.c (100%) rename {src/hal => hal}/filesystem/linux/file_provider_linux.c (100%) rename {src/hal => hal}/filesystem/win32/file_provider_win32.c (100%) rename {src/hal => hal}/inc/hal_ethernet.h (100%) rename {src/hal => hal}/inc/hal_filesystem.h (100%) rename {src/hal => hal}/inc/hal_socket.h (100%) rename {src/hal => hal}/inc/hal_thread.h (100%) rename {src/hal => hal}/inc/hal_time.h (100%) rename {src/hal => hal}/inc/platform_endian.h (100%) create mode 100644 hal/serial/linux/serial_port_linux.c create mode 100644 hal/serial/win32/serial_port_win32.c rename {src/hal => hal}/socket/bsd/socket_bsd.c (100%) rename {src/hal => hal}/socket/linux/socket_linux.c (100%) rename {src/hal => hal}/socket/win32/socket_win32.c (100%) rename {src/hal => hal}/thread/bsd/thread_bsd.c (100%) rename {src/hal => hal}/thread/linux/thread_linux.c (100%) rename {src/hal => hal}/thread/win32/thread_win32.c (100%) rename {src/hal => hal}/time/unix/time.c (100%) rename {src/hal => hal}/time/win32/time.c (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55b76d0d3..07fbcdb75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,11 +92,11 @@ include_directories( ) set(API_HEADERS - src/hal/inc/hal_time.h - src/hal/inc/hal_thread.h - src/hal/inc/hal_filesystem.h - src/hal/inc/hal_ethernet.h - src/hal/inc/platform_endian.h + hal/inc/hal_time.h + hal/inc/hal_thread.h + hal/inc/hal_filesystem.h + hal/inc/hal_ethernet.h + hal/inc/platform_endian.h src/common/inc/libiec61850_common_api.h src/common/inc/libiec61850_platform_includes.h src/common/inc/linked_list.h @@ -161,6 +161,8 @@ configure_file( ${CMAKE_CURRENT_BINARY_DIR}/config/stack_config.h ) +include("${CMAKE_CURRENT_LIST_DIR}/hal/CMakeLists.txt") + if(BUILD_EXAMPLES) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/examples) endif(BUILD_EXAMPLES) diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt new file mode 100644 index 000000000..b28033091 --- /dev/null +++ b/hal/CMakeLists.txt @@ -0,0 +1,107 @@ +cmake_minimum_required(VERSION 2.8) + +# automagically detect if we should cross-compile +if(DEFINED ENV{TOOLCHAIN}) + set(CMAKE_C_COMPILER $ENV{TOOLCHAIN}gcc) + set(CMAKE_CXX_COMPILER $ENV{TOOLCHAIN}g++) + set(CMAKE_AR "$ENV{TOOLCHAIN}ar" CACHE FILEPATH "CW archiver" FORCE) +endif() + +project(hal) + +set(LIBHAL_VERSION_MAJOR "2") +set(LIBHAL_VERSION_MINOR "0") +set(LIBHAL_VERSION_PATCH "0") + +include_directories( + ${CMAKE_CURRENT_LIST_DIR}/inc +) + +set (libhal_linux_SRCS + ${CMAKE_CURRENT_LIST_DIR}/socket/linux/socket_linux.c + ${CMAKE_CURRENT_LIST_DIR}/ethernet/linux/ethernet_linux.c + ${CMAKE_CURRENT_LIST_DIR}/thread/linux/thread_linux.c + ${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c + ${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c + ${CMAKE_CURRENT_LIST_DIR}/serial/linux/serial_port_linux.c +) + +set (libhal_windows_SRCS + ${CMAKE_CURRENT_LIST_DIR}/socket/win32/socket_win32.c + ${CMAKE_CURRENT_LIST_DIR}/ethernet/win32/ethernet_win32.c + ${CMAKE_CURRENT_LIST_DIR}/thread/win32/thread_win32.c + ${CMAKE_CURRENT_LIST_DIR}/filesystem/win32/file_provider_win32.c + ${CMAKE_CURRENT_LIST_DIR}/time/win32/time.c + ${CMAKE_CURRENT_LIST_DIR}/serial/win32/serial_port_win32.c +) + +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_bsd.c + ${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c + ${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c +) + +IF(WIN32) + +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib") +message("Found winpcap -> can compile with GOOSE support") +set(WITH_WPCAP 1) +endif() + + + +set (libhal_SRCS + ${libhal_windows_SRCS} +) + +ELSEIF(UNIX) +IF(APPLE) +set (libhal_SRCS + ${libhal_bsd_SRCS} +) +ELSEIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") +set (libhal_SRCS + ${libhal_bsd_SRCS}} +) +ELSE() +set (libhal_SRCS + ${libhal_linux_SRCS} +) +ENDIF(APPLE) +ENDIF(WIN32) + +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC" ) + +add_library (hal STATIC ${libhal_SRCS}) + +add_library (hal-shared STATIC ${libhal_SRCS}) + +SET_TARGET_PROPERTIES(hal-shared PROPERTIES + COMPILE_FLAGS "-fPIC" +) + +IF(UNIX) + IF (CONFIG_SYSTEM_HAS_CLOCK_GETTIME) + target_link_libraries (hal + -lpthread + -lrt + ) + ELSE () + target_link_libraries (hal + -lpthread + ) + ENDIF (CONFIG_SYSTEM_HAS_CLOCK_GETTIME) +ENDIF(UNIX) +IF(MINGW) + target_link_libraries(hal ws2_32 iphlpapi) +ENDIF(MINGW) + +iF(WITH_WPCAP) +target_link_libraries(hal + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/lib/wpcap.lib + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/lib/packet.lib +) +ENDIF(WITH_WPCAP) diff --git a/src/hal/ethernet/bsd/ethernet_bsd.c b/hal/ethernet/bsd/ethernet_bsd.c similarity index 100% rename from src/hal/ethernet/bsd/ethernet_bsd.c rename to hal/ethernet/bsd/ethernet_bsd.c diff --git a/src/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c similarity index 100% rename from src/hal/ethernet/linux/ethernet_linux.c rename to hal/ethernet/linux/ethernet_linux.c diff --git a/src/hal/ethernet/win32/ethernet_win32.c b/hal/ethernet/win32/ethernet_win32.c similarity index 100% rename from src/hal/ethernet/win32/ethernet_win32.c rename to hal/ethernet/win32/ethernet_win32.c diff --git a/src/hal/filesystem/linux/file_provider_linux.c b/hal/filesystem/linux/file_provider_linux.c similarity index 100% rename from src/hal/filesystem/linux/file_provider_linux.c rename to hal/filesystem/linux/file_provider_linux.c diff --git a/src/hal/filesystem/win32/file_provider_win32.c b/hal/filesystem/win32/file_provider_win32.c similarity index 100% rename from src/hal/filesystem/win32/file_provider_win32.c rename to hal/filesystem/win32/file_provider_win32.c diff --git a/src/hal/inc/hal_ethernet.h b/hal/inc/hal_ethernet.h similarity index 100% rename from src/hal/inc/hal_ethernet.h rename to hal/inc/hal_ethernet.h diff --git a/src/hal/inc/hal_filesystem.h b/hal/inc/hal_filesystem.h similarity index 100% rename from src/hal/inc/hal_filesystem.h rename to hal/inc/hal_filesystem.h diff --git a/src/hal/inc/hal_socket.h b/hal/inc/hal_socket.h similarity index 100% rename from src/hal/inc/hal_socket.h rename to hal/inc/hal_socket.h diff --git a/src/hal/inc/hal_thread.h b/hal/inc/hal_thread.h similarity index 100% rename from src/hal/inc/hal_thread.h rename to hal/inc/hal_thread.h diff --git a/src/hal/inc/hal_time.h b/hal/inc/hal_time.h similarity index 100% rename from src/hal/inc/hal_time.h rename to hal/inc/hal_time.h diff --git a/src/hal/inc/platform_endian.h b/hal/inc/platform_endian.h similarity index 100% rename from src/hal/inc/platform_endian.h rename to hal/inc/platform_endian.h diff --git a/hal/serial/linux/serial_port_linux.c b/hal/serial/linux/serial_port_linux.c new file mode 100644 index 000000000..e6308c054 --- /dev/null +++ b/hal/serial/linux/serial_port_linux.c @@ -0,0 +1,277 @@ +/* + * serial_port_linux.c + * + * Copyright 2017 MZ Automation GmbH + * + * This file is part of lib60870-C + * + * lib60870-C is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lib60870-C is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lib60870-C. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "lib_memory.h" + +#include +#include +#include +#include +#include +#include + +#include "hal_serial.h" +#include "hal_time.h" + +struct sSerialPort { + char interfaceName[100]; + int fd; + int baudRate; + uint8_t dataBits; + char parity; + uint8_t stopBits; + uint64_t lastSentTime; + struct timeval timeout; + SerialPortError lastError; +}; + + +SerialPort +SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits) +{ + SerialPort self = (SerialPort) GLOBAL_MALLOC(sizeof(struct sSerialPort)); + + if (self != NULL) { + self->fd = -1; + self->baudRate = baudRate; + self->dataBits = dataBits; + self->stopBits = stopBits; + self->parity = parity; + self->lastSentTime = 0; + self->timeout.tv_sec = 0; + self->timeout.tv_usec = 100000; /* 100 ms */ + strncpy(self->interfaceName, interfaceName, 100); + self->lastError = SERIAL_PORT_ERROR_NONE; + } + + return self; +} + +void +SerialPort_destroy(SerialPort self) +{ + if (self != NULL) { + GLOBAL_FREEMEM(self); + } +} + +bool +SerialPort_open(SerialPort self) +{ + self->fd = open(self->interfaceName, O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL); + + if (self->fd == -1) { + self->lastError = SERIAL_PORT_ERROR_OPEN_FAILED; + return false; + } + + struct termios tios; + speed_t baudrate; + + tcgetattr(self->fd, &tios); + + switch (self->baudRate) { + case 110: + baudrate = B110; + break; + case 300: + baudrate = B300; + break; + case 600: + baudrate = B600; + break; + case 1200: + baudrate = B1200; + break; + case 2400: + baudrate = B2400; + break; + case 4800: + baudrate = B4800; + break; + case 9600: + baudrate = B9600; + break; + case 19200: + baudrate = B19200; + break; + case 38400: + baudrate = B38400; + break; + case 57600: + baudrate = B57600; + break; + case 115200: + baudrate = B115200; + break; + default: + baudrate = B9600; + self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE; + } + + /* Set baud rate */ + if ((cfsetispeed(&tios, baudrate) < 0) || (cfsetospeed(&tios, baudrate) < 0)) { + close(self->fd); + self->fd = -1; + self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE; + return false; + } + + tios.c_cflag |= (CREAD | CLOCAL); + + /* Set data bits (5/6/7/8) */ + tios.c_cflag &= ~CSIZE; + switch (self->dataBits) { + case 5: + tios.c_cflag |= CS5; + break; + case 6: + tios.c_cflag |= CS6; + break; + case 7: + tios.c_cflag |= CS7; + break; + case 8: + default: + tios.c_cflag |= CS8; + break; + } + + /* Set stop bits (1/2) */ + if (self->stopBits == 1) + tios.c_cflag &=~ CSTOPB; + else /* 2 */ + tios.c_cflag |= CSTOPB; + + if (self->parity == 'N') { + tios.c_cflag &=~ PARENB; + } else if (self->parity == 'E') { + tios.c_cflag |= PARENB; + tios.c_cflag &=~ PARODD; + } else { /* 'O' */ + tios.c_cflag |= PARENB; + tios.c_cflag |= PARODD; + } + + tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + if (self->parity == 'N') { + tios.c_iflag &= ~INPCK; + } else { + tios.c_iflag |= INPCK; + } + + tios.c_iflag &= ~(IXON | IXOFF | IXANY); + tios.c_iflag |= IGNBRK; /* Set ignore break to allow 0xff characters */ + tios.c_iflag |= IGNPAR; + tios.c_oflag &=~ OPOST; + + tios.c_cc[VMIN] = 0; + tios.c_cc[VTIME] = 0; + + if (tcsetattr(self->fd, TCSANOW, &tios) < 0) { + close(self->fd); + self->fd = -1; + self->lastError = SERIAL_PORT_ERROR_INVALID_ARGUMENT; + + return false; + } + + return true; +} + +void +SerialPort_close(SerialPort self) +{ + if (self->fd != -1) { + close(self->fd); + self->fd = 0; + } +} + +int +SerialPort_getBaudRate(SerialPort self) +{ + return self->baudRate; +} + +void +SerialPort_discardInBuffer(SerialPort self) +{ + tcflush(self->fd, TCIOFLUSH); +} + +void +SerialPort_setTimeout(SerialPort self, int timeout) +{ + self->timeout.tv_sec = timeout / 1000; + self->timeout.tv_usec = (timeout % 1000) * 1000; +} + +SerialPortError +SerialPort_getLastError(SerialPort self) +{ + return self->lastError; +} + +int +SerialPort_readByte(SerialPort self) +{ + uint8_t buf[1]; + fd_set set; + + self->lastError = SERIAL_PORT_ERROR_NONE; + + FD_ZERO(&set); + FD_SET(self->fd, &set); + + int ret = select(self->fd + 1, &set, NULL, NULL, &(self->timeout)); + + if (ret == -1) { + self->lastError = SERIAL_PORT_ERROR_UNKNOWN; + return -1; + } + else if (ret == 0) + return -1; + else { + read(self->fd, (char*) buf, 1); + + return (int) buf[0]; + } +} + +int +SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int bufSize) +{ + //TODO assure minimum line idle time + + self->lastError = SERIAL_PORT_ERROR_NONE; + + ssize_t result = write(self->fd, buffer + startPos, bufSize); + + tcdrain(self->fd); + + self->lastSentTime = Hal_getTimeInMs(); + + return result; +} diff --git a/hal/serial/win32/serial_port_win32.c b/hal/serial/win32/serial_port_win32.c new file mode 100644 index 000000000..19eb30bf9 --- /dev/null +++ b/hal/serial/win32/serial_port_win32.c @@ -0,0 +1,283 @@ +/* +* serial_port_win32.c +* +* Copyright 2017 MZ Automation GmbH +* +* This file is part of lib60870-C +* +* lib60870-C is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* lib60870-C is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with lib60870-C. If not, see . +* +* See COPYING file for the complete license text. +*/ + +#include +#include + +#include + +#include "lib_memory.h" + +#include "hal_serial.h" +#include "hal_time.h" + +struct sSerialPort { + char interfaceName[100]; + HANDLE comPort; + int baudRate; + uint8_t dataBits; + char parity; + uint8_t stopBits; + uint64_t lastSentTime; + int timeout; + SerialPortError lastError; +}; + +SerialPort +SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits) +{ + SerialPort self = (SerialPort)GLOBAL_MALLOC(sizeof(struct sSerialPort)); + + if (self != NULL) { + self->comPort = NULL; + self->baudRate = baudRate; + self->dataBits = dataBits; + self->stopBits = stopBits; + self->parity = parity; + self->lastSentTime = 0; + self->timeout = 100; /* 100 ms */ + strncpy(self->interfaceName, interfaceName, 100); + self->lastError = SERIAL_PORT_ERROR_NONE; + } + + return self; +} + +void +SerialPort_destroy(SerialPort self) +{ + if (self != NULL) { + + if (self->comPort != INVALID_HANDLE_VALUE) + SerialPort_close(self); + + GLOBAL_FREEMEM(self); + } +} + +bool +SerialPort_open(SerialPort self) +{ + self->comPort = CreateFile(self->interfaceName, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, NULL); + + + if (self->comPort == INVALID_HANDLE_VALUE) { + self->lastError = SERIAL_PORT_ERROR_OPEN_FAILED; + return false; + } + + DCB _serialParams = { 0 }; + _serialParams.DCBlength = sizeof(DCB); + + LPDCB serialParams = &_serialParams; + + BOOL status = GetCommState(self->comPort, serialParams); + + if (status == false) { + self->lastError = SERIAL_PORT_ERROR_UNKNOWN; + goto exit_error; + } + + /* set baud rate */ + switch (self->baudRate) { + case 110: + serialParams->BaudRate = CBR_110; + break; + case 300: + serialParams->BaudRate = CBR_300; + break; + case 600: + serialParams->BaudRate = CBR_600; + break; + case 1200: + serialParams->BaudRate = CBR_1200; + break; + case 2400: + serialParams->BaudRate = CBR_2400; + break; + case 4800: + serialParams->BaudRate = CBR_4800; + break; + case 9600: + serialParams->BaudRate = CBR_9600; + break; + case 19200: + serialParams->BaudRate = CBR_19200; + break; + case 38400: + serialParams->BaudRate = CBR_38400; + break; + case 57600: + serialParams->BaudRate = CBR_57600; + break; + case 115200: + serialParams->BaudRate = CBR_115200; + break; + default: + serialParams->BaudRate = CBR_9600; + self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE; + } + + /* Set data bits (5/6/7/8) */ + serialParams->ByteSize = self->dataBits; + + /* Set stop bits (1/2) */ + if (self->stopBits == 1) + serialParams->StopBits = ONESTOPBIT; + else /* 2 */ + serialParams->StopBits = TWOSTOPBITS; + + if (self->parity == 'N') + serialParams->Parity = NOPARITY; + else if (self->parity == 'E') + serialParams->Parity = EVENPARITY; + else /* 'O' */ + serialParams->Parity = ODDPARITY; + + status = SetCommState(self->comPort, serialParams); + + if (status == false) { + self->lastError = SERIAL_PORT_ERROR_INVALID_ARGUMENT; + goto exit_error; + } + + COMMTIMEOUTS timeouts = { 0 }; + + timeouts.ReadIntervalTimeout = 100; + timeouts.ReadTotalTimeoutConstant = 50; + timeouts.ReadTotalTimeoutMultiplier = 10; + timeouts.WriteTotalTimeoutConstant = 100; + timeouts.WriteTotalTimeoutMultiplier = 10; + + status = SetCommTimeouts(self->comPort, &timeouts); + + if (status == false) { + self->lastError = SERIAL_PORT_ERROR_UNKNOWN; + goto exit_error; + } + + status = SetCommMask(self->comPort, EV_RXCHAR); + + if (status == false) { + printf("SetCommMask failed!\n"); + self->lastError = SERIAL_PORT_ERROR_UNKNOWN; + goto exit_error; + } + + self->lastError = SERIAL_PORT_ERROR_NONE; + + return true; + +exit_error: + + if (self->comPort != INVALID_HANDLE_VALUE) { + CloseHandle(self->comPort); + self->comPort = INVALID_HANDLE_VALUE; + } + + return false; +} + + +void +SerialPort_close(SerialPort self) +{ + if (self->comPort != INVALID_HANDLE_VALUE) { + CloseHandle(self->comPort); + self->comPort = INVALID_HANDLE_VALUE; + } +} + +int +SerialPort_getBaudRate(SerialPort self) +{ + return self->baudRate; +} + +void +SerialPort_discardInBuffer(SerialPort self) +{ + PurgeComm(self->comPort, PURGE_RXCLEAR | PURGE_TXCLEAR); +} + +void +SerialPort_setTimeout(SerialPort self, int timeout) +{ + self->timeout = timeout; +} + +SerialPortError +SerialPort_getLastError(SerialPort self) +{ + return self->lastError; +} + +int +SerialPort_readByte(SerialPort self) +{ + uint8_t buf[1]; + + DWORD bytesRead = 0; + + BOOL status = ReadFile(self->comPort, buf, 1, &bytesRead, NULL); + + if (status == false) { + self->lastError = SERIAL_PORT_ERROR_UNKNOWN; + return -1; + } + + self->lastError = SERIAL_PORT_ERROR_NONE; + + if (bytesRead == 0) + return -1; + else + return (int) buf[0]; +} + +int +SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int bufSize) +{ + //TODO assure minimum line idle time + + self->lastError = SERIAL_PORT_ERROR_NONE; + + DWORD numberOfBytesWritten; + + BOOL status = WriteFile(self->comPort, buffer + startPos, bufSize, &numberOfBytesWritten, NULL); + + if (status == false) { + self->lastError = SERIAL_PORT_ERROR_UNKNOWN; + return -1; + } + + status = FlushFileBuffers(self->comPort); + + if (status == false) { + printf("FlushFileBuffers failed!\n"); + } + + self->lastSentTime = Hal_getTimeInMs(); + + return (int) numberOfBytesWritten; +} diff --git a/src/hal/socket/bsd/socket_bsd.c b/hal/socket/bsd/socket_bsd.c similarity index 100% rename from src/hal/socket/bsd/socket_bsd.c rename to hal/socket/bsd/socket_bsd.c diff --git a/src/hal/socket/linux/socket_linux.c b/hal/socket/linux/socket_linux.c similarity index 100% rename from src/hal/socket/linux/socket_linux.c rename to hal/socket/linux/socket_linux.c diff --git a/src/hal/socket/win32/socket_win32.c b/hal/socket/win32/socket_win32.c similarity index 100% rename from src/hal/socket/win32/socket_win32.c rename to hal/socket/win32/socket_win32.c diff --git a/src/hal/thread/bsd/thread_bsd.c b/hal/thread/bsd/thread_bsd.c similarity index 100% rename from src/hal/thread/bsd/thread_bsd.c rename to hal/thread/bsd/thread_bsd.c diff --git a/src/hal/thread/linux/thread_linux.c b/hal/thread/linux/thread_linux.c similarity index 100% rename from src/hal/thread/linux/thread_linux.c rename to hal/thread/linux/thread_linux.c diff --git a/src/hal/thread/win32/thread_win32.c b/hal/thread/win32/thread_win32.c similarity index 100% rename from src/hal/thread/win32/thread_win32.c rename to hal/thread/win32/thread_win32.c diff --git a/src/hal/time/unix/time.c b/hal/time/unix/time.c similarity index 100% rename from src/hal/time/unix/time.c rename to hal/time/unix/time.c diff --git a/src/hal/time/win32/time.c b/hal/time/win32/time.c similarity index 100% rename from src/hal/time/win32/time.c rename to hal/time/win32/time.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b5424e18..f8cad77b1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -195,27 +195,12 @@ set (lib_sv_SRCS ) set (lib_linux_SRCS -./hal/socket/linux/socket_linux.c -./hal/ethernet/linux/ethernet_linux.c -./hal/thread/linux/thread_linux.c -./hal/filesystem/linux/file_provider_linux.c -./hal/time/unix/time.c ) set (lib_windows_SRCS -./hal/socket/win32/socket_win32.c -./hal/ethernet/win32/ethernet_win32.c -./hal/thread/win32/thread_win32.c -./hal/filesystem/win32/file_provider_win32.c -./hal/time/win32/time.c ) set (lib_bsd_SRCS -./hal/socket/bsd/socket_bsd.c -./hal/ethernet/bsd/ethernet_bsd.c -./hal/thread/bsd/thread_bsd.c -./hal/filesystem/linux/file_provider_linux.c -./hal/time/unix/time.c ) IF(WIN32) @@ -324,6 +309,9 @@ set_target_properties(iec61850-shared PROPERTIES SOVERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}" ) +target_link_libraries(iec61850-shared + hal-shared +) GENERATE_EXPORT_HEADER(iec61850-shared BASE_NAME iec61850-shared @@ -334,6 +322,10 @@ GENERATE_EXPORT_HEADER(iec61850-shared add_library (iec61850 STATIC ${library_SRCS}) +target_link_libraries(iec61850 + hal +) + IF(UNIX) IF (CONFIG_SYSTEM_HAS_CLOCK_GETTIME) target_link_libraries (iec61850 From 210a81d14e8f5f14517adc4663ea4d3855ef1625 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 24 Jul 2018 06:50:23 +0200 Subject: [PATCH 086/123] - updated Makefiles for separate HAL --- Makefile | 40 +++++++++++++++++++++------------------- README.md | 2 +- make/stack_includes.mk | 2 +- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index f4ccb697d..58562b28f 100644 --- a/Makefile +++ b/Makefile @@ -31,30 +31,32 @@ LIB_SOURCE_DIRS += src/iec61850/server/model LIB_SOURCE_DIRS += src/iec61850/server/mms_mapping LIB_SOURCE_DIRS += src/iec61850/server/impl ifeq ($(HAL_IMPL), WIN32) -LIB_SOURCE_DIRS += src/hal/socket/win32 -LIB_SOURCE_DIRS += src/hal/thread/win32 -LIB_SOURCE_DIRS += src/hal/ethernet/win32 -LIB_SOURCE_DIRS += src/hal/filesystem/win32 -LIB_SOURCE_DIRS += src/hal/time/win32 +LIB_SOURCE_DIRS += hal/socket/win32 +LIB_SOURCE_DIRS += hal/thread/win32 +LIB_SOURCE_DIRS += hal/ethernet/win32 +LIB_SOURCE_DIRS += hal/filesystem/win32 +LIB_SOURCE_DIRS += hal/time/win32 +LIB_SOURCE_DIRS += hal/serial/win32 else ifeq ($(HAL_IMPL), POSIX) -LIB_SOURCE_DIRS += src/hal/socket/linux -LIB_SOURCE_DIRS += src/hal/thread/linux -LIB_SOURCE_DIRS += src/hal/ethernet/linux -LIB_SOURCE_DIRS += src/hal/filesystem/linux -LIB_SOURCE_DIRS += src/hal/time/unix +LIB_SOURCE_DIRS += hal/socket/linux +LIB_SOURCE_DIRS += hal/thread/linux +LIB_SOURCE_DIRS += hal/ethernet/linux +LIB_SOURCE_DIRS += hal/filesystem/linux +LIB_SOURCE_DIRS += hal/time/unix +LIB_SOURCE_DIRS += hal/serial/linux else ifeq ($(HAL_IMPL), BSD) -LIB_SOURCE_DIRS += src/hal/socket/bsd -LIB_SOURCE_DIRS += src/hal/thread/bsd -LIB_SOURCE_DIRS += src/hal/ethernet/bsd -LIB_SOURCE_DIRS += src/hal/filesystem/linux -LIB_SOURCE_DIRS += src/hal/time/unix +LIB_SOURCE_DIRS += hal/socket/bsd +LIB_SOURCE_DIRS += hal/thread/bsd +LIB_SOURCE_DIRS += hal/ethernet/bsd +LIB_SOURCE_DIRS += hal/filesystem/linux +LIB_SOURCE_DIRS += hal/time/unix endif LIB_INCLUDE_DIRS += config +LIB_INCLUDE_DIRS += hal/inc LIB_INCLUDE_DIRS += src/common/inc LIB_INCLUDE_DIRS += src/mms/iso_mms/asn1c LIB_INCLUDE_DIRS += src/mms/inc LIB_INCLUDE_DIRS += src/mms/inc_private -LIB_INCLUDE_DIRS += src/hal/inc LIB_INCLUDE_DIRS += src/goose LIB_INCLUDE_DIRS += src/sampled_values LIB_INCLUDE_DIRS += src/iec61850/inc @@ -80,9 +82,9 @@ ifndef INSTALL_PREFIX INSTALL_PREFIX = ./.install endif -LIB_API_HEADER_FILES = src/hal/inc/hal_time.h -LIB_API_HEADER_FILES += src/hal/inc/hal_thread.h -LIB_API_HEADER_FILES += src/hal/inc/hal_filesystem.h +LIB_API_HEADER_FILES = hal/inc/hal_time.h +LIB_API_HEADER_FILES += hal/inc/hal_thread.h +LIB_API_HEADER_FILES += hal/inc/hal_filesystem.h LIB_API_HEADER_FILES += src/common/inc/libiec61850_common_api.h LIB_API_HEADER_FILES += src/common/inc/linked_list.h LIB_API_HEADER_FILES += src/common/inc/byte_buffer.h diff --git a/README.md b/README.md index 4fa3d56af..d03dfbd23 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ In the main libiec61850 folder run ``` make WITH_MBEDTLS=1 -``` +``` ## Installing the library and the API headers diff --git a/make/stack_includes.mk b/make/stack_includes.mk index 8812255b5..74031182b 100644 --- a/make/stack_includes.mk +++ b/make/stack_includes.mk @@ -1,11 +1,11 @@ INCLUDES = -I$(LIBIEC_HOME)/config +INCLUDES += -I$(LIBIEC_HOME)/hal/inc INCLUDES += -I$(LIBIEC_HOME)/src/common/inc INCLUDES += -I$(LIBIEC_HOME)/src/mms/inc INCLUDES += -I$(LIBIEC_HOME)/src/mms/inc_private INCLUDES += -I$(LIBIEC_HOME)/src/mms/asn1 INCLUDES += -I$(LIBIEC_HOME)/src/iec61850/inc INCLUDES += -I$(LIBIEC_HOME)/src/iec61850/inc_private -INCLUDES += -I$(LIBIEC_HOME)/src/hal/inc INCLUDES += -I$(LIBIEC_HOME)/src/goose INCLUDES += -I$(LIBIEC_HOME)/src/sampled_values INCLUDES += -I$(LIBIEC_HOME)/src/logging From 7d890b7450ffa472403c2acda8629754b3a52b55 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 24 Jul 2018 07:13:29 +0200 Subject: [PATCH 087/123] - updated version to 1.3.0 --- CHANGELOG | 11 +++++++++++ CMakeLists.txt | 4 ++-- src/common/inc/libiec61850_platform_includes.h | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 24eae2960..e64ef4855 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +Changes to version 1.3.0 +------------------------ +- HAL: unified platform abstraction layer (to simply use together with lib60870) +- IEC 61850 server: fixed bug when calling write access handler (wrong pointer for ClientConnection object) +- updated IEC 61850-9-2 LE example to be more realistic +- added server side example for the substitution service +- MMS server: fixed wrong preprocessor defines that can cause problems in some configurations (unlimited number of client connections/ multi-threaded server) +- IEC 61850 client: added new function ControlObjectClient_getCtlValType to simplify control handling +- fixed bug in MmsValue_update + + Changes to version 1.2.2 ------------------------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 07fbcdb75..f9f395eab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,8 @@ project(libiec61850) ENABLE_TESTING() set(LIB_VERSION_MAJOR "1") -set(LIB_VERSION_MINOR "2") -set(LIB_VERSION_PATCH "2") +set(LIB_VERSION_MINOR "3") +set(LIB_VERSION_PATCH "0") set(LIB_VERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/") diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index d25ac59cb..41e12b397 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -15,7 +15,7 @@ #include "platform_endian.h" -#define LIBIEC61850_VERSION "1.2.2" +#define LIBIEC61850_VERSION "1.3.0" #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" From de04f3630b319f3492f6cdaab14644d6f173f76f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 25 Jul 2018 18:02:17 +0200 Subject: [PATCH 088/123] - moved lib_memory to hal project --- CMakeLists.txt | 25 +--- hal/CMakeLists.txt | 3 + hal/ethernet/bsd/ethernet_bsd.c | 2 +- hal/ethernet/linux/ethernet_linux.c | 3 +- hal/ethernet/win32/ethernet_win32.c | 4 +- hal/filesystem/linux/file_provider_linux.c | 10 +- hal/filesystem/win32/file_provider_win32.c | 3 +- hal/inc/hal_serial.h | 155 +++++++++++++++++++++ {src/common => hal}/inc/lib_memory.h | 2 +- {src/common => hal/memory}/lib_memory.c | 5 +- hal/socket/linux/socket_linux.c | 6 +- hal/socket/win32/socket_win32.c | 3 +- hal/thread/bsd/thread_bsd.c | 2 +- hal/thread/linux/thread_linux.c | 2 +- hal/thread/win32/thread_win32.c | 2 +- src/CMakeLists.txt | 1 - 16 files changed, 190 insertions(+), 38 deletions(-) create mode 100644 hal/inc/hal_serial.h rename {src/common => hal}/inc/lib_memory.h (99%) rename {src/common => hal/memory}/lib_memory.c (95%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9f395eab..5afc22fb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ option(CONFIG_IEC61850_REPORT_SERVICE "Build with support for IEC 61850 reportin option(CONFIG_IEC61850_LOG_SERVICE "Build with support for IEC 61850 logging services" ON) option(CONFIG_IEC61850_SETTING_GROUPS "Build with support for IEC 61850 setting group services" ON) -set(CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE "8000" CACHE STRING "Default buffer size for buffered reports in byte" ) +set(CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE "65536" CACHE STRING "Default buffer size for buffered reports in byte" ) # advanced options option(DEBUG "Enable debugging mode (include assertions)" OFF) @@ -60,22 +60,6 @@ option(DEBUG_SV_SUBSCRIBER "Enable Sampled Values subscriber debugging" ${DEBUG} option(DEBUG_SV_PUBLISHER "Enable Sampled Values publisher debugging" ${DEBUG}) option(DEBUG_HAL_ETHERNET "Enable Ethernet HAL printf debugging" ${DEBUG}) -#mark_as_advanced( -# DEBUG_SOCKET -# DEBUG_COTP -# DEBUG_ISO_SERVER -# DEBUG_ISO_CLIENT -# DEBUG_IED_SERVER -# DEBUG_IED_CLIENT -# DEBUG_MMS_SERVER -# DEBUG_MMS_CLIENT -# DEBUG_GOOSE_SUBSCRIBER -# DEBUG_GOOSE_PUBLISHER -# DEBUG_SV_SUBSCRIBER -# DEBUG_SV_PUBLISHER -# DEBUG_HAL_ETHERNET -#) - include_directories( ${CMAKE_CURRENT_BINARY_DIR}/config ${CMAKE_CURRENT_LIST_DIR}/src/common/inc @@ -161,7 +145,12 @@ configure_file( ${CMAKE_CURRENT_BINARY_DIR}/config/stack_config.h ) -include("${CMAKE_CURRENT_LIST_DIR}/hal/CMakeLists.txt") +#include("${CMAKE_CURRENT_LIST_DIR}/hal/CMakeLists.txt") +include_directories( + ${CMAKE_CURRENT_LIST_DIR}/hal/inc +) + +add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/hal") if(BUILD_EXAMPLES) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/examples) diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt index b28033091..850fe052d 100644 --- a/hal/CMakeLists.txt +++ b/hal/CMakeLists.txt @@ -24,6 +24,7 @@ set (libhal_linux_SRCS ${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c ${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c ${CMAKE_CURRENT_LIST_DIR}/serial/linux/serial_port_linux.c + ${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c ) set (libhal_windows_SRCS @@ -33,6 +34,7 @@ set (libhal_windows_SRCS ${CMAKE_CURRENT_LIST_DIR}/filesystem/win32/file_provider_win32.c ${CMAKE_CURRENT_LIST_DIR}/time/win32/time.c ${CMAKE_CURRENT_LIST_DIR}/serial/win32/serial_port_win32.c + ${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c ) set (libhal_bsd_SRCS @@ -41,6 +43,7 @@ set (libhal_bsd_SRCS ${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 ) IF(WIN32) diff --git a/hal/ethernet/bsd/ethernet_bsd.c b/hal/ethernet/bsd/ethernet_bsd.c index f35d8b5d4..f0eb48dab 100644 --- a/hal/ethernet/bsd/ethernet_bsd.c +++ b/hal/ethernet/bsd/ethernet_bsd.c @@ -35,7 +35,7 @@ #include #include -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" #include "hal_ethernet.h" struct sEthernetSocket { diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index 538a5e0ee..36dde5cef 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -29,10 +29,11 @@ #include #include #include +#include #include -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" #include "hal_ethernet.h" struct sEthernetSocket { diff --git a/hal/ethernet/win32/ethernet_win32.c b/hal/ethernet/win32/ethernet_win32.c index 8dca819ce..a005c7c7b 100644 --- a/hal/ethernet/win32/ethernet_win32.c +++ b/hal/ethernet/win32/ethernet_win32.c @@ -24,10 +24,10 @@ #include "stack_config.h" #include +#include #include "hal_ethernet.h" - -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" #ifndef DEBUG_HAL_ETHERNET #define DEBUG_HAL_ETHERNET 1 diff --git a/hal/filesystem/linux/file_provider_linux.c b/hal/filesystem/linux/file_provider_linux.c index 235c59694..23805da13 100644 --- a/hal/filesystem/linux/file_provider_linux.c +++ b/hal/filesystem/linux/file_provider_linux.c @@ -1,7 +1,7 @@ /* * file_provider_linux.c * - * Copyright 2014 Michael Zillgith + * Copyright 2014-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -23,16 +23,16 @@ #include #include - +#include +#include +#include #include #include #include -#include "libiec61850_platform_includes.h" - #include "hal_filesystem.h" - #include "stack_config.h" +#include "lib_memory.h" struct sDirectoryHandle { DIR* handle; diff --git a/hal/filesystem/win32/file_provider_win32.c b/hal/filesystem/win32/file_provider_win32.c index 2f531e1d8..ba7958726 100644 --- a/hal/filesystem/win32/file_provider_win32.c +++ b/hal/filesystem/win32/file_provider_win32.c @@ -30,8 +30,9 @@ #include "hal_filesystem.h" -#include "libiec61850_platform_includes.h" +#include "platform_endian.h" #include "stack_config.h" +#include "lib_memory.h" #include diff --git a/hal/inc/hal_serial.h b/hal/inc/hal_serial.h new file mode 100644 index 000000000..b28804d5f --- /dev/null +++ b/hal/inc/hal_serial.h @@ -0,0 +1,155 @@ +/* + * hal_serial.h + * + * Copyright 2017 MZ Automation GmbH + * + * This file is part of lib60870-C + * + * lib60870-C is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * lib60870-C is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with lib60870-C. If not, see . + * + * See COPYING file for the complete license text. + */ + +#ifndef SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_ +#define SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file hal_serial.h + * \brief Abstraction layer for serial ports. + * Has to be implemented for the serial link layer of CS 101. + */ + +/*! \addtogroup hal Platform (Hardware/OS) abstraction layer + * + * @{ + */ + +/** + * @defgroup HAL_SERIAL Access to serial interfaces + * + * Serial interface abstraction layer. This functions have to be implemented to + * port lib60870 to new platforms when the serial link layers are required. + * + * @{ + */ + +typedef struct sSerialPort* SerialPort; + +typedef enum { + SERIAL_PORT_ERROR_NONE = 0, + SERIAL_PORT_ERROR_INVALID_ARGUMENT = 1, + SERIAL_PORT_ERROR_INVALID_BAUDRATE = 2, + SERIAL_PORT_ERROR_OPEN_FAILED = 3, + SERIAL_PORT_ERROR_UNKNOWN = 99 +} SerialPortError; + +/** + * \brief Create a new SerialPort instance + * + * \param interfaceName identifier or name of the serial interface (e.g. "/dev/ttyS1" or "COM4") + * \param baudRate the baud rate in baud (e.g. 9600) + * \param dataBits the number of data bits (usually 8) + * \param parity defines what kind of parity to use ('E' - even parity, 'O' - odd parity, 'N' - no parity) + * \param stopBits the number of stop buts (usually 1) + * + * \return the new SerialPort instance + */ +SerialPort +SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits); + +/** + * \brief Destroy the SerialPort instance and release all resources + */ +void +SerialPort_destroy(SerialPort self); + +/** + * \brief Open the serial interface + * + * \return true in case of success, false otherwise (use \ref SerialPort_getLastError for a detailed error code) + */ +bool +SerialPort_open(SerialPort self); + +/** + * \brief Close (release) the serial interface + */ +void +SerialPort_close(SerialPort self); + +/** + * \brief Get the baudrate used by the serial interface + * + * \return the baud rate in baud + */ +int +SerialPort_getBaudRate(SerialPort self); + +/** + * \brief Set the timeout used for message reception + * + * \param timeout the timeout value in ms. + */ +void +SerialPort_setTimeout(SerialPort self, int timeout); + +/** + * \brief Discard all data in the input buffer of the serial interface + */ +void +SerialPort_discardInBuffer(SerialPort self); + +/** + * \brief Read a byte from the interface + * + * \return number of read bytes of -1 in case of an error + */ +int +SerialPort_readByte(SerialPort self); + +/** + * \brief Write the number of bytes from the buffer to the serial interface + * + * \param buffer the buffer containing the data to write + * \param startPos start position in the buffer of the data to write + * \param numberOfBytes number of bytes to write + * + * \return number of bytes written, or -1 in case of an error + */ +int +SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int numberOfBytes); + +/** + * \brief Get the error code of the last operation + */ +SerialPortError +SerialPort_getLastError(SerialPort self); + +/*! @} */ + +/*! @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /* SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_ */ diff --git a/src/common/inc/lib_memory.h b/hal/inc/lib_memory.h similarity index 99% rename from src/common/inc/lib_memory.h rename to hal/inc/lib_memory.h index 506a24ca0..95314af02 100644 --- a/src/common/inc/lib_memory.h +++ b/hal/inc/lib_memory.h @@ -1,5 +1,5 @@ /* - * memory.h + * lib_memory.h * * Copyright 2014 Michael Zillgith * diff --git a/src/common/lib_memory.c b/hal/memory/lib_memory.c similarity index 95% rename from src/common/lib_memory.c rename to hal/memory/lib_memory.c index 0a4106b35..623ba2127 100644 --- a/src/common/lib_memory.c +++ b/hal/memory/lib_memory.c @@ -1,7 +1,7 @@ /* * lib_memory.c * - * Copyright 2014 Michael Zillgith + * Copyright 2014-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -21,7 +21,8 @@ * See COPYING file for the complete license text. */ -#include "libiec61850_platform_includes.h" +#include +#include "lib_memory.h" static MemoryExceptionHandler exceptionHandler = NULL; static void* exceptionHandlerParameter = NULL; diff --git a/hal/socket/linux/socket_linux.c b/hal/socket/linux/socket_linux.c index e1761d472..2dded7545 100644 --- a/hal/socket/linux/socket_linux.c +++ b/hal/socket/linux/socket_linux.c @@ -1,7 +1,7 @@ /* * socket_linux.c * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -38,7 +39,8 @@ #include "hal_thread.h" -#include "libiec61850_platform_includes.h" +#include "stack_config.h" +#include "lib_memory.h" #ifndef DEBUG_SOCKET #define DEBUG_SOCKET 0 diff --git a/hal/socket/win32/socket_win32.c b/hal/socket/win32/socket_win32.c index b699816e2..065e80b22 100644 --- a/hal/socket/win32/socket_win32.c +++ b/hal/socket/win32/socket_win32.c @@ -25,10 +25,11 @@ #include #include #include +#include #pragma comment (lib, "Ws2_32.lib") -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" #include "hal_socket.h" #include "stack_config.h" diff --git a/hal/thread/bsd/thread_bsd.c b/hal/thread/bsd/thread_bsd.c index df16fc9c5..7d882d226 100644 --- a/hal/thread/bsd/thread_bsd.c +++ b/hal/thread/bsd/thread_bsd.c @@ -26,7 +26,7 @@ #include #include "hal_thread.h" -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" struct sThread { ThreadExecutionFunction function; diff --git a/hal/thread/linux/thread_linux.c b/hal/thread/linux/thread_linux.c index 0fd19e5a0..488b73393 100644 --- a/hal/thread/linux/thread_linux.c +++ b/hal/thread/linux/thread_linux.c @@ -27,7 +27,7 @@ #include #include "hal_thread.h" -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" struct sThread { ThreadExecutionFunction function; diff --git a/hal/thread/win32/thread_win32.c b/hal/thread/win32/thread_win32.c index 0eb39f659..aa72e613c 100644 --- a/hal/thread/win32/thread_win32.c +++ b/hal/thread/win32/thread_win32.c @@ -22,7 +22,7 @@ */ #include -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" #include "hal_thread.h" struct sThread { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8cad77b1..43aeb3b7a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,7 +5,6 @@ set (lib_common_SRCS ./common/map.c ./common/linked_list.c ./common/byte_buffer.c -./common/lib_memory.c ./common/string_utilities.c ./common/buffer_chain.c ./common/conversions.c From e6765585cf82ce7386ae8f6d42f5099d05d0dcf0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 25 Jul 2018 21:19:59 +0200 Subject: [PATCH 089/123] - updated cmake file for hal to compile with Visual Studio --- hal/CMakeLists.txt | 23 ++++++++++++++++++++-- hal/filesystem/win32/file_provider_win32.c | 1 + hal/socket/win32/socket_win32.c | 1 - 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt index 850fe052d..2a3e67907 100644 --- a/hal/CMakeLists.txt +++ b/hal/CMakeLists.txt @@ -13,6 +13,17 @@ set(LIBHAL_VERSION_MAJOR "2") set(LIBHAL_VERSION_MINOR "0") set(LIBHAL_VERSION_PATCH "0") +if(WIN32) + +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib") +message("Found winpcap -> compile ethernet HAL layer (required for GOOSE/SV support)") +set(WITH_WPCAP 1) +else() +message("winpcap not found -> skip ethernet HAL layer (no GOOSE/SV support)") +endif() + +endif(WIN32) + include_directories( ${CMAKE_CURRENT_LIST_DIR}/inc ) @@ -29,7 +40,6 @@ set (libhal_linux_SRCS set (libhal_windows_SRCS ${CMAKE_CURRENT_LIST_DIR}/socket/win32/socket_win32.c - ${CMAKE_CURRENT_LIST_DIR}/ethernet/win32/ethernet_win32.c ${CMAKE_CURRENT_LIST_DIR}/thread/win32/thread_win32.c ${CMAKE_CURRENT_LIST_DIR}/filesystem/win32/file_provider_win32.c ${CMAKE_CURRENT_LIST_DIR}/time/win32/time.c @@ -37,6 +47,12 @@ set (libhal_windows_SRCS ${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c ) +if(WITH_WPCAP) +set (libhal_windows_SRCS ${libhal_windows_SRCS} + ${CMAKE_CURRENT_LIST_DIR}/ethernet/win32/ethernet_win32.c +) +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 @@ -53,7 +69,10 @@ message("Found winpcap -> can compile with GOOSE support") set(WITH_WPCAP 1) endif() - +IF(MSVC) +set_source_files_properties(${libhal_windows_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF() set (libhal_SRCS ${libhal_windows_SRCS} diff --git a/hal/filesystem/win32/file_provider_win32.c b/hal/filesystem/win32/file_provider_win32.c index ba7958726..8b9a8f984 100644 --- a/hal/filesystem/win32/file_provider_win32.c +++ b/hal/filesystem/win32/file_provider_win32.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/hal/socket/win32/socket_win32.c b/hal/socket/win32/socket_win32.c index 065e80b22..9cfc02c6b 100644 --- a/hal/socket/win32/socket_win32.c +++ b/hal/socket/win32/socket_win32.c @@ -326,7 +326,6 @@ bool Socket_connect(Socket self, const char* address, int port) { struct sockaddr_in serverAddress; - int ec; if (wsaStartUp() == false) return false; From af35ee17fa52e21ace1c0dce4a0977914bcff657 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 25 Jul 2018 21:38:11 +0200 Subject: [PATCH 090/123] - removed unused code --- src/CMakeLists.txt | 1 - src/common/array_list.c | 35 ------------------- src/common/inc/array_list.h | 31 ---------------- src/iec61850/server/mms_mapping/logging.c | 1 - src/iec61850/server/mms_mapping/mms_goose.c | 1 - src/iec61850/server/mms_mapping/mms_mapping.c | 1 - src/iec61850/server/mms_mapping/mms_sv.c | 1 - src/iec61850/server/mms_mapping/reporting.c | 1 - 8 files changed, 72 deletions(-) delete mode 100644 src/common/array_list.c delete mode 100644 src/common/inc/array_list.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 43aeb3b7a..b362e4b00 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ set (lib_common_SRCS ./common/string_map.c -./common/array_list.c ./common/map.c ./common/linked_list.c ./common/byte_buffer.c diff --git a/src/common/array_list.c b/src/common/array_list.c deleted file mode 100644 index ba9eb963e..000000000 --- a/src/common/array_list.c +++ /dev/null @@ -1,35 +0,0 @@ -/* - * array_list.c - * - * Copyright 2013 Michael Zillgith - * - * This file is part of libIEC61850. - * - * libIEC61850 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * libIEC61850 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with libIEC61850. If not, see . - * - * See COPYING file for the complete license text. - */ - -#include "libiec61850_platform_includes.h" -#include "array_list.h" - -int -ArrayList_listSize(void** list) -{ - int size = 0; - - while (list[size] != NULL) - size++; - return size; -} diff --git a/src/common/inc/array_list.h b/src/common/inc/array_list.h deleted file mode 100644 index 50245c950..000000000 --- a/src/common/inc/array_list.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * array_list.h - * - * Copyright 2013 Michael Zillgith - * - * This file is part of libIEC61850. - * - * libIEC61850 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * libIEC61850 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with libIEC61850. If not, see . - * - * See COPYING file for the complete license text. - */ - -#ifndef ARRAY_LIST_H_ -#define ARRAY_LIST_H_ - -int -ArrayList_listSize(void** list); - - -#endif /* ARRAY_LIST_H_ */ diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 2a59c43f5..83f88ca21 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -26,7 +26,6 @@ #include "mms_mapping.h" #include "logging.h" #include "linked_list.h" -#include "array_list.h" #include "hal_thread.h" #include "simple_allocator.h" diff --git a/src/iec61850/server/mms_mapping/mms_goose.c b/src/iec61850/server/mms_mapping/mms_goose.c index e186a49d5..b15be8304 100644 --- a/src/iec61850/server/mms_mapping/mms_goose.c +++ b/src/iec61850/server/mms_mapping/mms_goose.c @@ -28,7 +28,6 @@ #include "libiec61850_platform_includes.h" #include "mms_mapping.h" #include "linked_list.h" -#include "array_list.h" #include "hal_thread.h" diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 5931a1c9f..811039bee 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -24,7 +24,6 @@ #include "libiec61850_platform_includes.h" #include "mms_mapping.h" #include "mms_mapping_internal.h" -#include "array_list.h" #include "stack_config.h" #include "mms_goose.h" diff --git a/src/iec61850/server/mms_mapping/mms_sv.c b/src/iec61850/server/mms_mapping/mms_sv.c index 67f387cbd..030874031 100644 --- a/src/iec61850/server/mms_mapping/mms_sv.c +++ b/src/iec61850/server/mms_mapping/mms_sv.c @@ -28,7 +28,6 @@ #include "libiec61850_platform_includes.h" #include "mms_mapping.h" #include "linked_list.h" -#include "array_list.h" #include "mms_sv.h" diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index ce2509c37..bf16795c1 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -24,7 +24,6 @@ #include "libiec61850_platform_includes.h" #include "mms_mapping.h" #include "linked_list.h" -#include "array_list.h" #include "stack_config.h" #include "hal_thread.h" From 143bc977c03156cdc9042e932cfc41d534812d1f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 26 Jul 2018 06:47:54 +0200 Subject: [PATCH 091/123] - IEC 61850 server/MMS server: make file service configurable at runtime with IedServerConfig object (new functions IedServerConfig_enableFileService and IedServerConfig_isFileServiceEnabled) --- config/stack_config.h | 3 + config/stack_config.h.cmake | 3 + src/iec61850/inc/iec61850_server.h | 17 ++ src/iec61850/server/impl/ied_server.c | 5 + src/iec61850/server/impl/ied_server_config.c | 14 ++ src/mms/inc/mms_server.h | 11 ++ src/mms/inc_private/mms_server_internal.h | 4 + .../iso_mms/server/mms_association_service.c | 180 ++++++++++++------ src/mms/iso_mms/server/mms_server.c | 13 ++ .../iso_mms/server/mms_server_connection.c | 70 +++++++ src/vs/libiec61850-wo-goose.def | 2 + src/vs/libiec61850.def | 2 + 12 files changed, 268 insertions(+), 56 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index 5332b048f..3e79f1211 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -232,6 +232,9 @@ */ #define CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME 1 +/* enable to configure MmsServer at runtime */ +#define CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME 1 + /************************************************************************************ * Check configuration for consistency - DO NOT MODIFY THIS PART! ************************************************************************************/ diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index e6766b47c..621c359ff 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -215,6 +215,9 @@ */ #define CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME 1 +/* enable to configure MmsServer at runtime */ +#define CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME 1 + /************************************************************************************ * Check configuration for consistency - DO NOT MODIFY THIS PART! ************************************************************************************/ diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 39589a64f..9ad4507c2 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -53,6 +53,9 @@ struct sIedServerConfig /** Base path (directory where the file service serves files */ char* fileServiceBasepath; + + /** when true (default) enable MMS file service */ + bool enableFileService; }; /** @@ -99,7 +102,21 @@ IedServerConfig_setFileServiceBasePath(IedServerConfig self, const char* basepat const char* IedServerConfig_getFileServiceBasePath(IedServerConfig self); +/** + * \brief Enable/disable the MMS file service support + * + * \param[in] enable set true to enable the file services, otherwise false + */ +void +IedServerConfig_enableFileService(IedServerConfig self, bool enable); +/** + * \brief Is the MMS file service enabled or disabled + * + * \return true if enabled, false otherwise + */ +bool +IedServerConfig_isFileServiceEnabled(IedServerConfig self); /** * An opaque handle for an IED server instance diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 8fa94f6ef..c88c78234 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -421,6 +421,11 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->mmsServer = MmsServer_create(self->mmsDevice, tlsConfiguration); +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if (serverConfiguration) + MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); +#endif + if (serverConfiguration) MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index 2fa5065a2..23552f40e 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -32,6 +32,7 @@ IedServerConfig_create() if (self) { self->reportBufferSize = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->fileServiceBasepath = StringUtils_copyString(CONFIG_VIRTUAL_FILESTORE_BASEPATH); + self->enableFileService = true; } return self; @@ -73,3 +74,16 @@ IedServerConfig_getFileServiceBasePath(IedServerConfig self) { return self->fileServiceBasepath; } + +void +IedServerConfig_enableFileService(IedServerConfig self, bool enable) +{ + self->enableFileService = enable; +} + +bool +IedServerConfig_isFileServiceEnabled(IedServerConfig self) +{ + return self->enableFileService; +} + diff --git a/src/mms/inc/mms_server.h b/src/mms/inc/mms_server.h index 553f33550..f47760b15 100644 --- a/src/mms/inc/mms_server.h +++ b/src/mms/inc/mms_server.h @@ -225,6 +225,17 @@ MmsServer_installFileAccessHandler(MmsServer self, MmsFileAccessHandler handler, void MmsServer_setFilestoreBasepath(MmsServer self, const char* basepath); +/** + * \brief Enable/disable MMS file services at runtime + * + * NOTE: requires CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME = 1 in stack configuration + * + * \param[in] self the MmsServer instance + * \param[in] enable true to enable file services, false to disable + */ +void +MmsServer_enableFileService(MmsServer self, bool enable); + /** * \brief lock the cached server data model * diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index fdc05de7f..bb350fb45 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -164,6 +164,10 @@ struct sMmsServer { char* filestoreBasepath; #endif +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + bool fileServiceEnabled; +#endif /* (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) */ + }; struct sMmsServerConnection { diff --git a/src/mms/iso_mms/server/mms_association_service.c b/src/mms/iso_mms/server/mms_association_service.c index 7a5245c7e..46b7d1911 100644 --- a/src/mms/iso_mms/server/mms_association_service.c +++ b/src/mms/iso_mms/server/mms_association_service.c @@ -1,7 +1,7 @@ /* * mms_association_service.c * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -62,58 +62,6 @@ #define MMS_SERVICE_CONCLUDE 0x10 #define MMS_SERVICE_CANCEL 0x08 -//TODO make dependent on stack configuration! -/* servicesSupported MMS bitstring */ -static uint8_t servicesSupported[] = -{ - 0x00 -#if (MMS_STATUS_SERVICE == 1) - | MMS_SERVICE_STATUS -#endif - | MMS_SERVICE_GET_NAME_LIST -#if (MMS_IDENTIFY_SERVICE == 1) - | MMS_SERVICE_IDENTIFY -#endif - | MMS_SERVICE_READ - | MMS_SERVICE_WRITE - | MMS_SERVICE_GET_VARIABLE_ACCESS_ATTRIBUTES - , - 0x00 - | MMS_SERVICE_DEFINE_NAMED_VARIABLE_LIST - | MMS_SERVICE_DELETE_NAMED_VARIABLE_LIST - | MMS_SERVICE_GET_NAMED_VARIABLE_LIST_ATTRIBUTES - , - 0x00, - 0x00, - 0x00, - 0x00 -#if (MMS_OBTAIN_FILE_SERVICE == 1) - | MMS_SERVICE_OBTAIN_FILE -#endif - , - 0x00, - 0x00, - 0x00 -#if (MMS_JOURNAL_SERVICE == 1) - | MMS_SERVICE_READ_JOURNAL -#endif - , - 0x00 -#if (MMS_FILE_SERVICE == 1) - | MMS_SERVICE_FILE_OPEN - | MMS_SERVICE_FILE_READ - | MMS_SERVICE_FILE_CLOSE - | MMS_SERVICE_FILE_RENAME - | MMS_SERVICE_FILE_DELETE - | MMS_SERVICE_FILE_DIRECTORY -#endif - | MMS_SERVICE_INFORMATION_REPORT - , - 0x00 - | MMS_SERVICE_CONCLUDE - | MMS_SERVICE_CANCEL -}; - /* negotiated parameter CBB */ static uint8_t parameterCBB[] = { @@ -127,7 +75,7 @@ static uint8_t parameterCBB[] = *********************************************************************************************/ static int -encodeInitResponseDetail(uint8_t* buffer, int bufPos, bool encode) +encodeInitResponseDetail(MmsServerConnection self, uint8_t* buffer, int bufPos, bool encode) { int initResponseDetailSize = 14 + 5 + 3; @@ -140,6 +88,126 @@ encodeInitResponseDetail(uint8_t* buffer, int bufPos, bool encode) bufPos = BerEncoder_encodeBitString(0x81, 11, parameterCBB, buffer, bufPos); +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + + uint8_t servicesSupported[] = + { + 0x00 + #if (MMS_STATUS_SERVICE == 1) + | MMS_SERVICE_STATUS + #endif + | MMS_SERVICE_GET_NAME_LIST + #if (MMS_IDENTIFY_SERVICE == 1) + | MMS_SERVICE_IDENTIFY + #endif + | MMS_SERVICE_READ + | MMS_SERVICE_WRITE + | MMS_SERVICE_GET_VARIABLE_ACCESS_ATTRIBUTES + , + 0x00 + #if ((MMS_DATA_SET_SERVICE == 1) && (MMS_DYNAMIC_DATA_SETS == 1)) + | MMS_SERVICE_DEFINE_NAMED_VARIABLE_LIST + | MMS_SERVICE_DELETE_NAMED_VARIABLE_LIST + #endif + #if ((MMS_DATA_SET_SERVICE == 1) && (MMS_GET_DATA_SET_ATTRIBUTES == 1)) + | MMS_SERVICE_GET_NAMED_VARIABLE_LIST_ATTRIBUTES + #endif + , + 0x00, + 0x00, + 0x00, + 0x00 + , + 0x00, + 0x00, + 0x00 + #if (MMS_JOURNAL_SERVICE == 1) + | MMS_SERVICE_READ_JOURNAL + #endif + , + 0x00 + | MMS_SERVICE_INFORMATION_REPORT + , + 0x00 + | MMS_SERVICE_CONCLUDE + | MMS_SERVICE_CANCEL + }; + + + if (self->server->fileServiceEnabled) { + +#if (MMS_OBTAIN_FILE_SERVICE == 1) + servicesSupported[5] |= MMS_SERVICE_OBTAIN_FILE; +#endif + +#if (MMS_FILE_SERVICE == 1) + servicesSupported[9] |= MMS_SERVICE_FILE_OPEN; + servicesSupported[9] |= MMS_SERVICE_FILE_READ; + servicesSupported[9] |= MMS_SERVICE_FILE_CLOSE; + servicesSupported[9] |= MMS_SERVICE_FILE_RENAME; + servicesSupported[9] |= MMS_SERVICE_FILE_DELETE; + servicesSupported[9] |= MMS_SERVICE_FILE_DIRECTORY; +#endif /* (MMS_FILE_SERVICE == 1) */ + + } + + +#else + uint8_t servicesSupported[] = + { + 0x00 + #if (MMS_STATUS_SERVICE == 1) + | MMS_SERVICE_STATUS + #endif + | MMS_SERVICE_GET_NAME_LIST + #if (MMS_IDENTIFY_SERVICE == 1) + | MMS_SERVICE_IDENTIFY + #endif + | MMS_SERVICE_READ + | MMS_SERVICE_WRITE + | MMS_SERVICE_GET_VARIABLE_ACCESS_ATTRIBUTES + , + 0x00 + #if ((MMS_DATA_SET_SERVICE == 1) && (MMS_DYNAMIC_DATA_SETS == 1)) + | MMS_SERVICE_DEFINE_NAMED_VARIABLE_LIST + | MMS_SERVICE_DELETE_NAMED_VARIABLE_LIST + #endif + #if ((MMS_DATA_SET_SERVICE == 1) && (MMS_GET_DATA_SET_ATTRIBUTES == 1)) + | MMS_SERVICE_GET_NAMED_VARIABLE_LIST_ATTRIBUTES + #endif + , + 0x00, + 0x00, + 0x00, + 0x00 + #if (MMS_OBTAIN_FILE_SERVICE == 1) + | MMS_SERVICE_OBTAIN_FILE + #endif + , + 0x00, + 0x00, + 0x00 + #if (MMS_JOURNAL_SERVICE == 1) + | MMS_SERVICE_READ_JOURNAL + #endif + , + 0x00 + #if (MMS_FILE_SERVICE == 1) + | MMS_SERVICE_FILE_OPEN + | MMS_SERVICE_FILE_READ + | MMS_SERVICE_FILE_CLOSE + | MMS_SERVICE_FILE_RENAME + | MMS_SERVICE_FILE_DELETE + | MMS_SERVICE_FILE_DIRECTORY + #endif + | MMS_SERVICE_INFORMATION_REPORT + , + 0x00 + | MMS_SERVICE_CONCLUDE + | MMS_SERVICE_CANCEL + }; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + bufPos = BerEncoder_encodeBitString(0x82, 85, servicesSupported, buffer, bufPos); return bufPos; @@ -159,7 +227,7 @@ createInitiateResponse(MmsServerConnection self, ByteBuffer* writeBuffer) initiateResponseLength += 2 + BerEncoder_UInt32determineEncodedSize(self->maxServOutstandingCalled); initiateResponseLength += 2 + BerEncoder_UInt32determineEncodedSize(self->dataStructureNestingLevel); - initiateResponseLength += encodeInitResponseDetail(NULL, 0, false); + initiateResponseLength += encodeInitResponseDetail(self, NULL, 0, false); /* Initiate response pdu */ bufPos = BerEncoder_encodeTL(0xa9, initiateResponseLength, buffer, bufPos); @@ -172,7 +240,7 @@ createInitiateResponse(MmsServerConnection self, ByteBuffer* writeBuffer) bufPos = BerEncoder_encodeUInt32WithTL(0x83, self->dataStructureNestingLevel, buffer, bufPos); - bufPos = encodeInitResponseDetail(buffer, bufPos, true); + bufPos = encodeInitResponseDetail(self, buffer, bufPos, true); writeBuffer->size = bufPos; diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 5781812c8..46d08ad56 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -67,6 +67,10 @@ MmsServer_create(MmsDevice* device, TLSConfiguration tlsConfiguration) IsoServer_setUserLock(self->isoServer, self->modelMutex); #endif +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + self->fileServiceEnabled = true; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + return self; } @@ -99,6 +103,15 @@ MmsServer_setFilestoreBasepath(MmsServer self, const char* basepath) #endif /* (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) */ } +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + +void +MmsServer_enableFileService(MmsServer self, bool enable) +{ + self->fileServiceEnabled = enable; +} + +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ void MmsServer_lockModel(MmsServer self) diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index 6297ea55c..4767ca7e4 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -171,9 +171,22 @@ handleConfirmedRequestPdu( { #if (MMS_OBTAIN_FILE_SERVICE == 1) + + +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x2e: /* obtain-file */ + if (self->server->fileServiceEnabled) + mmsServer_handleObtainFileRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; +#else case 0x2e: /* obtain-file */ mmsServer_handleObtainFileRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + + #endif /* MMS_OBTAIN_FILE_SERVICE == 1 */ #if (MMS_JOURNAL_SERVICE == 1) @@ -183,29 +196,86 @@ handleConfirmedRequestPdu( #endif /* (MMS_JOURNAL_SERVICE == 1) */ #if (MMS_FILE_SERVICE == 1) + +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x48: /* file-open-request */ + if (self->server->fileServiceEnabled) + mmsServer_handleFileOpenRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; + +#else case 0x48: /* file-open-request */ mmsServer_handleFileOpenRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x49: /* file-read-request */ + if (self->server->fileServiceEnabled) + mmsServer_handleFileReadRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; +#else case 0x49: /* file-read-request */ mmsServer_handleFileReadRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x4a: /* file-close-request */ + if (self->server->fileServiceEnabled) + mmsServer_handleFileCloseRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; +#else case 0x4a: /* file-close-request */ mmsServer_handleFileCloseRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x4b: /* file-rename-request */ + if (self->server->fileServiceEnabled) + mmsServer_handleFileRenameRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; +#else case 0x4b: /* file-rename-request */ mmsServer_handleFileRenameRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x4c: /* file-delete-request */ + if (self->server->fileServiceEnabled) + mmsServer_handleFileDeleteRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; +#else case 0x4c: /* file-delete-request */ mmsServer_handleFileDeleteRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x4d: /* file-directory-request */ + if (self->server->fileServiceEnabled) + mmsServer_handleFileDirectoryRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; +#else case 0x4d: /* file-directory-request */ mmsServer_handleFileDirectoryRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + #endif /* MMS_FILE_SERVICE == 1 */ default: diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index af416b2cb..092b30512 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -604,3 +604,5 @@ EXPORTS ClientGooseControlBlock_getMaxTime ClientGooseControlBlock_getFixedOffs ControlObjectClient_getCtlValType + IedServerConfig_enableFileService + IedServerConfig_isFileServiceEnabled diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 7faa74db6..991b2f8c1 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -732,3 +732,5 @@ EXPORTS ClientGooseControlBlock_getFixedOffs ControlObjectClient_getCtlValType SVPublisher_ASDU_setSmpCntWrap + IedServerConfig_enableFileService + IedServerConfig_isFileServiceEnabled From 27e883a1bc2f019933a9a5c079648fecf319d257 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 26 Jul 2018 07:11:29 +0200 Subject: [PATCH 092/123] - MMS server: fixed bug in association service (file rename was not reported as supported service) --- examples/server_example_basic_io/server_example_basic_io.c | 3 +++ src/mms/iso_mms/server/mms_association_service.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index b77aa0868..7e4139228 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -93,6 +93,9 @@ main(int argc, char** argv) /* Set the base path for the MMS file services */ IedServerConfig_setFileServiceBasePath(config, "./vmd-filestore/"); + /* disable MMS file service */ + IedServerConfig_enableFileService(config, false); + /* Create a new IEC 61850 server instance */ iedServer = IedServer_createWithConfig(&iedModel, NULL, config); diff --git a/src/mms/iso_mms/server/mms_association_service.c b/src/mms/iso_mms/server/mms_association_service.c index 46b7d1911..c1d313181 100644 --- a/src/mms/iso_mms/server/mms_association_service.c +++ b/src/mms/iso_mms/server/mms_association_service.c @@ -53,7 +53,7 @@ #define MMS_SERVICE_FILE_OPEN 0x80 #define MMS_SERVICE_FILE_READ 0x40 #define MMS_SERVICE_FILE_CLOSE 0x20 -#define MMS_SERVICE_FILE_RENAME 0x01 +#define MMS_SERVICE_FILE_RENAME 0x10 #define MMS_SERVICE_FILE_DELETE 0x08 #define MMS_SERVICE_FILE_DIRECTORY 0x04 #define MMS_SERVICE_UNSOLICITED_STATUS 0x02 From 0a3d86601b3d8ce0f607c6b9ba52f03fffeb73e7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 26 Jul 2018 07:48:33 +0200 Subject: [PATCH 093/123] - IEC 61850 server/MMS server: make dynamic data set service configurable at runtime with IedServerConfig object (new functions IedServerConfig_enableDynamicDataSetService and IedServerConfig_isDynamicDataSetServiceEnabled) --- .../server_example_basic_io.c | 3 ++ src/iec61850/inc/iec61850_server.h | 19 +++++++++ src/iec61850/server/impl/ied_server.c | 4 +- src/iec61850/server/impl/ied_server_config.c | 12 ++++++ src/mms/inc/mms_server.h | 11 +++++ src/mms/inc_private/mms_server_internal.h | 1 + .../iso_mms/server/mms_association_service.c | 13 ++++-- src/mms/iso_mms/server/mms_server.c | 7 ++++ .../iso_mms/server/mms_server_connection.c | 41 +++++++++++++++---- src/vs/libiec61850-wo-goose.def | 2 + src/vs/libiec61850.def | 2 + 11 files changed, 101 insertions(+), 14 deletions(-) diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index 7e4139228..548871876 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -96,6 +96,9 @@ main(int argc, char** argv) /* disable MMS file service */ IedServerConfig_enableFileService(config, false); + /* disable dynamic data set service */ + IedServerConfig_enableDynamicDataSetService(config, true); + /* Create a new IEC 61850 server instance */ iedServer = IedServer_createWithConfig(&iedModel, NULL, config); diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 9ad4507c2..1f17d2263 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -56,6 +56,9 @@ struct sIedServerConfig /** when true (default) enable MMS file service */ bool enableFileService; + + /** when true (default) enable dynamic data set services for MMS */ + bool enableDynamicDataSetService; }; /** @@ -118,6 +121,22 @@ IedServerConfig_enableFileService(IedServerConfig self, bool enable); bool IedServerConfig_isFileServiceEnabled(IedServerConfig self); +/** + * \brief Enable/disable the dynamic data set service for MMS + * + * \param[in] enable set true to enable dynamic data set service, otherwise false + */ +void +IedServerConfig_enableDynamicDataSetService(IedServerConfig self, bool enable); + +/** + * \brief Is the dynamic data set service for MMS enabled or disabled + * + * \return true if enabled, false otherwise + */ +bool +IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self); + /** * An opaque handle for an IED server instance */ diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index c88c78234..200032fae 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -422,8 +422,10 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->mmsServer = MmsServer_create(self->mmsDevice, tlsConfiguration); #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) - if (serverConfiguration) + if (serverConfiguration) { MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); + MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); + } #endif if (serverConfiguration) diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index 23552f40e..956b06455 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -33,6 +33,7 @@ IedServerConfig_create() self->reportBufferSize = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->fileServiceBasepath = StringUtils_copyString(CONFIG_VIRTUAL_FILESTORE_BASEPATH); self->enableFileService = true; + self->enableDynamicDataSetService = true; } return self; @@ -87,3 +88,14 @@ IedServerConfig_isFileServiceEnabled(IedServerConfig self) return self->enableFileService; } +void +IedServerConfig_enableDynamicDataSetService(IedServerConfig self, bool enable) +{ + self->enableDynamicDataSetService = enable; +} + +bool +IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self) +{ + return self->enableDynamicDataSetService; +} diff --git a/src/mms/inc/mms_server.h b/src/mms/inc/mms_server.h index f47760b15..d0a2cd60c 100644 --- a/src/mms/inc/mms_server.h +++ b/src/mms/inc/mms_server.h @@ -236,6 +236,17 @@ MmsServer_setFilestoreBasepath(MmsServer self, const char* basepath); void MmsServer_enableFileService(MmsServer self, bool enable); +/** + * \brief Enable/disable dynamic named variable list (data set) service + * + * NOTE: requires CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME = 1 in stack configuration + * + * \param[in] self the MmsServer instance + * \param[in] enable true to enable named variable list services, false to disable + */ +void +MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable); + /** * \brief lock the cached server data model * diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index bb350fb45..0013d63ad 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -166,6 +166,7 @@ struct sMmsServer { #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) bool fileServiceEnabled; + bool dynamicVariableListServiceEnabled; #endif /* (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) */ }; diff --git a/src/mms/iso_mms/server/mms_association_service.c b/src/mms/iso_mms/server/mms_association_service.c index c1d313181..e31a411a7 100644 --- a/src/mms/iso_mms/server/mms_association_service.c +++ b/src/mms/iso_mms/server/mms_association_service.c @@ -105,10 +105,6 @@ encodeInitResponseDetail(MmsServerConnection self, uint8_t* buffer, int bufPos, | MMS_SERVICE_GET_VARIABLE_ACCESS_ATTRIBUTES , 0x00 - #if ((MMS_DATA_SET_SERVICE == 1) && (MMS_DYNAMIC_DATA_SETS == 1)) - | MMS_SERVICE_DEFINE_NAMED_VARIABLE_LIST - | MMS_SERVICE_DELETE_NAMED_VARIABLE_LIST - #endif #if ((MMS_DATA_SET_SERVICE == 1) && (MMS_GET_DATA_SET_ATTRIBUTES == 1)) | MMS_SERVICE_GET_NAMED_VARIABLE_LIST_ATTRIBUTES #endif @@ -151,6 +147,15 @@ encodeInitResponseDetail(MmsServerConnection self, uint8_t* buffer, int bufPos, } + if (self->server->dynamicVariableListServiceEnabled) { + +#if ((MMS_DATA_SET_SERVICE == 1) && (MMS_DYNAMIC_DATA_SETS == 1)) + servicesSupported[1] |= MMS_SERVICE_DEFINE_NAMED_VARIABLE_LIST; + servicesSupported[1] |= MMS_SERVICE_DELETE_NAMED_VARIABLE_LIST; +#endif + + } + #else uint8_t servicesSupported[] = diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 46d08ad56..3b029e171 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -69,6 +69,7 @@ MmsServer_create(MmsDevice* device, TLSConfiguration tlsConfiguration) #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) self->fileServiceEnabled = true; + self->dynamicVariableListServiceEnabled = true; #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ return self; @@ -111,6 +112,12 @@ MmsServer_enableFileService(MmsServer self, bool enable) self->fileServiceEnabled = enable; } +void +MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable) +{ + self->dynamicVariableListServiceEnabled = enable; +} + #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ void diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index 4767ca7e4..4017b636f 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -1,7 +1,7 @@ /* * mms_server_connection.c * - * Copyright 2013-2016 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -334,11 +334,42 @@ handleConfirmedRequestPdu( #endif /* MMS_GET_VARIABLE_ACCESS_ATTRIBUTES == 1 */ #if (MMS_DYNAMIC_DATA_SETS == 1) + +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + + case 0xab: /* define-named-variable-list */ + if (self->server->dynamicVariableListServiceEnabled) + mmsServer_handleDefineNamedVariableListRequest(self, + buffer, bufPos, bufPos + length, + invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; + + case 0xad: /* delete-named-variable-list-request */ + if (self->server->dynamicVariableListServiceEnabled) + mmsServer_handleDeleteNamedVariableListRequest(self, + buffer, bufPos, bufPos + length, + invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; + +#else case 0xab: /* define-named-variable-list */ mmsServer_handleDefineNamedVariableListRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; + + case 0xad: /* delete-named-variable-list-request */ + mmsServer_handleDeleteNamedVariableListRequest(self, + buffer, bufPos, bufPos + length, + invokeId, response); + break; + +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ #if (MMS_GET_DATA_SET_ATTRIBUTES == 1) @@ -349,14 +380,6 @@ handleConfirmedRequestPdu( break; #endif /* (MMS_GET_DATA_SET_ATTRIBUTES == 1) */ -#if (MMS_DYNAMIC_DATA_SETS == 1) - case 0xad: /* delete-named-variable-list-request */ - mmsServer_handleDeleteNamedVariableListRequest(self, - buffer, bufPos, bufPos + length, - invokeId, response); - break; -#endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ - default: mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); return; diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 092b30512..284acd03a 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -606,3 +606,5 @@ EXPORTS ControlObjectClient_getCtlValType IedServerConfig_enableFileService IedServerConfig_isFileServiceEnabled + IedServerConfig_enableDynamicDataSetService + IedServerConfig_isDynamicDataSetServiceEnabled diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 991b2f8c1..b11db7481 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -734,3 +734,5 @@ EXPORTS SVPublisher_ASDU_setSmpCntWrap IedServerConfig_enableFileService IedServerConfig_isFileServiceEnabled + IedServerConfig_enableDynamicDataSetService + IedServerConfig_isDynamicDataSetServiceEnabled From 71493036dcb160b3cca29cb3b1178aeea8799d4b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 26 Jul 2018 09:55:31 +0200 Subject: [PATCH 094/123] - IEC 61850 server/MMS server: make dynamic data set service configurable at runtime with IedServerConfig object (new functions IedServerConfig_enableLogService and IedServerConfig_isLogServiceEnabled) --- .../server_example_basic_io.c | 4 +- src/iec61850/inc/iec61850_server.h | 19 ++++++ src/iec61850/inc_private/ied_server_private.h | 4 ++ src/iec61850/server/impl/ied_server.c | 11 +++- src/iec61850/server/impl/ied_server_config.c | 13 ++++ src/iec61850/server/mms_mapping/mms_mapping.c | 61 ++++++++++++------- src/mms/inc/mms_server.h | 11 ++++ src/mms/inc_private/mms_server_internal.h | 1 + .../iso_mms/server/mms_association_service.c | 15 ++--- .../iso_mms/server/mms_get_namelist_service.c | 7 +-- src/mms/iso_mms/server/mms_server.c | 7 +++ .../iso_mms/server/mms_server_connection.c | 12 ++++ src/vs/libiec61850-wo-goose.def | 2 + src/vs/libiec61850.def | 2 + 14 files changed, 135 insertions(+), 34 deletions(-) diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index 548871876..d5c9a6b80 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -97,7 +97,9 @@ main(int argc, char** argv) IedServerConfig_enableFileService(config, false); /* disable dynamic data set service */ - IedServerConfig_enableDynamicDataSetService(config, true); + IedServerConfig_enableDynamicDataSetService(config, false); + + IedServerConfig_enableLogService(config, true); /* Create a new IEC 61850 server instance */ iedServer = IedServer_createWithConfig(&iedModel, NULL, config); diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 1f17d2263..d5f7b954d 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -59,6 +59,9 @@ struct sIedServerConfig /** when true (default) enable dynamic data set services for MMS */ bool enableDynamicDataSetService; + + /** when true (default) enable log service */ + bool enableLogService; }; /** @@ -137,6 +140,22 @@ IedServerConfig_enableDynamicDataSetService(IedServerConfig self, bool enable); bool IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self); +/** + * \brief Enable/disable the log service for MMS + * + * \param[in] enable set true to enable dynamic data set service, otherwise false + */ +void +IedServerConfig_enableLogService(IedServerConfig self, bool enable); + +/** + * \brief Is the log service for MMS enabled or disabled + * + * \return true if enabled, false otherwise + */ +bool +IedServerConfig_isLogServiceEnabled(IedServerConfig self); + /** * An opaque handle for an IED server instance */ diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index d78702ccc..825c032bc 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -51,6 +51,10 @@ struct sIedServer Semaphore dataModelLock; #endif +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + bool logServiceEnabled; +#endif + bool running; }; diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 200032fae..c5f9a7452 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -404,9 +404,17 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->running = false; self->localIpAddress = NULL; +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + self->logServiceEnabled = true; + + if (serverConfiguration) { + self->logServiceEnabled = serverConfiguration->enableLogService; + } +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + #if (CONFIG_MMS_THREADLESS_STACK != 1) self->dataModelLock = Semaphore_create(1); -#endif +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ #if (CONFIG_IEC61850_REPORT_SERVICE == 1) if (serverConfiguration) @@ -425,6 +433,7 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio if (serverConfiguration) { MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); + MmsServer_enableJournalService(self->mmsServer, serverConfiguration->enableLogService); } #endif diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index 956b06455..fe8c7ea5a 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -34,6 +34,7 @@ IedServerConfig_create() self->fileServiceBasepath = StringUtils_copyString(CONFIG_VIRTUAL_FILESTORE_BASEPATH); self->enableFileService = true; self->enableDynamicDataSetService = true; + self->enableLogService = true; } return self; @@ -99,3 +100,15 @@ IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self) { return self->enableDynamicDataSetService; } + +void +IedServerConfig_enableLogService(IedServerConfig self, bool enable) +{ + self->enableLogService = enable; +} + +bool +IedServerConfig_isLogServiceEnabled(IedServerConfig self) +{ + return self->enableLogService; +} diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 811039bee..e7958bb69 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1010,12 +1010,22 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ #if (CONFIG_IEC61850_LOG_SERVICE == 1) - if (lcbCount > 0) { - namedVariable->typeSpec.structure.elements[currentComponent] = - Logging_createLCBs(self, domain, logicalNode, lcbCount); - currentComponent++; +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if (self->iedServer->logServiceEnabled) { +#endif + + if (lcbCount > 0) { + namedVariable->typeSpec.structure.elements[currentComponent] = + Logging_createLCBs(self, domain, logicalNode, lcbCount); + + currentComponent++; + } + +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) } +#endif + #endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ @@ -1118,33 +1128,42 @@ createMmsDomainFromIedDevice(MmsMapping* self, LogicalDevice* logicalDevice) goto exit_function; #if (CONFIG_IEC61850_LOG_SERVICE == 1) - /* add logs (journals) */ - Log* log = self->model->logs; - while (log != NULL) { +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if (self->iedServer->logServiceEnabled) { +#endif + /* add logs (journals) */ + Log* log = self->model->logs; - char journalName[65]; + while (log != NULL) { - int nameLength = strlen(log->parent->name) + strlen(log->name); + char journalName[65]; - if (nameLength > 63) { - if (DEBUG_IED_SERVER) - printf("IED_SERVER: Log name %s invalid! Resulting journal name too long! Skip log\n", log->name); - } - else { - strcpy(journalName, log->parent->name); - strcat(journalName, "$"); - strcat(journalName, log->name); + int nameLength = strlen(log->parent->name) + strlen(log->name); + + if (nameLength > 63) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Log name %s invalid! Resulting journal name too long! Skip log\n", log->name); + } + else { + strcpy(journalName, log->parent->name); + strcat(journalName, "$"); + strcat(journalName, log->name); + + MmsDomain_addJournal(domain, journalName); - MmsDomain_addJournal(domain, journalName); + LogInstance* logInstance = LogInstance_create(log->parent, log->name); - LogInstance* logInstance = LogInstance_create(log->parent, log->name); + LinkedList_add(self->logInstances, (void*) logInstance); + } - LinkedList_add(self->logInstances, (void*) logInstance); + log = log->sibling; } - log = log->sibling; +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) } +#endif + #endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ int nodesCount = LogicalDevice_getLogicalNodeCount(logicalDevice); diff --git a/src/mms/inc/mms_server.h b/src/mms/inc/mms_server.h index d0a2cd60c..5d6cd93b3 100644 --- a/src/mms/inc/mms_server.h +++ b/src/mms/inc/mms_server.h @@ -247,6 +247,17 @@ MmsServer_enableFileService(MmsServer self, bool enable); void MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable); +/** + * \brief Enable/disable journal service + * + * NOTE: requires CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME = 1 in stack configuration + * + * \param[in] self the MmsServer instance + * \param[in] enable true to enable journal service, false to disable + */ +void +MmsServer_enableJournalService(MmsServer self, bool enable); + /** * \brief lock the cached server data model * diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 0013d63ad..37cffb35e 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -167,6 +167,7 @@ struct sMmsServer { #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) bool fileServiceEnabled; bool dynamicVariableListServiceEnabled; + bool journalServiceEnabled; #endif /* (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) */ }; diff --git a/src/mms/iso_mms/server/mms_association_service.c b/src/mms/iso_mms/server/mms_association_service.c index e31a411a7..25c69dde2 100644 --- a/src/mms/iso_mms/server/mms_association_service.c +++ b/src/mms/iso_mms/server/mms_association_service.c @@ -112,15 +112,10 @@ encodeInitResponseDetail(MmsServerConnection self, uint8_t* buffer, int bufPos, 0x00, 0x00, 0x00, - 0x00 - , 0x00, 0x00, - 0x00 - #if (MMS_JOURNAL_SERVICE == 1) - | MMS_SERVICE_READ_JOURNAL - #endif - , + 0x00, + 0x00, 0x00 | MMS_SERVICE_INFORMATION_REPORT , @@ -156,6 +151,12 @@ encodeInitResponseDetail(MmsServerConnection self, uint8_t* buffer, int bufPos, } + if (self->server->journalServiceEnabled) { +#if (MMS_JOURNAL_SERVICE == 1) + servicesSupported[8] |= MMS_SERVICE_READ_JOURNAL; +#endif + } + #else uint8_t servicesSupported[] = diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index bf0637f1c..486383bc3 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -615,11 +615,10 @@ mmsServer_handleGetNameListRequest( #endif /* (MMS_DATA_SET_SERVICE == 1) */ else if (objectClass == OBJECT_CLASS_JOURNAL) { - LinkedList nameList = LinkedList_create(); -#if (CONFIG_MMS_SORT_NAME_LIST == 1) - StringUtils_sortList(nameList); -#endif + /* response with empty list */ + + LinkedList nameList = LinkedList_create(); createNameListResponse(connection, invokeId, nameList, response, continueAfter); diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 3b029e171..2352926de 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -70,6 +70,7 @@ MmsServer_create(MmsDevice* device, TLSConfiguration tlsConfiguration) #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) self->fileServiceEnabled = true; self->dynamicVariableListServiceEnabled = true; + self->journalServiceEnabled = true; #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ return self; @@ -118,6 +119,12 @@ MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable) self->dynamicVariableListServiceEnabled = enable; } +void +MmsServer_enableJournalService(MmsServer self, bool enable) +{ + self->journalServiceEnabled = enable; +} + #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ void diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index 4017b636f..c689fdb5e 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -190,9 +190,21 @@ handleConfirmedRequestPdu( #endif /* MMS_OBTAIN_FILE_SERVICE == 1 */ #if (MMS_JOURNAL_SERVICE == 1) + +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + case 0x41: /* read-journal */ + if (self->server->journalServiceEnabled) + mmsServer_handleReadJournalRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + else + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE, response); + break; + +#else case 0x41: /* read-journal */ mmsServer_handleReadJournalRequest(self, buffer, bufPos, bufPos + length, invokeId, response); break; +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + #endif /* (MMS_JOURNAL_SERVICE == 1) */ #if (MMS_FILE_SERVICE == 1) diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 284acd03a..c769a4d81 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -608,3 +608,5 @@ EXPORTS IedServerConfig_isFileServiceEnabled IedServerConfig_enableDynamicDataSetService IedServerConfig_isDynamicDataSetServiceEnabled + IedServerConfig_enableLogService + IedServerConfig_isLogServiceEnabled diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index b11db7481..8e5edab4c 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -736,3 +736,5 @@ EXPORTS IedServerConfig_isFileServiceEnabled IedServerConfig_enableDynamicDataSetService IedServerConfig_isDynamicDataSetServiceEnabled + IedServerConfig_enableLogService + IedServerConfig_isLogServiceEnabled From 4d8e256967032c2d926b1c7f11abec2c4bf34c86 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 6 Aug 2018 08:04:13 +0200 Subject: [PATCH 095/123] - .NET API: Added array/structure handlung functions to MmsValue --- dotnet/IEC61850forCSharp/MmsValue.cs | 88 ++++++++++++++++++- .../MmsVariableSpecification.cs | 2 +- dotnet/tests/Test.cs | 44 ++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/dotnet/IEC61850forCSharp/MmsValue.cs b/dotnet/IEC61850forCSharp/MmsValue.cs index 889126f63..f85b32181 100644 --- a/dotnet/IEC61850forCSharp/MmsValue.cs +++ b/dotnet/IEC61850forCSharp/MmsValue.cs @@ -136,6 +136,15 @@ public class MmsValue : IEnumerable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_newVisibleString(string value); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_createArray(IntPtr elementType, int size); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_createEmptyArray(int size); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_createEmptyStructure(int size); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_newOctetString(int size, int maxSize); @@ -164,9 +173,12 @@ public class MmsValue : IEnumerable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern ulong MmsValue_getBinaryTimeAsUtcMs (IntPtr self); - [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] + [DllImport("iec61850", CallingConvention=CallingConvention.Cdecl)] static extern int MmsValue_getDataAccessError(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void MmsValue_setElement(IntPtr complexValue, int index, IntPtr elementValue); + internal IntPtr valueReference; /* reference to native MmsValue instance */ private bool responsableForDeletion; /* if .NET wrapper is responsable for the deletion of the native MmsValue instance */ @@ -214,6 +226,10 @@ public MmsValue (long value) valueReference = MmsValue_newIntegerFromInt64 (value); } + /// + /// Create a new instance of type MMS_VISIBLE_STRING. + /// + /// Value. public MmsValue (string value) { valueReference = MmsValue_newVisibleString(value); @@ -270,6 +286,53 @@ public MmsValue(byte[] octetString) this.setOctetString (octetString); } + /// + /// Create a new MmsValue instance of type MMS_ARRAY. Array elements have the fiven type + /// + /// the newly created array + /// array element type + /// number of array elements + public static MmsValue NewArray(MmsVariableSpecification elementType, int size) + { + if (size < 1) + throw new MmsValueException ("array requires at least one element"); + + IntPtr newValue = MmsValue_createArray (elementType.self, size); + + return new MmsValue (newValue, true); + } + + /// + /// Create a new MmsValue instance of type MMS_ARRAY. Array elements are not initialized! + /// + /// the newly created array + /// number of array elements + public static MmsValue NewEmptyArray(int size) + { + if (size < 1) + throw new MmsValueException ("array requires at least one element"); + + IntPtr newValue = MmsValue_createEmptyArray (size); + + return new MmsValue (newValue, true); + } + + /// + /// Create a new MmsValue instance of type MMS_STRUCTURE. Structure elements are not initialized! + /// + /// the newly created array + /// number of structure elements + public static MmsValue NewEmptyStructure(int size) + { + if (size < 1) + throw new MmsValueException ("structure requires at least one element"); + + IntPtr newValue = MmsValue_createEmptyStructure (size); + + return new MmsValue (newValue, true); + } + + /// /// Create a new MmsValue instance of type MMS_BINARY_TIME /// @@ -427,6 +490,29 @@ public MmsValue GetElement (int index) throw new MmsValueException ("Value is of wrong type"); } + /// + /// Sets the element of an array of structure + /// + /// index of the element starting with 0 + /// MmsValue instance that will be used as element value + /// This exception is thrown if the value has the wrong type. + /// This exception is thrown if the index is out of range. + public void SetElement(int index, MmsValue elementValue) + { + MmsType elementType = GetType (); + + if ((elementType == MmsType.MMS_ARRAY) || (elementType == MmsType.MMS_STRUCTURE)) { + + if ((index >= 0) && (index < Size ())) { + MmsValue_setElement (valueReference, index, elementValue.valueReference); + + } else + throw new MmsValueException ("Index out of bounds"); + + } else + throw new MmsValueException ("Value is of wrong type"); + + } public MmsDataAccessError GetDataAccessError () { diff --git a/dotnet/IEC61850forCSharp/MmsVariableSpecification.cs b/dotnet/IEC61850forCSharp/MmsVariableSpecification.cs index 159f10e6d..1fe97503d 100644 --- a/dotnet/IEC61850forCSharp/MmsVariableSpecification.cs +++ b/dotnet/IEC61850forCSharp/MmsVariableSpecification.cs @@ -63,7 +63,7 @@ public class MmsVariableSpecification : IEnumerable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int MmsVariableSpecification_getExponentWidth(IntPtr self); - private IntPtr self; + internal IntPtr self; private bool responsableForDeletion; internal MmsVariableSpecification (IntPtr self) diff --git a/dotnet/tests/Test.cs b/dotnet/tests/Test.cs index ce22168f9..d0bb0e40f 100644 --- a/dotnet/tests/Test.cs +++ b/dotnet/tests/Test.cs @@ -105,6 +105,50 @@ public void MmsValueDouble() Assert.AreEqual (val.ToFloat (), (float)0.1234); } + [Test()] + public void MmsValueArray() + { + MmsValue val = MmsValue.NewEmptyArray (3); + + val.SetElement (0, new MmsValue (1)); + val.SetElement (1, new MmsValue (2)); + val.SetElement (2, new MmsValue (3)); + + Assert.AreEqual (val.GetType (), MmsType.MMS_ARRAY); + Assert.AreEqual (val.Size (), 3); + + MmsValue elem0 = val.GetElement (0); + + Assert.AreEqual (elem0.GetType (), MmsType.MMS_INTEGER); + Assert.AreEqual (elem0.ToInt32 (), 1); + + MmsValue elem2 = val.GetElement (2); + + Assert.AreEqual (elem2.GetType (), MmsType.MMS_INTEGER); + Assert.AreEqual (elem2.ToInt32 (), 3); + } + + [Test()] + public void MmsValueStructure() + { + MmsValue val = MmsValue.NewEmptyStructure (2); + + val.SetElement (0, new MmsValue(true)); + val.SetElement (1, MmsValue.NewBitString (10)); + + Assert.AreEqual (val.GetType (), MmsType.MMS_STRUCTURE); + Assert.AreEqual (val.Size (), 2); + + MmsValue elem0 = val.GetElement (0); + + Assert.AreEqual (elem0.GetType (), MmsType.MMS_BOOLEAN); + Assert.AreEqual (elem0.GetBoolean(), true); + + MmsValue elem1 = val.GetElement (1); + + Assert.AreEqual (elem1.GetType (), MmsType.MMS_BIT_STRING); + } + [Test ()] public void Timestamps() { From 9995a7cfc4f9fc57cf9ed102a7b3da2b94968592 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 8 Aug 2018 21:43:44 +0200 Subject: [PATCH 096/123] - IEC 61850 client: fixed bug in select response handling --- src/iec61850/client/client_control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iec61850/client/client_control.c b/src/iec61850/client/client_control.c index 6bc3add74..173a9be4e 100644 --- a/src/iec61850/client/client_control.c +++ b/src/iec61850/client/client_control.c @@ -605,7 +605,7 @@ ControlObjectClient_select(ControlObjectClient self) if (DEBUG_IED_CLIENT) printf("select-response-\n"); } - else if (strcmp(MmsValue_toString(value), sboReference)) { + else if (strcmp(MmsValue_toString(value), sboReference) == 0) { if (DEBUG_IED_CLIENT) printf("select-response+: (%s)\n", MmsValue_toString(value)); selected = true; From 02cda481287d4fb853caec711bd13888581ebfcd Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 Aug 2018 21:37:56 +0200 Subject: [PATCH 097/123] - moved TLS API to platform abstraction layer --- CHANGELOG | 2 +- CMakeLists.txt | 6 +- Makefile | 11 ++- hal/CMakeLists.txt | 23 ++++++ src/tls/tls_api.h => hal/inc/tls_config.h | 43 +++++++--- {src/tls => hal/inc}/tls_socket.h | 66 +++++++++++---- {src => hal}/tls/mbedtls/mbedtls_config.h | 0 {src => hal}/tls/mbedtls/tls_mbedtls.c | 82 +++++++------------ src/CMakeLists.txt | 9 -- src/mms/inc/iso_connection_parameters.h | 2 +- src/mms/inc/mms_client_connection.h | 3 +- src/mms/inc_private/iso_server_private.h | 2 +- src/mms/iso_client/iso_client_connection.c | 3 +- .../iso_mms/client/mms_client_connection.c | 3 +- 14 files changed, 149 insertions(+), 106 deletions(-) rename src/tls/tls_api.h => hal/inc/tls_config.h (84%) rename {src/tls => hal/inc}/tls_socket.h (50%) rename {src => hal}/tls/mbedtls/mbedtls_config.h (100%) rename {src => hal}/tls/mbedtls/tls_mbedtls.c (87%) diff --git a/CHANGELOG b/CHANGELOG index e64ef4855..11edb14b5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ Changes to version 1.3.0 ------------------------ -- HAL: unified platform abstraction layer (to simply use together with lib60870) +- HAL: unified platform abstraction layer (to simplify using the library together with lib60870) - IEC 61850 server: fixed bug when calling write access handler (wrong pointer for ClientConnection object) - updated IEC 61850-9-2 LE example to be more realistic - added server side example for the substitution service diff --git a/CMakeLists.txt b/CMakeLists.txt index 5afc22fb2..c4d457a75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,6 @@ include_directories( ${CMAKE_CURRENT_LIST_DIR}/src/mms/inc_private ${CMAKE_CURRENT_LIST_DIR}/src/mms/iso_mms/asn1c ${CMAKE_CURRENT_LIST_DIR}/src/logging - ${CMAKE_CURRENT_LIST_DIR}/src/tls ) set(API_HEADERS @@ -80,12 +79,13 @@ set(API_HEADERS hal/inc/hal_thread.h hal/inc/hal_filesystem.h hal/inc/hal_ethernet.h + hal/inc/tls_config.h hal/inc/platform_endian.h + hal/inc/lib_memory.h src/common/inc/libiec61850_common_api.h src/common/inc/libiec61850_platform_includes.h src/common/inc/linked_list.h src/common/inc/byte_buffer.h - src/common/inc/lib_memory.h src/common/inc/string_utilities.h src/iec61850/inc/iec61850_client.h src/iec61850/inc/iec61850_common.h @@ -112,7 +112,6 @@ set(API_HEADERS src/sampled_values/sv_subscriber.h src/sampled_values/sv_publisher.h src/logging/logging_api.h - src/tls/tls_api.h ${CMAKE_CURRENT_BINARY_DIR}/config/stack_config.h ) @@ -145,7 +144,6 @@ configure_file( ${CMAKE_CURRENT_BINARY_DIR}/config/stack_config.h ) -#include("${CMAKE_CURRENT_LIST_DIR}/hal/CMakeLists.txt") include_directories( ${CMAKE_CURRENT_LIST_DIR}/hal/inc ) diff --git a/Makefile b/Makefile index 58562b28f..1c6ce5714 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ LIB_SOURCE_DIRS += hal/ethernet/win32 LIB_SOURCE_DIRS += hal/filesystem/win32 LIB_SOURCE_DIRS += hal/time/win32 LIB_SOURCE_DIRS += hal/serial/win32 +LIB_SOURCE_DIRS += hal/memory else ifeq ($(HAL_IMPL), POSIX) LIB_SOURCE_DIRS += hal/socket/linux LIB_SOURCE_DIRS += hal/thread/linux @@ -44,12 +45,14 @@ LIB_SOURCE_DIRS += hal/ethernet/linux LIB_SOURCE_DIRS += hal/filesystem/linux LIB_SOURCE_DIRS += hal/time/unix LIB_SOURCE_DIRS += hal/serial/linux +LIB_SOURCE_DIRS += hal/memory else ifeq ($(HAL_IMPL), BSD) LIB_SOURCE_DIRS += hal/socket/bsd LIB_SOURCE_DIRS += hal/thread/bsd 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 @@ -69,9 +72,9 @@ endif ifdef WITH_MBEDTLS LIB_SOURCE_DIRS += third_party/mbedtls/mbedtls-2.6.0/library -LIB_SOURCE_DIRS += src/tls/mbedtls +LIB_SOURCE_DIRS += hal/tls/mbedtls LIB_INCLUDE_DIRS += third_party/mbedtls/mbedtls-2.6.0/include -LIB_INCLUDE_DIRS += src/tls/mbedtls +LIB_INCLUDE_DIRS += hal/tls/mbedtls CFLAGS += -D'MBEDTLS_CONFIG_FILE="mbedtls_config.h"' CFLAGS += -D'CONFIG_MMS_SUPPORT_TLS=1' endif @@ -84,7 +87,8 @@ endif LIB_API_HEADER_FILES = hal/inc/hal_time.h LIB_API_HEADER_FILES += hal/inc/hal_thread.h -LIB_API_HEADER_FILES += hal/inc/hal_filesystem.h +LIB_API_HEADER_FILES += hal/inc/hal_filesystem.h +LIB_API_HEADER_FILES += hal/inc/tls_config.h LIB_API_HEADER_FILES += src/common/inc/libiec61850_common_api.h LIB_API_HEADER_FILES += src/common/inc/linked_list.h LIB_API_HEADER_FILES += src/common/inc/byte_buffer.h @@ -114,7 +118,6 @@ LIB_API_HEADER_FILES += src/goose/goose_publisher.h LIB_API_HEADER_FILES += src/sampled_values/sv_subscriber.h LIB_API_HEADER_FILES += src/sampled_values/sv_publisher.h LIB_API_HEADER_FILES += src/logging/logging_api.h -LIB_API_HEADER_FILES += src/tls/tls_api.h get_sources_from_directory = $(wildcard $1/*.c) get_sources = $(foreach dir, $1, $(call get_sources_from_directory,$(dir))) diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt index 2a3e67907..a225a92ba 100644 --- a/hal/CMakeLists.txt +++ b/hal/CMakeLists.txt @@ -97,6 +97,29 @@ ENDIF(WIN32) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC" ) +if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.6.0) +message("Found mbedtls -> can compile with TLS support") +set(WITH_MBEDTLS 1) +endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.6.0) + +if(WITH_MBEDTLS) +include_directories( + ${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls + ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.6.0/include +) + +file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.6.0/library/*.c) + +add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h") + +set (libhal_SRCS ${libhal_SRCS} + ${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls/tls_mbedtls.c +) + +list (APPEND libhal_SRCS ${tls_SRCS}) + +endif(WITH_MBEDTLS) + add_library (hal STATIC ${libhal_SRCS}) add_library (hal-shared STATIC ${libhal_SRCS}) diff --git a/src/tls/tls_api.h b/hal/inc/tls_config.h similarity index 84% rename from src/tls/tls_api.h rename to hal/inc/tls_config.h index e23923695..ec70fe9bf 100644 --- a/src/tls/tls_api.h +++ b/hal/inc/tls_config.h @@ -1,27 +1,40 @@ /* - * tls_api.h + * tls_config.h * - * TLS API for TCP/IP protocol stacks + * TLS Configuration API for protocol stacks using TCP/IP * - * Copyright 2017 MZ Automation GmbH + * Copyright 2017-2018 MZ Automation GmbH * - * Abstraction layer for different TLS implementations - * - * Implementation connects the TLS API layer with the socket API layer - * and performs all TLS tasks like handshake, encryption/decryption. + * Abstraction layer for configuration of different TLS implementations * */ -#ifndef SRC_TLS_TLS_API_H_ -#define SRC_TLS_TLS_API_H_ - -#include -#include +#ifndef SRC_TLS_CONFIG_H_ +#define SRC_TLS_CONFIG_H_ #ifdef __cplusplus extern "C" { #endif +#include +#include + +/** + * \file tls_config.h + * \brief TLS API functions + */ + +/*! \addtogroup hal Platform (Hardware/OS) abstraction layer + * + * @{ + */ + +/** + * @defgroup TLS_CONFIG_API TLS configuration + * + * @{ + */ + typedef struct sTLSConfiguration* TLSConfiguration; /** @@ -107,8 +120,12 @@ TLSConfiguration_setRenegotiationTime(TLSConfiguration self, int timeInMs); void TLSConfiguration_destroy(TLSConfiguration self); +/** @} */ + +/** @} */ + #ifdef __cplusplus } #endif -#endif /* SRC_TLS_TLS_API_H_ */ +#endif /* SRC_TLS_CONFIG_H_ */ diff --git a/src/tls/tls_socket.h b/hal/inc/tls_socket.h similarity index 50% rename from src/tls/tls_socket.h rename to hal/inc/tls_socket.h index 009a9906e..553cb82be 100644 --- a/src/tls/tls_socket.h +++ b/hal/inc/tls_socket.h @@ -1,30 +1,64 @@ /* * tls_socket.h * - * TLS API for TCP/IP protocol stacks + * TLS socket API for protocol libraries using TCP/IP * - * Copyright 2017 MZ Automation GmbH + * Copyright 2017-2018 Michael Zillgith, MZ Automation GmbH * * Abstraction layer for different TLS implementations * - * Implementation connects the TLS API layer with the socket API layer - * and performs all TLS tasks like handshake, encryption/decryption. + * The implementation has to connect the TLS API layer with the socket API layer + * and perform all TLS tasks like handshake, encryption/decryption. * */ -#ifndef SRC_TLS_TLS_SOCKET_H_ -#define SRC_TLS_TLS_SOCKET_H_ - -#include "tls_api.h" - -#include "hal_socket.h" +#ifndef SRC_TLS_SOCKET_API_H_ +#define SRC_TLS_SOCKET_API_H_ #ifdef __cplusplus extern "C" { #endif +/** + * \file tls_socket.h + * \brief Abstraction layer for different TLS implementations. + * + * The implementation has to connect the TLS API layer with the socket API layer + * and perform all TLS tasks like handshake, encryption/decryption. + */ + +/*! \addtogroup hal Platform (Hardware/OS) abstraction layer + * + * @{ + */ + +/** + * @defgroup HAL_TLS_SOCKET Abstraction layer for different TLS implementations. + * + * The implementation has to connect the TLS API layer with the socket API layer + * and perform all TLS tasks like handshake, encryption/decryption. + * + * @{ + */ + +#include +#include "tls_config.h" +#include "hal_socket.h" + typedef struct sTLSSocket* TLSSocket; +/** + * \brief This function create a new TLSSocket instance using the given Socket instance + * + * NOTE: This function also has to perform the TLS handshake + * + * \param socket the socket instance to use for the TLS connection + * \param configuration the TLS configuration object to use + * \param storeClientCert if true, the client certificate will be stored + * for later access by \ref TLSSocket_getPeerCertificate + * + * \return new TLS connection instance + */ TLSSocket TLSSocket_create(Socket socket, TLSConfiguration configuration, bool storeClientCert); @@ -50,10 +84,6 @@ TLSSocket_getPeerCertificate(TLSSocket self, int* certSize); * The function shall return immediately if no data is available. In this case * the function returns 0. If an error happens the function shall return -1. * - * Implementation of this function is MANDATORY - * - * NOTE: The behaviour of this function changed with version 0.8! - * * \param self the client, connection or server socket instance * \param buf the buffer where the read bytes are copied to * \param size the maximum number of bytes to read (size of the provided buffer) @@ -76,13 +106,17 @@ int TLSSocket_write(TLSSocket self, uint8_t* buf, int size); /** - * \brief Close the TLS socket and release all resources + * \brief Closes the TLS connection and released all resources */ void TLSSocket_close(TLSSocket self); +/*! @} */ + +/*! @} */ + #ifdef __cplusplus } #endif -#endif /* SRC_TLS_TLS_SOCKET_H_ */ +#endif /* SRC_TLS_SOCKET_API_H_ */ diff --git a/src/tls/mbedtls/mbedtls_config.h b/hal/tls/mbedtls/mbedtls_config.h similarity index 100% rename from src/tls/mbedtls/mbedtls_config.h rename to hal/tls/mbedtls/mbedtls_config.h diff --git a/src/tls/mbedtls/tls_mbedtls.c b/hal/tls/mbedtls/tls_mbedtls.c similarity index 87% rename from src/tls/mbedtls/tls_mbedtls.c rename to hal/tls/mbedtls/tls_mbedtls.c index f0dc1da27..2319d647e 100644 --- a/src/tls/mbedtls/tls_mbedtls.c +++ b/hal/tls/mbedtls/tls_mbedtls.c @@ -11,13 +11,11 @@ #include -#include "tls_api.h" #include "tls_socket.h" #include "hal_thread.h" #include "lib_memory.h" #include "linked_list.h" -//#include "mbedtls/config.h" #include "mbedtls/platform.h" #include "mbedtls/entropy.h" #include "mbedtls/ctr_drbg.h" @@ -28,10 +26,10 @@ #include "mbedtls/error.h" #include "mbedtls/debug.h" -#define CONFIG_DEBUG_TLS 0 - #if (CONFIG_DEBUG_TLS == 1) -#define DEBUG_PRINT(appId, fmt, ...) fprintf(stderr, "%s: " fmt, appId, ## __VA_ARGS__) +#define DEBUG_PRINT(appId, fmt, ...) fprintf(stderr, "%s: " fmt, \ + appId, \ + __VA_ARGS__) #else #define DEBUG_PRINT(fmt, ...) {do {} while(0);} #endif @@ -212,7 +210,7 @@ TLSConfiguration_setOwnCertificate(TLSConfiguration self, uint8_t* certificate, int ret = mbedtls_x509_crt_parse(&(self->ownCertificate), certificate, certLen); if (ret != 0) - printf("mbedtls_x509_crt_parse returned %d\n", ret); + DEBUG_PRINT("mbedtls_x509_crt_parse returned %d\n", ret); return (ret == 0); } @@ -344,7 +342,6 @@ readFunction(void* ctx, unsigned char* buf, size_t len) int ret = Socket_read((Socket) ctx, buf, len); if ((ret == 0) && (len > 0)) { - Thread_sleep(1); return MBEDTLS_ERR_SSL_WANT_READ; } @@ -422,47 +419,30 @@ TLSSocket_performHandshake(TLSSocket self) int TLSSocket_read(TLSSocket self, uint8_t* buf, int size) { - int ret; - int len = size; - - do - { - ret = mbedtls_ssl_read( &(self->ssl), buf, len ); + int ret = mbedtls_ssl_read(&(self->ssl), buf, size); - if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ) - continue; + if ((ret == MBEDTLS_ERR_SSL_WANT_READ) || (ret == MBEDTLS_ERR_SSL_WANT_WRITE)) + return 0; - if( ret <= 0 ) - { - switch( ret ) - { - case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: - DEBUG_PRINT("TLS", " connection was closed gracefully\n" ); - len = -1; - break; + if (ret < 0) { - case MBEDTLS_ERR_NET_CONN_RESET: - len = -1; - DEBUG_PRINT("TLS", " connection was reset by peer\n" ); - break; - - default: - DEBUG_PRINT("TLS", " mbedtls_ssl_read returned -0x%x\n", -ret ); - len = -1; //TODO is this the correct return value? - break; - } - - break; - } + switch (ret) + { + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + DEBUG_PRINT("TLS", " connection was closed gracefully\n"); + return -1; - len = ret; + case MBEDTLS_ERR_NET_CONN_RESET: + DEBUG_PRINT("TLS", " connection was reset by peer\n"); + return -1; - if( ret > 0 ) - break; - } - while( 1 ); + default: + DEBUG_PRINT("TLS", " mbedtls_ssl_read returned -0x%x\n", -ret); + return -1; + } + } - return len; + return ret; } int @@ -471,18 +451,17 @@ TLSSocket_write(TLSSocket self, uint8_t* buf, int size) int ret; int len = size; - - while( ( ret = mbedtls_ssl_write( &(self->ssl), buf, len ) ) <= 0 ) + while ((ret = mbedtls_ssl_write(&(self->ssl), buf, len)) <= 0) { - if( ret == MBEDTLS_ERR_NET_CONN_RESET ) + if (ret == MBEDTLS_ERR_NET_CONN_RESET) { - DEBUG_PRINT("TLS", "peer closed the connection\n" ); + DEBUG_PRINT("TLS", "peer closed the connection\n"); return -1; } - if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + if ((ret != MBEDTLS_ERR_SSL_WANT_READ) && (ret != MBEDTLS_ERR_SSL_WANT_WRITE)) { - DEBUG_PRINT("TLS", "mbedtls_ssl_write returned %d\n", ret ); + DEBUG_PRINT("TLS", "mbedtls_ssl_write returned %d\n", ret); return -1; } } @@ -499,12 +478,11 @@ TLSSocket_close(TLSSocket self) //TODO add timeout? - while( ( ret = mbedtls_ssl_close_notify( &(self->ssl) ) ) < 0 ) + while ((ret = mbedtls_ssl_close_notify(&(self->ssl))) < 0) { - if( ret != MBEDTLS_ERR_SSL_WANT_READ && - ret != MBEDTLS_ERR_SSL_WANT_WRITE ) + if ((ret != MBEDTLS_ERR_SSL_WANT_READ) && (ret != MBEDTLS_ERR_SSL_WANT_WRITE)) { - DEBUG_PRINT("TLS", "mbedtls_ssl_close_notify returned %d\n", ret ); + DEBUG_PRINT("TLS", "mbedtls_ssl_close_notify returned %d\n", ret); break; } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b362e4b00..e9f4f903b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,15 +79,6 @@ set (lib_common_SRCS ./logging/log_storage.c ) -if(WITH_MBEDTLS) -set (lib_common_SRCS ${lib_common_SRCS} -./tls/mbedtls/tls_mbedtls.c -) - -list (APPEND lib_common_SRCS ${tls_SRCS}) - -endif(WITH_MBEDTLS) - set (lib_asn1c_SRCS ./mms/iso_mms/asn1c/DataAccessError.c ./mms/iso_mms/asn1c/DeleteNamedVariableListRequest.c diff --git a/src/mms/inc/iso_connection_parameters.h b/src/mms/inc/iso_connection_parameters.h index 8d9a054a5..81d340b27 100644 --- a/src/mms/inc/iso_connection_parameters.h +++ b/src/mms/inc/iso_connection_parameters.h @@ -28,7 +28,7 @@ extern "C" { #endif -#include "tls_api.h" +#include "tls_config.h" /** * \addtogroup mms_client_api_group diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index aa33938ca..d5bd40ed2 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -40,8 +40,7 @@ extern "C" { #include "mms_value.h" #include "iso_connection_parameters.h" #include "linked_list.h" - -#include "tls_api.h" +#include "tls_config.h" /** * Contains MMS layer specific parameters diff --git a/src/mms/inc_private/iso_server_private.h b/src/mms/inc_private/iso_server_private.h index 4ae788086..e700c9ece 100644 --- a/src/mms/inc_private/iso_server_private.h +++ b/src/mms/inc_private/iso_server_private.h @@ -24,8 +24,8 @@ #ifndef ISO_SERVER_PRIVATE_H_ #define ISO_SERVER_PRIVATE_H_ +#include "tls_config.h" #include "hal_socket.h" -#include "tls_api.h" IsoConnection IsoConnection_create(Socket socket, IsoServer isoServer); diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c index 8563270ce..3b2e80609 100644 --- a/src/mms/iso_client/iso_client_connection.c +++ b/src/mms/iso_client/iso_client_connection.c @@ -33,8 +33,9 @@ #include "iso_session.h" #include "iso_presentation.h" #include "iso_client_connection.h" + +#include "tls_config.h" #include "acse.h" -#include "tls_api.h" #ifndef DEBUG_ISO_CLIENT diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 93fb0d18a..49f8f0df7 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -28,14 +28,13 @@ #include "mms_client_internal.h" #include "stack_config.h" -#include "tls_api.h" - #include #include "byte_buffer.h" #include "ber_decode.h" #include +#include "tls_config.h" #define CONFIG_MMS_CONNECTION_DEFAULT_TIMEOUT 5000 #define CONFIG_MMS_CONNECTION_DEFAULT_CONNECT_TIMEOUT 10000 From 0b51d6841a4521b9e5f6ad233e090c937a2a2542 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 12 Aug 2018 08:59:17 +0200 Subject: [PATCH 098/123] - added Socket_activateTcpKeepAlive function --- CMakeLists.txt | 7 ---- hal/CMakeLists.txt | 8 +++++ hal/filesystem/linux/file_provider_linux.c | 1 - hal/filesystem/win32/file_provider_win32.c | 1 - hal/inc/hal_socket.h | 14 +++++++- hal/inc/hal_time.h | 2 ++ hal/inc/platform_endian.h | 6 ++-- hal/socket/bsd/socket_bsd.c | 34 +++++++----------- hal/socket/linux/socket_linux.c | 30 +++++----------- hal/socket/win32/socket_win32.c | 35 ++++++++----------- hal/time/unix/time.c | 5 +-- hal/time/win32/time.c | 5 +-- .../inc/libiec61850_platform_includes.h | 2 ++ src/iec61850/client/ied_connection.c | 3 +- src/iec61850/server/model/model.c | 1 + src/mms/iso_client/iso_client_connection.c | 7 ++++ src/mms/iso_server/iso_server.c | 7 ++++ 17 files changed, 80 insertions(+), 88 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4d457a75..c5c53a154 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,15 +126,8 @@ set(WITH_MBEDTLS 1) endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0) if(WITH_MBEDTLS) -include_directories( - ${CMAKE_CURRENT_LIST_DIR}/src/tls/mbedtls - ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0/include -) - -file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0/library/*.c) add_definitions(-DCONFIG_MMS_SUPPORT_TLS=1) -add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h") endif(WITH_MBEDTLS) diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt index a225a92ba..1441d5ac7 100644 --- a/hal/CMakeLists.txt +++ b/hal/CMakeLists.txt @@ -13,6 +13,14 @@ set(LIBHAL_VERSION_MAJOR "2") set(LIBHAL_VERSION_MINOR "0") set(LIBHAL_VERSION_PATCH "0") +# feature checks +include(CheckLibraryExists) +check_library_exists(rt clock_gettime "time.h" CONFIG_SYSTEM_HAS_CLOCK_GETTIME) + +# check if we are on a little or a big endian +include (TestBigEndian) +test_big_endian(PLATFORM_IS_BIGENDIAN) + if(WIN32) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib") diff --git a/hal/filesystem/linux/file_provider_linux.c b/hal/filesystem/linux/file_provider_linux.c index 23805da13..e9f03e87f 100644 --- a/hal/filesystem/linux/file_provider_linux.c +++ b/hal/filesystem/linux/file_provider_linux.c @@ -31,7 +31,6 @@ #include #include "hal_filesystem.h" -#include "stack_config.h" #include "lib_memory.h" struct sDirectoryHandle { diff --git a/hal/filesystem/win32/file_provider_win32.c b/hal/filesystem/win32/file_provider_win32.c index 8b9a8f984..1a08e8d19 100644 --- a/hal/filesystem/win32/file_provider_win32.c +++ b/hal/filesystem/win32/file_provider_win32.c @@ -32,7 +32,6 @@ #include "hal_filesystem.h" #include "platform_endian.h" -#include "stack_config.h" #include "lib_memory.h" #include diff --git a/hal/inc/hal_socket.h b/hal/inc/hal_socket.h index 41ec55de9..e1d02bb6c 100644 --- a/hal/inc/hal_socket.h +++ b/hal/inc/hal_socket.h @@ -147,9 +147,21 @@ ServerSocket_listen(ServerSocket self); Socket ServerSocket_accept(ServerSocket self); +/** + * \brief active TCP keep alive for socket and set keep alive parameters + * + * NOTE: implementation is mandatory for IEC 61850 MMS + * + * \param self server socket instance + * \param idleTime time (in s) between last received message and first keep alive message + * \param interval time (in s) between subsequent keep alive messages if no ACK received + * \param count number of not missing keep alive ACKs until socket is considered dead + */ +void +Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count); /** - * \brief set the maximum number of pending connection in the queue + * \brief set the maximum number of pending connections in the queue * * Implementation of this function is OPTIONAL. * diff --git a/hal/inc/hal_time.h b/hal/inc/hal_time.h index 121649014..5ac8080e7 100644 --- a/hal/inc/hal_time.h +++ b/hal/inc/hal_time.h @@ -28,6 +28,8 @@ extern "C" { #endif +#include + /** * \file hal_time.h * \brief Abstraction layer for system time access diff --git a/hal/inc/platform_endian.h b/hal/inc/platform_endian.h index 9bc7a5af0..76c410af5 100644 --- a/hal/inc/platform_endian.h +++ b/hal/inc/platform_endian.h @@ -1,7 +1,7 @@ /* - * endian.h + * platform_endian.h * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -24,8 +24,6 @@ #ifndef ENDIAN_H_ #define ENDIAN_H_ -#include "stack_config.h" - #ifndef PLATFORM_IS_BIGENDIAN #ifdef __GNUC__ #ifdef __BYTE_ORDER__ diff --git a/hal/socket/bsd/socket_bsd.c b/hal/socket/bsd/socket_bsd.c index 6207c71a6..18079db7a 100644 --- a/hal/socket/bsd/socket_bsd.c +++ b/hal/socket/bsd/socket_bsd.c @@ -1,7 +1,8 @@ /* * socket_bsd.c * - * Copyright 2013, 2014 Michael Zillgith, contributions by Michael Clausen (School of engineering Valais). + * Copyright 2013-2018 Michael Zillgith + * contributions by Michael Clausen (School of engineering Valais). * * This file is part of libIEC61850. * @@ -36,8 +37,7 @@ #include // required for TCP keepalive #include "hal_thread.h" - -#include "libiec61850_platform_includes.h" +#include "lib_memory.h" #ifndef DEBUG_SOCKET #define DEBUG_SOCKET 0 @@ -113,30 +113,28 @@ Handleset_destroy(HandleSet self) GLOBAL_FREEMEM(self); } -#if (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) -static void -activateKeepAlive(int sd) +void +Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count) { #if defined SO_KEEPALIVE int optval; socklen_t optlen = sizeof(optval); - optval = CONFIG_TCP_KEEPALIVE_IDLE; - setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); + optval = idleTime; + setsockopt(self->fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); optval = 1; - setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &optval, optlen); + setsockopt(self->fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, optlen); #if defined TCP_KEEPCNT - optval = CONFIG_TCP_KEEPALIVE_INTERVAL; - setsockopt(sd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen); + optval = interval; + setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen); - optval = CONFIG_TCP_KEEPALIVE_CNT; - setsockopt(sd, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen); + optval = count; + setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen); #endif /* TCP_KEEPCNT */ #endif /* SO_KEEPALIVE */ } -#endif /* (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) */ static bool prepareServerAddress(const char* address, int port, struct sockaddr_in* sockaddr) @@ -196,10 +194,6 @@ TcpServerSocket_create(const char* address, int port) return NULL ; } -#if CONFIG_ACTIVATE_TCP_KEEPALIVE == 1 - activateKeepAlive(fd); -#endif - setSocketNonBlocking((Socket) serverSocket); } @@ -294,10 +288,6 @@ Socket_connect(Socket self, const char* address, int port) self->fd = socket(AF_INET, SOCK_STREAM, 0); -#if CONFIG_ACTIVATE_TCP_KEEPALIVE == 1 - activateKeepAlive(self->fd); -#endif - fd_set fdSet; FD_ZERO(&fdSet); FD_SET(self->fd, &fdSet); diff --git a/hal/socket/linux/socket_linux.c b/hal/socket/linux/socket_linux.c index 2dded7545..69fca6a68 100644 --- a/hal/socket/linux/socket_linux.c +++ b/hal/socket/linux/socket_linux.c @@ -32,14 +32,10 @@ #include #include #include - #include - #include // required for TCP keepalive #include "hal_thread.h" - -#include "stack_config.h" #include "lib_memory.h" #ifndef DEBUG_SOCKET @@ -115,25 +111,25 @@ Handleset_destroy(HandleSet self) GLOBAL_FREEMEM(self); } -static void -activateKeepAlive(int sd) +void +Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count) { #if defined SO_KEEPALIVE int optval; socklen_t optlen = sizeof(optval); optval = 1; - setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); + setsockopt(self->fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); #if defined TCP_KEEPCNT - optval = CONFIG_TCP_KEEPALIVE_IDLE; - setsockopt(sd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, optlen); + optval = idleTime; + setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, optlen); - optval = CONFIG_TCP_KEEPALIVE_INTERVAL; - setsockopt(sd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen); + optval = interval; + setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen); - optval = CONFIG_TCP_KEEPALIVE_CNT; - setsockopt(sd, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen); + optval = count; + setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen); #endif /* TCP_KEEPCNT */ #endif /* SO_KEEPALIVE */ @@ -217,10 +213,6 @@ TcpServerSocket_create(const char* address, int port) close(fd); return NULL ; } - -#if CONFIG_ACTIVATE_TCP_KEEPALIVE == 1 - activateKeepAlive(fd); -#endif } return serverSocket; @@ -326,10 +318,6 @@ Socket_connect(Socket self, const char* address, int port) activateTcpNoDelay(self); -#if (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) - activateKeepAlive(self->fd); -#endif - fcntl(self->fd, F_SETFL, O_NONBLOCK); if (connect(self->fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) { diff --git a/hal/socket/win32/socket_win32.c b/hal/socket/win32/socket_win32.c index 9cfc02c6b..c686ddc7a 100644 --- a/hal/socket/win32/socket_win32.c +++ b/hal/socket/win32/socket_win32.c @@ -1,7 +1,7 @@ /* * socket_win32.c * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -116,25 +116,26 @@ Handleset_destroy(HandleSet self) static bool wsaStartupCalled = false; static int socketCount = 0; -static void -activateKeepAlive(SOCKET s) +void +Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count) { - struct tcp_keepalive keepalive; - DWORD retVal=0; + 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.onoff = 1; + keepalive.keepalivetime = CONFIG_TCP_KEEPALIVE_IDLE * 1000; + keepalive.keepaliveinterval = CONFIG_TCP_KEEPALIVE_INTERVAL * 1000; - if (WSAIoctl(s, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), - NULL, 0, &retVal, NULL, NULL) == SOCKET_ERROR) - { - if (DEBUG_SOCKET) + if (WSAIoctl(self->fd, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), + NULL, 0, &retVal, NULL, NULL) == SOCKET_ERROR) + { + if (DEBUG_SOCKET) printf("WIN32_SOCKET: WSAIotcl(SIO_KEEPALIVE_VALS) failed: %d\n", WSAGetLastError()); - } + } } + static void setSocketNonBlocking(Socket self) { @@ -223,10 +224,6 @@ TcpServerSocket_create(const char* address, int port) listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); -#if CONFIG_ACTIVATE_TCP_KEEPALIVE == 1 - activateKeepAlive(listen_socket); -#endif - if (listen_socket == INVALID_SOCKET) { if (DEBUG_SOCKET) printf("WIN32_SOCKET: socket failed with error: %i\n", WSAGetLastError()); @@ -335,10 +332,6 @@ Socket_connect(Socket self, const char* address, int port) self->fd = socket(AF_INET, SOCK_STREAM, 0); -#if CONFIG_ACTIVATE_TCP_KEEPALIVE == 1 - activateKeepAlive(self->fd); -#endif - setSocketNonBlocking(self); fd_set fdSet; diff --git a/hal/time/unix/time.c b/hal/time/unix/time.c index d4a1a2cd2..f7951fde0 100644 --- a/hal/time/unix/time.c +++ b/hal/time/unix/time.c @@ -21,10 +21,7 @@ * See COPYING file for the complete license text. */ - -#include "libiec61850_platform_includes.h" - -#include "stack_config.h" +#include "hal_time.h" #include diff --git a/hal/time/win32/time.c b/hal/time/win32/time.c index 1af39b5f8..af58793fe 100644 --- a/hal/time/win32/time.c +++ b/hal/time/win32/time.c @@ -21,10 +21,7 @@ * See COPYING file for the complete license text. */ - -#include "libiec61850_platform_includes.h" - -#include "stack_config.h" +#include "hal_time.h" #include diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index 41e12b397..f2770139a 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -5,6 +5,8 @@ #ifndef LIBIEC61850_PLATFORM_INCLUDES_H_ #define LIBIEC61850_PLATFORM_INCLUDES_H_ +#include "stack_config.h" + #include "libiec61850_common_api.h" #include "string_utilities.h" diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 269474adc..f271dadcb 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -21,12 +21,11 @@ * See COPYING file for the complete license text. */ +#include "stack_config.h" #include "libiec61850_platform_includes.h" #include "iec61850_client.h" -#include "stack_config.h" - #include "mms_client_connection.h" #include "ied_connection_private.h" diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 0b5f84b7f..c6d06c6d3 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -23,6 +23,7 @@ #include "iec61850_model.h" +#include "stack_config.h" #include "libiec61850_platform_includes.h" static void diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c index 3b2e80609..4e5a315ae 100644 --- a/src/mms/iso_client/iso_client_connection.c +++ b/src/mms/iso_client/iso_client_connection.c @@ -289,6 +289,13 @@ IsoClientConnection_associate(IsoClientConnection self, IsoConnectionParameters Socket_setConnectTimeout(self->socket, connectTimeoutInMs); +#if (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) + Socket_activateTcpKeepAlive(self->socket, + CONFIG_TCP_KEEPALIVE_IDLE, + CONFIG_TCP_KEEPALIVE_INTERVAL, + CONFIG_TCP_KEEPALIVE_CNT); +#endif + if (!Socket_connect(self->socket, params->hostname, params->tcpPort)) goto returnError; diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index 1b5628b9b..f36af1dde 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -342,6 +342,13 @@ setupIsoServer(IsoServer self) goto exit_function; } +#if (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) + Socket_activateTcpKeepAlive(self->serverSocket, + CONFIG_TCP_KEEPALIVE_IDLE, + CONFIG_TCP_KEEPALIVE_INTERVAL, + CONFIG_TCP_KEEPALIVE_CNT); +#endif + ServerSocket_setBacklog((ServerSocket) self->serverSocket, BACKLOG); ServerSocket_listen((ServerSocket) self->serverSocket); From 58b4d6c107dd884a28c23fa69183a90571473b5a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 13 Aug 2018 11:51:36 +0200 Subject: [PATCH 099/123] -IEC 61850 server: added ReadAccessHandler to control read access --- config/stack_config.h | 3 + config/stack_config.h.cmake | 3 + .../server_example_password_auth.c | 18 ++++ src/common/inc/linked_list.h | 5 +- src/iec61850/inc/iec61850_server.h | 63 +++++++++++++- src/iec61850/inc_private/mms_mapping.h | 3 + .../inc_private/mms_mapping_internal.h | 5 ++ src/iec61850/server/impl/ied_server.c | 6 ++ src/iec61850/server/mms_mapping/mms_mapping.c | 85 ++++++++++++++++++- 9 files changed, 184 insertions(+), 7 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index 3e79f1211..07917e0be 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -161,6 +161,9 @@ /* include support for IEC 61850 log services */ #define CONFIG_IEC61850_LOG_SERVICE 1 +/* allow user to control read access by callback */ +#define CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL 1 + /* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ #define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 621c359ff..b3fcadf7c 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -151,6 +151,9 @@ /* include support for IEC 61850 log services */ #cmakedefine01 CONFIG_IEC61850_LOG_SERVICE +/* allow user to control read access by callback */ +#cmakedefine01 CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL + /* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ #define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 diff --git a/examples/server_example_password_auth/server_example_password_auth.c b/examples/server_example_password_auth/server_example_password_auth.c index 46715b1bc..0e94522fb 100644 --- a/examples/server_example_password_auth/server_example_password_auth.c +++ b/examples/server_example_password_auth/server_example_password_auth.c @@ -143,6 +143,21 @@ writeAccessHandler (DataAttribute* dataAttribute, MmsValue* value, ClientConnect return DATA_ACCESS_ERROR_SUCCESS; } +static MmsDataAccessError +readAccessHandler(LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject, FunctionalConstraint fc, ClientConnection connection, void* parameter) +{ + void* securityToken = ClientConnection_getSecurityToken(connection); + + if (securityToken != password2) { + + if ((dataObject == IEDMODEL_GenericIO_GGIO1_Ind1) || (dataObject == IEDMODEL_GenericIO_GGIO1_Ind2)) { + printf(" Access denied\n"); + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + } + } + + return DATA_ACCESS_ERROR_SUCCESS; +} int main(int argc, char** argv) { @@ -178,6 +193,9 @@ int main(int argc, char** argv) { /* Set write access handler */ IedServer_handleWriteAccess(iedServer, IEDMODEL_GenericIO_LLN0_ModAuto_setVal, writeAccessHandler, NULL); + /* Set read access handler */ + IedServer_setReadAccessHandler(iedServer, readAccessHandler, NULL); + /* MMS server will be instructed to start listening to client connections. */ IedServer_start(iedServer, 102); diff --git a/src/common/inc/linked_list.h b/src/common/inc/linked_list.h index 1c8a6848e..4cb6b4519 100644 --- a/src/common/inc/linked_list.h +++ b/src/common/inc/linked_list.h @@ -1,7 +1,7 @@ /* * linked_list.h * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -24,7 +24,8 @@ #ifndef LINKED_LIST_H_ #define LINKED_LIST_H_ -#include "libiec61850_common_api.h" +#include +#include #ifdef __cplusplus extern "C" { diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index d5f7b954d..7e146468c 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -94,6 +94,12 @@ IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize); int IedServerConfig_getReportBufferSize(IedServerConfig self); +void +IedServerConfig_setMaxMmsConnections(IedServerConfig self, int maxConnections); + +int +IedServerConfig_getMaxMmsConnections(IedServerConfig self); + /** * \brief Set the basepath of the file services * @@ -124,6 +130,12 @@ IedServerConfig_enableFileService(IedServerConfig self, bool enable); bool IedServerConfig_isFileServiceEnabled(IedServerConfig self); +void +IedServerConfig_enableFileWriteService(IedServerConfig self, bool enable); + +bool +IedServerConfig_isFileWriteServiceEnabled(IedServerConfig self); + /** * \brief Enable/disable the dynamic data set service for MMS * @@ -140,6 +152,22 @@ IedServerConfig_enableDynamicDataSetService(IedServerConfig self, bool enable); bool IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self); + +void +IedServerConfig_setMaxAssociationSpecificDataSets(IedServerConfig self, int maxDataSets); + +void +IedServerConfig_setMaxDomainSpecificDataSets(IedServerConfig self, int maxDataSets); + +void +IedServerConfig_setMaxDataSetEntries(IedServerConfig self, int maxDataSetEntries); + +void +IedServerConfig_enableWriteDataSetService(IedServerConfig self, bool enable); + +bool +IedServerConfig_isWriteDataSetServiceEnabled(IedServerConfig self); + /** * \brief Enable/disable the log service for MMS * @@ -1135,7 +1163,7 @@ IedServer_setSVCBHandler(IedServer self, SVControlBlock* svcb, SVCBEventHandler **************************************************************************/ /** - * \brief callback handler to intercept/control client access to data attributes + * \brief callback handler to intercept/control client write access to data attributes * * User provided callback function to intercept/control MMS client access to * IEC 61850 data attributes. The application can install the same handler @@ -1150,7 +1178,7 @@ IedServer_setSVCBHandler(IedServer self, SVControlBlock* svcb, SVCBEventHandler * \param connection the connection object of the client connection that invoked the write operation * \param parameter the user provided parameter * - * \return true if access is accepted, false if access is denied. + * \return DATA_ACCESS_ERROR_SUCCESS if access is accepted, DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED if access is denied. */ typedef MmsDataAccessError (*WriteAccessHandler) (DataAttribute* dataAttribute, MmsValue* value, ClientConnection connection, void* parameter); @@ -1190,6 +1218,37 @@ typedef enum { void IedServer_setWriteAccessPolicy(IedServer self, FunctionalConstraint fc, AccessPolicy policy); +/** + * \brief callback handler to control client read access to data attributes + * + * User provided callback function to control MMS client read access to IEC 61850 + * data objects. The application is to allow read access to data objects for specific clients only. + * It can be used to implement a role based access control (RBAC). + * + * \param ld the logical device the client wants to access + * \param ln the logical node the client wants to access + * \param dataObject the data object the client wants to access + * \param fc the functional constraint of the access + * \param connection the client connection that causes the access + * \param parameter the user provided parameter + * + * \return DATA_ACCESS_ERROR_SUCCESS if access is accepted, DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED if access is denied. + */ +typedef MmsDataAccessError +(*ReadAccessHandler) (LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject, FunctionalConstraint fc, ClientConnection connection, void* parameter); + +/** + * \brief Install the global read access handler + * + * The read access handler will be called for every read access before the server grants access to the client. + * + * \param self the instance of IedServer to operate on. + * \param handler the callback function that is invoked if a client tries to read a data object. + * \param parameter a user provided parameter that is passed to the callback function. + */ +void +IedServer_setReadAccessHandler(IedServer self, ReadAccessHandler handler, void* parameter); + /**@}*/ /**@}*/ diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index a97e51d69..9c6f3cea3 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -145,6 +145,9 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto void MmsMapping_installWriteAccessHandler(MmsMapping* self, DataAttribute* dataAttribute, WriteAccessHandler handler, void* parameter); +void +MmsMapping_installReadAccessHandler(MmsMapping* self, ReadAccessHandler handler, void* paramter); + MmsDataAccessError Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, MmsValue* value, MmsServerConnection connection); diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index f136a39ac..24cc22c1d 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -54,6 +54,11 @@ struct sMmsMapping { LinkedList attributeAccessHandlers; +#if (CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL == 1) + ReadAccessHandler readAccessHandler; + void* readAccessHandlerParameter; +#endif + #if (CONFIG_IEC61850_SETTING_GROUPS == 1) LinkedList settingGroups; #endif diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index c5f9a7452..d93d8f680 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -1329,6 +1329,12 @@ IedServer_handleWriteAccess(IedServer self, DataAttribute* dataAttribute, WriteA MmsMapping_installWriteAccessHandler(self->mmsMapping, dataAttribute, handler, parameter); } +void +IedServer_setReadAccessHandler(IedServer self, ReadAccessHandler handler, void* parameter) +{ + MmsMapping_installReadAccessHandler(self->mmsMapping, handler, parameter); +} + void IedServer_setConnectionIndicationHandler(IedServer self, IedConnectionIndicationHandler handler, void* parameter) { diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index e7958bb69..c6e28b0ed 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2175,6 +2175,15 @@ MmsMapping_installWriteAccessHandler(MmsMapping* self, DataAttribute* dataAttrib accessHandler->handler = handler; } +void +MmsMapping_installReadAccessHandler(MmsMapping* self, ReadAccessHandler handler, void* parameter) +{ +#if (CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL == 1) + self->readAccessHandler = handler; + self->readAccessHandlerParameter = parameter; +#endif +} + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) static MmsValue* @@ -2334,6 +2343,9 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo } #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ + /* handle read access to other objects */ + + exit_function: return retValue; } @@ -2419,9 +2431,6 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS char* separator = strchr(variableId, '$'); - if (separator == NULL) - return DATA_ACCESS_ERROR_SUCCESS; - #if (CONFIG_IEC61850_SETTING_GROUPS == 1) if (isFunctionalConstraintSE(separator)) { @@ -2437,6 +2446,76 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS #endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */ +#if (CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL == 1) + if (self->readAccessHandler != NULL) + { + char* ldName = MmsDomain_getName(domain); + + LogicalDevice* ld = IedModel_getDevice(self->model, ldName); + + if (ld != NULL) { + + char str[65]; + + FunctionalConstraint fc; + + if (separator != NULL) { + fc = FunctionalConstraint_fromString(separator + 1); + + if (fc == IEC61850_FC_BR || fc == IEC61850_FC_US || + fc == IEC61850_FC_MS || fc == IEC61850_FC_RP || + fc == IEC61850_FC_LG) + { + return DATA_ACCESS_ERROR_SUCCESS; + } + else { + + StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) variableId, separator - variableId); + + LogicalNode* ln = LogicalDevice_getLogicalNode(ld, str); + + if (ln != NULL) { + + + char* doStart = strchr(separator + 1, '$'); + + + if (doStart != NULL) { + + char* doEnd = strchr(doStart + 1, '$'); + + if (doEnd == NULL) { + StringUtils_copyStringToBuffer(doStart + 1, str); + } + else { + doEnd--; + + StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) (doStart + 1), doEnd - doStart); + } + + ModelNode* dobj = ModelNode_getChild((ModelNode*) ln, str); + + if (dobj != NULL) { + + if (dobj->modelType == DataObjectModelType) { + + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, + connection); + + return self->readAccessHandler(ld, ln, (DataObject*) dobj, fc, clientConnection, + self->readAccessHandlerParameter); + } + } + } + } + } + } + } + + return DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; + } +#endif /* CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL */ + return DATA_ACCESS_ERROR_SUCCESS; } From 3f07176dc377203c188d767aec03f7de1cb923c2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 20 Aug 2018 11:51:02 +0200 Subject: [PATCH 100/123] - MMS server: fixed bug in delete variable list service - scope of delete was not considered optional --- src/mms/iso_mms/server/mms_named_variable_list_service.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mms/iso_mms/server/mms_named_variable_list_service.c b/src/mms/iso_mms/server/mms_named_variable_list_service.c index 9192f68bf..469bc3d30 100644 --- a/src/mms/iso_mms/server/mms_named_variable_list_service.c +++ b/src/mms/iso_mms/server/mms_named_variable_list_service.c @@ -130,9 +130,10 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList); - long scopeOfDelete; + long scopeOfDelete = DeleteNamedVariableListRequest__scopeOfDelete_specific; - asn_INTEGER2long(request->scopeOfDelete, &scopeOfDelete); + if (request->scopeOfDelete) + asn_INTEGER2long(request->scopeOfDelete, &scopeOfDelete); MmsDevice* device = MmsServer_getDevice(connection->server); From 6618093f7a152c3e8e7748728d24b5875ef0ffc6 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 22 Aug 2018 10:29:09 +0200 Subject: [PATCH 101/123] - IEC 61850 server: functions IedModel_getModelNodeByShortObjectReference and IedModel_getModelNodeByObjectReference now also work with object references that have only LD part --- src/iec61850/server/model/model.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index c6d06c6d3..79a8db19b 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -3,7 +3,7 @@ * * Copyright 2013 Michael Zillgith * - * This file is part of libIEC61850. + * This file is part of libIEC61850. * * libIEC61850 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -302,15 +302,16 @@ IedModel_getModelNodeByObjectReference(IedModel* model, const char* objectRefere char* separator = strchr(objRef, '/'); - if (separator == NULL) - return NULL; - - *separator = 0; + if (separator != NULL) + *separator = 0; LogicalDevice* ld = IedModel_getDevice(model, objRef); if (ld == NULL) return NULL; + if ((separator == NULL) || (*(separator + 1) == 0)) + return (ModelNode*) ld; + return ModelNode_getChild((ModelNode*) ld, separator + 1); } @@ -350,10 +351,8 @@ IedModel_getModelNodeByShortObjectReference(IedModel* model, const char* objectR char* separator = strchr(objRef, '/'); - if (separator == NULL) - return NULL; - - *separator = 0; + if (separator != NULL) + *separator = 0; char ldName[65]; strcpy(ldName, model->name); @@ -363,6 +362,9 @@ IedModel_getModelNodeByShortObjectReference(IedModel* model, const char* objectR if (ld == NULL) return NULL; + if ((separator == NULL) || (*(separator + 1) == 0)) + return (ModelNode*) ld; + return ModelNode_getChild((ModelNode*) ld, separator + 1); } From 713ca54cd0ad20d19c3dbf66c9bfd8e738e1aea3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 25 Aug 2018 14:03:42 +0200 Subject: [PATCH 102/123] - IEC 61850 server: optimized dynamic memory management in control handling; memory will be initialized at server start --- src/iec61850/inc_private/control.h | 97 +++++-- src/iec61850/inc_private/mms_mapping.h | 3 + src/iec61850/server/impl/ied_server.c | 17 +- src/iec61850/server/mms_mapping/control.c | 239 ++++-------------- src/iec61850/server/mms_mapping/mms_mapping.c | 14 + 5 files changed, 149 insertions(+), 221 deletions(-) diff --git a/src/iec61850/inc_private/control.h b/src/iec61850/inc_private/control.h index cd127b028..3d42ad1d0 100644 --- a/src/iec61850/inc_private/control.h +++ b/src/iec61850/inc_private/control.h @@ -25,29 +25,89 @@ #define CONTROL_H_ #include "iec61850_model.h" +#include "iec61850_server.h" #include "mms_server_connection.h" #include "mms_device_model.h" -#include "iec61850_server.h" + +#include "mms_mapping_internal.h" +#include "mms_client_connection.h" + +#include "libiec61850_platform_includes.h" typedef struct sControlObject ControlObject; -ControlObject* -ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* name); +struct sControlObject +{ + MmsDomain* mmsDomain; + IedServer iedServer; + char* lnName; + char* name; -void -ControlObject_destroy(ControlObject* self); + int state; -void -ControlObject_setOper(ControlObject* self, MmsValue* oper); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore stateLock; +#endif -void -ControlObject_setCancel(ControlObject* self, MmsValue* cancel); + MmsValue* mmsValue; + MmsVariableSpecification* typeSpec; + + MmsValue* oper; + MmsValue* sbo; + MmsValue* sbow; + MmsValue* cancel; + + MmsValue* ctlVal; + MmsValue* ctlNum; + MmsValue* origin; + MmsValue* timestamp; + + char ctlObjectName[130]; + + /* for LastAppIError */ + MmsValue* error; + MmsValue* addCause; + + bool selected; + uint64_t selectTime; + uint32_t selectTimeout; + MmsValue* sboClass; + MmsValue* sboTimeout; + + bool timeActivatedOperate; + uint64_t operateTime; + + bool operateOnce; + MmsServerConnection mmsConnection; + + MmsValue* emptyString; + + uint32_t ctlModel; + + bool testMode; + bool interlockCheck; + bool synchroCheck; + + uint32_t operateInvokeId; + + ControlHandler operateHandler; + void* operateHandlerParameter; + + ControlPerformCheckHandler checkHandler; + void* checkHandlerParameter; + + ControlWaitForExecutionHandler waitForExecutionHandler; + void* waitForExecutionHandlerParameter; +}; + +ControlObject* +ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* name); void -ControlObject_setSBO(ControlObject* self, MmsValue* sbo); +ControlObject_initialize(ControlObject* self); void -ControlObject_setSBOw(ControlObject* self, MmsValue* sbow); +ControlObject_destroy(ControlObject* self); void ControlObject_setMmsValue(ControlObject* self, MmsValue* value); @@ -61,21 +121,6 @@ ControlObject_setTypeSpec(ControlObject* self, MmsVariableSpecification* typeSpe MmsVariableSpecification* ControlObject_getTypeSpec(ControlObject* self); -MmsValue* -ControlObject_getOper(ControlObject* self); - -MmsValue* -ControlObject_getSBOw(ControlObject* self); - -MmsValue* -ControlObject_getSBO(ControlObject* self); - -MmsValue* -ControlObject_getCancel(ControlObject* self); - -void -ControlObject_setCtlVal(ControlObject* self, MmsValue* ctlVal); - char* ControlObject_getName(ControlObject* self); diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index 9c6f3cea3..56935060c 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -49,6 +49,9 @@ MmsMapping_create(IedModel* model, IedServer iedServer); MmsDevice* MmsMapping_getMmsDeviceModel(MmsMapping* mapping); +void +MmsMapping_initializeControlObjects(MmsMapping* self); + void MmsMapping_configureSettingGroups(MmsMapping* self); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index d93d8f680..16686fc57 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -119,18 +119,13 @@ createControlObjects(IedServer self, MmsDomain* domain, char* lnName, MmsVariabl ControlObject_setTypeSpec(controlObject, coSpec); - MmsValue* operVal = MmsValue_getElement(structure, operIndex); - ControlObject_setOper(controlObject, operVal); + controlObject->oper = MmsValue_getElement(structure, operIndex); - if (hasCancel) { - MmsValue* cancelVal = MmsValue_getElement(structure, cancelIndex); - ControlObject_setCancel(controlObject, cancelVal); - } + if (hasCancel) + controlObject->cancel = MmsValue_getElement(structure, cancelIndex); - if (hasSBOw) { - MmsValue* sbowVal = MmsValue_getElement(structure, sBOwIndex); - ControlObject_setSBOw(controlObject, sbowVal); - } + if (hasSBOw) + controlObject->sbow = MmsValue_getElement(structure, sBOwIndex); MmsMapping_addControlObject(mapping, controlObject); } @@ -457,6 +452,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio /* 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); + #if (CONFIG_IEC61850_REPORT_SERVICE == 1) Reporting_activateBufferedReports(self->mmsMapping); #endif diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index b761e3ce5..6eec9335e 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 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -22,17 +22,12 @@ */ #include "control.h" -#include "mms_mapping.h" -#include "mms_mapping_internal.h" - -#include "mms_client_connection.h" +#include "mms_mapping.h" +#include "iec61850_server.h" #include "ied_server_private.h" - #include "mms_value_internal.h" -#include "libiec61850_platform_includes.h" - #if (CONFIG_IEC61850_CONTROL_SERVICE == 1) #ifndef DEBUG_IED_SERVER @@ -51,71 +46,6 @@ #define STATE_WAIT_FOR_EXECUTION 4 #define STATE_OPERATE 5 -struct sControlObject -{ - MmsDomain* mmsDomain; - IedServer iedServer; - char* lnName; - char* name; - - int state; - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore stateLock; -#endif - - MmsValue* mmsValue; - MmsVariableSpecification* typeSpec; - - MmsValue* oper; - MmsValue* sbo; - MmsValue* sbow; - MmsValue* cancel; - - MmsValue* ctlVal; - MmsValue* ctlNum; - MmsValue* origin; - MmsValue* timestamp; - - char* ctlObjectName; - - /* for LastAppIError */ - MmsValue* error; - MmsValue* addCause; - - bool selected; - uint64_t selectTime; - uint32_t selectTimeout; - MmsValue* sboClass; - MmsValue* sboTimeout; - - bool timeActivatedOperate; - uint64_t operateTime; - - bool operateOnce; - MmsServerConnection mmsConnection; - - MmsValue* emptyString; - - bool initialized; - uint32_t ctlModel; - - bool testMode; - bool interlockCheck; - bool synchroCheck; - - uint32_t operateInvokeId; - - ControlHandler operateHandler; - void* operateHandlerParameter; - - ControlPerformCheckHandler checkHandler; - void* checkHandlerParameter; - - ControlWaitForExecutionHandler waitForExecutionHandler; - void* waitForExecutionHandlerParameter; -}; - void ControlObject_sendLastApplError(ControlObject* self, MmsServerConnection connection, char* ctlVariable, int error, ControlAddCause addCause, MmsValue* ctlNum, MmsValue* origin, bool handlerMode); @@ -183,78 +113,68 @@ updateSboTimeoutValue(ControlObject* self) static void initialize(ControlObject* self) { - if (!(self->initialized)) { - - MmsServer mmsServer = IedServer_getMmsServer(self->iedServer); + MmsServer mmsServer = IedServer_getMmsServer(self->iedServer); - self->emptyString = MmsValue_newVisibleString(NULL); + self->emptyString = MmsValue_newVisibleString(NULL); - char* ctlModelName = StringUtils_createString(4, self->lnName, "$CF$", self->name, "$ctlModel"); + char strBuf[129]; - if (DEBUG_IED_SERVER) - printf("initialize control for %s\n", ctlModelName); - - MmsValue* ctlModel = MmsServer_getValueFromCache(mmsServer, - self->mmsDomain, ctlModelName); - - if (ctlModel == NULL) { - if (DEBUG_IED_SERVER) - printf("No control model found for variable %s\n", ctlModelName); - } + char* ctlModelName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$ctlModel"); - GLOBAL_FREEMEM(ctlModelName); + if (DEBUG_IED_SERVER) + printf("initialize control for %s\n", ctlModelName); - char* sboClassName = StringUtils_createString(4, self->lnName, "$CF$", self->name, "$sboClass"); + MmsValue* ctlModel = MmsServer_getValueFromCache(mmsServer, + self->mmsDomain, ctlModelName); - self->sboClass = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, sboClassName); + if (ctlModel == NULL) { + if (DEBUG_IED_SERVER) + printf("No control model found for variable %s\n", ctlModelName); + } - GLOBAL_FREEMEM(sboClassName); + char* sboClassName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$sboClass"); - self->ctlObjectName = (char*) GLOBAL_MALLOC(130); + self->sboClass = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, sboClassName); - StringUtils_createStringInBuffer(self->ctlObjectName, 5, MmsDomain_getName(self->mmsDomain), "/", - self->lnName, "$CO$", self->name); + StringUtils_createStringInBuffer(self->ctlObjectName, 5, MmsDomain_getName(self->mmsDomain), "/", + self->lnName, "$CO$", self->name); - self->error = MmsValue_newIntegerFromInt32(0); - self->addCause = MmsValue_newIntegerFromInt32(0); + self->error = MmsValue_newIntegerFromInt32(0); + self->addCause = MmsValue_newIntegerFromInt32(0); - if (ctlModel != NULL) { - uint32_t ctlModelVal = MmsValue_toInt32(ctlModel); + if (ctlModel != NULL) { + uint32_t ctlModelVal = MmsValue_toInt32(ctlModel); - self->ctlModel = ctlModelVal; + self->ctlModel = ctlModelVal; - if (DEBUG_IED_SERVER) - printf(" ctlModel: %i\n", ctlModelVal); + if (DEBUG_IED_SERVER) + printf(" ctlModel: %i\n", ctlModelVal); - if ((ctlModelVal == 2) || (ctlModelVal == 4)) { /* SBO */ - char* sboTimeoutName = StringUtils_createString(4, self->lnName, "$CF$", self->name, "$sboTimeout"); + if ((ctlModelVal == 2) || (ctlModelVal == 4)) { /* SBO */ - char* controlObjectReference = StringUtils_createString(6, self->mmsDomain->domainName, "/", self->lnName, "$", - self->name, "$SBO"); - self->sbo = MmsValue_newVisibleString(controlObjectReference); + char* controlObjectReference = StringUtils_createStringInBuffer(strBuf, 6, self->mmsDomain->domainName, + "/", self->lnName, "$", self->name, "$SBO"); - self->sboTimeout = MmsServer_getValueFromCache(mmsServer, - self->mmsDomain, sboTimeoutName); + self->sbo = MmsValue_newVisibleString(controlObjectReference); - updateSboTimeoutValue(self); + char* sboTimeoutName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$sboTimeout"); - setState(self, STATE_UNSELECTED); + self->sboTimeout = MmsServer_getValueFromCache(mmsServer, + self->mmsDomain, sboTimeoutName); - if (DEBUG_IED_SERVER) - printf("timeout for %s is %i\n", sboTimeoutName, self->selectTimeout); + updateSboTimeoutValue(self); - GLOBAL_FREEMEM(controlObjectReference); - GLOBAL_FREEMEM(sboTimeoutName); - } - else { - self->sbo = MmsValue_newVisibleString(NULL); + setState(self, STATE_UNSELECTED); - setState(self, STATE_READY); - } + if (DEBUG_IED_SERVER) + printf("timeout for %s is %i\n", sboTimeoutName, self->selectTimeout); } + else { + self->sbo = MmsValue_newVisibleString(NULL); - self->initialized = true; + setState(self, STATE_READY); + } } } @@ -467,6 +387,12 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* return self; } +void +ControlObject_initialize(ControlObject* self) +{ + initialize(self); +} + void ControlObject_destroy(ControlObject* self) { @@ -479,9 +405,6 @@ ControlObject_destroy(ControlObject* self) if (self->emptyString != NULL) MmsValue_delete(self->emptyString); - if (self->ctlObjectName != NULL) - GLOBAL_FREEMEM(self->ctlObjectName); - if (self->error != NULL) MmsValue_delete(self->error); @@ -508,36 +431,6 @@ ControlObject_destroy(ControlObject* self) GLOBAL_FREEMEM(self); } -void -ControlObject_setOper(ControlObject* self, MmsValue* oper) -{ - self->oper = oper; -} - -void -ControlObject_setCancel(ControlObject* self, MmsValue* cancel) -{ - self->cancel = cancel; -} - -void -ControlObject_setSBOw(ControlObject* self, MmsValue* sbow) -{ - self->sbow = sbow; -} - -void -ControlObject_setSBO(ControlObject* self, MmsValue* sbo) -{ - self->sbo = sbo; -} - -void -ControlObject_setCtlVal(ControlObject* self, MmsValue* ctlVal) -{ - self->ctlVal = ctlVal; -} - char* ControlObject_getName(ControlObject* self) { @@ -556,30 +449,6 @@ ControlObject_getDomain(ControlObject* self) return self->mmsDomain; } -MmsValue* -ControlObject_getOper(ControlObject* self) -{ - return self->oper; -} - -MmsValue* -ControlObject_getSBOw(ControlObject* self) -{ - return self->sbow; -} - -MmsValue* -ControlObject_getSBO(ControlObject* self) -{ - return self->sbo; -} - -MmsValue* -ControlObject_getCancel(ControlObject* self) -{ - return self->cancel; -} - void ControlObject_setMmsValue(ControlObject* self, MmsValue* value) { @@ -1122,13 +991,13 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia if (controlObject != NULL) { - initialize(controlObject); + //initialize(controlObject); if (varName != NULL) { if (strcmp(varName, "Oper") == 0) - value = ControlObject_getOper(controlObject); + value = controlObject->oper; else if (strcmp(varName, "SBOw") == 0) - value = ControlObject_getSBOw(controlObject); + value = controlObject->sbow; else if (strcmp(varName, "SBO") == 0) { if (controlObject->ctlModel == 2) { @@ -1153,7 +1022,7 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia if (checkResult == CONTROL_ACCEPTED) { selectObject(controlObject, currentTime, connection); - value = ControlObject_getSBO(controlObject); + value = controlObject->sbo; } } @@ -1162,12 +1031,12 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia if (DEBUG_IED_SERVER) printf("IED_SERVER: select not applicable for control model %i\n", controlObject->ctlModel); - value = ControlObject_getSBO(controlObject); + value = controlObject->sbo; } } else if (strcmp(varName, "Cancel") == 0) - value = ControlObject_getCancel(controlObject); + value = controlObject->cancel; else { value = MmsValue_getSubElement(ControlObject_getMmsValue(controlObject), ControlObject_getTypeSpec(controlObject), varName); @@ -1289,7 +1158,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari goto free_and_return; } - initialize(controlObject); + //initialize(controlObject); if (strcmp(varName, "SBOw") == 0) { /* select with value */ diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index c6e28b0ed..1572ce73f 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -599,6 +599,20 @@ MmsMapping_checkForSettingGroupReservationTimeouts(MmsMapping* self, uint64_t cu } } +void +MmsMapping_initializeControlObjects(MmsMapping* self) +{ + LinkedList element = LinkedList_getNext(self->controlObjects); + + while (element) { + ControlObject* controlObject = (ControlObject*) LinkedList_getData(element); + + ControlObject_initialize(controlObject); + + element = LinkedList_getNext(element); + } +} + void MmsMapping_configureSettingGroups(MmsMapping* self) { From 020b1f40aa45375ec5aa13b1540d535c3348caff Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 25 Aug 2018 14:26:03 +0200 Subject: [PATCH 103/123] - IEC 61850 server: integrated automatic handling of "origin" and "ctlVal" status (ST) values for controllable CDCs --- .../simpleIO_direct_control.icd | 6 +- .../server_example_basic_io/static_model.c | 112 +++++++++++++----- .../server_example_basic_io/static_model.h | 16 ++- src/iec61850/inc_private/control.h | 3 + src/iec61850/server/mms_mapping/control.c | 24 ++-- 5 files changed, 117 insertions(+), 44 deletions(-) diff --git a/examples/server_example_basic_io/simpleIO_direct_control.icd b/examples/server_example_basic_io/simpleIO_direct_control.icd index 0a0c63552..ce3d8cce8 100644 --- a/examples/server_example_basic_io/simpleIO_direct_control.icd +++ b/examples/server_example_basic_io/simpleIO_direct_control.icd @@ -212,11 +212,13 @@ + + - - + + diff --git a/examples/server_example_basic_io/static_model.c b/examples/server_example_basic_io/static_model.c index ddc8dd644..a1c2383d8 100644 --- a/examples/server_example_basic_io/static_model.c +++ b/examples/server_example_basic_io/static_model.c @@ -3,7 +3,7 @@ * * automatically generated from simpleIO_direct_control.icd */ -#include "../server_example_basic_io/static_model.h" +#include "static_model.h" static void initializeValues(); @@ -1571,10 +1571,62 @@ DataObject iedModel_GenericIO_GGIO1_SPCSO4 = { "SPCSO4", (ModelNode*) &iedModel_GenericIO_GGIO1, (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, 0 }; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat, + 0, + IEC61850_FC_ST, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT8U, + 0, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal = { DataAttributeModelType, "stVal", @@ -1592,7 +1644,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { DataAttributeModelType, "q", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, NULL, 0, IEC61850_FC_ST, @@ -1601,11 +1653,37 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { NULL, 0}; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper = { DataAttributeModelType, "Oper", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, + NULL, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal, 0, IEC61850_FC_CO, @@ -1718,32 +1796,6 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check = { NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { - DataAttributeModelType, - "ctlModel", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, - NULL, - 0, - IEC61850_FC_CF, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { - DataAttributeModelType, - "t", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - NULL, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_TIMESTAMP, - 0, - NULL, - 0}; - DataObject iedModel_GenericIO_GGIO1_Ind1 = { DataObjectModelType, "Ind1", diff --git a/examples/server_example_basic_io/static_model.h b/examples/server_example_basic_io/static_model.h index b5670e9f3..938d789a6 100644 --- a/examples/server_example_basic_io/static_model.h +++ b/examples/server_example_basic_io/static_model.h @@ -123,8 +123,14 @@ extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t; extern DataObject iedModel_GenericIO_GGIO1_SPCSO4; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin; @@ -134,8 +140,6 @@ extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t; extern DataObject iedModel_GenericIO_GGIO1_Ind1; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_q; @@ -267,8 +271,14 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_GGIO1_SPCSO3_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO3_ctlModel) #define IEDMODEL_GenericIO_GGIO1_SPCSO3_t (&iedModel_GenericIO_GGIO1_SPCSO3_t) #define IEDMODEL_GenericIO_GGIO1_SPCSO4 (&iedModel_GenericIO_GGIO1_SPCSO4) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin (&iedModel_GenericIO_GGIO1_SPCSO4_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO4_ctlNum) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal (&iedModel_GenericIO_GGIO1_SPCSO4_stVal) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_q (&iedModel_GenericIO_GGIO1_SPCSO4_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_t (&iedModel_GenericIO_GGIO1_SPCSO4_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper (&iedModel_GenericIO_GGIO1_SPCSO4_Oper) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin) @@ -278,8 +288,6 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_T) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_t (&iedModel_GenericIO_GGIO1_SPCSO4_t) #define IEDMODEL_GenericIO_GGIO1_Ind1 (&iedModel_GenericIO_GGIO1_Ind1) #define IEDMODEL_GenericIO_GGIO1_Ind1_stVal (&iedModel_GenericIO_GGIO1_Ind1_stVal) #define IEDMODEL_GenericIO_GGIO1_Ind1_q (&iedModel_GenericIO_GGIO1_Ind1_q) diff --git a/src/iec61850/inc_private/control.h b/src/iec61850/inc_private/control.h index 3d42ad1d0..1c8a1ac33 100644 --- a/src/iec61850/inc_private/control.h +++ b/src/iec61850/inc_private/control.h @@ -62,6 +62,9 @@ struct sControlObject MmsValue* origin; MmsValue* timestamp; + MmsValue* ctlNumSt; + MmsValue* originSt; + char ctlObjectName[130]; /* for LastAppIError */ diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 6eec9335e..4d50585ce 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -110,8 +110,8 @@ updateSboTimeoutValue(ControlObject* self) self->selectTimeout = CONFIG_CONTROL_DEFAULT_SBO_TIMEOUT; } -static void -initialize(ControlObject* self) +void +ControlObject_initialize(ControlObject* self) { MmsServer mmsServer = IedServer_getMmsServer(self->iedServer); @@ -139,6 +139,14 @@ initialize(ControlObject* self) StringUtils_createStringInBuffer(self->ctlObjectName, 5, MmsDomain_getName(self->mmsDomain), "/", self->lnName, "$CO$", self->name); + char* ctlNumName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$ST$", self->name, "$ctlNum"); + + self->ctlNumSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, ctlNumName); + + char* originName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$ST$", self->name, "$origin"); + + self->originSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, originName); + self->error = MmsValue_newIntegerFromInt32(0); self->addCause = MmsValue_newIntegerFromInt32(0); @@ -387,12 +395,6 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* return self; } -void -ControlObject_initialize(ControlObject* self) -{ - initialize(self); -} - void ControlObject_destroy(ControlObject* self) { @@ -900,6 +902,12 @@ updateControlParameters(ControlObject* controlObject, MmsValue* ctlVal, MmsValue controlObject->ctlVal = MmsValue_clone(ctlVal); controlObject->ctlNum = MmsValue_clone(ctlNum); controlObject->origin = MmsValue_clone(origin); + + if (controlObject->ctlNumSt) + MmsValue_update(controlObject->ctlNumSt, ctlNum); + + if (controlObject->originSt) + MmsValue_update(controlObject->originSt, origin); } static bool From e7905cc16b93bbe6e1b2d30fbbe6c18f7e297651 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 25 Aug 2018 15:11:12 +0200 Subject: [PATCH 104/123] - IEC 61850 server: memory handling optimization in control module --- src/iec61850/inc_private/control.h | 2 +- src/iec61850/server/impl/ied_server.c | 9 +- src/iec61850/server/mms_mapping/control.c | 181 +++++++++++----------- 3 files changed, 94 insertions(+), 98 deletions(-) diff --git a/src/iec61850/inc_private/control.h b/src/iec61850/inc_private/control.h index 1c8a1ac33..844c74b6b 100644 --- a/src/iec61850/inc_private/control.h +++ b/src/iec61850/inc_private/control.h @@ -104,7 +104,7 @@ struct sControlObject }; ControlObject* -ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* name); +ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* name, MmsVariableSpecification* operSpec); void ControlObject_initialize(ControlObject* self); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 16686fc57..dc487c3ad 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -59,7 +59,6 @@ createControlObjects(IedServer self, MmsDomain* domain, char* lnName, MmsVariabl strcat(objectName, "$"); } - bool isControlObject = false; bool hasCancel = false; int cancelIndex = 0; bool hasSBOw = false; @@ -72,12 +71,14 @@ createControlObjects(IedServer self, MmsDomain* domain, char* lnName, MmsVariabl int coElementCount = coSpec->typeSpec.structure.elementCount; + MmsVariableSpecification* operSpec = NULL; + int j; for (j = 0; j < coElementCount; j++) { MmsVariableSpecification* coElementSpec = coSpec->typeSpec.structure.elements[j]; if (strcmp(coElementSpec->name, "Oper") == 0) { - isControlObject = true; + operSpec = coElementSpec; operIndex = j; } else if (strcmp(coElementSpec->name, "Cancel") == 0) { @@ -96,14 +97,14 @@ createControlObjects(IedServer self, MmsDomain* domain, char* lnName, MmsVariabl } } - if (isControlObject) { + if (operSpec) { strcat(objectName, coSpec->name); if (DEBUG_IED_SERVER) printf("IED_SERVER: create control object LN:%s DO:%s\n", lnName, objectName); - ControlObject* controlObject = ControlObject_create(self, domain, lnName, objectName); + ControlObject* controlObject = ControlObject_create(self, domain, lnName, objectName, operSpec); if (controlObject == NULL) goto exit_function; diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 4d50585ce..d8f37e1af 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -110,82 +110,6 @@ updateSboTimeoutValue(ControlObject* self) self->selectTimeout = CONFIG_CONTROL_DEFAULT_SBO_TIMEOUT; } -void -ControlObject_initialize(ControlObject* self) -{ - MmsServer mmsServer = IedServer_getMmsServer(self->iedServer); - - self->emptyString = MmsValue_newVisibleString(NULL); - - char strBuf[129]; - - char* ctlModelName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$ctlModel"); - - if (DEBUG_IED_SERVER) - printf("initialize control for %s\n", ctlModelName); - - MmsValue* ctlModel = MmsServer_getValueFromCache(mmsServer, - self->mmsDomain, ctlModelName); - - if (ctlModel == NULL) { - if (DEBUG_IED_SERVER) - printf("No control model found for variable %s\n", ctlModelName); - } - - char* sboClassName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$sboClass"); - - self->sboClass = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, sboClassName); - - StringUtils_createStringInBuffer(self->ctlObjectName, 5, MmsDomain_getName(self->mmsDomain), "/", - self->lnName, "$CO$", self->name); - - char* ctlNumName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$ST$", self->name, "$ctlNum"); - - self->ctlNumSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, ctlNumName); - - char* originName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$ST$", self->name, "$origin"); - - self->originSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, originName); - - self->error = MmsValue_newIntegerFromInt32(0); - self->addCause = MmsValue_newIntegerFromInt32(0); - - if (ctlModel != NULL) { - uint32_t ctlModelVal = MmsValue_toInt32(ctlModel); - - self->ctlModel = ctlModelVal; - - if (DEBUG_IED_SERVER) - printf(" ctlModel: %i\n", ctlModelVal); - - if ((ctlModelVal == 2) || (ctlModelVal == 4)) { /* SBO */ - - - char* controlObjectReference = StringUtils_createStringInBuffer(strBuf, 6, self->mmsDomain->domainName, - "/", self->lnName, "$", self->name, "$SBO"); - - self->sbo = MmsValue_newVisibleString(controlObjectReference); - - char* sboTimeoutName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$sboTimeout"); - - self->sboTimeout = MmsServer_getValueFromCache(mmsServer, - self->mmsDomain, sboTimeoutName); - - updateSboTimeoutValue(self); - - setState(self, STATE_UNSELECTED); - - if (DEBUG_IED_SERVER) - printf("timeout for %s is %i\n", sboTimeoutName, self->selectTimeout); - } - else { - self->sbo = MmsValue_newVisibleString(NULL); - - setState(self, STATE_READY); - } - } -} - static bool isSboClassOperateOnce(ControlObject* self) { @@ -359,7 +283,7 @@ executeControlTask(ControlObject* self) } ControlObject* -ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* name) +ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* name, MmsVariableSpecification* operSpec) { ControlObject* self = (ControlObject*) GLOBAL_CALLOC(1, sizeof(ControlObject)); @@ -391,10 +315,94 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* self->mmsDomain = domain; self->iedServer = iedServer; + MmsVariableSpecification* ctlValSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "ctlVal", NULL); + self->ctlVal = MmsValue_newDefaultValue(ctlValSpec); + + MmsVariableSpecification* originSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "origin", NULL); + self->origin = MmsValue_newDefaultValue(originSpec); + + self->ctlNum = MmsValue_newUnsigned(8); + exit_function: return self; } +void +ControlObject_initialize(ControlObject* self) +{ + MmsServer mmsServer = IedServer_getMmsServer(self->iedServer); + + self->emptyString = MmsValue_newVisibleString(NULL); + + char strBuf[129]; + + char* ctlModelName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$ctlModel"); + + if (DEBUG_IED_SERVER) + printf("initialize control for %s\n", ctlModelName); + + MmsValue* ctlModel = MmsServer_getValueFromCache(mmsServer, + self->mmsDomain, ctlModelName); + + if (ctlModel == NULL) { + if (DEBUG_IED_SERVER) + printf("No control model found for variable %s\n", ctlModelName); + } + + char* sboClassName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$sboClass"); + + self->sboClass = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, sboClassName); + + StringUtils_createStringInBuffer(self->ctlObjectName, 5, MmsDomain_getName(self->mmsDomain), "/", + self->lnName, "$CO$", self->name); + + char* ctlNumName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$ST$", self->name, "$ctlNum"); + + self->ctlNumSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, ctlNumName); + + char* originName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$ST$", self->name, "$origin"); + + self->originSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, originName); + + self->error = MmsValue_newIntegerFromInt32(0); + self->addCause = MmsValue_newIntegerFromInt32(0); + + if (ctlModel != NULL) { + uint32_t ctlModelVal = MmsValue_toInt32(ctlModel); + + self->ctlModel = ctlModelVal; + + if (DEBUG_IED_SERVER) + printf(" ctlModel: %i\n", ctlModelVal); + + if ((ctlModelVal == 2) || (ctlModelVal == 4)) { /* SBO */ + + + char* controlObjectReference = StringUtils_createStringInBuffer(strBuf, 6, self->mmsDomain->domainName, + "/", self->lnName, "$", self->name, "$SBO"); + + self->sbo = MmsValue_newVisibleString(controlObjectReference); + + char* sboTimeoutName = StringUtils_createStringInBuffer(strBuf, 4, self->lnName, "$CF$", self->name, "$sboTimeout"); + + self->sboTimeout = MmsServer_getValueFromCache(mmsServer, + self->mmsDomain, sboTimeoutName); + + updateSboTimeoutValue(self); + + setState(self, STATE_UNSELECTED); + + if (DEBUG_IED_SERVER) + printf("timeout for %s is %i\n", sboTimeoutName, self->selectTimeout); + } + else { + self->sbo = MmsValue_newVisibleString(NULL); + + setState(self, STATE_READY); + } + } +} + void ControlObject_destroy(ControlObject* self) { @@ -890,18 +898,9 @@ ControlObject_sendLastApplError(ControlObject* self, MmsServerConnection connect static void updateControlParameters(ControlObject* controlObject, MmsValue* ctlVal, MmsValue* ctlNum, MmsValue* origin) { - if (controlObject->ctlVal != NULL) - MmsValue_delete(controlObject->ctlVal); - - if (controlObject->ctlNum != NULL) - MmsValue_delete(controlObject->ctlNum); - - if (controlObject->origin != NULL) - MmsValue_delete(controlObject->origin); - - controlObject->ctlVal = MmsValue_clone(ctlVal); - controlObject->ctlNum = MmsValue_clone(ctlNum); - controlObject->origin = MmsValue_clone(origin); + MmsValue_update(controlObject->ctlVal, ctlVal); + MmsValue_update(controlObject->ctlNum, ctlNum); + MmsValue_update(controlObject->origin, origin); if (controlObject->ctlNumSt) MmsValue_update(controlObject->ctlNumSt, ctlNum); @@ -999,8 +998,6 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia if (controlObject != NULL) { - //initialize(controlObject); - if (varName != NULL) { if (strcmp(varName, "Oper") == 0) value = controlObject->oper; @@ -1166,8 +1163,6 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari goto free_and_return; } - //initialize(controlObject); - if (strcmp(varName, "SBOw") == 0) { /* select with value */ if (controlObject->ctlModel == 4) { From 14c84342010b0b2cc54d271342ffa9968b3ad55e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 31 Aug 2018 07:08:57 +0200 Subject: [PATCH 105/123] - server_example_basic_io: fixed ICD/CID file --- dotnet/files/FileServicesExample.cs | 66 ++-- examples/server_example_basic_io/Makefile | 2 +- ...ontrol.icd => simpleIO_direct_control.cid} | 246 ++++++++------- .../simpleIO_direct_control.icd | 162 +++++----- .../server_example_basic_io/static_model.c | 285 ++++++++++-------- .../server_example_basic_io/static_model.h | 36 +-- 6 files changed, 440 insertions(+), 357 deletions(-) rename examples/server_example_basic_io/{simpleIO_sbo_control.icd => simpleIO_direct_control.cid} (55%) diff --git a/dotnet/files/FileServicesExample.cs b/dotnet/files/FileServicesExample.cs index 6aed827ac..f77743fc5 100644 --- a/dotnet/files/FileServicesExample.cs +++ b/dotnet/files/FileServicesExample.cs @@ -7,6 +7,10 @@ namespace files { + /// + /// This example connects to an IEC 61850 device, list the available files, and then + /// tries to read the file "IEDSERVER.BIN" from the server. + /// class MainClass { public static void printFiles (IedConnection con, string prefix, string parent) @@ -14,11 +18,11 @@ public static void printFiles (IedConnection con, string prefix, string parent) List files = con.GetFileDirectory (parent); foreach (FileDirectoryEntry file in files) { - Console.WriteLine(prefix + file.GetFileName() + "\t" + file.GetFileSize() + "\t" + - MmsValue.MsTimeToDateTimeOffset(file.GetLastModified())); + Console.WriteLine (prefix + file.GetFileName () + "\t" + file.GetFileSize () + "\t" + + MmsValue.MsTimeToDateTimeOffset (file.GetLastModified ())); - if (file.GetFileName().EndsWith("/")) { - printFiles (con, prefix + " ", parent + file.GetFileName()); + if (file.GetFileName ().EndsWith ("/")) { + printFiles (con, prefix + " ", parent + file.GetFileName ()); } } @@ -26,64 +30,60 @@ public static void printFiles (IedConnection con, string prefix, string parent) static bool getFileHandler (object parameter, byte[] data) { - Console.WriteLine("received " + data.Length + " bytes"); + Console.WriteLine ("received " + data.Length + " bytes"); - BinaryWriter binWriter = (BinaryWriter) parameter; + BinaryWriter binWriter = (BinaryWriter)parameter; - binWriter.Write(data); + binWriter.Write (data); return true; } - public static void Main (string[] args) { IedConnection con = new IedConnection (); - string hostname; + string hostname; - if (args.Length > 0) - hostname = args[0]; - else - hostname = "10.0.2.2"; + if (args.Length > 0) + hostname = args [0]; + else + hostname = "10.0.2.2"; - Console.WriteLine("Connect to " + hostname); + Console.WriteLine ("Connect to " + hostname); - try - { - con.Connect(hostname, 102); + try { + con.Connect (hostname, 102); Console.WriteLine ("Files in server root directory:"); - List serverDirectory = con.GetServerDirectory(true); + List serverDirectory = con.GetServerDirectory (true); foreach (string entry in serverDirectory) { - Console.WriteLine(entry); + Console.WriteLine (entry); } - Console.WriteLine(); + Console.WriteLine (); Console.WriteLine ("File directory tree at server:"); - printFiles(con, "", ""); - Console.WriteLine(); + printFiles (con, "", ""); + Console.WriteLine (); string filename = "IEDSERVER.BIN"; - Console.WriteLine("Download file " + filename); + Console.WriteLine ("Download file " + filename); /* Download file from server and write it to a new local file */ - FileStream fs = new FileStream(filename, FileMode.Create); - BinaryWriter w = new BinaryWriter(fs); + FileStream fs = new FileStream (filename, FileMode.Create); + BinaryWriter w = new BinaryWriter (fs); - con.GetFile(filename, new IedConnection.GetFileHandler(getFileHandler), w); + con.GetFile (filename, new IedConnection.GetFileHandler (getFileHandler), w); - fs.Close(); + fs.Close (); - con.Abort(); - } - catch (IedConnectionException e) - { - Console.WriteLine(e.Message); - } + con.Abort (); + } catch (IedConnectionException e) { + Console.WriteLine (e.Message); + } // release all resources - do NOT use the object after this call!! con.Dispose (); diff --git a/examples/server_example_basic_io/Makefile b/examples/server_example_basic_io/Makefile index ed1ce6a17..f27d9f651 100644 --- a/examples/server_example_basic_io/Makefile +++ b/examples/server_example_basic_io/Makefile @@ -4,7 +4,7 @@ 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.icd +PROJECT_ICD_FILE = simpleIO_direct_control.cid include $(LIBIEC_HOME)/make/target_system.mk include $(LIBIEC_HOME)/make/stack_includes.mk diff --git a/examples/server_example_basic_io/simpleIO_sbo_control.icd b/examples/server_example_basic_io/simpleIO_direct_control.cid similarity index 55% rename from examples/server_example_basic_io/simpleIO_sbo_control.icd rename to examples/server_example_basic_io/simpleIO_direct_control.cid index 5f65ff6ab..cb763780c 100644 --- a/examples/server_example_basic_io/simpleIO_sbo_control.icd +++ b/examples/server_example_basic_io/simpleIO_direct_control.cid @@ -8,12 +8,15 @@ 10
-

10.0.0.2

+

0.0.0.0

255.255.255.0

-

10.0.0.1

-

0001

+

192.168.2.1

+

1,3,9999,33

+

33

00000001

0001

+

0001

+

102

@@ -39,73 +42,126 @@ + - + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + on + status-only + + + on + + + + + ok + + + + + + MZ Automation + + + 1.3.0 + + + libiec61850 server example + + - + + + + ok + + + + + on + status-only + + + on + + + + + ok + + - sbo-with-normal-security + direct-with-normal-security - - 30000 - - - operate-once - - sbo-with-normal-security + direct-with-normal-security - - 30000 - - - operate-once - - sbo-with-normal-security + direct-with-normal-security - - 30000 - - - operate-once - - sbo-with-normal-security + direct-with-normal-security - - 30000 - - - operate-once - @@ -113,45 +169,58 @@ + - - - + + + + - + + - - - + + + - - - - + + + + - + + + - - + + + + + + + + + + @@ -159,88 +228,47 @@ + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - @@ -259,6 +287,20 @@ + + on + blocked + test + test/blocked + off + + + + ok + warning + alarm + + status-only direct-with-normal-security @@ -267,11 +309,6 @@ sbo-with-enhanced-security - - operate-once - operate-many - - not-supported bay-control @@ -283,5 +320,6 @@ maintenance process + diff --git a/examples/server_example_basic_io/simpleIO_direct_control.icd b/examples/server_example_basic_io/simpleIO_direct_control.icd index ce3d8cce8..258e9ae61 100644 --- a/examples/server_example_basic_io/simpleIO_direct_control.icd +++ b/examples/server_example_basic_io/simpleIO_direct_control.icd @@ -2,23 +2,8 @@
- - - Station bus - 10 - -
-

10.0.0.2

-

255.255.255.0

-

10.0.0.1

-

0001

-

00000001

-

0001

-
-
-
-
- + + @@ -67,13 +52,13 @@ - + - + @@ -82,44 +67,64 @@ - - - - - - - - - - - - - + + + on + status-only + + + on + + + + + ok + + MZ Automation - 0.7.3 + 1.3.0 libiec61850 server example - + + + + ok + + + + + on + status-only + + + on + + + + + ok + + direct-with-normal-security @@ -146,46 +151,58 @@ + - - - + + + + - + + - - - + + + - - - - + + + + - - + + + - - + + + + + + + + + + @@ -193,24 +210,29 @@ + + + + + @@ -220,39 +242,24 @@ - - - - - - - - - - - - - - - - - - - - + + + + + @@ -261,6 +268,21 @@ + + + on + blocked + test + test/blocked + off + + + + ok + warning + alarm + + status-only direct-with-normal-security @@ -268,6 +290,7 @@ direct-with-enhanced-security sbo-with-enhanced-security + not-supported bay-control @@ -279,5 +302,6 @@ maintenance process +
diff --git a/examples/server_example_basic_io/static_model.c b/examples/server_example_basic_io/static_model.c index a1c2383d8..63ef37bb4 100644 --- a/examples/server_example_basic_io/static_model.c +++ b/examples/server_example_basic_io/static_model.c @@ -1,7 +1,7 @@ /* * static_model.c * - * automatically generated from simpleIO_direct_control.icd + * automatically generated from simpleIO_direct_control.cid */ #include "static_model.h" @@ -248,7 +248,7 @@ DataAttribute iedModel_GenericIO_LLN0_Mod_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -309,7 +309,7 @@ DataAttribute iedModel_GenericIO_LLN0_Beh_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -357,7 +357,7 @@ DataAttribute iedModel_GenericIO_LLN0_Health_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -509,7 +509,7 @@ DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -601,10 +601,23 @@ DataObject iedModel_GenericIO_GGIO1_Mod = { "Mod", (ModelNode*) &iedModel_GenericIO_GGIO1, (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, - (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_stVal, 0 }; +DataAttribute iedModel_GenericIO_GGIO1_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_Mod_q = { DataAttributeModelType, "q", @@ -661,7 +674,7 @@ DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -709,7 +722,7 @@ DataAttribute iedModel_GenericIO_GGIO1_Health_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -1037,10 +1050,62 @@ DataObject iedModel_GenericIO_GGIO1_SPCSO1 = { "SPCSO1", (ModelNode*) &iedModel_GenericIO_GGIO1, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_stVal, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin, 0 }; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat, + 0, + IEC61850_FC_ST, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_stVal, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT8U, + 0, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal = { DataAttributeModelType, "stVal", @@ -1058,7 +1123,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q = { DataAttributeModelType, "q", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_t, NULL, 0, IEC61850_FC_ST, @@ -1067,11 +1132,37 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q = { NULL, 0}; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper = { DataAttributeModelType, "Oper", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlModel, + NULL, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal, 0, IEC61850_FC_CO, @@ -1184,32 +1275,6 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check = { NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel = { - DataAttributeModelType, - "ctlModel", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_t, - NULL, - 0, - IEC61850_FC_CF, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t = { - DataAttributeModelType, - "t", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - NULL, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_TIMESTAMP, - 0, - NULL, - 0}; - DataObject iedModel_GenericIO_GGIO1_SPCSO2 = { DataObjectModelType, "SPCSO2", @@ -1571,62 +1636,10 @@ DataObject iedModel_GenericIO_GGIO1_SPCSO4 = { "SPCSO4", (ModelNode*) &iedModel_GenericIO_GGIO1, (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, 0 }; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin = { - DataAttributeModelType, - "origin", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlNum, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat, - 0, - IEC61850_FC_ST, - IEC61850_CONSTRUCTED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat = { - DataAttributeModelType, - "orCat", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent = { - DataAttributeModelType, - "orIdent", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, - NULL, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_OCTET_STRING_64, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlNum = { - DataAttributeModelType, - "ctlNum", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_INT8U, - 0, - NULL, - 0}; - DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal = { DataAttributeModelType, "stVal", @@ -1644,7 +1657,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { DataAttributeModelType, "q", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, NULL, 0, IEC61850_FC_ST, @@ -1653,37 +1666,11 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { - DataAttributeModelType, - "t", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_TIMESTAMP, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { - DataAttributeModelType, - "ctlModel", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, - NULL, - 0, - IEC61850_FC_CF, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper = { DataAttributeModelType, "Oper", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal, 0, IEC61850_FC_CO, @@ -1796,6 +1783,32 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check = { NULL, 0}; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + DataObject iedModel_GenericIO_GGIO1_Ind1 = { DataObjectModelType, "Ind1", @@ -1996,10 +2009,10 @@ extern ReportControlBlock iedModel_GenericIO_LLN0_report4; extern ReportControlBlock iedModel_GenericIO_LLN0_report5; extern ReportControlBlock iedModel_GenericIO_LLN0_report6; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report2}; -ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report3}; -ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report4}; ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report5}; ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report6}; ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, NULL}; @@ -2007,15 +2020,7 @@ ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, -extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; -extern LogControlBlock iedModel_GenericIO_LLN0_lcb1; -LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, &iedModel_GenericIO_LLN0_lcb1}; -LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", NULL, NULL, 3, 0, true, true, NULL}; -extern Log iedModel_GenericIO_LLN0_log0; -extern Log iedModel_GenericIO_LLN0_log1; -Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1}; -Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL}; IedModel iedModel = { @@ -2026,8 +2031,8 @@ IedModel iedModel = { NULL, NULL, NULL, - &iedModel_GenericIO_LLN0_lcb0, - &iedModel_GenericIO_LLN0_log0, + NULL, + NULL, initializeValues }; @@ -2035,16 +2040,30 @@ static void initializeValues() { +iedModel_GenericIO_LLN0_Mod_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_LLN0_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); +iedModel_GenericIO_LLN0_Beh_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_LLN0_Health_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_LLN0_NamPlt_vendor.mmsValue = MmsValue_newVisibleString("MZ Automation"); -iedModel_GenericIO_LLN0_NamPlt_swRev.mmsValue = MmsValue_newVisibleString("0.7.3"); +iedModel_GenericIO_LLN0_NamPlt_swRev.mmsValue = MmsValue_newVisibleString("1.3.0"); iedModel_GenericIO_LLN0_NamPlt_d.mmsValue = MmsValue_newVisibleString("libiec61850 server example"); +iedModel_GenericIO_LPHD1_PhyHealth_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_Mod_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); +iedModel_GenericIO_GGIO1_Beh_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_Health_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); diff --git a/examples/server_example_basic_io/static_model.h b/examples/server_example_basic_io/static_model.h index 938d789a6..b6030e514 100644 --- a/examples/server_example_basic_io/static_model.h +++ b/examples/server_example_basic_io/static_model.h @@ -1,7 +1,7 @@ /* * static_model.h * - * automatically generated from simpleIO_direct_control.icd + * automatically generated from simpleIO_direct_control.cid */ #ifndef STATIC_MODEL_H_ @@ -45,6 +45,7 @@ extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_q; extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_t; extern LogicalNode iedModel_GenericIO_GGIO1; extern DataObject iedModel_GenericIO_GGIO1_Mod; +extern DataAttribute iedModel_GenericIO_GGIO1_Mod_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_Mod_q; extern DataAttribute iedModel_GenericIO_GGIO1_Mod_t; extern DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel; @@ -81,8 +82,14 @@ extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag_f; extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_q; extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_t; extern DataObject iedModel_GenericIO_GGIO1_SPCSO1; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin; @@ -92,8 +99,6 @@ extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_T; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t; extern DataObject iedModel_GenericIO_GGIO1_SPCSO2; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q; @@ -123,14 +128,8 @@ extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t; extern DataObject iedModel_GenericIO_GGIO1_SPCSO4; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin; @@ -140,6 +139,8 @@ extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t; extern DataObject iedModel_GenericIO_GGIO1_Ind1; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_q; @@ -193,6 +194,7 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_LPHD1_Proxy_t (&iedModel_GenericIO_LPHD1_Proxy_t) #define IEDMODEL_GenericIO_GGIO1 (&iedModel_GenericIO_GGIO1) #define IEDMODEL_GenericIO_GGIO1_Mod (&iedModel_GenericIO_GGIO1_Mod) +#define IEDMODEL_GenericIO_GGIO1_Mod_stVal (&iedModel_GenericIO_GGIO1_Mod_stVal) #define IEDMODEL_GenericIO_GGIO1_Mod_q (&iedModel_GenericIO_GGIO1_Mod_q) #define IEDMODEL_GenericIO_GGIO1_Mod_t (&iedModel_GenericIO_GGIO1_Mod_t) #define IEDMODEL_GenericIO_GGIO1_Mod_ctlModel (&iedModel_GenericIO_GGIO1_Mod_ctlModel) @@ -229,8 +231,14 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_GGIO1_AnIn4_q (&iedModel_GenericIO_GGIO1_AnIn4_q) #define IEDMODEL_GenericIO_GGIO1_AnIn4_t (&iedModel_GenericIO_GGIO1_AnIn4_t) #define IEDMODEL_GenericIO_GGIO1_SPCSO1 (&iedModel_GenericIO_GGIO1_SPCSO1) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_origin (&iedModel_GenericIO_GGIO1_SPCSO1_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO1_ctlNum) #define IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal (&iedModel_GenericIO_GGIO1_SPCSO1_stVal) #define IEDMODEL_GenericIO_GGIO1_SPCSO1_q (&iedModel_GenericIO_GGIO1_SPCSO1_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_t (&iedModel_GenericIO_GGIO1_SPCSO1_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO1_ctlModel) #define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper (&iedModel_GenericIO_GGIO1_SPCSO1_Oper) #define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal) #define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin) @@ -240,8 +248,6 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_T) #define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test) #define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check) -#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO1_ctlModel) -#define IEDMODEL_GenericIO_GGIO1_SPCSO1_t (&iedModel_GenericIO_GGIO1_SPCSO1_t) #define IEDMODEL_GenericIO_GGIO1_SPCSO2 (&iedModel_GenericIO_GGIO1_SPCSO2) #define IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal (&iedModel_GenericIO_GGIO1_SPCSO2_stVal) #define IEDMODEL_GenericIO_GGIO1_SPCSO2_q (&iedModel_GenericIO_GGIO1_SPCSO2_q) @@ -271,14 +277,8 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_GGIO1_SPCSO3_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO3_ctlModel) #define IEDMODEL_GenericIO_GGIO1_SPCSO3_t (&iedModel_GenericIO_GGIO1_SPCSO3_t) #define IEDMODEL_GenericIO_GGIO1_SPCSO4 (&iedModel_GenericIO_GGIO1_SPCSO4) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin (&iedModel_GenericIO_GGIO1_SPCSO4_origin) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO4_ctlNum) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal (&iedModel_GenericIO_GGIO1_SPCSO4_stVal) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_q (&iedModel_GenericIO_GGIO1_SPCSO4_q) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_t (&iedModel_GenericIO_GGIO1_SPCSO4_t) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper (&iedModel_GenericIO_GGIO1_SPCSO4_Oper) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin) @@ -288,6 +288,8 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_T) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test) #define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_t (&iedModel_GenericIO_GGIO1_SPCSO4_t) #define IEDMODEL_GenericIO_GGIO1_Ind1 (&iedModel_GenericIO_GGIO1_Ind1) #define IEDMODEL_GenericIO_GGIO1_Ind1_stVal (&iedModel_GenericIO_GGIO1_Ind1_stVal) #define IEDMODEL_GenericIO_GGIO1_Ind1_q (&iedModel_GenericIO_GGIO1_Ind1_q) From 6199da1e55da72e5532a2fccc24892167285bd5b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 31 Aug 2018 10:17:42 +0200 Subject: [PATCH 106/123] - IEC 61850 server: CONFIG_REPORTING_SUPPORTS_OWNER replaced by CONFIG_IEC61850_EDITION_1 --- config/stack_config.h | 3 -- src/iec61850/server/mms_mapping/reporting.c | 33 ++++++++------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index 07917e0be..4cac7cfdd 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -215,9 +215,6 @@ /* support flat named variable name space required by IEC 61850-8-1 MMS mapping */ #define CONFIG_MMS_SUPPORT_FLATTED_NAME_SPACE 1 -/* VMD scope named variables are not used by IEC 61850 (one application is ICCP) */ -#define CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES 0 - /* Sort getNameList response according to the MMS specified collation order - this is required by the standard * Set to 0 only for performance reasons and when no certification is required! */ #define CONFIG_MMS_SORT_NAME_LIST 1 diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index bf16795c1..b32f65256 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -52,15 +52,6 @@ #define CONFIG_IEC61850_EDITION_1 0 #endif -#if (CONFIG_IEC61850_EDITION_1 == 1) -#define CONFIG_REPORTING_SUPPORTS_OWNER 0 -#endif - -#ifndef CONFIG_REPORTING_SUPPORTS_OWNER -#define CONFIG_REPORTING_SUPPORTS_OWNER 1 -#endif - - static ReportBuffer* ReportBuffer_create(int bufferSize) { @@ -260,6 +251,7 @@ ReportControl_getRCBValue(ReportControl* rc, char* elementName) return MmsValue_getElement(rc->rcbValues, 11); else if (strcmp(elementName, "TimeofEntry") == 0) return MmsValue_getElement(rc->rcbValues, 12); +#if (CONFIG_IEC61850_EDITION_1 == 0) #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) else if (strcmp(elementName, "ResvTms") == 0) return MmsValue_getElement(rc->rcbValues, 13); @@ -268,6 +260,7 @@ ReportControl_getRCBValue(ReportControl* rc, char* elementName) #else else if (strcmp(elementName, "Owner") == 0) return MmsValue_getElement(rc->rcbValues, 13); +#endif #endif } else { if (strcmp(elementName, "RptID") == 0) @@ -826,7 +819,7 @@ createUnbufferedReportControlBlock(ReportControlBlock* reportControlBlock, mmsValue->deleteValue = false; mmsValue->type = MMS_STRUCTURE; -#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1) +#if ((CONFIG_IEC61850_EDITION_1 == 0)) int structSize = 12; #else int structSize = 11; @@ -932,14 +925,14 @@ createUnbufferedReportControlBlock(ReportControlBlock* reportControlBlock, rcb->typeSpec.structure.elements[10] = namedVariable; mmsValue->value.structure.components[10] = MmsValue_newBoolean(false); -#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1) +#if (CONFIG_IEC61850_EDITION_1 == 0) namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = StringUtils_copyString("Owner"); namedVariable->type = MMS_OCTET_STRING; namedVariable->typeSpec.octetString = -64; rcb->typeSpec.structure.elements[11] = namedVariable; mmsValue->value.structure.components[11] = MmsValue_newOctetString(0, 128); -#endif /* (CONFIG_REPORTING_SUPPORTS_OWNER == 1) */ +#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ reportControl->rcbValues = mmsValue; @@ -962,13 +955,13 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, int brcbElementCount = 13; +#if (CONFIG_IEC61850_EDITION_1 == 0) #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) brcbElementCount++; #endif -#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1) brcbElementCount++; -#endif +#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); mmsValue->deleteValue = false; @@ -1090,11 +1083,11 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, reportControl->timeOfEntry = mmsValue->value.structure.components[12]; -#if ((CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) || (CONFIG_REPORTING_SUPPORTS_OWNER == 1)) +#if (CONFIG_IEC61850_EDITION_1 == 0) int currentIndex = 13; #endif -#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) +#if ((CONFIG_IEC61850_EDITION_1 == 0) && (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1)) namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = StringUtils_copyString("ResvTms"); namedVariable->type = MMS_INTEGER; @@ -1104,14 +1097,14 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, currentIndex++; #endif /* (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) */ -#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1) +#if (CONFIG_IEC61850_EDITION_1 == 0) namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = StringUtils_copyString("Owner"); namedVariable->type = MMS_OCTET_STRING; namedVariable->typeSpec.octetString = -64; rcb->typeSpec.structure.elements[currentIndex] = namedVariable; mmsValue->value.structure.components[currentIndex] = MmsValue_newOctetString(0, 128); /* size 4 is enough to store client IPv4 address */ -#endif /* (CONFIG_REPORTING_SUPPORTS_OWNER == 1) */ +#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ reportControl->rcbValues = mmsValue; @@ -1228,7 +1221,7 @@ updateOwner(ReportControl* rc, MmsServerConnection connection) { rc->clientConnection = connection; -#if (CONFIG_REPORTING_SUPPORTS_OWNER == 1) +#if (CONFIG_IEC61850_EDITION_1 == 0) MmsValue* owner = ReportControl_getRCBValue(rc, "Owner"); if (owner != NULL) { @@ -1279,7 +1272,7 @@ updateOwner(ReportControl* rc, MmsServerConnection connection) } } -#endif /* CONFIG_REPORTING_SUPPORTS_OWNER == 1*/ +#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ } From 26af0d93c2b9959daab551249be56e8ed59b7c6c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 31 Aug 2018 11:22:31 +0200 Subject: [PATCH 107/123] - IEC 61850 server: made IEC 61850 edition configurable at runtime --- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 7 + dotnet/IEC61850forCSharp/IedServerConfig.cs | 17 ++ .../server_example_basic_io.c | 2 + src/iec61850/inc/iec61850_common.h | 9 + src/iec61850/inc/iec61850_server.h | 19 ++ src/iec61850/inc_private/ied_server_private.h | 2 + src/iec61850/inc_private/reporting.h | 4 +- src/iec61850/server/impl/ied_server.c | 12 +- src/iec61850/server/impl/ied_server_config.c | 13 ++ src/iec61850/server/mms_mapping/reporting.c | 172 +++++++++--------- src/vs/libiec61850-wo-goose.def | 2 + src/vs/libiec61850.def | 2 + 12 files changed, 172 insertions(+), 89 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 516929016..19acd5595 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -29,6 +29,13 @@ namespace IEC61850 namespace Common { + public enum Iec61850Edition : byte + { + EDITION_1 = 0, + EDITION_2 = 1, + EDITION_2_1 = 2 + } + public class LibIEC61850 { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs index 550c3a26d..9f1d7bde2 100644 --- a/dotnet/IEC61850forCSharp/IedServerConfig.cs +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -23,6 +23,7 @@ using System; using System.Runtime.InteropServices; +using IEC61850.Common; namespace IEC61850.Server { @@ -49,6 +50,12 @@ public class IedServerConfig : IDisposable [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedServerConfig_getFileServiceBasePath(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); + internal IntPtr self; public IedServerConfig () @@ -84,6 +91,16 @@ public string FileServiceBasePath } } + public Iec61850Edition Edition + { + get { + return (Iec61850Edition)IedServerConfig_getEdition (self); + } + set { + IedServerConfig_setEdition (self, (byte) value); + } + } + /// /// Releases all resource used by the object. /// diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index d5c9a6b80..55e789ec3 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -90,6 +90,8 @@ main(int argc, char** argv) /* Set buffer size for buffered report control blocks to 200000 bytes */ IedServerConfig_setReportBufferSize(config, 200000); + IedServerConfig_setEdition(config, IEC_61850_EDITION_2); + /* Set the base path for the MMS file services */ IedServerConfig_setFileServiceBasePath(config, "./vmd-filestore/"); diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index f0a67246a..a41a7db4d 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -37,6 +37,15 @@ extern "C" { */ /**@{*/ +/** IEC 61850 edition 1 */ +#define IEC_61850_EDITION_1 0 + +/** IEC 61850 edition 2 */ +#define IEC_61850_EDITION_2 1 + +/** IEC 61850 edition 2.1 */ +#define IEC_61850_EDITION_2_1 2 + /** PhyComAddress type contains Ethernet address and VLAN attributes */ typedef struct { uint8_t vlanPriority; diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 7e146468c..8f6cabce2 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -62,6 +62,9 @@ struct sIedServerConfig /** when true (default) enable log service */ bool enableLogService; + + /** IEC 61850 edition (0 = edition 1, 1 = edition 2, 2 = edition 2.1, ...) */ + uint8_t edition; }; /** @@ -78,6 +81,22 @@ IedServerConfig_create(void); void IedServerConfig_destroy(IedServerConfig self); +/** + * \brief Set the IEC 61850 standard edition to use (default is edition 2) + * + * \param edition IEC_61850_EDITION_1, IEC_61850_EDITION_2, or IEC_61850_EDITION_2_1 + */ +void +IedServerConfig_setEdition(IedServerConfig self, uint8_t edition); + +/** + * \brief Get the configued IEC 61850 standard edition + * + * \returns IEC_61850_EDITION_1, IEC_61850_EDITION_2, or IEC_61850_EDITION_2_1 + */ +uint8_t +IedServerConfig_getEdition(IedServerConfig self); + /** * \brief Set the report buffer size for buffered reporting * diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index 825c032bc..003c1613d 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -55,6 +55,8 @@ struct sIedServer bool logServiceEnabled; #endif + uint8_t edition; + bool running; }; diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index b603efd42..540078b19 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -94,10 +94,12 @@ typedef struct { ReportBuffer* reportBuffer; MmsValue* timeOfEntry; + + IedServer server; } ReportControl; ReportControl* -ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize); +ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize, IedServer server); void ReportControl_destroy(ReportControl* self); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index dc487c3ad..d33a1e434 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -400,12 +400,20 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->running = false; self->localIpAddress = NULL; +#if (CONFIG_IEC61850_EDITION_1 == 1) + self->edition = IEC_61850_EDITION_1; +#else + self->edition = IEC_61850_EDITION_2; +#endif + #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) self->logServiceEnabled = true; if (serverConfiguration) { self->logServiceEnabled = serverConfiguration->enableLogService; + self->edition = serverConfiguration->edition; } + #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ #if (CONFIG_MMS_THREADLESS_STACK != 1) @@ -430,12 +438,10 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); MmsServer_enableJournalService(self->mmsServer, serverConfiguration->enableLogService); + MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); } #endif - if (serverConfiguration) - MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); - MmsMapping_setMmsServer(self->mmsMapping, self->mmsServer); MmsMapping_installHandlers(self->mmsMapping); diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index fe8c7ea5a..6712c0b96 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -35,6 +35,7 @@ IedServerConfig_create() self->enableFileService = true; self->enableDynamicDataSetService = true; self->enableLogService = true; + self->edition = IEC_61850_EDITION_2; } return self; @@ -47,6 +48,18 @@ IedServerConfig_destroy(IedServerConfig self) GLOBAL_FREEMEM(self); } +void +IedServerConfig_setEdition(IedServerConfig self, uint8_t edition) +{ + self->edition = edition; +} + +uint8_t +IedServerConfig_getEdition(IedServerConfig self) +{ + return self->edition; +} + void IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize) { diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index b32f65256..1e2e5ce1f 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -47,11 +47,6 @@ #define CONFIG_IEC61850_BRCB_WITH_RESVTMS 0 #endif - -#ifndef CONFIG_IEC61850_EDITION_1 -#define CONFIG_IEC61850_EDITION_1 0 -#endif - static ReportBuffer* ReportBuffer_create(int bufferSize) { @@ -84,7 +79,7 @@ ReportBuffer_destroy(ReportBuffer* self) } ReportControl* -ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize) +ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize, IedServer iedServer) { ReportControl* self = (ReportControl*) GLOBAL_MALLOC(sizeof(ReportControl)); self->name = NULL; @@ -119,6 +114,8 @@ ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize) self->valueReferences = NULL; self->lastEntryId = 0; + self->server = iedServer; + if (buffered) { self->reportBuffer = ReportBuffer_create(reportBufferSize); } @@ -251,17 +248,21 @@ ReportControl_getRCBValue(ReportControl* rc, char* elementName) return MmsValue_getElement(rc->rcbValues, 11); else if (strcmp(elementName, "TimeofEntry") == 0) return MmsValue_getElement(rc->rcbValues, 12); -#if (CONFIG_IEC61850_EDITION_1 == 0) + + if (rc->server->edition >= IEC_61850_EDITION_2) { #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - else if (strcmp(elementName, "ResvTms") == 0) - return MmsValue_getElement(rc->rcbValues, 13); - else if (strcmp(elementName, "Owner") == 0) - return MmsValue_getElement(rc->rcbValues, 14); + if (strcmp(elementName, "ResvTms") == 0) + return MmsValue_getElement(rc->rcbValues, 13); + if (strcmp(elementName, "Owner") == 0) + return MmsValue_getElement(rc->rcbValues, 14); #else - else if (strcmp(elementName, "Owner") == 0) - return MmsValue_getElement(rc->rcbValues, 13); -#endif + if (strcmp(elementName, "Owner") == 0) + return MmsValue_getElement(rc->rcbValues, 13); #endif + } + + + } else { if (strcmp(elementName, "RptID") == 0) return MmsValue_getElement(rc->rcbValues, 0); @@ -819,11 +820,12 @@ createUnbufferedReportControlBlock(ReportControlBlock* reportControlBlock, mmsValue->deleteValue = false; mmsValue->type = MMS_STRUCTURE; -#if ((CONFIG_IEC61850_EDITION_1 == 0)) - int structSize = 12; -#else - int structSize = 11; -#endif + int structSize; + + if (reportControl->server->edition >= IEC_61850_EDITION_2) + structSize = 12; + else + structSize = 11; mmsValue->value.structure.size = structSize; mmsValue->value.structure.components = (MmsValue**) GLOBAL_CALLOC(structSize, sizeof(MmsValue*)); @@ -925,14 +927,14 @@ createUnbufferedReportControlBlock(ReportControlBlock* reportControlBlock, rcb->typeSpec.structure.elements[10] = namedVariable; mmsValue->value.structure.components[10] = MmsValue_newBoolean(false); -#if (CONFIG_IEC61850_EDITION_1 == 0) - namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); - namedVariable->name = StringUtils_copyString("Owner"); - namedVariable->type = MMS_OCTET_STRING; - namedVariable->typeSpec.octetString = -64; - rcb->typeSpec.structure.elements[11] = namedVariable; - mmsValue->value.structure.components[11] = MmsValue_newOctetString(0, 128); -#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ + if (reportControl->server->edition >= IEC_61850_EDITION_2) { + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = StringUtils_copyString("Owner"); + namedVariable->type = MMS_OCTET_STRING; + namedVariable->typeSpec.octetString = -64; + rcb->typeSpec.structure.elements[11] = namedVariable; + mmsValue->value.structure.components[11] = MmsValue_newOctetString(0, 128); + } reportControl->rcbValues = mmsValue; @@ -955,13 +957,14 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, int brcbElementCount = 13; -#if (CONFIG_IEC61850_EDITION_1 == 0) + if (reportControl->server->edition >= IEC_61850_EDITION_2) { + #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) - brcbElementCount++; + brcbElementCount++; #endif - brcbElementCount++; -#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ + brcbElementCount++; + } MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); mmsValue->deleteValue = false; @@ -1083,28 +1086,26 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, reportControl->timeOfEntry = mmsValue->value.structure.components[12]; -#if (CONFIG_IEC61850_EDITION_1 == 0) - int currentIndex = 13; -#endif + if (reportControl->server->edition >= IEC_61850_EDITION_2) { + int currentIndex = 13; -#if ((CONFIG_IEC61850_EDITION_1 == 0) && (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1)) - 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 (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) + 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 (CONFIG_IEC61850_EDITION_1 == 0) - namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); - namedVariable->name = StringUtils_copyString("Owner"); - namedVariable->type = MMS_OCTET_STRING; - namedVariable->typeSpec.octetString = -64; - rcb->typeSpec.structure.elements[currentIndex] = namedVariable; - mmsValue->value.structure.components[currentIndex] = MmsValue_newOctetString(0, 128); /* size 4 is enough to store client IPv4 address */ -#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = StringUtils_copyString("Owner"); + namedVariable->type = MMS_OCTET_STRING; + namedVariable->typeSpec.octetString = -64; + rcb->typeSpec.structure.elements[currentIndex] = namedVariable; + mmsValue->value.structure.components[currentIndex] = MmsValue_newOctetString(0, 128); /* size 4 is enough to store client IPv4 address */ + } reportControl->rcbValues = mmsValue; @@ -1158,7 +1159,7 @@ Reporting_createMmsBufferedRCBs(MmsMapping* self, MmsDomain* domain, int currentReport = 0; while (currentReport < reportsCount) { - ReportControl* rc = ReportControl_create(true, logicalNode, self->iedServer->reportBufferSize); + ReportControl* rc = ReportControl_create(true, logicalNode, self->iedServer->reportBufferSize, self->iedServer); rc->domain = domain; @@ -1195,7 +1196,7 @@ Reporting_createMmsUnbufferedRCBs(MmsMapping* self, MmsDomain* domain, int currentReport = 0; while (currentReport < reportsCount) { - ReportControl* rc = ReportControl_create(false, logicalNode, self->iedServer->reportBufferSize); + ReportControl* rc = ReportControl_create(false, logicalNode, self->iedServer->reportBufferSize, self->iedServer); rc->domain = domain; @@ -1221,58 +1222,59 @@ updateOwner(ReportControl* rc, MmsServerConnection connection) { rc->clientConnection = connection; -#if (CONFIG_IEC61850_EDITION_1 == 0) - MmsValue* owner = ReportControl_getRCBValue(rc, "Owner"); + if (rc->server->edition >= IEC_61850_EDITION_2) { - if (owner != NULL) { + MmsValue* owner = ReportControl_getRCBValue(rc, "Owner"); - if (connection != NULL) { - char* clientAddressString = MmsServerConnection_getClientAddress(connection); + if (owner != NULL) { - if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c: set owner to %s\n", clientAddressString); + if (connection != NULL) { + char* clientAddressString = MmsServerConnection_getClientAddress(connection); - if (strchr(clientAddressString, '.') != NULL) { - if (DEBUG_IED_SERVER) - printf("IED_SERVER: reporting.c: client address is IPv4 address\n"); + if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c: set owner to %s\n", clientAddressString); - uint8_t ipV4Addr[4]; + if (strchr(clientAddressString, '.') != NULL) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: reporting.c: client address is IPv4 address\n"); - int addrElementCount = 0; + uint8_t ipV4Addr[4]; - char* separator = clientAddressString; + int addrElementCount = 0; - while (separator != NULL && addrElementCount < 4) { - int intVal = atoi(separator); + char* separator = clientAddressString; - ipV4Addr[addrElementCount] = intVal; + while (separator != NULL && addrElementCount < 4) { + int intVal = atoi(separator); - separator = strchr(separator, '.'); + ipV4Addr[addrElementCount] = intVal; - if (separator != NULL) - separator++; // skip '.' character + separator = strchr(separator, '.'); - addrElementCount ++; - } + if (separator != NULL) + separator++; // skip '.' character - if (addrElementCount == 4) - MmsValue_setOctetString(owner, ipV4Addr, 4); - else - MmsValue_setOctetString(owner, ipV4Addr, 0); + addrElementCount ++; + } + + if (addrElementCount == 4) + MmsValue_setOctetString(owner, ipV4Addr, 4); + else + MmsValue_setOctetString(owner, ipV4Addr, 0); + } + else { + uint8_t ipV6Addr[16]; + MmsValue_setOctetString(owner, ipV6Addr, 0); + if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c: client address is IPv6 address or unknown\n"); + } } else { - uint8_t ipV6Addr[16]; - MmsValue_setOctetString(owner, ipV6Addr, 0); - if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c: client address is IPv6 address or unknown\n"); + uint8_t emptyAddr[1]; + MmsValue_setOctetString(owner, emptyAddr, 0); } } - else { - uint8_t emptyAddr[1]; - MmsValue_setOctetString(owner, emptyAddr, 0); - } - } -#endif /* (CONFIG_IEC61850_EDITION_1 == 0) */ + } } diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index c769a4d81..30d98eb4b 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -610,3 +610,5 @@ EXPORTS IedServerConfig_isDynamicDataSetServiceEnabled IedServerConfig_enableLogService IedServerConfig_isLogServiceEnabled + IedServerConfig_setEdition + IedServerConfig_getEdition diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 8e5edab4c..039e5ea8b 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -738,3 +738,5 @@ EXPORTS IedServerConfig_isDynamicDataSetServiceEnabled IedServerConfig_enableLogService IedServerConfig_isLogServiceEnabled + IedServerConfig_setEdition + IedServerConfig_getEdition From eef34cf40e9b520e59a173c77e8c041d6086cb44 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 31 Aug 2018 21:18:26 +0200 Subject: [PATCH 108/123] - MMS server: add support for array element (index) access with nested component --- src/mms/iso_mms/server/mms_read_service.c | 41 +++++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index 7d69193ce..5a7a62d2d 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -1,7 +1,7 @@ /* * mms_read_service.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -140,9 +140,17 @@ deleteValueList(LinkedList values) static bool isAccessToArrayComponent(AlternateAccess_t* alternateAccess) { - if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess. - alternateAccess != NULL) - return true; + if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess != NULL) + { + if (alternateAccess->list.array[0]->choice.unnamed-> + choice.selectAlternateAccess.alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.present == + AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_component) + { + return true; + } + else + return false; + } else return false; } @@ -161,19 +169,28 @@ getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecif if (component.size > 129) goto exit_function; - int elementCount = namedVariable->typeSpec.structure.elementCount; + MmsVariableSpecification* structSpec; - - MmsVariableSpecification* structSpec = namedVariable->typeSpec.array.elementTypeSpec; + if (namedVariable->type == MMS_ARRAY) + structSpec = namedVariable->typeSpec.array.elementTypeSpec; + else if (namedVariable->type == MMS_STRUCTURE) + structSpec = namedVariable; + else + goto exit_function; int i; - for (i = 0; i < elementCount; i++) { + for (i = 0; i < structSpec->typeSpec.structure.elementCount; i++) { if (strncmp (structSpec->typeSpec.structure.elements[i]->name, (char*) component.buf, component.size) == 0) { MmsValue* value = MmsValue_getElement(structuredValue, i); - retValue = value; + if (isAccessToArrayComponent(alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess)) { + retValue = getComponentOfArrayElement(alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess, + structSpec->typeSpec.structure.elements[i], value); + } + else + retValue = value; goto exit_function; } @@ -242,8 +259,10 @@ alternateArrayAccess(MmsServerConnection connection, } } - appendValueToResultList(value, values); - + if (value) + appendValueToResultList(value, values); + else + appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); } else /* access error */ appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT); From 8b957b8f82fbc4b1c2c38a36012cffefac7bf0ec Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 31 Aug 2018 22:12:05 +0200 Subject: [PATCH 109/123] - IEC 61850 client: add support for single array element access (with component specification) --- src/iec61850/client/ied_connection.c | 30 ++++- src/mms/inc/mms_client_connection.h | 19 ++- src/mms/inc_private/mms_client_internal.h | 4 + .../iso_mms/client/mms_client_connection.c | 31 ++++- src/mms/iso_mms/client/mms_client_read.c | 114 ++++++++++++++++++ 5 files changed, 195 insertions(+), 3 deletions(-) diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index f271dadcb..de323ef4c 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -694,7 +694,35 @@ IedConnection_readObject(IedConnection self, IedClientError* error, const char* MmsError mmsError; - value = MmsConnection_readVariable(self->connection, &mmsError, domainId, itemId); + /* check if item ID contains an array "(..)" */ + char* brace = strchr(itemId, '('); + + if (brace) { + char* secondBrace = strchr(brace, ')'); + + if (secondBrace) { + char* endPtr; + + int index = (int) strtol(brace + 1, &endPtr, 10); + + if (endPtr == secondBrace) { + char* component = NULL; + + if (strlen(secondBrace + 1) > 1) + component = secondBrace + 2; /* skip "." after array element specifier */ + + *brace = 0; + + value = MmsConnection_readSingleArrayElementWithComponent(self->connection, &mmsError, domainId, itemId, index, component); + } + else + *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; + } + else + *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; + } + else + value = MmsConnection_readVariable(self->connection, &mmsError, domainId, itemId); if (value != NULL) *error = IED_ERROR_OK; diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index d5bd40ed2..f4d631b3c 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -374,7 +374,7 @@ MmsValue* MmsConnection_readVariable(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId); /** - * \brief Read an element of a single array variable from the server. + * \brief Read one or more elements of a single array variable from the server. * * \param self MmsConnection instance to operate on * \param mmsError user provided variable to store error code @@ -391,6 +391,23 @@ MmsValue* MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, uint32_t startIndex, uint32_t numberOfElements); + +/** + * \brief Read a single element (with optional component specification) from the server + * + * \param self MmsConnection instance to operate on + * \param mmsError user provided variable to store error code + * \param domainId the domain name of the variable to be read + * \param itemId name of the variable to be read + * \param index array element index + * \param componentId array element component name + * + * \return Returns a MmsValue object or NULL if the request failed. + */ +MmsValue* +MmsConnection_readSingleArrayElementWithComponent(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, uint32_t index, const char* componentId); + /** * \brief Read multiple variables of a domain from the server with one request message. * diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 45c64c258..9ffdb7319 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -164,6 +164,10 @@ int mmsClient_createReadRequestAlternateAccessIndex(uint32_t invokeId, const char* domainId, const char* itemId, uint32_t index, uint32_t elementCount, ByteBuffer* writeBuffer); +int +mmsClient_createReadRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, + uint32_t index, const char* component, ByteBuffer* writeBuffer); + int mmsClient_createReadRequestMultipleValues(uint32_t invokeId, const char* domainId, LinkedList /**/ items, ByteBuffer* writeBuffer); diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 49f8f0df7..b6a055467 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1615,7 +1615,36 @@ MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError, releaseResponse(self); - exit_function: +exit_function: + return value; +} + +MmsValue* +MmsConnection_readSingleArrayElementWithComponent(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, uint32_t index, const char* componentId) +{ + MmsValue* value = NULL; + + if (getAssociationState(self) != MMS_STATE_CONNECTED) { + *mmsError = MMS_ERROR_CONNECTION_LOST; + goto exit_function; + } + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + mmsClient_createReadRequestAlternateAccessSingleIndexComponent(invokeId, domainId, itemId, index, componentId, + payload); + + ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload, mmsError); + + if (responseMessage != NULL) + value = mmsClient_parseReadResponse(self->lastResponse, NULL, false); + + releaseResponse(self); + +exit_function: return value; } diff --git a/src/mms/iso_mms/client/mms_client_read.c b/src/mms/iso_mms/client/mms_client_read.c index 2aea38c0a..8f68f1316 100644 --- a/src/mms/iso_mms/client/mms_client_read.c +++ b/src/mms/iso_mms/client/mms_client_read.c @@ -461,6 +461,85 @@ createAlternateAccess(uint32_t index, uint32_t elementCount) return alternateAccess; } +static AlternateAccess_t* +createAlternateAccessComponent(const char* componentName) +{ + AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); + alternateAccess->list.count = 1; + alternateAccess->list.array = (struct AlternateAccess__Member**) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member*)); + alternateAccess->list.array[0] = (struct AlternateAccess__Member*) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member)); + alternateAccess->list.array[0]->present = AlternateAccess__Member_PR_unnamed; + + alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); + + char* separator = strchr(componentName, '$'); + + if (separator) { + int size = separator - componentName; + + alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAlternateAccess; + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.present = + AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_component; + + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.buf = (uint8_t*) strndup(componentName, size); + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.size = size; + + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess = createAlternateAccessComponent(separator + 1); + } + else { + int size = strlen(componentName); + + alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess; + + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present = + AlternateAccessSelection__selectAccess_PR_component; + + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.buf = (uint8_t*) strndup(componentName, size); + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.size = size; + } + + return alternateAccess; +} + +static AlternateAccess_t* +createAlternateAccessIndexComponent(uint32_t index, const char* componentName) +{ + AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); + alternateAccess->list.count = 1; + alternateAccess->list.array = (struct AlternateAccess__Member**) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member*)); + alternateAccess->list.array[0] = (struct AlternateAccess__Member*) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member)); + alternateAccess->list.array[0]->present = AlternateAccess__Member_PR_unnamed; + + alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); + + if (componentName) { + alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAlternateAccess; + + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.present = + AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_index; + + INTEGER_t* asnIndex = + &(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index); + + asn_long2INTEGER(asnIndex, index); + + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess = createAlternateAccessComponent(componentName); + } + else { + alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess; + + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present = + AlternateAccessSelection__selectAccess_PR_index; + + INTEGER_t* asnIndex = + &(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index); + + asn_long2INTEGER(asnIndex, index); + } + + return alternateAccess; +} + static ListOfVariableSeq_t* createVariableIdentifier(const char* domainId, const char* itemId) { @@ -511,6 +590,41 @@ mmsClient_createReadRequestAlternateAccessIndex(uint32_t invokeId, const char* d return rval.encoded; } +int +mmsClient_createReadRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, + uint32_t index, const char* component, ByteBuffer* writeBuffer) +{ + MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); + ReadRequest_t* readRequest = createReadRequest(mmsPdu); + + readRequest->specificationWithResult = NULL; + + readRequest->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable; + + readRequest->variableAccessSpecification.choice.listOfVariable.list.array = (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); + readRequest->variableAccessSpecification.choice.listOfVariable.list.count = 1; + + ListOfVariableSeq_t* variableIdentifier = createVariableIdentifier(domainId, itemId); + + readRequest->variableAccessSpecification.choice.listOfVariable.list.array[0] = variableIdentifier; + + variableIdentifier->alternateAccess = createAlternateAccessIndexComponent(index, component); + + asn_enc_rval_t rval; + + rval = der_encode(&asn_DEF_MmsPdu, mmsPdu, + (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer); + + variableIdentifier->variableSpecification.choice.name.choice.domainspecific.domainId.buf = 0; + variableIdentifier->variableSpecification.choice.name.choice.domainspecific.domainId.size = 0; + variableIdentifier->variableSpecification.choice.name.choice.domainspecific.itemId.buf = 0; + variableIdentifier->variableSpecification.choice.name.choice.domainspecific.itemId.size = 0; + + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + + return rval.encoded; +} + static ListOfVariableSeq_t** createListOfVariables(ReadRequest_t* readRequest, int valuesCount) { From d0061fce96970af052aa6befc2c03b63f92afb2e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 1 Sep 2018 10:23:33 +0200 Subject: [PATCH 110/123] - updated ICD file for server_example_complex_array - added client example for array handling --- .../CMakeLists.txt | 17 + .../iec61850_client_example_array/Makefile | 17 + .../client_example_array.c | 99 + .../mhai_array.icd | 289 +-- .../server_example_ca.c | 5 - .../static_model.c | 1667 +++++++++++++++-- .../static_model.h | 232 +++ 7 files changed, 2075 insertions(+), 251 deletions(-) create mode 100644 examples/iec61850_client_example_array/CMakeLists.txt create mode 100644 examples/iec61850_client_example_array/Makefile create mode 100644 examples/iec61850_client_example_array/client_example_array.c diff --git a/examples/iec61850_client_example_array/CMakeLists.txt b/examples/iec61850_client_example_array/CMakeLists.txt new file mode 100644 index 000000000..398e3b7b9 --- /dev/null +++ b/examples/iec61850_client_example_array/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_array_SRCS + client_example_array.c +) + +IF(WIN32) +set_source_files_properties(${iec61850_client_array_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(iec61850_client_array + ${iec61850_client_array_SRCS} +) + +target_link_libraries(iec61850_client_array + iec61850 +) diff --git a/examples/iec61850_client_example_array/Makefile b/examples/iec61850_client_example_array/Makefile new file mode 100644 index 000000000..6e28aaded --- /dev/null +++ b/examples/iec61850_client_example_array/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_array +PROJECT_SOURCES = client_example_array.c + +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 + +$(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/iec61850_client_example_array/client_example_array.c b/examples/iec61850_client_example_array/client_example_array.c new file mode 100644 index 000000000..ee1d60165 --- /dev/null +++ b/examples/iec61850_client_example_array/client_example_array.c @@ -0,0 +1,99 @@ +/* + * client_example_array.c + * + * SHows how to handle array access from client side + * + * This example is intended to be used with server_example_complex_array + */ + +#include "iec61850_client.h" + +#include +#include + +#include "hal_thread.h" + +int main(int argc, char** argv) { + + char* hostname; + int tcpPort = 102; + + if (argc > 1) + hostname = argv[1]; + else + hostname = "localhost"; + + if (argc > 2) + tcpPort = atoi(argv[2]); + + IedClientError error; + + IedConnection con = IedConnection_create(); + + IedConnection_connect(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) { + + /* read complete array */ + MmsValue* array = IedConnection_readObject(con, &error, "testComplexArray/MHAI1.HA.phsAHar", IEC61850_FC_MX); + + if (array != NULL) { + MmsValue_delete(array); + } + else + printf("Failed to read array!\n"); + + /* read array element at different component levels */ + char* arrayElementRef = "testComplexArray/MHAI1.HA.phsAHar(2).cVal.mag.f"; + + MmsValue* element = IedConnection_readObject(con, &error, arrayElementRef, IEC61850_FC_MX); + + if (element != NULL) { + MmsValue_delete(element); + } + else + printf("Failed to read array element %s!\n", arrayElementRef); + + + arrayElementRef = "testComplexArray/MHAI1.HA.phsAHar(2).cVal.mag"; + + element = IedConnection_readObject(con, &error, arrayElementRef, IEC61850_FC_MX); + + if (element != NULL) { + MmsValue_delete(element); + } + else + printf("Failed to read array element %s!\n", arrayElementRef); + + arrayElementRef = "testComplexArray/MHAI1.HA.phsAHar(2).cVal"; + + element = IedConnection_readObject(con, &error, arrayElementRef, IEC61850_FC_MX); + + if (element != NULL) { + MmsValue_delete(element); + } + else + printf("Failed to read array element %s!\n", arrayElementRef); + + arrayElementRef = "testComplexArray/MHAI1.HA.phsAHar(2)"; + + element = IedConnection_readObject(con, &error, arrayElementRef, IEC61850_FC_MX); + + if (element != NULL) { + MmsValue_delete(element); + } + else + printf("Failed to read array element %s!\n", arrayElementRef); + + close_connection: + + IedConnection_close(con); + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + IedConnection_destroy(con); +} + + diff --git a/examples/server_example_complex_array/mhai_array.icd b/examples/server_example_complex_array/mhai_array.icd index e6f09be91..4a622f06d 100644 --- a/examples/server_example_complex_array/mhai_array.icd +++ b/examples/server_example_complex_array/mhai_array.icd @@ -1,24 +1,31 @@ -
+ +
+ Station bus 10 - +
-

10.0.0.2

+

0.0.0.0

255.255.255.0

-

10.0.0.1

-

0001

+

192.168.2.1

+

1,3,9999,33

+

33

00000001

0001

+

0001

+

102

+ + @@ -28,16 +35,16 @@ - - - - - + + + + + @@ -46,7 +53,56 @@ - + + + + + ok + + + + + + + + on + + + status-only + + + + + on + + + + + ok + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + @@ -54,39 +110,43 @@ + + + + + + - - - + + + + - + + - - - + + + - - - - - - - + + + @@ -97,6 +157,25 @@ + + + + + + + + + + + + + + + + + + + @@ -104,28 +183,13 @@ - + - - - - - - - - - - - - - - - @@ -133,140 +197,84 @@ + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + - - - - - - - + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - - - - - - + + on + blocked + test + test/blocked + off + + + ok + warning + alarm + status-only @@ -275,10 +283,7 @@ direct-with-enhanced-security sbo-with-enhanced-security - - operate-once - operate-many - + not-supported bay-control @@ -290,5 +295,7 @@ maintenance process + + diff --git a/examples/server_example_complex_array/server_example_ca.c b/examples/server_example_complex_array/server_example_ca.c index ca8bffeac..b2146cc56 100644 --- a/examples/server_example_complex_array/server_example_ca.c +++ b/examples/server_example_complex_array/server_example_ca.c @@ -62,11 +62,6 @@ int main(int argc, char** argv) { DataObject* mhai1_ha_phsAHar = (DataObject*) IedModel_getModelNodeByObjectReference(&iedModel, "testComplexArray/MHAI1.HA.phsAHar"); - /* alternative: only for static model */ -// DataObject* mhai1_ha_phsAHar = IEDMODEL_ComplexArray_MHAI1_HA_phsAHar; - - assert(mhai1_ha_phsAHar != NULL); - /* Get access to the corresponding MMS value data structure - the MX(FC) part of the data object */ MmsValue* mhai1_ha_phsAHar_mx = IedServer_getFunctionalConstrainedData(iedServer, mhai1_ha_phsAHar, IEC61850_FC_MX); diff --git a/examples/server_example_complex_array/static_model.c b/examples/server_example_complex_array/static_model.c index 760061975..d9bc2a48d 100644 --- a/examples/server_example_complex_array/static_model.c +++ b/examples/server_example_complex_array/static_model.c @@ -30,10 +30,23 @@ DataObject iedModel_ComplexArray_LLN0_Mod = { "Mod", (ModelNode*) &iedModel_ComplexArray_LLN0, (ModelNode*) &iedModel_ComplexArray_LLN0_Beh, - (ModelNode*) &iedModel_ComplexArray_LLN0_Mod_q, + (ModelNode*) &iedModel_ComplexArray_LLN0_Mod_stVal, 0 }; +DataAttribute iedModel_ComplexArray_LLN0_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_LLN0_Mod, + (ModelNode*) &iedModel_ComplexArray_LLN0_Mod_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + DataAttribute iedModel_ComplexArray_LLN0_Mod_q = { DataAttributeModelType, "q", @@ -90,7 +103,7 @@ DataAttribute iedModel_ComplexArray_LLN0_Beh_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -138,7 +151,7 @@ DataAttribute iedModel_ComplexArray_LLN0_Health_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -247,7 +260,7 @@ LogicalNode iedModel_ComplexArray_LPHD1 = { LogicalNodeModelType, "LPHD1", (ModelNode*) &iedModel_ComplexArray, - (ModelNode*) &iedModel_ComplexArray_MHAI1, + (ModelNode*) &iedModel_ComplexArray_GGIO1, (ModelNode*) &iedModel_ComplexArray_LPHD1_PhyNam, }; @@ -290,7 +303,7 @@ DataAttribute iedModel_ComplexArray_LPHD1_PhyHealth_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -369,201 +382,1645 @@ DataAttribute iedModel_ComplexArray_LPHD1_Proxy_t = { NULL, 0}; -LogicalNode iedModel_ComplexArray_MHAI1 = { +LogicalNode iedModel_ComplexArray_GGIO1 = { LogicalNodeModelType, - "MHAI1", + "GGIO1", (ModelNode*) &iedModel_ComplexArray, - NULL, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, -}; - -DataObject iedModel_ComplexArray_MHAI1_HA = { - DataObjectModelType, - "HA", (ModelNode*) &iedModel_ComplexArray_MHAI1, - NULL, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, - 0 + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod, }; -DataObject iedModel_ComplexArray_MHAI1_HA_phsAHar = { +DataObject iedModel_ComplexArray_GGIO1_Mod = { DataObjectModelType, - "phsAHar", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_numHar, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal, - 16 + "Mod", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Beh, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod_stVal, + 0 }; -DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal = { +DataAttribute iedModel_ComplexArray_GGIO1_Mod_stVal = { DataAttributeModelType, - "cVal", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_q, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod_q, + NULL, 0, - IEC61850_FC_MX, - IEC61850_CONSTRUCTED, - 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag = { +DataAttribute iedModel_ComplexArray_GGIO1_Mod_q = { DataAttributeModelType, - "mag", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag_f, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod_t, + NULL, 0, - IEC61850_FC_MX, - IEC61850_CONSTRUCTED, - 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag_f = { +DataAttribute iedModel_ComplexArray_GGIO1_Mod_t = { DataAttributeModelType, - "f", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag, - NULL, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod_ctlModel, NULL, 0, - IEC61850_FC_MX, - IEC61850_FLOAT32, - 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang = { +DataAttribute iedModel_ComplexArray_GGIO1_Mod_ctlModel = { DataAttributeModelType, - "ang", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal, + "ctlModel", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Mod, + NULL, NULL, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang_f, 0, - IEC61850_FC_MX, - IEC61850_CONSTRUCTED, - 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang_f = { +DataObject iedModel_ComplexArray_GGIO1_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Health, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Beh_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_Beh_stVal = { DataAttributeModelType, - "f", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang, - NULL, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Beh, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Beh_q, NULL, 0, - IEC61850_FC_MX, - IEC61850_FLOAT32, - 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_q = { +DataAttribute iedModel_ComplexArray_GGIO1_Beh_q = { DataAttributeModelType, "q", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_t, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Beh, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Beh_t, NULL, 0, - IEC61850_FC_MX, + IEC61850_FC_ST, IEC61850_QUALITY, 0 + TRG_OPT_QUALITY_CHANGED, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_t = { +DataAttribute iedModel_ComplexArray_GGIO1_Beh_t = { DataAttributeModelType, "t", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Beh, NULL, NULL, 0, - IEC61850_FC_MX, + IEC61850_FC_ST, IEC61850_TIMESTAMP, 0, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_numHar = { +DataObject iedModel_ComplexArray_GGIO1_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Health_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_Health_stVal = { DataAttributeModelType, - "numHar", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_numCyc, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Health, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Health_q, NULL, 0, - IEC61850_FC_CF, - IEC61850_INT16U, + IEC61850_FC_ST, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_numCyc = { +DataAttribute iedModel_ComplexArray_GGIO1_Health_q = { DataAttributeModelType, - "numCyc", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_evalTm, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Health, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Health_t, NULL, 0, - IEC61850_FC_CF, - IEC61850_INT16U, - 0 + TRG_OPT_DATA_CHANGED, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_evalTm = { +DataAttribute iedModel_ComplexArray_GGIO1_Health_t = { DataAttributeModelType, - "evalTm", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_frequency, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Health, + NULL, NULL, 0, - IEC61850_FC_CF, - IEC61850_INT16U, - 0 + TRG_OPT_DATA_CHANGED, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, NULL, 0}; -DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency = { +DataObject iedModel_ComplexArray_GGIO1_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt_vendor, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_vendor = { DataAttributeModelType, - "frequency", - (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, - NULL, + "vendor", + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt, + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt_swRev, NULL, 0, - IEC61850_FC_CF, - IEC61850_FLOAT32, - 0 + TRG_OPT_DATA_CHANGED, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, NULL, 0}; +DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt, + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt_d, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; +DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt, + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt_dU, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; +DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_dU = { + DataAttributeModelType, + "dU", + (ModelNode*) &iedModel_ComplexArray_GGIO1_NamPlt, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_UNICODE_STRING_255, + 0, + NULL, + 0}; +DataObject iedModel_ComplexArray_GGIO1_AnIn1 = { + DataObjectModelType, + "AnIn1", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1_mag, + 0 +}; +DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1_q, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; - - - -IedModel iedModel = { - "test", - &iedModel_ComplexArray, +DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1_mag, + NULL, NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1_t, NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn1, NULL, NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, NULL, - initializeValues + 0}; + +DataObject iedModel_ComplexArray_GGIO1_AnIn2 = { + DataObjectModelType, + "AnIn2", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2_mag, + 0 }; -static void -initializeValues() -{ +DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2_q, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; -iedModel_ComplexArray_LLN0_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); +DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn2, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_AnIn3 = { + DataObjectModelType, + "AnIn3", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3_mag, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3_q, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn3, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_AnIn4 = { + DataObjectModelType, + "AnIn4", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4_mag, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4_q, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_AnIn4, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_SPCSO1 = { + DataObjectModelType, + "SPCSO1", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_origin, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_ctlNum, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_origin_orCat, + 0, + IEC61850_FC_ST, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_origin, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_origin_orIdent, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_origin, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_stVal, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1, + NULL, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlNum, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO1_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_SPCSO2 = { + DataObjectModelType, + "SPCSO2", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_ctlModel, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlNum, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO2, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_SPCSO3 = { + DataObjectModelType, + "SPCSO3", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_ctlModel, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlNum, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO3, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_SPCSO4 = { + DataObjectModelType, + "SPCSO4", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_ctlModel, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlNum, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_SPCSO4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_Ind1 = { + DataObjectModelType, + "Ind1", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind1_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind1_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind1_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind1, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_Ind2 = { + DataObjectModelType, + "Ind2", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind2_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind2_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind2, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind2_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind2, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_Ind3 = { + DataObjectModelType, + "Ind3", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind3_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind3_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind3, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind3_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind3, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_ComplexArray_GGIO1_Ind4 = { + DataObjectModelType, + "Ind4", + (ModelNode*) &iedModel_ComplexArray_GGIO1, + NULL, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind4_stVal, + 0 +}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind4_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind4, + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind4_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_GGIO1_Ind4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_GGIO1_Ind4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +LogicalNode iedModel_ComplexArray_MHAI1 = { + LogicalNodeModelType, + "MHAI1", + (ModelNode*) &iedModel_ComplexArray, + NULL, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, +}; + +DataObject iedModel_ComplexArray_MHAI1_HA = { + DataObjectModelType, + "HA", + (ModelNode*) &iedModel_ComplexArray_MHAI1, + NULL, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, + 0 +}; + +DataObject iedModel_ComplexArray_MHAI1_HA_phsAHar = { + DataObjectModelType, + "phsAHar", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_numHar, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal, + 16 +}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal = { + DataAttributeModelType, + "cVal", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_q, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang = { + DataAttributeModelType, + "ang", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal, + NULL, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_cVal_ang, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED + TRG_OPT_DATA_UPDATE, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_phsAHar_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_phsAHar, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_numHar = { + DataAttributeModelType, + "numHar", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_numCyc, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT16U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_numCyc = { + DataAttributeModelType, + "numCyc", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_evalTm, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT16U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_evalTm = { + DataAttributeModelType, + "evalTm", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA_frequency, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_INT16U, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency = { + DataAttributeModelType, + "frequency", + (ModelNode*) &iedModel_ComplexArray_MHAI1_HA, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + + + + + + + + + +IedModel iedModel = { + "test", + &iedModel_ComplexArray, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + initializeValues +}; + +static void +initializeValues() +{ + +iedModel_ComplexArray_LLN0_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); + +iedModel_ComplexArray_LPHD1_PhyHealth_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_ComplexArray_GGIO1_Mod_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_ComplexArray_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); + +iedModel_ComplexArray_GGIO1_Beh_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_ComplexArray_GGIO1_Health_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_ComplexArray_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_ComplexArray_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_ComplexArray_GGIO1_SPCSO3_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_ComplexArray_GGIO1_SPCSO4_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); iedModel_ComplexArray_MHAI1_HA_numHar.mmsValue = MmsValue_newUnsignedFromUint32(16); } diff --git a/examples/server_example_complex_array/static_model.h b/examples/server_example_complex_array/static_model.h index 0999d5812..18f5206e1 100644 --- a/examples/server_example_complex_array/static_model.h +++ b/examples/server_example_complex_array/static_model.h @@ -14,6 +14,7 @@ extern IedModel iedModel; extern LogicalDevice iedModel_ComplexArray; extern LogicalNode iedModel_ComplexArray_LLN0; extern DataObject iedModel_ComplexArray_LLN0_Mod; +extern DataAttribute iedModel_ComplexArray_LLN0_Mod_stVal; extern DataAttribute iedModel_ComplexArray_LLN0_Mod_q; extern DataAttribute iedModel_ComplexArray_LLN0_Mod_t; extern DataAttribute iedModel_ComplexArray_LLN0_Mod_ctlModel; @@ -42,6 +43,121 @@ extern DataObject iedModel_ComplexArray_LPHD1_Proxy; extern DataAttribute iedModel_ComplexArray_LPHD1_Proxy_stVal; extern DataAttribute iedModel_ComplexArray_LPHD1_Proxy_q; extern DataAttribute iedModel_ComplexArray_LPHD1_Proxy_t; +extern LogicalNode iedModel_ComplexArray_GGIO1; +extern DataObject iedModel_ComplexArray_GGIO1_Mod; +extern DataAttribute iedModel_ComplexArray_GGIO1_Mod_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_Mod_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_Mod_t; +extern DataAttribute iedModel_ComplexArray_GGIO1_Mod_ctlModel; +extern DataObject iedModel_ComplexArray_GGIO1_Beh; +extern DataAttribute iedModel_ComplexArray_GGIO1_Beh_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_Beh_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_Beh_t; +extern DataObject iedModel_ComplexArray_GGIO1_Health; +extern DataAttribute iedModel_ComplexArray_GGIO1_Health_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_Health_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_Health_t; +extern DataObject iedModel_ComplexArray_GGIO1_NamPlt; +extern DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_vendor; +extern DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_swRev; +extern DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_d; +extern DataAttribute iedModel_ComplexArray_GGIO1_NamPlt_dU; +extern DataObject iedModel_ComplexArray_GGIO1_AnIn1; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_mag; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_mag_f; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn1_t; +extern DataObject iedModel_ComplexArray_GGIO1_AnIn2; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_mag; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_mag_f; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn2_t; +extern DataObject iedModel_ComplexArray_GGIO1_AnIn3; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_mag; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_mag_f; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn3_t; +extern DataObject iedModel_ComplexArray_GGIO1_AnIn4; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_mag; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_mag_f; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_AnIn4_t; +extern DataObject iedModel_ComplexArray_GGIO1_SPCSO1; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_origin; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_origin_orCat; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_origin_orIdent; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_ctlNum; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_t; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_ctlModel; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orCat; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orIdent; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlNum; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_T; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Test; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Check; +extern DataObject iedModel_ComplexArray_GGIO1_SPCSO2; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orCat; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orIdent; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlNum; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_T; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Test; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Check; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_ctlModel; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO2_t; +extern DataObject iedModel_ComplexArray_GGIO1_SPCSO3; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orCat; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orIdent; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlNum; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_T; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Test; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Check; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_ctlModel; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO3_t; +extern DataObject iedModel_ComplexArray_GGIO1_SPCSO4; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orCat; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orIdent; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlNum; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_T; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Test; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Check; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_ctlModel; +extern DataAttribute iedModel_ComplexArray_GGIO1_SPCSO4_t; +extern DataObject iedModel_ComplexArray_GGIO1_Ind1; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind1_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind1_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind1_t; +extern DataObject iedModel_ComplexArray_GGIO1_Ind2; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind2_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind2_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind2_t; +extern DataObject iedModel_ComplexArray_GGIO1_Ind3; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind3_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind3_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind3_t; +extern DataObject iedModel_ComplexArray_GGIO1_Ind4; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind4_stVal; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind4_q; +extern DataAttribute iedModel_ComplexArray_GGIO1_Ind4_t; extern LogicalNode iedModel_ComplexArray_MHAI1; extern DataObject iedModel_ComplexArray_MHAI1_HA; extern DataObject iedModel_ComplexArray_MHAI1_HA_phsAHar; @@ -62,6 +178,7 @@ extern DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency; #define IEDMODEL_ComplexArray (&iedModel_ComplexArray) #define IEDMODEL_ComplexArray_LLN0 (&iedModel_ComplexArray_LLN0) #define IEDMODEL_ComplexArray_LLN0_Mod (&iedModel_ComplexArray_LLN0_Mod) +#define IEDMODEL_ComplexArray_LLN0_Mod_stVal (&iedModel_ComplexArray_LLN0_Mod_stVal) #define IEDMODEL_ComplexArray_LLN0_Mod_q (&iedModel_ComplexArray_LLN0_Mod_q) #define IEDMODEL_ComplexArray_LLN0_Mod_t (&iedModel_ComplexArray_LLN0_Mod_t) #define IEDMODEL_ComplexArray_LLN0_Mod_ctlModel (&iedModel_ComplexArray_LLN0_Mod_ctlModel) @@ -90,6 +207,121 @@ extern DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency; #define IEDMODEL_ComplexArray_LPHD1_Proxy_stVal (&iedModel_ComplexArray_LPHD1_Proxy_stVal) #define IEDMODEL_ComplexArray_LPHD1_Proxy_q (&iedModel_ComplexArray_LPHD1_Proxy_q) #define IEDMODEL_ComplexArray_LPHD1_Proxy_t (&iedModel_ComplexArray_LPHD1_Proxy_t) +#define IEDMODEL_ComplexArray_GGIO1 (&iedModel_ComplexArray_GGIO1) +#define IEDMODEL_ComplexArray_GGIO1_Mod (&iedModel_ComplexArray_GGIO1_Mod) +#define IEDMODEL_ComplexArray_GGIO1_Mod_stVal (&iedModel_ComplexArray_GGIO1_Mod_stVal) +#define IEDMODEL_ComplexArray_GGIO1_Mod_q (&iedModel_ComplexArray_GGIO1_Mod_q) +#define IEDMODEL_ComplexArray_GGIO1_Mod_t (&iedModel_ComplexArray_GGIO1_Mod_t) +#define IEDMODEL_ComplexArray_GGIO1_Mod_ctlModel (&iedModel_ComplexArray_GGIO1_Mod_ctlModel) +#define IEDMODEL_ComplexArray_GGIO1_Beh (&iedModel_ComplexArray_GGIO1_Beh) +#define IEDMODEL_ComplexArray_GGIO1_Beh_stVal (&iedModel_ComplexArray_GGIO1_Beh_stVal) +#define IEDMODEL_ComplexArray_GGIO1_Beh_q (&iedModel_ComplexArray_GGIO1_Beh_q) +#define IEDMODEL_ComplexArray_GGIO1_Beh_t (&iedModel_ComplexArray_GGIO1_Beh_t) +#define IEDMODEL_ComplexArray_GGIO1_Health (&iedModel_ComplexArray_GGIO1_Health) +#define IEDMODEL_ComplexArray_GGIO1_Health_stVal (&iedModel_ComplexArray_GGIO1_Health_stVal) +#define IEDMODEL_ComplexArray_GGIO1_Health_q (&iedModel_ComplexArray_GGIO1_Health_q) +#define IEDMODEL_ComplexArray_GGIO1_Health_t (&iedModel_ComplexArray_GGIO1_Health_t) +#define IEDMODEL_ComplexArray_GGIO1_NamPlt (&iedModel_ComplexArray_GGIO1_NamPlt) +#define IEDMODEL_ComplexArray_GGIO1_NamPlt_vendor (&iedModel_ComplexArray_GGIO1_NamPlt_vendor) +#define IEDMODEL_ComplexArray_GGIO1_NamPlt_swRev (&iedModel_ComplexArray_GGIO1_NamPlt_swRev) +#define IEDMODEL_ComplexArray_GGIO1_NamPlt_d (&iedModel_ComplexArray_GGIO1_NamPlt_d) +#define IEDMODEL_ComplexArray_GGIO1_NamPlt_dU (&iedModel_ComplexArray_GGIO1_NamPlt_dU) +#define IEDMODEL_ComplexArray_GGIO1_AnIn1 (&iedModel_ComplexArray_GGIO1_AnIn1) +#define IEDMODEL_ComplexArray_GGIO1_AnIn1_mag (&iedModel_ComplexArray_GGIO1_AnIn1_mag) +#define IEDMODEL_ComplexArray_GGIO1_AnIn1_mag_f (&iedModel_ComplexArray_GGIO1_AnIn1_mag_f) +#define IEDMODEL_ComplexArray_GGIO1_AnIn1_q (&iedModel_ComplexArray_GGIO1_AnIn1_q) +#define IEDMODEL_ComplexArray_GGIO1_AnIn1_t (&iedModel_ComplexArray_GGIO1_AnIn1_t) +#define IEDMODEL_ComplexArray_GGIO1_AnIn2 (&iedModel_ComplexArray_GGIO1_AnIn2) +#define IEDMODEL_ComplexArray_GGIO1_AnIn2_mag (&iedModel_ComplexArray_GGIO1_AnIn2_mag) +#define IEDMODEL_ComplexArray_GGIO1_AnIn2_mag_f (&iedModel_ComplexArray_GGIO1_AnIn2_mag_f) +#define IEDMODEL_ComplexArray_GGIO1_AnIn2_q (&iedModel_ComplexArray_GGIO1_AnIn2_q) +#define IEDMODEL_ComplexArray_GGIO1_AnIn2_t (&iedModel_ComplexArray_GGIO1_AnIn2_t) +#define IEDMODEL_ComplexArray_GGIO1_AnIn3 (&iedModel_ComplexArray_GGIO1_AnIn3) +#define IEDMODEL_ComplexArray_GGIO1_AnIn3_mag (&iedModel_ComplexArray_GGIO1_AnIn3_mag) +#define IEDMODEL_ComplexArray_GGIO1_AnIn3_mag_f (&iedModel_ComplexArray_GGIO1_AnIn3_mag_f) +#define IEDMODEL_ComplexArray_GGIO1_AnIn3_q (&iedModel_ComplexArray_GGIO1_AnIn3_q) +#define IEDMODEL_ComplexArray_GGIO1_AnIn3_t (&iedModel_ComplexArray_GGIO1_AnIn3_t) +#define IEDMODEL_ComplexArray_GGIO1_AnIn4 (&iedModel_ComplexArray_GGIO1_AnIn4) +#define IEDMODEL_ComplexArray_GGIO1_AnIn4_mag (&iedModel_ComplexArray_GGIO1_AnIn4_mag) +#define IEDMODEL_ComplexArray_GGIO1_AnIn4_mag_f (&iedModel_ComplexArray_GGIO1_AnIn4_mag_f) +#define IEDMODEL_ComplexArray_GGIO1_AnIn4_q (&iedModel_ComplexArray_GGIO1_AnIn4_q) +#define IEDMODEL_ComplexArray_GGIO1_AnIn4_t (&iedModel_ComplexArray_GGIO1_AnIn4_t) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1 (&iedModel_ComplexArray_GGIO1_SPCSO1) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_origin (&iedModel_ComplexArray_GGIO1_SPCSO1_origin) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_origin_orCat (&iedModel_ComplexArray_GGIO1_SPCSO1_origin_orCat) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_origin_orIdent (&iedModel_ComplexArray_GGIO1_SPCSO1_origin_orIdent) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_ctlNum (&iedModel_ComplexArray_GGIO1_SPCSO1_ctlNum) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_stVal (&iedModel_ComplexArray_GGIO1_SPCSO1_stVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_q (&iedModel_ComplexArray_GGIO1_SPCSO1_q) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_t (&iedModel_ComplexArray_GGIO1_SPCSO1_t) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_ctlModel (&iedModel_ComplexArray_GGIO1_SPCSO1_ctlModel) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_ctlVal (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_origin (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_origin_orCat (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orCat) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_origin_orIdent (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_origin_orIdent) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_ctlNum (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_ctlNum) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_T (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_T) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_Test (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Test) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO1_Oper_Check (&iedModel_ComplexArray_GGIO1_SPCSO1_Oper_Check) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2 (&iedModel_ComplexArray_GGIO1_SPCSO2) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_stVal (&iedModel_ComplexArray_GGIO1_SPCSO2_stVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_q (&iedModel_ComplexArray_GGIO1_SPCSO2_q) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_ctlVal (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_origin (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_origin_orCat (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orCat) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_origin_orIdent (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_origin_orIdent) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_ctlNum (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_ctlNum) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_T (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_T) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_Test (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Test) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_Oper_Check (&iedModel_ComplexArray_GGIO1_SPCSO2_Oper_Check) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_ctlModel (&iedModel_ComplexArray_GGIO1_SPCSO2_ctlModel) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO2_t (&iedModel_ComplexArray_GGIO1_SPCSO2_t) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3 (&iedModel_ComplexArray_GGIO1_SPCSO3) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_stVal (&iedModel_ComplexArray_GGIO1_SPCSO3_stVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_q (&iedModel_ComplexArray_GGIO1_SPCSO3_q) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_ctlVal (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_origin (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_origin_orCat (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orCat) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_origin_orIdent (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_origin_orIdent) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_ctlNum (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_ctlNum) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_T (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_T) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_Test (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Test) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_Oper_Check (&iedModel_ComplexArray_GGIO1_SPCSO3_Oper_Check) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_ctlModel (&iedModel_ComplexArray_GGIO1_SPCSO3_ctlModel) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO3_t (&iedModel_ComplexArray_GGIO1_SPCSO3_t) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4 (&iedModel_ComplexArray_GGIO1_SPCSO4) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_stVal (&iedModel_ComplexArray_GGIO1_SPCSO4_stVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_q (&iedModel_ComplexArray_GGIO1_SPCSO4_q) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_ctlVal (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlVal) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_origin (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_origin_orCat (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orCat) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_origin_orIdent (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_origin_orIdent) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_ctlNum (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_ctlNum) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_T (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_T) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_Test (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Test) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_Oper_Check (&iedModel_ComplexArray_GGIO1_SPCSO4_Oper_Check) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_ctlModel (&iedModel_ComplexArray_GGIO1_SPCSO4_ctlModel) +#define IEDMODEL_ComplexArray_GGIO1_SPCSO4_t (&iedModel_ComplexArray_GGIO1_SPCSO4_t) +#define IEDMODEL_ComplexArray_GGIO1_Ind1 (&iedModel_ComplexArray_GGIO1_Ind1) +#define IEDMODEL_ComplexArray_GGIO1_Ind1_stVal (&iedModel_ComplexArray_GGIO1_Ind1_stVal) +#define IEDMODEL_ComplexArray_GGIO1_Ind1_q (&iedModel_ComplexArray_GGIO1_Ind1_q) +#define IEDMODEL_ComplexArray_GGIO1_Ind1_t (&iedModel_ComplexArray_GGIO1_Ind1_t) +#define IEDMODEL_ComplexArray_GGIO1_Ind2 (&iedModel_ComplexArray_GGIO1_Ind2) +#define IEDMODEL_ComplexArray_GGIO1_Ind2_stVal (&iedModel_ComplexArray_GGIO1_Ind2_stVal) +#define IEDMODEL_ComplexArray_GGIO1_Ind2_q (&iedModel_ComplexArray_GGIO1_Ind2_q) +#define IEDMODEL_ComplexArray_GGIO1_Ind2_t (&iedModel_ComplexArray_GGIO1_Ind2_t) +#define IEDMODEL_ComplexArray_GGIO1_Ind3 (&iedModel_ComplexArray_GGIO1_Ind3) +#define IEDMODEL_ComplexArray_GGIO1_Ind3_stVal (&iedModel_ComplexArray_GGIO1_Ind3_stVal) +#define IEDMODEL_ComplexArray_GGIO1_Ind3_q (&iedModel_ComplexArray_GGIO1_Ind3_q) +#define IEDMODEL_ComplexArray_GGIO1_Ind3_t (&iedModel_ComplexArray_GGIO1_Ind3_t) +#define IEDMODEL_ComplexArray_GGIO1_Ind4 (&iedModel_ComplexArray_GGIO1_Ind4) +#define IEDMODEL_ComplexArray_GGIO1_Ind4_stVal (&iedModel_ComplexArray_GGIO1_Ind4_stVal) +#define IEDMODEL_ComplexArray_GGIO1_Ind4_q (&iedModel_ComplexArray_GGIO1_Ind4_q) +#define IEDMODEL_ComplexArray_GGIO1_Ind4_t (&iedModel_ComplexArray_GGIO1_Ind4_t) #define IEDMODEL_ComplexArray_MHAI1 (&iedModel_ComplexArray_MHAI1) #define IEDMODEL_ComplexArray_MHAI1_HA (&iedModel_ComplexArray_MHAI1_HA) #define IEDMODEL_ComplexArray_MHAI1_HA_phsAHar (&iedModel_ComplexArray_MHAI1_HA_phsAHar) From 2585aab5166a888dd18a329c5e7e5facdcf5d9fb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 1 Sep 2018 11:14:22 +0200 Subject: [PATCH 111/123] - cleanup examples makefiles - fixed bug in MMS mapping read handler --- examples/CMakeLists.txt | 1 + examples/Makefile | 5 ++--- src/iec61850/server/mms_mapping/mms_mapping.c | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 153ee6060..31552c2fe 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(iec61850_client_example4) add_subdirectory(iec61850_client_example5) add_subdirectory(iec61850_client_example_reporting) add_subdirectory(iec61850_client_example_log) +add_subdirectory(iec61850_client_example_array) if(NOT WIN32) add_subdirectory(iec61850_client_example_files) diff --git a/examples/Makefile b/examples/Makefile index 6bf1c8f91..a6d6d5d92 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,11 +1,11 @@ EXAMPLE_DIRS = iec61850_client_example1 EXAMPLE_DIRS += iec61850_client_example2 -EXAMPLE_DIRS += iec61850_client_example3 EXAMPLE_DIRS += iec61850_client_example4 EXAMPLE_DIRS += iec61850_client_example5 EXAMPLE_DIRS += iec61850_client_example_reporting -EXAMPLE_DIRS += iec61850_client_example_log +EXAMPLE_DIRS += iec61850_client_example_array +EXAMPLE_DIRS += iec61850_client_example_control EXAMPLE_DIRS += server_example_simple EXAMPLE_DIRS += server_example_basic_io EXAMPLE_DIRS += server_example_password_auth @@ -18,7 +18,6 @@ EXAMPLE_DIRS += server_example_complex_array EXAMPLE_DIRS += server_example_61400_25 EXAMPLE_DIRS += server_example_threadless EXAMPLE_DIRS += server_example_setting_groups -EXAMPLE_DIRS += server_example_logging EXAMPLE_DIRS += server_example_files EXAMPLE_DIRS += server_example_substitution EXAMPLE_DIRS += goose_subscriber diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 1572ce73f..b8d7ee8e1 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-2017 Michael Zillgith + * Copyright 2013-2018 Michael Zillgith * * This file is part of libIEC61850. * @@ -2447,15 +2447,17 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS #if (CONFIG_IEC61850_SETTING_GROUPS == 1) - if (isFunctionalConstraintSE(separator)) { - SettingGroup* sg = getSettingGroupByMmsDomain(self, domain); + if (separator) { + if (isFunctionalConstraintSE(separator)) { + SettingGroup* sg = getSettingGroupByMmsDomain(self, domain); - if (sg != NULL) { - if (sg->sgcb->editSG == 0) - return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + if (sg != NULL) { + if (sg->sgcb->editSG == 0) + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + else + return DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; } - else - return DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; } #endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */ From d108dae1155aa5dd7c7a9ed5853a51baffdc9e90 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 1 Sep 2018 15:03:13 +0200 Subject: [PATCH 112/123] - MMS server: fixed locking bug in file obtain service --- src/mms/iso_mms/server/mms_file_service.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index 132039e01..1bd29ef5e 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -667,7 +667,7 @@ mmsServer_handleObtainFileRequest( mmsClient_createFileOpenRequest(task->lastRequestInvokeId, request, sourceFilename, 0); - IsoConnection_sendMessage(task->connection->isoConnection, request, false); + IsoConnection_sendMessage(task->connection->isoConnection, request, true); MmsServer_releaseTransmitBuffer(connection->server); From e980a519ae0a17592265255789c2b6a437b08942 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 1 Sep 2018 16:37:01 +0200 Subject: [PATCH 113/123] - IEC 61850 server: control objects - fixed bug in select response for SBO control model --- 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 d8f37e1af..2febf4847 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -379,7 +379,7 @@ ControlObject_initialize(ControlObject* self) char* controlObjectReference = StringUtils_createStringInBuffer(strBuf, 6, self->mmsDomain->domainName, - "/", self->lnName, "$", self->name, "$SBO"); + "/", self->lnName, "$CO$", self->name, "$SBO"); self->sbo = MmsValue_newVisibleString(controlObjectReference); From 51c29fe9a7e2a9faffa05a419b66fcd095672723 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 1 Sep 2018 21:37:20 +0200 Subject: [PATCH 114/123] - IEC 61850 server/ MMS server: maximum number of client connections configurable at runtime --- config/stack_config.h | 2 +- dotnet/IEC61850forCSharp/IedServerConfig.cs | 41 +++++++++++++++++ .../server_example_basic_io.c | 6 ++- src/iec61850/inc/iec61850_server.h | 20 +++++++- src/iec61850/server/impl/ied_server.c | 1 + src/iec61850/server/impl/ied_server_config.c | 12 +++++ src/mms/inc/iso_server.h | 3 ++ src/mms/inc/mms_server.h | 8 ++++ src/mms/inc_private/mms_server_internal.h | 1 + src/mms/iso_mms/server/mms_server.c | 6 +++ src/mms/iso_server/iso_server.c | 46 ++++++++++++++----- src/vs/libiec61850-wo-goose.def | 2 + src/vs/libiec61850.def | 2 + 13 files changed, 135 insertions(+), 15 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index 4cac7cfdd..a3c419df5 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -51,7 +51,7 @@ #define CONFIG_MMS_THREADLESS_STACK 0 /* number of concurrent MMS client connections the server accepts, -1 for no limit */ -#define CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS 5 +#define CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS 100 /* activate TCP keep alive mechanism. 1 -> activate */ #define CONFIG_ACTIVATE_TCP_KEEPALIVE 1 diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs index 9f1d7bde2..586f547bd 100644 --- a/dotnet/IEC61850forCSharp/IedServerConfig.cs +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -56,6 +56,19 @@ public class IedServerConfig : IDisposable [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); + internal IntPtr self; public IedServerConfig () @@ -91,6 +104,10 @@ public string FileServiceBasePath } } + /// + /// Gets or sets the edition of the IEC 61850 standard to use + /// + /// The IEC 61850 edition to use. public Iec61850Edition Edition { get { @@ -101,6 +118,30 @@ public Iec61850Edition Edition } } + /// + /// 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); + } + } + + public bool DynamicDataSetServiceEnabled + { + get { + return IedServerConfig_isDynamicDataSetServiceEnabled (self); + } + set { + IedServerConfig_enableDynamicDataSetService (self, value); + } + } + /// /// Releases all resource used by the object. /// diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index 55e789ec3..1f9285312 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -90,6 +90,7 @@ main(int argc, char** argv) /* 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); /* Set the base path for the MMS file services */ @@ -103,6 +104,9 @@ main(int argc, char** argv) IedServerConfig_enableLogService(config, true); + /* set maximum number of clients */ + IedServerConfig_setMaxMmsConnections(config, 2); + /* Create a new IEC 61850 server instance */ iedServer = IedServer_createWithConfig(&iedModel, NULL, config); @@ -132,7 +136,7 @@ main(int argc, char** argv) IedServer_start(iedServer, 102); if (!IedServer_isRunning(iedServer)) { - printf("Starting server failed! Exit.\n"); + printf("Starting server failed (maybe need root permissions or another server is already using the port)! Exit.\n"); IedServer_destroy(iedServer); exit(-1); } diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 8f6cabce2..bffed0360 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -65,6 +65,9 @@ struct sIedServerConfig /** IEC 61850 edition (0 = edition 1, 1 = edition 2, 2 = edition 2.1, ...) */ uint8_t edition; + + /** maximum number of MMS (TCP) connections */ + int maxMmsConnections; }; /** @@ -113,9 +116,22 @@ IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize); int IedServerConfig_getReportBufferSize(IedServerConfig self); +/** + * \brief Set the maximum number of MMS (TCP) connections the server accepts + * + * NOTE: Parameter has to be smaller than CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS if + * CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1 + * + * \param maxConnection maximum number of TCP connections + */ void IedServerConfig_setMaxMmsConnections(IedServerConfig self, int maxConnections); +/** + * \brief Get the maximum number of MMS (TCP) connections the server accepts + * + * \return maximum number of TCP connections + */ int IedServerConfig_getMaxMmsConnections(IedServerConfig self); @@ -150,10 +166,10 @@ bool IedServerConfig_isFileServiceEnabled(IedServerConfig self); void -IedServerConfig_enableFileWriteService(IedServerConfig self, bool enable); +IedServerConfig_enableSetFileService(IedServerConfig self, bool enable); bool -IedServerConfig_isFileWriteServiceEnabled(IedServerConfig self); +IedServerConfig_isSetFileServiceEnabled(IedServerConfig self); /** * \brief Enable/disable the dynamic data set service for MMS diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index d33a1e434..a1a351b65 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -439,6 +439,7 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); MmsServer_enableJournalService(self->mmsServer, serverConfiguration->enableLogService); MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); + MmsServer_setMaxConnections(self->mmsServer, serverConfiguration->maxMmsConnections); } #endif diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index 6712c0b96..cc01c773e 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -125,3 +125,15 @@ IedServerConfig_isLogServiceEnabled(IedServerConfig self) { return self->enableLogService; } + +void +IedServerConfig_setMaxMmsConnections(IedServerConfig self, int maxConnections) +{ + self->maxMmsConnections = maxConnections; +} + +int +IedServerConfig_getMaxMmsConnections(IedServerConfig self) +{ + return self->maxMmsConnections; +} diff --git a/src/mms/inc/iso_server.h b/src/mms/inc/iso_server.h index a5da64954..849df2cfb 100644 --- a/src/mms/inc/iso_server.h +++ b/src/mms/inc/iso_server.h @@ -94,6 +94,9 @@ IsoServer_create(TLSConfiguration tlsConfiguration); void IsoServer_setTcpPort(IsoServer self, int port); +void +IsoServer_setMaxConnections(IsoServer self, int maxConnections); + void IsoServer_setLocalIpAddress(IsoServer self, const char* ipAddress); diff --git a/src/mms/inc/mms_server.h b/src/mms/inc/mms_server.h index 5d6cd93b3..1780bc396 100644 --- a/src/mms/inc/mms_server.h +++ b/src/mms/inc/mms_server.h @@ -225,6 +225,14 @@ MmsServer_installFileAccessHandler(MmsServer self, MmsFileAccessHandler handler, void MmsServer_setFilestoreBasepath(MmsServer self, const char* basepath); +/** + * \brief Set the maximum number of TCP client connections + * + * \param[in] maxConnections the maximum number of TCP client connections to accept + */ +void +MmsServer_setMaxConnections(MmsServer self, int maxConnections); + /** * \brief Enable/disable MMS file services at runtime * diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 37cffb35e..62ea40616 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -165,6 +165,7 @@ struct sMmsServer { #endif #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + int maxConnections; bool fileServiceEnabled; bool dynamicVariableListServiceEnabled; bool journalServiceEnabled; diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 2352926de..5cf573902 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -107,6 +107,12 @@ MmsServer_setFilestoreBasepath(MmsServer self, const char* basepath) #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) +void +MmsServer_setMaxConnections(MmsServer self, int maxConnections) +{ + IsoServer_setMaxConnections(self->isoServer, maxConnections); +} + void MmsServer_enableFileService(MmsServer self, bool enable) { diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index f36af1dde..fe8554962 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -72,10 +72,14 @@ struct sIsoServer { TLSConfiguration tlsConfiguration; +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + int maxConnections; +#endif + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) LinkedList openClientConnections; #else - IsoConnection* openClientConnections; + IsoConnection openClientConnections[CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS]; #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ #if (CONFIG_MMS_THREADLESS_STACK != 1) @@ -355,11 +359,6 @@ setupIsoServer(IsoServer self) setState(self, ISO_SVR_STATE_RUNNING); -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1) - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: server is limited to %i client connections.\n", (int) CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS); -#endif - exit_function: return success; } @@ -374,6 +373,17 @@ handleIsoConnections(IsoServer self) if ((connectionSocket = ServerSocket_accept((ServerSocket) self->serverSocket)) != NULL) { +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if (private_IsoServer_getConnectionCounter(self) >= self->maxConnections) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); + + Socket_destroy(connectionSocket); + + return; + } +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1) if (private_IsoServer_getConnectionCounter(self) >= CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS) { if (DEBUG_ISO_SERVER) @@ -416,6 +426,17 @@ handleIsoConnectionsThreadless(IsoServer self) if ((connectionSocket = ServerSocket_accept((ServerSocket) self->serverSocket)) != NULL) { +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if (private_IsoServer_getConnectionCounter(self) >= self->maxConnections) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); + + Socket_destroy(connectionSocket); + + return; + } +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1) if (private_IsoServer_getConnectionCounter(self) >= CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS) { if (DEBUG_ISO_SERVER) @@ -499,9 +520,6 @@ IsoServer_create(TLSConfiguration tlsConfiguration) #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) self->openClientConnections = LinkedList_create(); -#else - self->openClientConnections = (IsoConnection*) - GLOBAL_CALLOC(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS, sizeof(IsoConnection)); #endif #if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) @@ -514,6 +532,14 @@ IsoServer_create(TLSConfiguration tlsConfiguration) return self; } +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) +void +IsoServer_setMaxConnections(IsoServer self, int maxConnections) +{ + self->maxConnections = maxConnections; +} +#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ + void IsoServer_setTcpPort(IsoServer self, int port) { @@ -769,8 +795,6 @@ IsoServer_destroy(IsoServer self) lockClientConnections(self); #endif -#else - GLOBAL_FREEMEM(self->openClientConnections); #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ #if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 30d98eb4b..27a7b3d69 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -612,3 +612,5 @@ EXPORTS IedServerConfig_isLogServiceEnabled IedServerConfig_setEdition IedServerConfig_getEdition + IedServerConfig_setMaxMmsConnections + IedServerConfig_getMaxMmsConnections diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 039e5ea8b..e90c3ff17 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -740,3 +740,5 @@ EXPORTS IedServerConfig_isLogServiceEnabled IedServerConfig_setEdition IedServerConfig_getEdition + IedServerConfig_setMaxMmsConnections + IedServerConfig_getMaxMmsConnections From 77f97dc0068e2a3cf519cdb7a546ef5e39821afa Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 3 Sep 2018 07:43:23 +0200 Subject: [PATCH 115/123] - replaced strndup --- examples/iec61850_client_example_array/CMakeLists.txt | 10 +++++----- src/mms/iso_mms/client/mms_client_read.c | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/iec61850_client_example_array/CMakeLists.txt b/examples/iec61850_client_example_array/CMakeLists.txt index 398e3b7b9..a4d4b9051 100644 --- a/examples/iec61850_client_example_array/CMakeLists.txt +++ b/examples/iec61850_client_example_array/CMakeLists.txt @@ -1,17 +1,17 @@ -set(iec61850_client_array_SRCS +set(iec61850_client_example_array_SRCS client_example_array.c ) IF(WIN32) -set_source_files_properties(${iec61850_client_array_SRCS} +set_source_files_properties(${iec61850_client_example_array_SRCS} PROPERTIES LANGUAGE CXX) ENDIF(WIN32) -add_executable(iec61850_client_array - ${iec61850_client_array_SRCS} +add_executable(iec61850_client_example_array + ${iec61850_client_example_array_SRCS} ) -target_link_libraries(iec61850_client_array +target_link_libraries(iec61850_client_example_array iec61850 ) diff --git a/src/mms/iso_mms/client/mms_client_read.c b/src/mms/iso_mms/client/mms_client_read.c index 8f68f1316..30a211c6e 100644 --- a/src/mms/iso_mms/client/mms_client_read.c +++ b/src/mms/iso_mms/client/mms_client_read.c @@ -472,7 +472,7 @@ createAlternateAccessComponent(const char* componentName) alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); - char* separator = strchr(componentName, '$'); + const char* separator = strchr(componentName, '$'); if (separator) { int size = separator - componentName; @@ -481,7 +481,8 @@ createAlternateAccessComponent(const char* componentName) alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.present = AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_component; - alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.buf = (uint8_t*) strndup(componentName, size); + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.buf = + (uint8_t*) StringUtils_copySubString((char*) componentName, (char*) separator); alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.size = size; alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess = createAlternateAccessComponent(separator + 1); @@ -494,7 +495,8 @@ createAlternateAccessComponent(const char* componentName) alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present = AlternateAccessSelection__selectAccess_PR_component; - alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.buf = (uint8_t*) strndup(componentName, size); + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.buf = + (uint8_t*) StringUtils_copyString(componentName); alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.size = size; } From e905bc242b3a0a685be37182b06ddd3d579cfd2b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 3 Sep 2018 07:51:42 +0200 Subject: [PATCH 116/123] - MMS server: initialize maxConnections in IsoServer --- src/mms/iso_server/iso_server.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index fe8554962..ddd43d81b 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -374,13 +374,15 @@ handleIsoConnections(IsoServer self) if ((connectionSocket = ServerSocket_accept((ServerSocket) self->serverSocket)) != NULL) { #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) - if (private_IsoServer_getConnectionCounter(self) >= self->maxConnections) { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); + if (self->maxConnections > -1) { + if (private_IsoServer_getConnectionCounter(self) >= self->maxConnections) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); - Socket_destroy(connectionSocket); + Socket_destroy(connectionSocket); - return; + return; + } } #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ @@ -427,13 +429,15 @@ handleIsoConnectionsThreadless(IsoServer self) if ((connectionSocket = ServerSocket_accept((ServerSocket) self->serverSocket)) != NULL) { #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) - if (private_IsoServer_getConnectionCounter(self) >= self->maxConnections) { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); + if (self->maxConnections > -1) { + if (private_IsoServer_getConnectionCounter(self) >= self->maxConnections) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); - Socket_destroy(connectionSocket); + Socket_destroy(connectionSocket); - return; + return; + } } #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ @@ -522,6 +526,10 @@ IsoServer_create(TLSConfiguration tlsConfiguration) self->openClientConnections = LinkedList_create(); #endif +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + self->maxConnections = CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS; +#endif + #if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) self->connectionCounterMutex = Semaphore_create(1); self->openClientConnectionsMutex = Semaphore_create(1); From a830fc3cfb464717eb3d5b7f58873c02d7f1354d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 3 Sep 2018 09:17:34 +0200 Subject: [PATCH 117/123] - IEC 61850 server: reporting - don't delete pending events when buffered report is enabled and dataset didn't change --- src/iec61850/server/mms_mapping/reporting.c | 25 ++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 1e2e5ce1f..1f5d8847f 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -639,23 +639,28 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ - deleteDataSetValuesShadowBuffer(rc); + if ((dataSet == NULL) || (dataSetChanged == true)) { - rc->dataSet = dataSet; + /* delete pending event and create buffer for new data set */ + deleteDataSetValuesShadowBuffer(rc); + + rc->dataSet = dataSet; - createDataSetValuesShadowBuffer(rc); + createDataSetValuesShadowBuffer(rc); - if (rc->inclusionField != NULL) - MmsValue_delete(rc->inclusionField); + if (rc->inclusionField != NULL) + MmsValue_delete(rc->inclusionField); - rc->inclusionField = MmsValue_newBitString(dataSet->elementCount); + rc->inclusionField = MmsValue_newBitString(dataSet->elementCount); - rc->triggered = false; + rc->triggered = false; - if (rc->inclusionFlags != NULL) - GLOBAL_FREEMEM(rc->inclusionFlags); + if (rc->inclusionFlags != NULL) + GLOBAL_FREEMEM(rc->inclusionFlags); - rc->inclusionFlags = (uint8_t*) GLOBAL_CALLOC(dataSet->elementCount, sizeof(uint8_t)); + rc->inclusionFlags = (uint8_t*) GLOBAL_CALLOC(dataSet->elementCount, sizeof(uint8_t)); + + } success = true; From c0ce64c7584e81e32207e3ab5bc772de10680d99 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 3 Sep 2018 20:23:19 +0200 Subject: [PATCH 118/123] - file-tool: file client example is now compatible with windows --- CHANGELOG | 5 ++ examples/CMakeLists.txt | 5 +- .../iec61850_client_example_files/file-tool.c | 62 ++++++++++++++++++- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 11edb14b5..b1cbb4436 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ Changes to version 1.3.0 ------------------------ +- IEC 61850 server: more features configurable at runtime +- IEC 61850 server: control objects - fixed bug in select response for SBO control model +- IEC 61850 client: add support for single array element access (with component specification) +- IEC 61850 server: made IEC 61850 edition configurable at runtime +- IEC 61850 server: added ReadAccessHandler to control read access - HAL: unified platform abstraction layer (to simplify using the library together with lib60870) - IEC 61850 server: fixed bug when calling write access handler (wrong pointer for ClientConnection object) - updated IEC 61850-9-2 LE example to be more realistic diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 31552c2fe..fafc146b6 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -21,10 +21,7 @@ add_subdirectory(iec61850_client_example5) add_subdirectory(iec61850_client_example_reporting) add_subdirectory(iec61850_client_example_log) add_subdirectory(iec61850_client_example_array) - -if(NOT WIN32) - add_subdirectory(iec61850_client_example_files) -endif() +add_subdirectory(iec61850_client_example_files) if(WIN32) if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib") diff --git a/examples/iec61850_client_example_files/file-tool.c b/examples/iec61850_client_example_files/file-tool.c index 53f23086c..396e8bf9d 100644 --- a/examples/iec61850_client_example_files/file-tool.c +++ b/examples/iec61850_client_example_files/file-tool.c @@ -8,15 +8,73 @@ * * Note: intended to be used with server_example3 or server_example_files * - * Note: DOES NOT WORK WITH VISUAL STUDIO because of libgen.h - * */ #include "iec61850_client.h" #include #include +#ifdef _WIN32 +#include +#else #include +#endif + +#ifdef _WIN32 +static char _dirname[1000]; + +static char* +dirname(char* path) +{ + char* lastSep = NULL; + + int len = strlen(path); + int i = 0; + + while (i < len) { + if (path[i] == '/' || path[i] == ':' || path[i] == '\\') + lastSep = path + i; + + i++; + } + + if (lastSep) { + strcpy(_dirname, path); + _dirname[lastSep - path] = 0; + } + else + strcpy("", path); + + return _dirname; +} + + +static char _basename[1000]; + +static char* +basename(char* path) +{ + char* lastSep = NULL; + + int len = strlen(path); + int i = 0; + + while (i < len) { + if (path[i] == '/' || path[i] == ':' || path[i] == '\\') + lastSep = path + i; + + i++; + } + + if (lastSep) + strcpy(_basename, lastSep + 1); + else + strcpy(_basename, path); + + return _basename; +} + +#endif static char* hostname = "localhost"; static int tcpPort = 102; From 2f0211b188d839a0e5c675a87696a10d02a04465 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 3 Sep 2018 20:42:04 +0200 Subject: [PATCH 119/123] - GOOSE publisher example: fixed memory leak --- examples/goose_publisher/goose_publisher_example.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/goose_publisher/goose_publisher_example.c b/examples/goose_publisher/goose_publisher_example.c index 7abf6f57a..f46b3fe77 100644 --- a/examples/goose_publisher/goose_publisher_example.c +++ b/examples/goose_publisher/goose_publisher_example.c @@ -66,6 +66,8 @@ main(int argc, char** argv) } GoosePublisher_destroy(publisher); + + LinkedList_destroyDeep(dataSetValues, (LinkedListValueDeleteFunction) MmsValue_delete); } From a0adcf94e7cafe2263093a56f0b538e642f29c30 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 3 Sep 2018 21:00:58 +0200 Subject: [PATCH 120/123] - IEC 61850 Server: made number of data set entries configurable at runtime --- src/iec61850/inc/iec61850_server.h | 11 +++++++++++ src/iec61850/server/impl/ied_server.c | 1 + src/iec61850/server/impl/ied_server_config.c | 17 +++++++++++++++++ src/mms/inc/mms_server.h | 9 +++++++++ src/mms/inc_private/mms_server_internal.h | 1 + .../server/mms_named_variable_list_service.c | 12 ++++++++---- src/mms/iso_mms/server/mms_server.c | 7 +++++++ src/vs/libiec61850-wo-goose.def | 2 ++ src/vs/libiec61850.def | 2 ++ 9 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index bffed0360..c0203674a 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -60,6 +60,9 @@ struct sIedServerConfig /** when true (default) enable dynamic data set services for MMS */ bool enableDynamicDataSetService; + /** maximum number of data set entries of dynamic data sets */ + int maxDataSetEntries; + /** when true (default) enable log service */ bool enableLogService; @@ -194,9 +197,17 @@ IedServerConfig_setMaxAssociationSpecificDataSets(IedServerConfig self, int maxD void IedServerConfig_setMaxDomainSpecificDataSets(IedServerConfig self, int maxDataSets); +/** + * \brief Set the maximum number of entries in a dynamic data set + * + * \param maxDataSetEntries the maximum number of entries allowed in a data set + */ void IedServerConfig_setMaxDataSetEntries(IedServerConfig self, int maxDataSetEntries); +int +IedServerConfig_getMaxDatasSetEntries(IedServerConfig self); + void IedServerConfig_enableWriteDataSetService(IedServerConfig self, bool enable); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index a1a351b65..4bcb12f1a 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -437,6 +437,7 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio if (serverConfiguration) { MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); + MmsServer_setMaxDataSetEntries(self->mmsServer, serverConfiguration->maxDataSetEntries); MmsServer_enableJournalService(self->mmsServer, serverConfiguration->enableLogService); MmsServer_setFilestoreBasepath(self->mmsServer, serverConfiguration->fileServiceBasepath); MmsServer_setMaxConnections(self->mmsServer, serverConfiguration->maxMmsConnections); diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index cc01c773e..a03e0975d 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -24,6 +24,10 @@ #include "iec61850_server.h" #include "libiec61850_platform_includes.h" +#ifndef CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS +#define CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS 100 +#endif + IedServerConfig IedServerConfig_create() { @@ -34,6 +38,7 @@ IedServerConfig_create() self->fileServiceBasepath = StringUtils_copyString(CONFIG_VIRTUAL_FILESTORE_BASEPATH); self->enableFileService = true; self->enableDynamicDataSetService = true; + self->maxDataSetEntries = CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS; self->enableLogService = true; self->edition = IEC_61850_EDITION_2; } @@ -114,6 +119,18 @@ IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self) return self->enableDynamicDataSetService; } +void +IedServerConfig_setMaxDataSetEntries(IedServerConfig self, int maxDataSetEntries) +{ + self->maxDataSetEntries = maxDataSetEntries; +} + +int +IedServerConfig_getMaxDatasSetEntries(IedServerConfig self) +{ + return self->maxDataSetEntries; +} + void IedServerConfig_enableLogService(IedServerConfig self, bool enable) { diff --git a/src/mms/inc/mms_server.h b/src/mms/inc/mms_server.h index 1780bc396..e4cd063f3 100644 --- a/src/mms/inc/mms_server.h +++ b/src/mms/inc/mms_server.h @@ -255,6 +255,15 @@ MmsServer_enableFileService(MmsServer self, bool enable); void MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable); +/** + * \brief Set the maximum number of data set entries (for dynamic data sets) + * + * \param[in] self the MmsServer instance + * \param[in] maximum number of dynamic data set entires + */ +void +MmsServer_setMaxDataSetEntries(MmsServer self, int maxDataSetEntries); + /** * \brief Enable/disable journal service * diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 62ea40616..bea5994b8 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -168,6 +168,7 @@ struct sMmsServer { int maxConnections; bool fileServiceEnabled; bool dynamicVariableListServiceEnabled; + int maxDataSetEntries; bool journalServiceEnabled; #endif /* (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) */ diff --git a/src/mms/iso_mms/server/mms_named_variable_list_service.c b/src/mms/iso_mms/server/mms_named_variable_list_service.c index 469bc3d30..8aee035d1 100644 --- a/src/mms/iso_mms/server/mms_named_variable_list_service.c +++ b/src/mms/iso_mms/server/mms_named_variable_list_service.c @@ -291,7 +291,7 @@ checkIfVariableExists(MmsDevice* device, MmsAccessSpecifier* accessSpecifier) static MmsNamedVariableList -createNamedVariableList(MmsDomain* domain, MmsDevice* device, +createNamedVariableList(MmsServer server, MmsDomain* domain, MmsDevice* device, DefineNamedVariableListRequest_t* request, char* variableListName, MmsError* mmsError) { @@ -299,7 +299,11 @@ createNamedVariableList(MmsDomain* domain, MmsDevice* device, int variableCount = request->listOfVariable.list.count; +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if ((variableCount == 0 ) || (variableCount > server->maxDataSetEntries)) { +#else if ((variableCount == 0 ) || (variableCount > CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS)) { +#endif *mmsError = MMS_ERROR_DEFINITION_OTHER; goto exit_function; } @@ -467,7 +471,7 @@ mmsServer_handleDefineNamedVariableListRequest( else { MmsError mmsError; - MmsNamedVariableList namedVariableList = createNamedVariableList(domain, device, + MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, domain, device, request, variableListName, &mmsError); if (namedVariableList != NULL) { @@ -513,7 +517,7 @@ mmsServer_handleDefineNamedVariableListRequest( else { MmsError mmsError; - MmsNamedVariableList namedVariableList = createNamedVariableList(NULL, device, + MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, NULL, device, request, variableListName, &mmsError); if (namedVariableList != NULL) { @@ -557,7 +561,7 @@ mmsServer_handleDefineNamedVariableListRequest( else { MmsError mmsError; - MmsNamedVariableList namedVariableList = createNamedVariableList(NULL, device, + MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, NULL, device, request, variableListName, &mmsError); if (namedVariableList != NULL) { diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 5cf573902..18ef0a423 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -71,6 +71,7 @@ MmsServer_create(MmsDevice* device, TLSConfiguration tlsConfiguration) self->fileServiceEnabled = true; self->dynamicVariableListServiceEnabled = true; self->journalServiceEnabled = true; + self->maxDataSetEntries = CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS; #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ return self; @@ -125,6 +126,12 @@ MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable) self->dynamicVariableListServiceEnabled = enable; } +void +MmsServer_setMaxDataSetEntries(MmsServer self, int maxDataSetEntries) +{ + self->maxDataSetEntries = maxDataSetEntries; +} + void MmsServer_enableJournalService(MmsServer self, bool enable) { diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 27a7b3d69..9382c85c6 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -614,3 +614,5 @@ EXPORTS IedServerConfig_getEdition IedServerConfig_setMaxMmsConnections IedServerConfig_getMaxMmsConnections + IedServerConfig_setMaxDataSetEntries + IedServerConfig_getMaxDatasSetEntries diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index e90c3ff17..79dc10eec 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -742,3 +742,5 @@ EXPORTS IedServerConfig_getEdition IedServerConfig_setMaxMmsConnections IedServerConfig_getMaxMmsConnections + IedServerConfig_setMaxDataSetEntries + IedServerConfig_getMaxDatasSetEntries From aa86d3b259893753fd669bee46e5200b1fa481e2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 4 Sep 2018 14:41:31 +0200 Subject: [PATCH 121/123] - IEC 61850 server: number of dynamic data sets configurable at runtime --- dotnet/IEC61850forCSharp/IedServerConfig.cs | 64 +++++++++++++++++++ src/iec61850/inc/iec61850_server.h | 61 ++++++++++++++---- src/iec61850/server/impl/ied_server.c | 2 + src/iec61850/server/impl/ied_server_config.c | 34 ++++++++++ src/mms/inc/mms_server.h | 20 +++++- src/mms/inc_private/mms_server_internal.h | 2 + .../server/mms_named_variable_list_service.c | 8 +++ src/mms/iso_mms/server/mms_server.c | 14 ++++ src/vs/libiec61850-wo-goose.def | 5 ++ src/vs/libiec61850.def | 4 ++ 10 files changed, 199 insertions(+), 15 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs index 586f547bd..13588af45 100644 --- a/dotnet/IEC61850forCSharp/IedServerConfig.cs +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -69,6 +69,24 @@ public class IedServerConfig : IDisposable [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); + internal IntPtr self; public IedServerConfig () @@ -132,6 +150,10 @@ public int MaxMmsConnections } } + /// + /// Enable/Disable dynamic data set service for MMS + /// + /// true if dynamic data set service enabled; otherwise, false. public bool DynamicDataSetServiceEnabled { get { @@ -142,6 +164,48 @@ public bool DynamicDataSetServiceEnabled } } + /// + /// 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); + } + } + /// /// Releases all resource used by the object. /// diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index c0203674a..b0ad9db98 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -60,6 +60,12 @@ struct sIedServerConfig /** when true (default) enable dynamic data set services for MMS */ bool enableDynamicDataSetService; + /** the maximum number of allowed association specific data sets */ + int maxAssociationSpecificDataSets; + + /** the maximum number of allowed domain specific data sets */ + int maxDomainSpecificDataSets; + /** maximum number of data set entries of dynamic data sets */ int maxDataSetEntries; @@ -141,6 +147,8 @@ IedServerConfig_getMaxMmsConnections(IedServerConfig self); /** * \brief Set the basepath of the file services * + * NOTE: the basepath specifies the local directory that is served by MMS file services + * * \param basepath new file service base path */ void @@ -168,12 +176,6 @@ IedServerConfig_enableFileService(IedServerConfig self, bool enable); bool IedServerConfig_isFileServiceEnabled(IedServerConfig self); -void -IedServerConfig_enableSetFileService(IedServerConfig self, bool enable); - -bool -IedServerConfig_isSetFileServiceEnabled(IedServerConfig self); - /** * \brief Enable/disable the dynamic data set service for MMS * @@ -190,30 +192,61 @@ IedServerConfig_enableDynamicDataSetService(IedServerConfig self, bool enable); bool IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self); - +/** + * \brief Set the maximum allowed number of association specific (non-permanent) data sets + * + * NOTE: This specifies the maximum number of non-permanent data sets per connection. When + * the connection is closed these data sets are deleted automatically. + * + * \param maxDataSets maximum number of allowed data sets. + */ void IedServerConfig_setMaxAssociationSpecificDataSets(IedServerConfig self, int maxDataSets); +/** + * \brief Get the maximum allowed number of association specific (non-permanent) data sets + * + * \return maximum number of allowed data sets. + */ +int +IedServerConfig_getMaxAssociationSpecificDataSets(IedServerConfig self); + +/** + * \brief Set the maximum allowed number of domain specific (permanent) data sets + * + * \param maxDataSets maximum number of allowed data sets. + */ void IedServerConfig_setMaxDomainSpecificDataSets(IedServerConfig self, int maxDataSets); /** - * \brief Set the maximum number of entries in a dynamic data set + * \brief Get the maximum allowed number of domain specific (permanent) data sets + * + * \return maximum number of allowed data sets. + */ +int +IedServerConfig_getMaxDomainSpecificDataSets(IedServerConfig self); + +/** + * \brief Set the maximum number of entries in dynamic data sets + * + * NOTE: this comprises the base data set entries (can be simple or complex variables). + * When the client tries to create a data set with more member the request will be + * rejected and the data set will not be created. * * \param maxDataSetEntries the maximum number of entries allowed in a data set */ void IedServerConfig_setMaxDataSetEntries(IedServerConfig self, int maxDataSetEntries); +/** + * \brief Get the maximum number of entries in dynamic data sets + * + * \return the maximum number of entries allowed in a data sets + */ int IedServerConfig_getMaxDatasSetEntries(IedServerConfig self); -void -IedServerConfig_enableWriteDataSetService(IedServerConfig self, bool enable); - -bool -IedServerConfig_isWriteDataSetServiceEnabled(IedServerConfig self); - /** * \brief Enable/disable the log service for MMS * diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 4bcb12f1a..035e33256 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -437,6 +437,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio 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); diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index a03e0975d..b9d1dd037 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -28,6 +28,14 @@ #define CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS 100 #endif +#ifndef CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS +#define CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS 10 +#endif + +#ifndef CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS +#define CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS 10 +#endif + IedServerConfig IedServerConfig_create() { @@ -38,6 +46,8 @@ IedServerConfig_create() self->fileServiceBasepath = StringUtils_copyString(CONFIG_VIRTUAL_FILESTORE_BASEPATH); self->enableFileService = true; self->enableDynamicDataSetService = true; + self->maxAssociationSpecificDataSets = CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS; + self->maxDomainSpecificDataSets = CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS; self->maxDataSetEntries = CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS; self->enableLogService = true; self->edition = IEC_61850_EDITION_2; @@ -119,6 +129,30 @@ IedServerConfig_isDynamicDataSetServiceEnabled(IedServerConfig self) return self->enableDynamicDataSetService; } +void +IedServerConfig_setMaxAssociationSpecificDataSets(IedServerConfig self, int maxDataSets) +{ + self->maxAssociationSpecificDataSets = maxDataSets; +} + +int +IedServerConfig_getMaxAssociationSpecificDataSets(IedServerConfig self) +{ + return self->maxAssociationSpecificDataSets; +} + +void +IedServerConfig_setMaxDomainSpecificDataSets(IedServerConfig self, int maxDataSets) +{ + self->maxDomainSpecificDataSets = maxDataSets; +} + +int +IedServerConfig_getMaxDomainSpecificDataSets(IedServerConfig self) +{ + return self->maxDomainSpecificDataSets; +} + void IedServerConfig_setMaxDataSetEntries(IedServerConfig self, int maxDataSetEntries) { diff --git a/src/mms/inc/mms_server.h b/src/mms/inc/mms_server.h index e4cd063f3..bf06de203 100644 --- a/src/mms/inc/mms_server.h +++ b/src/mms/inc/mms_server.h @@ -255,11 +255,29 @@ MmsServer_enableFileService(MmsServer self, bool enable); void MmsServer_enableDynamicNamedVariableListService(MmsServer self, bool enable); +/** + * \brief Set the maximum number of association specific data sets (per connection) + * + * \param[in] self the MmsServer instance + * \param[in] maxDataSets maximum number association specific data sets + */ +void +MmsServer_setMaxAssociationSpecificDataSets(MmsServer self, int maxDataSets); + +/** + * \brief Set the maximum number of domain specific data sets + * + * \param[in] self the MmsServer instance + * \param[in] maxDataSets maximum number domain specific data sets + */ +void +MmsServer_setMaxDomainSpecificDataSets(MmsServer self, int maxDataSets); + /** * \brief Set the maximum number of data set entries (for dynamic data sets) * * \param[in] self the MmsServer instance - * \param[in] maximum number of dynamic data set entires + * \param[in] maxDataSetEntries maximum number of dynamic data set entries */ void MmsServer_setMaxDataSetEntries(MmsServer self, int maxDataSetEntries); diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index bea5994b8..7a9155f43 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -170,6 +170,8 @@ struct sMmsServer { bool dynamicVariableListServiceEnabled; int maxDataSetEntries; bool journalServiceEnabled; + int maxAssociationSpecificDataSets; + int maxDomainSpecificDataSets; #endif /* (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) */ }; diff --git a/src/mms/iso_mms/server/mms_named_variable_list_service.c b/src/mms/iso_mms/server/mms_named_variable_list_service.c index 8aee035d1..3a0a78a79 100644 --- a/src/mms/iso_mms/server/mms_named_variable_list_service.c +++ b/src/mms/iso_mms/server/mms_named_variable_list_service.c @@ -453,7 +453,11 @@ mmsServer_handleDefineNamedVariableListRequest( goto exit_free_struct; } +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if (LinkedList_size(domain->namedVariableLists) < connection->server->maxDomainSpecificDataSets) { +#else if (LinkedList_size(domain->namedVariableLists) < CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS) { +#endif char variableListName[65]; if (request->variableListName.choice.domainspecific.itemId.size > 64) { @@ -498,7 +502,11 @@ mmsServer_handleDefineNamedVariableListRequest( } else if (request->variableListName.present == ObjectName_PR_aaspecific) { +#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) + if (LinkedList_size(connection->namedVariableLists) < connection->server->maxAssociationSpecificDataSets) { +#else if (LinkedList_size(connection->namedVariableLists) < CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS) { +#endif char variableListName[65]; diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 18ef0a423..7d5048879 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -72,6 +72,8 @@ MmsServer_create(MmsDevice* device, TLSConfiguration tlsConfiguration) self->dynamicVariableListServiceEnabled = true; self->journalServiceEnabled = true; self->maxDataSetEntries = CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS; + self->maxAssociationSpecificDataSets = CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS; + self->maxDomainSpecificDataSets = CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS; #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ return self; @@ -138,6 +140,18 @@ MmsServer_enableJournalService(MmsServer self, bool enable) self->journalServiceEnabled = enable; } +void +MmsServer_setMaxAssociationSpecificDataSets(MmsServer self, int maxDataSets) +{ + self->maxAssociationSpecificDataSets = maxDataSets; +} + +void +MmsServer_setMaxDomainSpecificDataSets(MmsServer self, int maxDataSets) +{ + self->maxDomainSpecificDataSets = maxDataSets; +} + #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ void diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 9382c85c6..33d4be39c 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -616,3 +616,8 @@ EXPORTS IedServerConfig_getMaxMmsConnections IedServerConfig_setMaxDataSetEntries IedServerConfig_getMaxDatasSetEntries + IedServerConfig_setMaxAssociationSpecificDataSets + IedServerConfig_getMaxAssociationSpecificDataSets + IedServerConfig_setMaxDomainSpecificDataSets + IedServerConfig_getMaxDomainSpecificDataSets + \ No newline at end of file diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 79dc10eec..df59cae1c 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -744,3 +744,7 @@ EXPORTS IedServerConfig_getMaxMmsConnections IedServerConfig_setMaxDataSetEntries IedServerConfig_getMaxDatasSetEntries + IedServerConfig_setMaxAssociationSpecificDataSets + IedServerConfig_getMaxAssociationSpecificDataSets + IedServerConfig_setMaxDomainSpecificDataSets + IedServerConfig_getMaxDomainSpecificDataSets From bed67e4f506880ede121dde0369a5e94c36b6113 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 4 Sep 2018 14:51:29 +0200 Subject: [PATCH 122/123] - added missing function definition in DLL export files --- src/vs/libiec61850-wo-goose.def | 2 +- src/vs/libiec61850.def | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 33d4be39c..1d98f484a 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -620,4 +620,4 @@ EXPORTS IedServerConfig_getMaxAssociationSpecificDataSets IedServerConfig_setMaxDomainSpecificDataSets IedServerConfig_getMaxDomainSpecificDataSets - \ No newline at end of file + IedServer_setReadAccessHandler diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index df59cae1c..1f8a50397 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -748,3 +748,4 @@ EXPORTS IedServerConfig_getMaxAssociationSpecificDataSets IedServerConfig_setMaxDomainSpecificDataSets IedServerConfig_getMaxDomainSpecificDataSets + IedServer_setReadAccessHandler From 6ba363bd4a4803dfb194733a9729ad421d6b49f0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 4 Sep 2018 15:01:19 +0200 Subject: [PATCH 123/123] - updated changelog for release 1.3.0 --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b1cbb4436..7f6e4b20d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Changes to version 1.3.0 - IEC 61850 server: more features configurable at runtime - IEC 61850 server: control objects - fixed bug in select response for SBO control model - IEC 61850 client: add support for single array element access (with component specification) +- MMS server: add support for array element (index) access with nested component - IEC 61850 server: made IEC 61850 edition configurable at runtime - IEC 61850 server: added ReadAccessHandler to control read access - HAL: unified platform abstraction layer (to simplify using the library together with lib60870) @@ -11,7 +12,10 @@ Changes to version 1.3.0 - added server side example for the substitution service - MMS server: fixed wrong preprocessor defines that can cause problems in some configurations (unlimited number of client connections/ multi-threaded server) - IEC 61850 client: added new function ControlObjectClient_getCtlValType to simplify control handling +- IEC 61850 server: reporting - don't delete pending events when buffered report is enabled and dataset didn't change - fixed bug in MmsValue_update +- MMS server: fixed bug in delete variable list service - scope of delete was not considered optional +- some more small bug fixes and optimizations Changes to version 1.2.2