diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 00000000..d52fd077 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,26 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'libiec61850' + dry-run: false + language: c + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'libiec61850' + fuzz-seconds: 300 + dry-run: false + language: c + - name: Upload Crash + uses: actions/upload-artifact@v3 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/CMakeLists.txt b/CMakeLists.txt index a538fb97..7499c8c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,13 +33,16 @@ option(BUILD_PYTHON_BINDINGS "Build Python bindings" OFF) option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" ON) option(CONFIG_MMS_THREADLESS_STACK "Optimize stack for threadless operation (warning: single- or multi-threaded server will not work!)" OFF) +set(CONFIG_MMS_SERVER_MAX_GET_FILE_TASKS 5 CACHE STRING "Configure the maximum number of get file tasks") +set(CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS 100 CACHE STRING "Configure the maximum number of dataSet members") + option(CONFIG_ACTIVATE_TCP_KEEPALIVE "Activate TCP keepalive" ON) option(CONFIG_INCLUDE_GOOSE_SUPPORT "Build with GOOSE support" ON) option(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB "Build with pre-compiled mbedtls dynamic library" OFF) -set(CONFIG_EXTERNAL_MBEDTLS_DYNLIB_PATH "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.16/library" CACHE STRING "Path to search for the mbedtls dynamic libraries" ) -set(CONFIG_EXTERNAL_MBEDTLS_INCLUDE_PATH "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.16/include" CACHE STRING "Path to search for the mbedtls include files" ) +set(CONFIG_EXTERNAL_MBEDTLS_DYNLIB_PATH "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.28/library" CACHE STRING "Path to search for the mbedtls dynamic libraries" ) +set(CONFIG_EXTERNAL_MBEDTLS_INCLUDE_PATH "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.28/include" CACHE STRING "Path to search for the mbedtls include files" ) # choose the library features which shall be included option(CONFIG_IEC61850_CONTROL_SERVICE "Build with support for IEC 61850 control features" ON) @@ -53,6 +56,8 @@ option(CONFIG_IEC61850_R_GOOSE "Build with support for R-GOOSE (mbedtls required option(CONFIG_IEC61850_R_SMV "Build with support for R-SMV (mbedtls required)" ON) option(CONFIG_IEC61850_SNTP_CLIENT "Build with SNTP client code" ON) +set(CONFIG_IEC61850_SG_RESVTMS 300 CACHE STRING "Configure the maximum number of SG RESVTMS") + set(CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE "65536" CACHE STRING "Default buffer size for buffered reports in byte" ) # advanced options @@ -132,10 +137,15 @@ set(USE_PREBUILD_MBEDTLS 1) set(MBEDTLS_INCLUDE_DIR ${CONFIG_EXTERNAL_MBEDTLS_INCLUDE_PATH}) endif(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB) -if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.16) +if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/sqlite/sqlite3.h) +set(FOUND_SQLITE3_SOURCE 1) +message("Found sqlite3 source in third_party folder -> can compile with log service support") +endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/sqlite/sqlite3.h) + +if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.28) set(WITH_MBEDTLS 1) -set(MBEDTLS_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.16/include") -endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.16) +set(MBEDTLS_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.28/include") +endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.28) if(WITH_MBEDTLS) @@ -162,6 +172,11 @@ if (SUPPORT_REDUNDANT_DECLS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wredundant-decls") endif(SUPPORT_REDUNDANT_DECLS) +check_c_compiler_flag("-Wundef" SUPPORT_UNDEF) +if (SUPPORT_UNDEF) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wundef") +endif(SUPPORT_UNDEF) + # write the detected stuff to this file configure_file( ${CMAKE_CURRENT_LIST_DIR}/config/stack_config.h.cmake diff --git a/Makefile b/Makefile index db06c7f8..ef530346 100644 --- a/Makefile +++ b/Makefile @@ -78,9 +78,9 @@ LIB_INCLUDE_DIRS += third_party/winpcap/Include endif ifdef WITH_MBEDTLS -LIB_SOURCE_DIRS += third_party/mbedtls/mbedtls-2.16/library +LIB_SOURCE_DIRS += third_party/mbedtls/mbedtls-2.28/library LIB_SOURCE_DIRS += hal/tls/mbedtls -LIB_INCLUDE_DIRS += third_party/mbedtls/mbedtls-2.16/include +LIB_INCLUDE_DIRS += third_party/mbedtls/mbedtls-2.28/include LIB_INCLUDE_DIRS += hal/tls/mbedtls CFLAGS += -D'MBEDTLS_CONFIG_FILE="mbedtls_config.h"' CFLAGS += -D'CONFIG_MMS_SUPPORT_TLS=1' diff --git a/README.md b/README.md index 08139382..d8024cff 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,9 @@ You can test the server examples by using a generic client or the provided clien ## Building the library with TLS support -Download, unpack, and copy mbedtls-2.16 into the third_party/mbedtls folder. +Download, unpack, and copy mbedtls-2.28 into the third_party/mbedtls folder. -NOTE: The current version support mbedtls version 2.16. When you download the source archive from https://tls.mbed.org/ you have to rename the extracted folder to "mbedtls-2.16". +NOTE: The current version support mbedtls version 2.28. When you download the source archive from https://tls.mbed.org/ you have to rename the extracted folder to "mbedtls-2.28". In the main libiec61850 folder run @@ -98,7 +98,7 @@ In the main libiec61850 folder run make WITH_MBEDTLS=1 ``` -When using CMake the library is built automatically with TLS support when the folder third_party/mbedtls/mbedtls-2.16 is present. +When using CMake the library is built automatically with TLS support when the folder third_party/mbedtls/mbedtls-2.28 is present. ## Installing the library and the API headers diff --git a/config/stack_config.h b/config/stack_config.h index 5518b9f0..3cd3e771 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -238,9 +238,6 @@ #define CONFIG_INCLUDE_PLATFORM_SPECIFIC_HEADERS 0 -/* use short FC defines as in old API */ -#define CONFIG_PROVIDE_OLD_FC_DEFINES 0 - /* Support user access to raw messages */ #define CONFIG_MMS_RAW_MESSAGE_LOGGING 1 diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 2901316d..52017dc2 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -155,7 +155,7 @@ #cmakedefine01 CONFIG_IEC61850_SETTING_GROUPS /* default reservation time of a setting group control block in s */ -#define CONFIG_IEC61850_SG_RESVTMS 100 +#cmakedefine CONFIG_IEC61850_SG_RESVTMS @CONFIG_IEC61850_SG_RESVTMS@ /* include support for IEC 61850 log services */ #cmakedefine01 CONFIG_IEC61850_LOG_SERVICE @@ -196,7 +196,10 @@ #define CONFIG_MMS_MAX_NUMBER_OF_VMD_SPECIFIC_DATA_SETS 10 /* Maximum number of the members in a data set (named variable list) */ -#define CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS 50 +#cmakedefine CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS @CONFIG_MMS_MAX_NUMBER_OF_DATA_SET_MEMBERS@ + +/* Maximum number of get file tasks */ +#cmakedefine CONFIG_MMS_SERVER_MAX_GET_FILE_TASKS @CONFIG_MMS_SERVER_MAX_GET_FILE_TASKS@ /* Definition of supported services */ #define MMS_DEFAULT_PROFILE 1 @@ -224,9 +227,6 @@ #define CONFIG_INCLUDE_PLATFORM_SPECIFIC_HEADERS 0 -/* use short FC defines as in old API */ -#define CONFIG_PROVIDE_OLD_FC_DEFINES 0 - /* Support user acccess to raw messages */ #cmakedefine01 CONFIG_MMS_RAW_MESSAGE_LOGGING diff --git a/dotnet/IEC61850forCSharp/Control.cs b/dotnet/IEC61850forCSharp/Control.cs index b3c2bcc9..97a3d767 100644 --- a/dotnet/IEC61850forCSharp/Control.cs +++ b/dotnet/IEC61850forCSharp/Control.cs @@ -775,7 +775,10 @@ namespace IEC61850 Dispose (true); } - + ~ControlObject() + { + Dispose (false); + } } } diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 1403cbec..16d79a56 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -1,7 +1,7 @@ /* * IEC61850ClientAPI.cs * - * Copyright 2014-2021 Michael Zillgith + * Copyright 2014-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -437,6 +437,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_connect(IntPtr self, out int error, string hostname, int tcpPort); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_setLocalAddress(IntPtr self, string localIpAddress, int localPort); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_abort(IntPtr self, out int error); @@ -894,6 +897,25 @@ namespace IEC61850 Connect(hostname, -1); } + /// + /// Set the local IP address and port to be used by the client + /// + /// the local IP address or hostname + /// the local TCP port to use. When 0 the OS will chose the TCP port to use. + public void SetLocalAddress(string localIpAddress, int localPort) + { + IedConnection_setLocalAddress(connection, localIpAddress, localPort); + } + + /// + /// Set the local IP address to be used by the client + /// + /// the local IP address or hostname + public void SetLocalAddress(string localIpAddress) + { + IedConnection_setLocalAddress(connection, localIpAddress, 0); + } + /// This exception is thrown if there is a connection or service error public ControlObject CreateControlObject(string objectReference) { @@ -1993,7 +2015,6 @@ namespace IEC61850 if (accessResults != IntPtr.Zero) { - IntPtr element = LinkedList_getNext(accessResults); while (element != IntPtr.Zero) @@ -2004,6 +2025,9 @@ namespace IEC61850 MmsDataAccessError dataAccessError = accessResultValue.GetDataAccessError(); + if (accessResultList == null) + accessResultList = new List(); + accessResultList.Add(dataAccessError); element = LinkedList_getNext(element); diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 5c6a40d6..b0d2c054 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -1866,6 +1866,14 @@ namespace IEC61850 [return: MarshalAs(UnmanagedType.I1)] static extern bool ControlAction_isSelect(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ControlAction_getSynchroCheck(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ControlAction_getInterlockCheck(IntPtr self); + private IntPtr self; private IedServer.ControlHandlerInfo info; private IedServer iedServer; @@ -1985,6 +1993,16 @@ namespace IEC61850 { return ControlAction_isSelect(self); } + + public bool GetSynchroCheck() + { + return ControlAction_getSynchroCheck(self); + } + + public bool GetInterlockCheck() + { + return ControlAction_getInterlockCheck(self); + } } public delegate void GoCBEventHandler(MmsGooseControlBlock goCB, int cbEvent, object parameter); @@ -2273,6 +2291,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServer_setRCBEventHandler(IntPtr self, InternalRCBEventHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setTimeQuality(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool leapSecondKnown, [MarshalAs(UnmanagedType.I1)] bool clockFailure, [MarshalAs(UnmanagedType.I1)] bool clockNotSynchronized, int subsecondPrecision); + private IntPtr self = IntPtr.Zero; private InternalControlHandler internalControlHandlerRef = null; @@ -2435,6 +2456,9 @@ namespace IEC61850 /* store IedModel instance to prevent garbage collector */ private IedModel iedModel = null; + /* store TLSConfiguration instance to prevent garbage collector */ + private TLSConfiguration tlsConfiguration = null; + public IedServer(IedModel iedModel, IedServerConfig config = null) { this.iedModel = iedModel; @@ -2450,6 +2474,7 @@ namespace IEC61850 public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null) { this.iedModel = iedModel; + this.tlsConfiguration = tlsConfig; IntPtr nativeConfig = IntPtr.Zero; IntPtr nativeTLSConfig = IntPtr.Zero; @@ -2539,6 +2564,7 @@ namespace IEC61850 self = IntPtr.Zero; internalConnectionHandler = null; this.iedModel = null; + this.tlsConfiguration = null; } } } @@ -2996,6 +3022,18 @@ namespace IEC61850 } } + /// + /// Set the time quality for all timestamps internally generated by this IedServer instance + /// + /// set/unset leap seconds known flag + /// set/unset clock failure flag + /// set/unset clock not synchronized flag + /// set the subsecond precision (number of significant bits of the fractionOfSecond part of the time stamp) + public void SetTimeQuality(bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision) + { + IedServer_setTimeQuality(self, leapSecondKnown, clockFailure, clockNotSynchronized, subsecondPrecision); + } + } } diff --git a/dotnet/IEC61850forCSharp/TLS.cs b/dotnet/IEC61850forCSharp/TLS.cs index 69a273b0..05512a9b 100644 --- a/dotnet/IEC61850forCSharp/TLS.cs +++ b/dotnet/IEC61850forCSharp/TLS.cs @@ -1,7 +1,7 @@ /* * TLS.cs * - * Copyright 2017 Michael Zillgith + * Copyright 2017-2022 Michael Zillgith * * This file is part of libIEC61850. * @@ -35,181 +35,400 @@ using IEC61850.Common; /// namespace IEC61850 { - namespace TLS - { - /// - /// A container for TLS configuration and certificates. - /// - public class TLSConfiguration : IDisposable - { - private IntPtr self = IntPtr.Zero; - - private bool allowOnlyKnownCerts = false; - private bool chainValidation = true; - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr TLSConfiguration_create(); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void TLSConfiguration_destroy(IntPtr self); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void TLSConfiguration_setAllowOnlyKnownCertificates(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); - - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - 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() { - self = TLSConfiguration_create (); - } - - ~TLSConfiguration() - { - Dispose (); - } - - internal IntPtr GetNativeInstance() - { - return self; - } - - public bool AllowOnlyKnownCertificates - { - set { - TLSConfiguration_setAllowOnlyKnownCertificates (self, value); - allowOnlyKnownCerts = value; - } - get { - return allowOnlyKnownCerts; - } - } - - public bool ChainValidation - { - set { - TLSConfiguration_setChainValidation (self, value); - chainValidation = value; - } - get { - return chainValidation; - } - } - - - public void SetClientMode() - { - TLSConfiguration_setClientMode (self); - } - - public void SetOwnCertificate(string filename) - { - if (TLSConfiguration_setOwnCertificateFromFile (self, filename) == false) { - throw new CryptographicException ("Failed to read certificate from file"); - } - } - - public void SetOwnCertificate(X509Certificate2 cert) - { - byte[] certBytes = cert.GetRawCertData (); - - if (TLSConfiguration_setOwnCertificate (self, certBytes, certBytes.Length) == false) { - throw new CryptographicException ("Failed to set certificate"); - } - } - - public void AddAllowedCertificate(string filename) - { - if (TLSConfiguration_addAllowedCertificateFromFile (self, filename) == false) { - throw new CryptographicException ("Failed to read allowed certificate from file"); - } - } - - public void AddAllowedCertificate(X509Certificate2 cert) - { - byte[] certBytes = cert.GetRawCertData (); - - if (TLSConfiguration_addAllowedCertificate (self, certBytes, certBytes.Length) == false) { - throw new CryptographicException ("Failed to add allowed certificate"); - } - } - - public void AddCACertificate(string filename) - { - if (TLSConfiguration_addCACertificateFromFile (self, filename) == false) { - throw new CryptographicException ("Failed to read CA certificate from file"); - } - } - - public void AddCACertificate(X509Certificate2 cert) - { - byte[] certBytes = cert.GetRawCertData (); - - if (TLSConfiguration_addCACertificate (self, certBytes, certBytes.Length) == false) { - throw new CryptographicException ("Failed to add CA certificate"); - } - } - - public void SetOwnKey (string filename, string password) - { - if (TLSConfiguration_setOwnKeyFromFile (self, filename, password) == false) { - throw new CryptographicException ("Failed to read own key from file"); - } - } - - public void SetOwnKey (X509Certificate2 key, string password) - { - byte[] certBytes = key.Export (X509ContentType.Pkcs12); - - if (TLSConfiguration_setOwnKey (self, certBytes, certBytes.Length, password) == false) { - throw new CryptographicException ("Failed to set own key"); - } - } - - public void Dispose() - { - lock (this) { - if (self != IntPtr.Zero) { - TLSConfiguration_destroy (self); - self = IntPtr.Zero; - } - } - } - - } - } -} \ No newline at end of file + namespace TLS + { + public enum TLSConfigVersion + { + NOT_SELECTED = 0, + SSL_3_0 = 3, + TLS_1_0 = 4, + TLS_1_1 = 5, + TLS_1_2 = 6, + TLS_1_3 = 7 + } + + public enum TLSEventLevel + { + INFO = 0, + WARNING = 1, + INCIDENT = 2 + } + + public enum TLSEventCode + { + ALM_ALGO_NOT_SUPPORTED = 1, + ALM_UNSECURE_COMMUNICATION = 2, + ALM_CERT_UNAVAILABLE = 3, + ALM_BAD_CERT = 4, + ALM_CERT_SIZE_EXCEEDED = 5, + ALM_CERT_VALIDATION_FAILED = 6, + ALM_CERT_REQUIRED = 7, + ALM_HANDSHAKE_FAILED_UNKNOWN_REASON = 8, + WRN_INSECURE_TLS_VERSION = 9, + INF_SESSION_RENEGOTIATION = 10, + ALM_CERT_EXPIRED = 11, + ALM_CERT_REVOKED = 12, + ALM_CERT_NOT_CONFIGURED = 13, + ALM_CERT_NOT_TRUSTED = 14 + } + + public class TLSConnection + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int TLSConnection_getTLSVersion(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr TLSConnection_getPeerAddress(IntPtr self, IntPtr peerAddrBuf); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr TLSConnection_getPeerCertificate(IntPtr self, out int certSize); + + private IntPtr self; + private bool isValid; + + internal TLSConnection(IntPtr self) + { + this.self = self; + isValid = true; + } + + // To be called by event callback caller after callback execution + internal void InValidate() + { + lock (this) + { + isValid = false; + } + } + + /// + /// TLS version used by the connection + /// + public TLSConfigVersion TLSVersion + { + get + { + lock (this) + { + if (isValid) + { + return (TLSConfigVersion)TLSConnection_getTLSVersion((IntPtr)self); + } + else + { + throw new InvalidOperationException("Object cannot be used outside of TLS event callback"); + } + } + } + } + + /// + /// Peer IP address and TCP port of the TLS connection + /// + public string PeerAddress + { + get + { + lock (this) + { + if (isValid) + { + IntPtr peerAddrBuf = Marshal.AllocHGlobal(130); + IntPtr peerAddrStr = TLSConnection_getPeerAddress(this.self, peerAddrBuf); + + string peerAddr = null; + + if (peerAddrStr != IntPtr.Zero) + { + peerAddr = Marshal.PtrToStringAnsi(peerAddrStr); + } + + Marshal.FreeHGlobal(peerAddrBuf); + + return peerAddr; + } + else + { + throw new InvalidOperationException("Object cannot be used outside of TLS event callback"); + } + } + } + } + + /// + /// TLS certificate used by the peer + /// + public byte[] PeerCertificate + { + get + { + lock (this) + { + if (isValid) + { + int certSize; + + IntPtr certBuffer = TLSConnection_getPeerCertificate(self, out certSize); + + if (certBuffer != IntPtr.Zero) + { + if (certSize > 0) + { + byte[] cert = new byte[certSize]; + + Marshal.Copy(certBuffer, cert, 0, certSize); + + return cert; + } + } + + return null; + } + else + { + throw new InvalidOperationException("Object cannot be used outside of TLS event callback"); + } + } + } + } + + } + + /// + /// TLS security event handler + /// + /// user provided context paramter to be passed to the handler + /// severity level of the event + /// code to identify the event type + /// text message describing the event + /// TLS connection that caused the event + public delegate void TLSEventHandler(object parameter, TLSEventLevel eventLevel, TLSEventCode eventCode, string message, TLSConnection connection); + + /// + /// A container for TLS configuration and certificates. + /// + public class TLSConfiguration : IDisposable + { + private IntPtr self = IntPtr.Zero; + + private bool allowOnlyKnownCerts = false; + private bool chainValidation = true; + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr TLSConfiguration_create(); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void TLSConfiguration_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void TLSConfiguration_setAllowOnlyKnownCertificates(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + 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); + + private TLSEventHandler eventHandler = null; + private object eventHandlerParameter = null; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void InternalTLSEventHandler(IntPtr parameter, int eventLevel, int eventCode, IntPtr message, IntPtr tlsCon); + + private InternalTLSEventHandler internalTLSEventHandlerRef = null; + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void TLSConfiguration_setEventHandler(IntPtr self, InternalTLSEventHandler handler, IntPtr parameter); + + void InternalTLSEventHandlerImpl(IntPtr parameter, int eventLevel, int eventCode, IntPtr message, IntPtr tlsCon) + { + if (eventHandler != null) + { + TLSConnection connection = new TLSConnection(tlsCon); + + string msg = Marshal.PtrToStringAnsi(message); + + eventHandler(eventHandlerParameter, (TLSEventLevel)eventLevel, (TLSEventCode)eventCode, msg, connection); + + connection.InValidate(); + } + } + + public void SetEventHandler(TLSEventHandler handler, object parameter) + { + this.eventHandler = handler; + this.eventHandlerParameter = parameter; + + if (internalTLSEventHandlerRef == null) + { + internalTLSEventHandlerRef = new InternalTLSEventHandler(InternalTLSEventHandlerImpl); + + TLSConfiguration_setEventHandler(self, internalTLSEventHandlerRef, IntPtr.Zero); + } + } + + public TLSConfiguration() + { + self = TLSConfiguration_create(); + } + + ~TLSConfiguration() + { + Dispose(); + } + + internal IntPtr GetNativeInstance() + { + return self; + } + + public bool AllowOnlyKnownCertificates + { + set + { + TLSConfiguration_setAllowOnlyKnownCertificates(self, value); + allowOnlyKnownCerts = value; + } + get + { + return allowOnlyKnownCerts; + } + } + + public bool ChainValidation + { + set + { + TLSConfiguration_setChainValidation(self, value); + chainValidation = value; + } + get + { + return chainValidation; + } + } + + public void SetClientMode() + { + TLSConfiguration_setClientMode(self); + } + + public void SetOwnCertificate(string filename) + { + if (TLSConfiguration_setOwnCertificateFromFile(self, filename) == false) + { + throw new CryptographicException("Failed to read certificate from file"); + } + } + + public void SetOwnCertificate(X509Certificate2 cert) + { + byte[] certBytes = cert.GetRawCertData(); + + if (TLSConfiguration_setOwnCertificate(self, certBytes, certBytes.Length) == false) + { + throw new CryptographicException("Failed to set certificate"); + } + } + + public void AddAllowedCertificate(string filename) + { + if (TLSConfiguration_addAllowedCertificateFromFile(self, filename) == false) + { + throw new CryptographicException("Failed to read allowed certificate from file"); + } + } + + public void AddAllowedCertificate(X509Certificate2 cert) + { + byte[] certBytes = cert.GetRawCertData(); + + if (TLSConfiguration_addAllowedCertificate(self, certBytes, certBytes.Length) == false) + { + throw new CryptographicException("Failed to add allowed certificate"); + } + } + + public void AddCACertificate(string filename) + { + if (TLSConfiguration_addCACertificateFromFile(self, filename) == false) + { + throw new CryptographicException("Failed to read CA certificate from file"); + } + } + + public void AddCACertificate(X509Certificate2 cert) + { + byte[] certBytes = cert.GetRawCertData(); + + if (TLSConfiguration_addCACertificate(self, certBytes, certBytes.Length) == false) + { + throw new CryptographicException("Failed to add CA certificate"); + } + } + + public void SetOwnKey(string filename, string password) + { + if (TLSConfiguration_setOwnKeyFromFile(self, filename, password) == false) + { + throw new CryptographicException("Failed to read own key from file"); + } + } + + public void SetOwnKey(X509Certificate2 key, string password) + { + byte[] certBytes = key.Export(X509ContentType.Pkcs12); + + if (TLSConfiguration_setOwnKey(self, certBytes, certBytes.Length, password) == false) + { + throw new CryptographicException("Failed to set own key"); + } + } + + public void Dispose() + { + lock (this) + { + if (self != IntPtr.Zero) + { + TLSConfiguration_destroy(self); + self = IntPtr.Zero; + } + } + } + + } + } +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ac08165a..f551ec6e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(server_example_files) add_subdirectory(server_example_substitution) add_subdirectory(server_example_service_tracking) add_subdirectory(server_example_deadband) +add_subdirectory(server_example_access_control) add_subdirectory(iec61850_client_example1) add_subdirectory(iec61850_client_example2) diff --git a/examples/iec61850_client_example1/client_example1.c b/examples/iec61850_client_example1/client_example1.c index f84d37a0..6c9241f0 100644 --- a/examples/iec61850_client_example1/client_example1.c +++ b/examples/iec61850_client_example1/client_example1.c @@ -33,6 +33,9 @@ int main(int argc, char** argv) { char* hostname; int tcpPort = 102; + const char* localIp = NULL; + int localTcpPort = -1; + if (argc > 1) hostname = argv[1]; @@ -42,19 +45,34 @@ int main(int argc, char** argv) { if (argc > 2) tcpPort = atoi(argv[2]); + if (argc > 3) + localIp = argv[3]; + + if (argc > 4) + localTcpPort = atoi(argv[4]); + IedClientError error; IedConnection con = IedConnection_create(); + /* Optional bind to local IP address/interface */ + if (localIp) { + IedConnection_setLocalAddress(con, localIp, localTcpPort); + printf("Bound to Local Address: %s:%i\n", localIp, localTcpPort); + } + IedConnection_connect(con, &error, hostname, tcpPort); + printf("Connecting to %s:%i\n", hostname, tcpPort); - if (error == IED_ERROR_OK) { + if (error == IED_ERROR_OK) + { + printf("Connected\n"); /* read an analog measurement value from server */ MmsValue* value = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX); - if (value != NULL) { - + if (value != NULL) + { if (MmsValue_getType(value) == MMS_FLOAT) { float fval = MmsValue_toFloat(value); printf("read float value: %f\n", fval); @@ -137,7 +155,6 @@ close_connection: } IedConnection_destroy(con); + return 0; } - - diff --git a/examples/iec61850_client_example_control/client_example_control.c b/examples/iec61850_client_example_control/client_example_control.c index 33da635a..37d6b6bc 100644 --- a/examples/iec61850_client_example_control/client_example_control.c +++ b/examples/iec61850_client_example_control/client_example_control.c @@ -45,8 +45,10 @@ int main(int argc, char** argv) { IedConnection_connect(con, &error, hostname, tcpPort); - if (error == IED_ERROR_OK) { - + if (error == IED_ERROR_OK) + { + MmsValue* ctlVal = NULL; + MmsValue* stVal = NULL; /************************ * Direct control @@ -55,99 +57,116 @@ int main(int argc, char** argv) { ControlObjectClient control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO1", con); - MmsValue* ctlVal = MmsValue_newBoolean(true); + if (control) + { + ctlVal = MmsValue_newBoolean(true); - ControlObjectClient_setOrigin(control, NULL, 3); + ControlObjectClient_setOrigin(control, NULL, 3); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO1 operated successfully\n"); - } - else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO1\n"); - } + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO1 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO1\n"); + } + + MmsValue_delete(ctlVal); - MmsValue_delete(ctlVal); + ControlObjectClient_destroy(control); - ControlObjectClient_destroy(control); + /* Check if status value has changed */ - /* Check if status value has changed */ + stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO1.stVal", IEC61850_FC_ST); - MmsValue* stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO1.stVal", IEC61850_FC_ST); + if (error == IED_ERROR_OK) { + bool state = MmsValue_getBoolean(stVal); + MmsValue_delete(stVal); - if (error == IED_ERROR_OK) { - bool state = MmsValue_getBoolean(stVal); - MmsValue_delete(stVal); + printf("New status of simpleIOGenericIO/GGIO1.SPCSO1.stVal: %i\n", state); + } + else { + printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO1 failed!\n"); + } - printf("New status of simpleIOGenericIO/GGIO1.SPCSO1.stVal: %i\n", state); } else { - printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO1 failed!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO1 not found in server\n"); } - /************************ * Select before operate ***********************/ control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO2", con); - if (ControlObjectClient_select(control)) { + if (control) + { + if (ControlObjectClient_select(control)) { - ctlVal = MmsValue_newBoolean(true); + ctlVal = MmsValue_newBoolean(true); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO2 operated successfully\n"); + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO2 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO2!\n"); + } + + MmsValue_delete(ctlVal); } else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO2!\n"); + printf("failed to select simpleIOGenericIO/GGIO1.SPCSO2!\n"); } - MmsValue_delete(ctlVal); + ControlObjectClient_destroy(control); } else { - printf("failed to select simpleIOGenericIO/GGIO1.SPCSO2!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO2 not found in server\n"); } - ControlObjectClient_destroy(control); - - /**************************************** * Direct control with enhanced security ****************************************/ control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO3", con); - ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); + if (control) + { + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - ctlVal = MmsValue_newBoolean(true); + ctlVal = MmsValue_newBoolean(true); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO3 operated successfully\n"); - } - else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO3\n"); - } + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO3 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO3\n"); + } - MmsValue_delete(ctlVal); + MmsValue_delete(ctlVal); - /* Wait for command termination message */ - Thread_sleep(1000); + /* Wait for command termination message */ + Thread_sleep(1000); - ControlObjectClient_destroy(control); + ControlObjectClient_destroy(control); - /* Check if status value has changed */ + /* Check if status value has changed */ - stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO3.stVal", IEC61850_FC_ST); + stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO3.stVal", IEC61850_FC_ST); - if (error == IED_ERROR_OK) { - bool state = MmsValue_getBoolean(stVal); + if (error == IED_ERROR_OK) { + bool state = MmsValue_getBoolean(stVal); - printf("New status of simpleIOGenericIO/GGIO1.SPCSO3.stVal: %i\n", state); + printf("New status of simpleIOGenericIO/GGIO1.SPCSO3.stVal: %i\n", state); - MmsValue_delete(stVal); + MmsValue_delete(stVal); + } + else { + printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO3 failed!\n"); + } } else { - printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO3 failed!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO3 not found in server\n"); } /*********************************************** @@ -156,56 +175,66 @@ int main(int argc, char** argv) { control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO4", con); - ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); + if (control) + { + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - ctlVal = MmsValue_newBoolean(true); + ctlVal = MmsValue_newBoolean(true); - if (ControlObjectClient_selectWithValue(control, ctlVal)) { + if (ControlObjectClient_selectWithValue(control, ctlVal)) { + + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO4 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO4!\n"); + } - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO4 operated successfully\n"); } else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO4!\n"); + printf("failed to select simpleIOGenericIO/GGIO1.SPCSO4!\n"); } + MmsValue_delete(ctlVal); + + /* Wait for command termination message */ + Thread_sleep(1000); + + ControlObjectClient_destroy(control); } else { - printf("failed to select simpleIOGenericIO/GGIO1.SPCSO4!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO4 not found in server\n"); } - MmsValue_delete(ctlVal); - - /* Wait for command termination message */ - Thread_sleep(1000); - - ControlObjectClient_destroy(control); - - /********************************************************************* * Direct control with enhanced security (expect CommandTermination-) *********************************************************************/ control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO9", con); - ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - - ctlVal = MmsValue_newBoolean(true); + if (control) + { + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO9 operated successfully\n"); - } - else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO9\n"); - } + ctlVal = MmsValue_newBoolean(true); - MmsValue_delete(ctlVal); + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO9 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO9\n"); + } - /* Wait for command termination message */ - Thread_sleep(1000); + MmsValue_delete(ctlVal); - ControlObjectClient_destroy(control); + /* Wait for command termination message */ + Thread_sleep(1000); + ControlObjectClient_destroy(control); + } + else { + printf("Control object simpleIOGenericIO/GGIO1.SPCSO9 not found in server\n"); + } IedConnection_close(con); } diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index 8b43d9d7..24c909c9 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -101,6 +101,7 @@ printRawMmsMessage(void* parameter, uint8_t* message, int messageLength, bool re int main(int argc, char** argv) { + int returnCode = 0; char* hostname = StringUtils_copyString("localhost"); int tcpPort = 102; @@ -213,6 +214,10 @@ int main(int argc, char** argv) if (!MmsConnection_connect(con, &error, hostname, tcpPort)) { printf("MMS connect failed!\n"); + + if (error != MMS_ERROR_NONE) + returnCode = error; + goto exit; } else @@ -222,6 +227,9 @@ int main(int argc, char** argv) MmsServerIdentity* identity = MmsConnection_identify(con, &error); + if (error != MMS_ERROR_NONE) + returnCode = error; + if (identity != NULL) { printf("\nServer identity:\n----------------\n"); printf(" vendor:\t%s\n", identity->vendorName); @@ -235,14 +243,23 @@ int main(int argc, char** argv) if (readDeviceList) { printf("\nDomains present on server:\n--------------------------\n"); LinkedList nameList = MmsConnection_getDomainNames(con, &error); - LinkedList_printStringList(nameList); - LinkedList_destroy(nameList); + + if (error != MMS_ERROR_NONE) + returnCode = error; + + if (nameList) { + LinkedList_printStringList(nameList); + LinkedList_destroy(nameList); + } } if (getDeviceDirectory) { LinkedList variableList = MmsConnection_getDomainVariableNames(con, &error, domainName); + if (error != MMS_ERROR_NONE) + returnCode = error; + if (variableList) { LinkedList element = LinkedList_getNext(variableList); @@ -264,6 +281,9 @@ int main(int argc, char** argv) variableList = MmsConnection_getDomainJournals(con, &error, domainName); + if (error != MMS_ERROR_NONE) + returnCode = error; + if (variableList) { LinkedList element = variableList; @@ -309,6 +329,9 @@ int main(int argc, char** argv) LinkedList journalEntries = MmsConnection_readJournalTimeRange(con, &error, logDomain, logName, startTime, endTime, &moreFollows); + if (error != MMS_ERROR_NONE) + returnCode = error; + MmsValue_delete(startTime); MmsValue_delete(endTime); @@ -375,6 +398,8 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Reading variable failed: (ERROR %i)\n", error); + + returnCode = error; } else { printf("Read SUCCESS\n"); @@ -403,6 +428,8 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Reading variable failed: (ERROR %i)\n", error); + + returnCode = error; } else { printf("Read SUCCESS\n"); @@ -421,6 +448,8 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Reading variable list directory failed: (ERROR %i)\n", error); + + returnCode = error; } else { LinkedList varListElem = LinkedList_getNext(varListDir); @@ -454,12 +483,19 @@ int main(int argc, char** argv) char* continueAfter = NULL; while (MmsConnection_getFileDirectory(con, &error, "", continueAfter, mmsFileDirectoryHandler, lastName)) { + + if (error != MMS_ERROR_NONE) + returnCode = error; + continueAfter = lastName; } } if (getFileAttributes) { MmsConnection_getFileDirectory(con, &error, filename, NULL, mmsGetFileAttributeHandler, NULL); + + if (error != MMS_ERROR_NONE) + returnCode = error; } if (deleteFile) { @@ -467,13 +503,14 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Delete file failed: (ERROR %i)\n", error); + returnCode = error; } else { printf("File deleted\n"); } } - exit: +exit: free(hostname); free(domainName); free(variableName); @@ -482,6 +519,6 @@ int main(int argc, char** argv) MmsConnection_destroy(con); - return 0; + return returnCode; } diff --git a/examples/r_goose_publisher_example/r_goose_publisher_example.c b/examples/r_goose_publisher_example/r_goose_publisher_example.c index 0434c343..9ec300bb 100644 --- a/examples/r_goose_publisher_example/r_goose_publisher_example.c +++ b/examples/r_goose_publisher_example/r_goose_publisher_example.c @@ -102,7 +102,3 @@ main(int argc, char **argv) return 0; } - - - - diff --git a/examples/server_example_access_control/CMakeLists.txt b/examples/server_example_access_control/CMakeLists.txt new file mode 100644 index 00000000..a71a7383 --- /dev/null +++ b/examples/server_example_access_control/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories( + . +) + +set(server_example_SRCS + server_example_access_control.c + static_model.c +) + +IF(MSVC) +set_source_files_properties(${server_example_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(server_example_access_control + ${server_example_SRCS} +) + +target_link_libraries(server_example_access_control + iec61850 +) diff --git a/examples/server_example_access_control/Makefile b/examples/server_example_access_control/Makefile new file mode 100644 index 00000000..4b288a51 --- /dev/null +++ b/examples/server_example_access_control/Makefile @@ -0,0 +1,32 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_access_control +PROJECT_SOURCES = server_example_access_control.c +PROJECT_SOURCES += static_model.c + +PROJECT_ICD_FILE = simpleIO_direct_control.cid + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +LDLIBS += -lm + +CP = cp + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + mkdir -p vmd-filestore + $(CP) $(PROJECT_BINARY_NAME) vmd-filestore/IEDSERVER.BIN + +clean: + rm -f $(PROJECT_BINARY_NAME) + rm -f vmd-filestore/IEDSERVER.BIN + + diff --git a/examples/server_example_access_control/server_example_access_control.c b/examples/server_example_access_control/server_example_access_control.c new file mode 100644 index 00000000..e0b789af --- /dev/null +++ b/examples/server_example_access_control/server_example_access_control.c @@ -0,0 +1,324 @@ +/* + * server_example_access_control.c + * + * - How to use access control mechanisms + * - How to implement RBAC features based on access control mechanisms + */ + +#include "iec61850_server.h" +#include "hal_thread.h" +#include +#include +#include +#include + +#include "static_model.h" + +static int running = 0; +static IedServer iedServer = NULL; + +void +sigint_handler(int signalId) +{ + running = 0; +} + +static ControlHandlerResult +controlHandlerForBinaryOutput(ControlAction action, void* parameter, MmsValue* value, bool test) +{ + if (test) + return CONTROL_RESULT_FAILED; + + if (MmsValue_getType(value) == MMS_BOOLEAN) { + printf("received binary control command: "); + + if (MmsValue_getBoolean(value)) + printf("on\n"); + else + printf("off\n"); + } + else + return CONTROL_RESULT_FAILED; + + uint64_t timeStamp = Hal_getTimeInMs(); + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO1) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO2) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO3) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO4) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal, value); + } + + return CONTROL_RESULT_OK; +} + + +static void +connectionHandler (IedServer self, ClientConnection connection, bool connected, void* parameter) +{ + if (connected) + printf("Connection opened\n"); + else + printf("Connection closed\n"); +} + +/* + * This handler is called before the rcbEventHandler and can be use to allow or permit read or write access to the RCB + */ +static bool +rcbAccessHandler(void* parameter, ReportControlBlock* rcb, ClientConnection connection, IedServer_RCBEventType operation) +{ + printf("RCB: %s access: %s\n", ReportControlBlock_getName(rcb), operation == RCB_EVENT_GET_PARAMETER ? "READ" : "WRITE"); + + if (operation == RCB_EVENT_GET_PARAMETER) { + return true; + } + else { + /* change to false to disallow write access to control block */ + return true; + } +} + +static bool +lcbAccessHandler(void* parameter, LogControlBlock* lcb, ClientConnection connection, IedServer_LCBEventType operation) +{ + printf("LCB: %s access: %s\n", LogControlBlock_getName(lcb), operation == LCB_EVENT_GET_PARAMETER ? "READ" : "WRITE"); + + if (operation == LCB_EVENT_GET_PARAMETER) { + return true; + } + else { + return false; + } +} + +static void +rcbEventHandler(void* parameter, ReportControlBlock* rcb, ClientConnection connection, IedServer_RCBEventType event, const char* parameterName, MmsDataAccessError serviceError) +{ + printf("RCB: %s event: %i\n", ReportControlBlock_getName(rcb), event); + + if ((event == RCB_EVENT_SET_PARAMETER) || (event == RCB_EVENT_GET_PARAMETER)) { + printf(" param: %s\n", parameterName); + printf(" result: %i\n", serviceError); + } + + if (event == RCB_EVENT_ENABLE) { + char* rptId = ReportControlBlock_getRptID(rcb); + printf(" rptID: %s\n", rptId); + char* dataSet = ReportControlBlock_getDataSet(rcb); + printf(" datSet: %s\n", dataSet); + + free(rptId); + free(dataSet); + } +} + +static bool +dataSetAccessHandler(void* parameter, ClientConnection connection, IedServer_DataSetOperation operation, const char* datasetRef) +{ + printf("Data set access: %s operation: %i\n", datasetRef, operation); + + return true; +} + +static MmsDataAccessError +readAccessHandler(LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject, FunctionalConstraint fc, ClientConnection connection, void* parameter) +{ + printf("Read access to %s/%s.%s\n", ld->name, ln->name, dataObject->name); + + if (!strcmp(ln->name, "GGIO1") && !strcmp(dataObject->name, "AnIn1")) { + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + } + + return DATA_ACCESS_ERROR_SUCCESS; +} + +static bool +directoryAccessHandler(void* parameter, ClientConnection connection, IedServer_DirectoryCategory category, LogicalDevice* logicalDevice) +{ + switch(category) { + case DIRECTORY_CAT_LD_LIST: + printf("Get list of logical devices from %s\n", ClientConnection_getPeerAddress(connection)); + break; + case DIRECTORY_CAT_DATASET_LIST: + printf("Get list of datasets for LD %s from %s\n", ModelNode_getName((ModelNode*)logicalDevice), ClientConnection_getPeerAddress(connection)); + break; + case DIRECTORY_CAT_DATA_LIST: + printf("Get list of data for LD %s from %s\n", ModelNode_getName((ModelNode*)logicalDevice), ClientConnection_getPeerAddress(connection)); + break; + case DIRECTORY_CAT_LOG_LIST: + printf("Get list of logs for LD %s from %s -> reject\n", ModelNode_getName((ModelNode*)logicalDevice), ClientConnection_getPeerAddress(connection)); + return false; + break; + } + + return true; +} + +int +main(int argc, char** argv) +{ + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); + + /* Create new server configuration object */ + IedServerConfig config = IedServerConfig_create(); + + /* Set buffer size for buffered report control blocks to 200000 bytes */ + IedServerConfig_setReportBufferSize(config, 200000); + + /* Set stack compliance to a specific edition of the standard (WARNING: data model has also to be checked for compliance) */ + IedServerConfig_setEdition(config, IEC_61850_EDITION_2); + + /* Set the base path for the MMS file services */ + IedServerConfig_setFileServiceBasePath(config, "./vmd-filestore/"); + + /* disable MMS file service */ + IedServerConfig_enableFileService(config, false); + + /* enable dynamic data set service */ + IedServerConfig_enableDynamicDataSetService(config, true); + + /* disable log service */ + IedServerConfig_enableLogService(config, false); + + /* set maximum number of clients */ + IedServerConfig_setMaxMmsConnections(config, 2); + + /* Create a new IEC 61850 server instance */ + iedServer = IedServer_createWithConfig(&iedModel, NULL, config); + + /* configuration object is no longer required */ + IedServerConfig_destroy(config); + + /* set the identity values for MMS identify service */ + IedServer_setServerIdentity(iedServer, "libiec61850.com", "access control example", "1.0.0"); + + /* Install handler for operate command */ + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO1); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO2); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO3); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO4); + + IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); + + /* Install handler to perform access control on RCB */ + IedServer_setRCBAccessHandler(iedServer, rcbAccessHandler, NULL); + + /* Install handler to perform access control on LCB */ + IedServer_setLCBAccessHandler(iedServer, lcbAccessHandler, NULL); + + /* Install handler to log RCB events */ + IedServer_setRCBEventHandler(iedServer, rcbEventHandler, NULL); + + /* By default access to variables with FC=DC and FC=CF is not allowed. + * This allow to write to simpleIOGenericIO/GGIO1.NamPlt.vendor variable used + * by iec61850_client_example1. + */ + IedServer_setWriteAccessPolicy(iedServer, IEC61850_FC_DC, ACCESS_POLICY_ALLOW); + + /* Install handler to perform access control on datasets */ + IedServer_setDataSetAccessHandler(iedServer, dataSetAccessHandler, NULL); + + /* Install handler to perform read access control on data model elements + * NOTE: when read access to a data model element is blocked this will also prevent the client + * to read the data model element in a data set or enable a RCB instance that uses a dataset + * containing the restricted data model element. + */ + IedServer_setReadAccessHandler(iedServer, readAccessHandler, NULL); + + IedServer_setDirectoryAccessHandler(iedServer, directoryAccessHandler, NULL); + + /* MMS server will be instructed to start listening for client connections. */ + IedServer_start(iedServer, tcpPort); + + if (!IedServer_isRunning(iedServer)) { + printf("Starting server failed (maybe need root permissions or another server is already using the port)! Exit.\n"); + IedServer_destroy(iedServer); + exit(-1); + } + + running = 1; + + signal(SIGINT, sigint_handler); + + float t = 0.f; + + while (running) { + uint64_t timestamp = Hal_getTimeInMs(); + + t += 0.1f; + + float an1 = sinf(t); + float an2 = sinf(t + 1.f); + float an3 = sinf(t + 2.f); + float an4 = sinf(t + 3.f); + + 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); + +#if 1 + IedServer_lockDataModel(iedServer); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f, an1); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f, an2); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f, an3); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f, an4); + + IedServer_unlockDataModel(iedServer); +#endif + + Thread_sleep(100); + } + + /* stop MMS server - close TCP server socket and all client sockets */ + IedServer_stop(iedServer); + + /* Cleanup - free all resources */ + IedServer_destroy(iedServer); + + return 0; +} /* main() */ diff --git a/examples/server_example_access_control/static_model.c b/examples/server_example_access_control/static_model.c new file mode 100644 index 00000000..26f5f4fc --- /dev/null +++ b/examples/server_example_access_control/static_model.c @@ -0,0 +1,2223 @@ +/* + * static_model.c + * + * automatically generated from simpleIO_direct_control.cid + */ +#include "static_model.h" + +static void initializeValues(); + +extern DataSet iedModelds_GenericIO_LLN0_Events; +extern DataSet iedModelds_GenericIO_LLN0_Events2; +extern DataSet iedModelds_GenericIO_LLN0_Measurements; + + +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda3; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda0 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO1$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda1 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO2$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda2 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO3$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda3 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO4$stVal", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Events = { + "GenericIO", + "LLN0$Events", + 4, + &iedModelds_GenericIO_LLN0_Events_fcda0, + &iedModelds_GenericIO_LLN0_Events2 +}; + +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda3; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda0 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO1", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda1 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO2", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda2 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO3", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda3 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO4", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Events2 = { + "GenericIO", + "LLN0$Events2", + 4, + &iedModelds_GenericIO_LLN0_Events2_fcda0, + &iedModelds_GenericIO_LLN0_Measurements +}; + +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda3; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda4; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda5; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda6; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda7; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda0 = { + "GenericIO", + false, + "GGIO1$MX$AnIn1$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda1 = { + "GenericIO", + false, + "GGIO1$MX$AnIn1$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda2 = { + "GenericIO", + false, + "GGIO1$MX$AnIn2$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda3 = { + "GenericIO", + false, + "GGIO1$MX$AnIn2$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda4 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda4 = { + "GenericIO", + false, + "GGIO1$MX$AnIn3$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda5 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda5 = { + "GenericIO", + false, + "GGIO1$MX$AnIn3$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda6 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda6 = { + "GenericIO", + false, + "GGIO1$MX$AnIn4$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda7 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda7 = { + "GenericIO", + false, + "GGIO1$MX$AnIn4$q", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Measurements = { + "GenericIO", + "LLN0$Measurements", + 8, + &iedModelds_GenericIO_LLN0_Measurements_fcda0, + NULL +}; + +LogicalDevice iedModel_GenericIO = { + LogicalDeviceModelType, + "GenericIO", + (ModelNode*) &iedModel, + NULL, + (ModelNode*) &iedModel_GenericIO_LLN0, + NULL +}; + +LogicalNode iedModel_GenericIO_LLN0 = { + LogicalNodeModelType, + "LLN0", + (ModelNode*) &iedModel_GenericIO, + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, +}; + +DataObject iedModel_GenericIO_LLN0_Mod = { + DataObjectModelType, + "Mod", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_ctlModel, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_LLN0_Health_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Health_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Health_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_GenericIO_LLN0, + NULL, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_vendor, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_swRev, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_d, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_configRev, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_configRev = { + DataAttributeModelType, + "configRev", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_ldNs, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs = { + DataAttributeModelType, + "ldNs", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + NULL, + NULL, + 0, + -1, + IEC61850_FC_EX, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +LogicalNode iedModel_GenericIO_LPHD1 = { + LogicalNodeModelType, + "LPHD1", + (ModelNode*) &iedModel_GenericIO, + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam, +}; + +DataObject iedModel_GenericIO_LPHD1_PhyNam = { + DataObjectModelType, + "PhyNam", + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam_vendor, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam, + NULL, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LPHD1_PhyHealth = { + DataObjectModelType, + "PhyHealth", + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LPHD1_Proxy = { + DataObjectModelType, + "Proxy", + (ModelNode*) &iedModel_GenericIO_LPHD1, + NULL, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +LogicalNode iedModel_GenericIO_GGIO1 = { + LogicalNodeModelType, + "GGIO1", + (ModelNode*) &iedModel_GenericIO, + NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, +}; + +DataObject iedModel_GenericIO_GGIO1_Mod = { + DataObjectModelType, + "Mod", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_ctlModel, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_vendor, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_swRev, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_d, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + NULL, + NULL, + 0, + -1, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn1 = { + DataObjectModelType, + "AnIn1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag_f, + 0, + -1, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_t, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn2 = { + DataObjectModelType, + "AnIn2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag_f, + 0, + -1, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_t, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn3 = { + DataObjectModelType, + "AnIn3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag_f, + 0, + -1, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_t, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn4 = { + DataObjectModelType, + "AnIn4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag_f, + 0, + -1, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_t, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + NULL, + NULL, + 0, + -1, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO1 = { + DataObjectModelType, + "SPCSO1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin, + 0, + -1 +}; + +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, + -1, + 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, + -1, + 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, + -1, + 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, + -1, + IEC61850_FC_ST, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlModel, + NULL, + 0, + -1, + 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, + -1, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_T, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO2 = { + DataObjectModelType, + "SPCSO2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_T, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_t, + NULL, + 0, + -1, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO3 = { + DataObjectModelType, + "SPCSO3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_T, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_t, + NULL, + 0, + -1, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO4 = { + DataObjectModelType, + "SPCSO4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_T, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + NULL, + NULL, + 0, + -1, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, + NULL, + 0, + -1, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind1 = { + DataObjectModelType, + "Ind1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind2 = { + DataObjectModelType, + "Ind2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind3 = { + DataObjectModelType, + "Ind3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind4 = { + DataObjectModelType, + "Ind4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_stVal, + 0, + -1 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_q, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_t, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + NULL, + NULL, + 0, + -1, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +extern ReportControlBlock iedModel_GenericIO_LLN0_report0; +extern ReportControlBlock iedModel_GenericIO_LLN0_report1; +extern ReportControlBlock iedModel_GenericIO_LLN0_report2; +extern ReportControlBlock iedModel_GenericIO_LLN0_report3; +extern ReportControlBlock iedModel_GenericIO_LLN0_report4; +extern ReportControlBlock iedModel_GenericIO_LLN0_report5; +extern ReportControlBlock iedModel_GenericIO_LLN0_report6; +extern ReportControlBlock iedModel_GenericIO_LLN0_report7; +extern ReportControlBlock iedModel_GenericIO_LLN0_report8; +extern ReportControlBlock iedModel_GenericIO_LLN0_report9; + +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 1, 88, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsRCBPreConf01", "Events1", false, "Events", 1, 88, 175, 50, 1000, {0x4, 0xc0, 0xa8, 0x2, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsBRCB01", "Events2", true, "Events", 1, 88, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsBRCBPreConf01", "Events2", true, "Events", 1, 88, 175, 50, 1000, {0x4, 0xc0, 0xa8, 0x2, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 88, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report5}; +ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 88, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report6}; +ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 88, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report7}; +ReportControlBlock iedModel_GenericIO_LLN0_report7 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 80, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report8}; +ReportControlBlock iedModel_GenericIO_LLN0_report8 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 80, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report9}; +ReportControlBlock iedModel_GenericIO_LLN0_report9 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 80, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; + + + + + + + +IedModel iedModel = { + "simpleIO", + &iedModel_GenericIO, + &iedModelds_GenericIO_LLN0_Events, + &iedModel_GenericIO_LLN0_report0, + NULL, + NULL, + NULL, + NULL, + NULL, + initializeValues +}; + +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("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); + +iedModel_GenericIO_GGIO1_SPCSO3_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_SPCSO4_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); +} diff --git a/examples/server_example_access_control/static_model.h b/examples/server_example_access_control/static_model.h new file mode 100644 index 00000000..b6030e51 --- /dev/null +++ b/examples/server_example_access_control/static_model.h @@ -0,0 +1,311 @@ +/* + * static_model.h + * + * automatically generated from simpleIO_direct_control.cid + */ + +#ifndef STATIC_MODEL_H_ +#define STATIC_MODEL_H_ + +#include +#include "iec61850_model.h" + +extern IedModel iedModel; +extern LogicalDevice iedModel_GenericIO; +extern LogicalNode iedModel_GenericIO_LLN0; +extern DataObject iedModel_GenericIO_LLN0_Mod; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_q; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_t; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_ctlModel; +extern DataObject iedModel_GenericIO_LLN0_Beh; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_q; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_t; +extern DataObject iedModel_GenericIO_LLN0_Health; +extern DataAttribute iedModel_GenericIO_LLN0_Health_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Health_q; +extern DataAttribute iedModel_GenericIO_LLN0_Health_t; +extern DataObject iedModel_GenericIO_LLN0_NamPlt; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_vendor; +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 LogicalNode iedModel_GenericIO_LPHD1; +extern DataObject iedModel_GenericIO_LPHD1_PhyNam; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor; +extern DataObject iedModel_GenericIO_LPHD1_PhyHealth; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_q; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_t; +extern DataObject iedModel_GenericIO_LPHD1_Proxy; +extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_stVal; +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; +extern DataObject iedModel_GenericIO_GGIO1_Beh; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_t; +extern DataObject iedModel_GenericIO_GGIO1_Health; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_t; +extern DataObject iedModel_GenericIO_GGIO1_NamPlt; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_vendor; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_swRev; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_d; +extern DataObject iedModel_GenericIO_GGIO1_AnIn1; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn2; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn3; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn4; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag; +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; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent; +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 DataObject iedModel_GenericIO_GGIO1_SPCSO2; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO3; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test; +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_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent; +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; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind2; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind3; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind4; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; + + + +#define IEDMODEL_GenericIO (&iedModel_GenericIO) +#define IEDMODEL_GenericIO_LLN0 (&iedModel_GenericIO_LLN0) +#define IEDMODEL_GenericIO_LLN0_Mod (&iedModel_GenericIO_LLN0_Mod) +#define IEDMODEL_GenericIO_LLN0_Mod_stVal (&iedModel_GenericIO_LLN0_Mod_stVal) +#define IEDMODEL_GenericIO_LLN0_Mod_q (&iedModel_GenericIO_LLN0_Mod_q) +#define IEDMODEL_GenericIO_LLN0_Mod_t (&iedModel_GenericIO_LLN0_Mod_t) +#define IEDMODEL_GenericIO_LLN0_Mod_ctlModel (&iedModel_GenericIO_LLN0_Mod_ctlModel) +#define IEDMODEL_GenericIO_LLN0_Beh (&iedModel_GenericIO_LLN0_Beh) +#define IEDMODEL_GenericIO_LLN0_Beh_stVal (&iedModel_GenericIO_LLN0_Beh_stVal) +#define IEDMODEL_GenericIO_LLN0_Beh_q (&iedModel_GenericIO_LLN0_Beh_q) +#define IEDMODEL_GenericIO_LLN0_Beh_t (&iedModel_GenericIO_LLN0_Beh_t) +#define IEDMODEL_GenericIO_LLN0_Health (&iedModel_GenericIO_LLN0_Health) +#define IEDMODEL_GenericIO_LLN0_Health_stVal (&iedModel_GenericIO_LLN0_Health_stVal) +#define IEDMODEL_GenericIO_LLN0_Health_q (&iedModel_GenericIO_LLN0_Health_q) +#define IEDMODEL_GenericIO_LLN0_Health_t (&iedModel_GenericIO_LLN0_Health_t) +#define IEDMODEL_GenericIO_LLN0_NamPlt (&iedModel_GenericIO_LLN0_NamPlt) +#define IEDMODEL_GenericIO_LLN0_NamPlt_vendor (&iedModel_GenericIO_LLN0_NamPlt_vendor) +#define IEDMODEL_GenericIO_LLN0_NamPlt_swRev (&iedModel_GenericIO_LLN0_NamPlt_swRev) +#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_LPHD1 (&iedModel_GenericIO_LPHD1) +#define IEDMODEL_GenericIO_LPHD1_PhyNam (&iedModel_GenericIO_LPHD1_PhyNam) +#define IEDMODEL_GenericIO_LPHD1_PhyNam_vendor (&iedModel_GenericIO_LPHD1_PhyNam_vendor) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth (&iedModel_GenericIO_LPHD1_PhyHealth) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_stVal (&iedModel_GenericIO_LPHD1_PhyHealth_stVal) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_q (&iedModel_GenericIO_LPHD1_PhyHealth_q) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_t (&iedModel_GenericIO_LPHD1_PhyHealth_t) +#define IEDMODEL_GenericIO_LPHD1_Proxy (&iedModel_GenericIO_LPHD1_Proxy) +#define IEDMODEL_GenericIO_LPHD1_Proxy_stVal (&iedModel_GenericIO_LPHD1_Proxy_stVal) +#define IEDMODEL_GenericIO_LPHD1_Proxy_q (&iedModel_GenericIO_LPHD1_Proxy_q) +#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) +#define IEDMODEL_GenericIO_GGIO1_Beh (&iedModel_GenericIO_GGIO1_Beh) +#define IEDMODEL_GenericIO_GGIO1_Beh_stVal (&iedModel_GenericIO_GGIO1_Beh_stVal) +#define IEDMODEL_GenericIO_GGIO1_Beh_q (&iedModel_GenericIO_GGIO1_Beh_q) +#define IEDMODEL_GenericIO_GGIO1_Beh_t (&iedModel_GenericIO_GGIO1_Beh_t) +#define IEDMODEL_GenericIO_GGIO1_Health (&iedModel_GenericIO_GGIO1_Health) +#define IEDMODEL_GenericIO_GGIO1_Health_stVal (&iedModel_GenericIO_GGIO1_Health_stVal) +#define IEDMODEL_GenericIO_GGIO1_Health_q (&iedModel_GenericIO_GGIO1_Health_q) +#define IEDMODEL_GenericIO_GGIO1_Health_t (&iedModel_GenericIO_GGIO1_Health_t) +#define IEDMODEL_GenericIO_GGIO1_NamPlt (&iedModel_GenericIO_GGIO1_NamPlt) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_vendor (&iedModel_GenericIO_GGIO1_NamPlt_vendor) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_swRev (&iedModel_GenericIO_GGIO1_NamPlt_swRev) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_d (&iedModel_GenericIO_GGIO1_NamPlt_d) +#define IEDMODEL_GenericIO_GGIO1_AnIn1 (&iedModel_GenericIO_GGIO1_AnIn1) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag (&iedModel_GenericIO_GGIO1_AnIn1_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f (&iedModel_GenericIO_GGIO1_AnIn1_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_q (&iedModel_GenericIO_GGIO1_AnIn1_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_t (&iedModel_GenericIO_GGIO1_AnIn1_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn2 (&iedModel_GenericIO_GGIO1_AnIn2) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag (&iedModel_GenericIO_GGIO1_AnIn2_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f (&iedModel_GenericIO_GGIO1_AnIn2_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_q (&iedModel_GenericIO_GGIO1_AnIn2_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_t (&iedModel_GenericIO_GGIO1_AnIn2_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn3 (&iedModel_GenericIO_GGIO1_AnIn3) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag (&iedModel_GenericIO_GGIO1_AnIn3_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f (&iedModel_GenericIO_GGIO1_AnIn3_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_q (&iedModel_GenericIO_GGIO1_AnIn3_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_t (&iedModel_GenericIO_GGIO1_AnIn3_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn4 (&iedModel_GenericIO_GGIO1_AnIn4) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag (&iedModel_GenericIO_GGIO1_AnIn4_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f (&iedModel_GenericIO_GGIO1_AnIn4_mag_f) +#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) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum) +#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_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) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper (&iedModel_GenericIO_GGIO1_SPCSO2_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO2_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_t (&iedModel_GenericIO_GGIO1_SPCSO2_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3 (&iedModel_GenericIO_GGIO1_SPCSO3) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal (&iedModel_GenericIO_GGIO1_SPCSO3_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_q (&iedModel_GenericIO_GGIO1_SPCSO3_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper (&iedModel_GenericIO_GGIO1_SPCSO3_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check) +#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_stVal (&iedModel_GenericIO_GGIO1_SPCSO4_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_q (&iedModel_GenericIO_GGIO1_SPCSO4_q) +#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) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum) +#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) +#define IEDMODEL_GenericIO_GGIO1_Ind1_t (&iedModel_GenericIO_GGIO1_Ind1_t) +#define IEDMODEL_GenericIO_GGIO1_Ind2 (&iedModel_GenericIO_GGIO1_Ind2) +#define IEDMODEL_GenericIO_GGIO1_Ind2_stVal (&iedModel_GenericIO_GGIO1_Ind2_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind2_q (&iedModel_GenericIO_GGIO1_Ind2_q) +#define IEDMODEL_GenericIO_GGIO1_Ind2_t (&iedModel_GenericIO_GGIO1_Ind2_t) +#define IEDMODEL_GenericIO_GGIO1_Ind3 (&iedModel_GenericIO_GGIO1_Ind3) +#define IEDMODEL_GenericIO_GGIO1_Ind3_stVal (&iedModel_GenericIO_GGIO1_Ind3_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind3_q (&iedModel_GenericIO_GGIO1_Ind3_q) +#define IEDMODEL_GenericIO_GGIO1_Ind3_t (&iedModel_GenericIO_GGIO1_Ind3_t) +#define IEDMODEL_GenericIO_GGIO1_Ind4 (&iedModel_GenericIO_GGIO1_Ind4) +#define IEDMODEL_GenericIO_GGIO1_Ind4_stVal (&iedModel_GenericIO_GGIO1_Ind4_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind4_q (&iedModel_GenericIO_GGIO1_Ind4_q) +#define IEDMODEL_GenericIO_GGIO1_Ind4_t (&iedModel_GenericIO_GGIO1_Ind4_t) + +#endif /* STATIC_MODEL_H_ */ + 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 6023f3b9..3f1ca077 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -100,6 +100,12 @@ rcbEventHandler(void* parameter, ReportControlBlock* rcb, ClientConnection conne int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); /* Create new server configuration object */ @@ -163,7 +169,7 @@ main(int argc, char** argv) IedServer_setWriteAccessPolicy(iedServer, IEC61850_FC_DC, ACCESS_POLICY_ALLOW); /* MMS server will be instructed to start listening for client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed (maybe need root permissions or another server is already using the port)! Exit.\n"); diff --git a/examples/server_example_control/server_example_control.c b/examples/server_example_control/server_example_control.c index 6159cdda..501acb46 100644 --- a/examples/server_example_control/server_example_control.c +++ b/examples/server_example_control/server_example_control.c @@ -126,6 +126,11 @@ int main(int argc, char** argv) { iedServer = IedServer_create(&iedModel); + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1, (ControlHandler) controlHandlerForBinaryOutput, @@ -169,7 +174,7 @@ main(int argc, char** argv) IEDMODEL_GenericIO_GGIO1_SPCSO9); /* MMS server will be instructed to start listening to client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed! Exit.\n"); diff --git a/examples/server_example_deadband/server_example_deadband.c b/examples/server_example_deadband/server_example_deadband.c index 044b72e8..a93236dd 100644 --- a/examples/server_example_deadband/server_example_deadband.c +++ b/examples/server_example_deadband/server_example_deadband.c @@ -36,6 +36,12 @@ connectionHandler (IedServer self, ClientConnection connection, bool connected, int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); /* Create new server configuration object */ @@ -74,7 +80,7 @@ main(int argc, char** argv) IedServer_setWriteAccessPolicy(iedServer, IEC61850_FC_CF, ACCESS_POLICY_ALLOW); /* MMS server will be instructed to start listening for client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed (maybe need root permissions or another server is already using the port)! Exit.\n"); diff --git a/examples/server_example_files/server_example_files.c b/examples/server_example_files/server_example_files.c index 702eed59..9974fe6f 100644 --- a/examples/server_example_files/server_example_files.c +++ b/examples/server_example_files/server_example_files.c @@ -55,6 +55,12 @@ fileAccessHandler (void* parameter, MmsServerConnection connection, MmsFileServi int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); iedServer = IedServer_create(&iedModel); @@ -70,7 +76,7 @@ main(int argc, char** argv) IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); /* MMS server will be instructed to start listening to client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed! Exit.\n"); diff --git a/examples/server_example_logging/server_example_logging.c b/examples/server_example_logging/server_example_logging.c index 01ad5ae2..ee388be8 100644 --- a/examples/server_example_logging/server_example_logging.c +++ b/examples/server_example_logging/server_example_logging.c @@ -115,9 +115,37 @@ entryDataCallback (void* parameter, const char* dataRef, const uint8_t* data, in return true; } +static bool +lcbAccessHandler(void* parameter, LogControlBlock* lcb, ClientConnection connection, IedServer_LCBEventType operation) +{ + printf("%s access to LCB %s from %s\n", operation == LCB_EVENT_GET_PARAMETER ? "read" : "write", LogControlBlock_getName(lcb), ClientConnection_getPeerAddress(connection)); + + /* only allow read access */ + if (operation == LCB_EVENT_GET_PARAMETER) { + return true; + } + else { + return false; + } +} + +static bool +logAccessHandler(void* parameter, const char* logName, ClientConnection connection) +{ + printf("Access to log %s from %s\n", logName, ClientConnection_getPeerAddress(connection)); + + return false; +} + int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); iedServer = IedServer_create(&iedModel); @@ -141,6 +169,10 @@ main(int argc, char** argv) IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); + IedServer_setLCBAccessHandler(iedServer, lcbAccessHandler, NULL); + + IedServer_setLogAccessHandler(iedServer, logAccessHandler, NULL); + LogStorage statusLog = SqliteLogStorage_createInstance("log_status.db"); LogStorage_setMaxLogEntries(statusLog, 10); @@ -174,7 +206,7 @@ main(int argc, char** argv) /* MMS server will be instructed to start listening to client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed! Exit.\n"); 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 2327923f..12938aa1 100644 --- a/examples/server_example_password_auth/server_example_password_auth.c +++ b/examples/server_example_password_auth/server_example_password_auth.c @@ -159,6 +159,12 @@ readAccessHandler(LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject, Fu int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + iedServer = IedServer_create(&iedModel); /* Activate authentication */ @@ -194,7 +200,7 @@ int main(int argc, char** argv) { IedServer_setReadAccessHandler(iedServer, readAccessHandler, NULL); /* MMS server will be instructed to start listening to client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed! Exit.\n"); diff --git a/examples/server_example_setting_groups/server_example_sg.c b/examples/server_example_setting_groups/server_example_sg.c index 4466c1ad..cb3e2ec7 100644 --- a/examples/server_example_setting_groups/server_example_sg.c +++ b/examples/server_example_setting_groups/server_example_sg.c @@ -97,7 +97,10 @@ readAccessHandler(LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject, Fu { void* securityToken = ClientConnection_getSecurityToken(connection); - printf("Read access to %s/%s.%s\n", ld->name, ln->name, dataObject->name); + if (dataObject) + printf("Read access to %s/%s.%s[%s]\n", ld->name, ln->name, dataObject->name, FunctionalConstraint_toString(fc)); + else + printf("Read access to %s/%s[%s]\n", ld->name, ln->name, FunctionalConstraint_toString(fc)); return DATA_ACCESS_ERROR_SUCCESS; } @@ -105,6 +108,12 @@ readAccessHandler(LogicalDevice* ld, LogicalNode* ln, DataObject* dataObject, Fu int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + IedServerConfig config = IedServerConfig_create(); //IedServerConfig_enableEditSG(config, false); @@ -112,6 +121,8 @@ main(int argc, char** argv) iedServer = IedServer_createWithConfig(&iedModel, NULL, config); + IedServer_setTimeQuality(iedServer, true, false, false, 10); + IedServerConfig_destroy(config); LogicalDevice* ld = IEDMODEL_PROT; @@ -127,7 +138,7 @@ main(int argc, char** argv) IedServer_setReadAccessHandler(iedServer, readAccessHandler, NULL); /* MMS server will be instructed to start listening to client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed! Exit.\n"); diff --git a/examples/server_example_substitution/server_example_substitution.c b/examples/server_example_substitution/server_example_substitution.c index c8292dee..e91ff199 100644 --- a/examples/server_example_substitution/server_example_substitution.c +++ b/examples/server_example_substitution/server_example_substitution.c @@ -200,6 +200,12 @@ writeAccessHandler (DataAttribute* dataAttribute, MmsValue* value, ClientConnect int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); /* Create a new IEC 61850 server instance */ @@ -220,7 +226,7 @@ main(int argc, char** argv) IedServer_handleWriteAccess(iedServer, IEDMODEL_LD1_GGIO1_Ind1_blkEna, writeAccessHandler, NULL); /* MMS server will be instructed to start listening for client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed! Exit.\n"); diff --git a/examples/server_example_threadless/server_example_threadless.c b/examples/server_example_threadless/server_example_threadless.c index ae63ce62..13f76927 100644 --- a/examples/server_example_threadless/server_example_threadless.c +++ b/examples/server_example_threadless/server_example_threadless.c @@ -65,6 +65,11 @@ controlHandlerForBinaryOutput(ControlAction action, void* parameter, MmsValue* v int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } iedServer = IedServer_create(&iedModel); @@ -86,7 +91,7 @@ main(int argc, char** argv) IEDMODEL_GenericIO_GGIO1_SPCSO4); /* MMS server will be instructed to start listening to client connections. */ - IedServer_startThreadless(iedServer, 102); + IedServer_startThreadless(iedServer, tcpPort); if (!IedServer_isRunning(iedServer)) { printf("Starting server failed! Exit.\n"); diff --git a/examples/server_example_write_handler/server_example_write_handler.c b/examples/server_example_write_handler/server_example_write_handler.c index 49151f06..41e04f4c 100644 --- a/examples/server_example_write_handler/server_example_write_handler.c +++ b/examples/server_example_write_handler/server_example_write_handler.c @@ -41,10 +41,16 @@ writeAccessHandler (DataAttribute* dataAttribute, MmsValue* value, ClientConnect int main(int argc, char** argv) { + int tcpPort = 102; + + if (argc > 1) { + tcpPort = atoi(argv[1]); + } + iedServer = IedServer_create(&iedModel); /* MMS server will be instructed to start listening to client connections. */ - IedServer_start(iedServer, 102); + IedServer_start(iedServer, tcpPort); /* Don't allow access to SP variables by default */ IedServer_setWriteAccessPolicy(iedServer, IEC61850_FC_SP, ACCESS_POLICY_DENY); diff --git a/examples/tls_client_example/tls_client_example.c b/examples/tls_client_example/tls_client_example.c index c546f425..db730f3f 100644 --- a/examples/tls_client_example/tls_client_example.c +++ b/examples/tls_client_example/tls_client_example.c @@ -30,11 +30,15 @@ reportCallbackFunction(void* parameter, ClientReport report) } static void -securityEventHandler(void* parameter, TLSConfiguration_EventLevel eventLevel, int eventCode, const char* msg) +securityEventHandler(void* parameter, TLSEventLevel eventLevel, int eventCode, const char* msg, TLSConnection con) { (void)parameter; - printf("[SECURITY EVENT] %s (t: %i, c: %i)\n", msg, eventLevel, eventCode); + char* peerAddr = TLSConnection_getPeerAddress(con, NULL); + + printf("[SECURITY EVENT] %s (%s)(t: %i, c: %i)\n", msg, peerAddr, eventLevel, eventCode); + + free(peerAddr); } int main(int argc, char** argv) { diff --git a/examples/tls_server_example/tls_server_example.c b/examples/tls_server_example/tls_server_example.c index 08bfd662..6db25222 100644 --- a/examples/tls_server_example/tls_server_example.c +++ b/examples/tls_server_example/tls_server_example.c @@ -103,11 +103,17 @@ clientAuthenticator(void* parameter, AcseAuthenticationParameter authParameter, } static void -securityEventHandler(void* parameter, TLSConfiguration_EventLevel eventLevel, int eventCode, const char* msg) +securityEventHandler(void* parameter, TLSEventLevel eventLevel, int eventCode, const char* msg, TLSConnection con) { (void)parameter; - printf("[SECURITY EVENT] %s (t: %i, c: %i)\n", msg, eventLevel, eventCode); + char* peerAddr = TLSConnection_getPeerAddress(con, NULL); + + const char* tlsVersionStr = TLSConfigVersion_toString(TLSConnection_getTLSVersion(con)); + + printf("[SECURITY EVENT - %s] %s (%s)(t: %i, c: %i)\n", tlsVersionStr, msg, peerAddr, eventLevel, eventCode); + + free(peerAddr); } int diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt index 33975c28..9b421f6b 100644 --- a/hal/CMakeLists.txt +++ b/hal/CMakeLists.txt @@ -10,7 +10,7 @@ endif() project(hal) set(LIBHAL_VERSION_MAJOR "2") -set(LIBHAL_VERSION_MINOR "0") +set(LIBHAL_VERSION_MINOR "1") set(LIBHAL_VERSION_PATCH "0") # feature checks @@ -129,7 +129,7 @@ include_directories( if(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB) link_directories(${CONFIG_EXTERNAL_MBEDTLS_DYNLIB_PATH}) else() -file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.16/library/*.c) +file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.28/library/*.c) endif(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB) add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h") diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index 2d97a826..eaf1897a 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -263,6 +263,8 @@ void Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress) { struct packet_mreq mreq; + memset(&mreq, 0, sizeof(struct packet_mreq)); + mreq.mr_ifindex = ethSocket->socketAddress.sll_ifindex; mreq.mr_alen = ETH_ALEN; mreq.mr_type = PACKET_MR_MULTICAST; diff --git a/hal/inc/hal_socket.h b/hal/inc/hal_socket.h index 33aaf12c..c55ad19e 100644 --- a/hal/inc/hal_socket.h +++ b/hal/inc/hal_socket.h @@ -357,7 +357,7 @@ Socket_getPeerAddress(Socket self); * * The peer address has to be returned as null terminated string * - * Implementation of this function is MANDATORY (lib60870) + * Implementation of this function is MANDATORY (lib60870 and libiec61850) * * \param self the client, connection or server socket instance * \param peerAddressString a string to store the peer address (the string should have space diff --git a/hal/inc/tls_config.h b/hal/inc/tls_config.h index 37af40c2..2896a108 100644 --- a/hal/inc/tls_config.h +++ b/hal/inc/tls_config.h @@ -3,7 +3,7 @@ * * TLS Configuration API for protocol stacks using TCP/IP * - * Copyright 2017-2021 Michael Zillgith + * Copyright 2017-2022 Michael Zillgith * * Abstraction layer for configuration of different TLS implementations * @@ -50,11 +50,30 @@ TLSConfiguration_create(void); PAL_API void TLSConfiguration_setClientMode(TLSConfiguration self); +typedef enum { + TLS_VERSION_NOT_SELECTED = 0, + TLS_VERSION_SSL_3_0 = 3, + TLS_VERSION_TLS_1_0 = 4, + TLS_VERSION_TLS_1_1 = 5, + TLS_VERSION_TLS_1_2 = 6, + TLS_VERSION_TLS_1_3 = 7 +} TLSConfigVersion; + +/** + * \brief Convert TLS version number to string + * + * \param version TLS version number + * + * \return the TLS version as null terminated string + */ +PAL_API const char* +TLSConfigVersion_toString(TLSConfigVersion version); + typedef enum { TLS_SEC_EVT_INFO = 0, TLS_SEC_EVT_WARNING = 1, TLS_SEC_EVT_INCIDENT = 2 -} TLSConfiguration_EventLevel; +} TLSEventLevel; #define TLS_EVENT_CODE_ALM_ALGO_NOT_SUPPORTED 1 #define TLS_EVENT_CODE_ALM_UNSECURE_COMMUNICATION 2 @@ -69,12 +88,50 @@ typedef enum { #define TLS_EVENT_CODE_ALM_CERT_EXPIRED 11 #define TLS_EVENT_CODE_ALM_CERT_REVOKED 12 #define TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED 13 -#define TLS_EVENT_CODE_ALM_CERT_NOT_TRUSTED 12 +#define TLS_EVENT_CODE_ALM_CERT_NOT_TRUSTED 14 +#define TLS_EVENT_CODE_ALM_NO_CIPHER 15 + +typedef struct sTLSConnection* TLSConnection; + +/** + * \brief Get the peer address of the TLS connection + * + * \param self the TLS connection instance + * \param peerAddrBuf user provided buffer that can hold at least 60 characters, or NULL to allow the function to allocate the memory for the buffer + * + * \returns peer address:port as null terminated string + */ +PAL_API char* +TLSConnection_getPeerAddress(TLSConnection self, char* peerAddrBuf); + +/** + * \brief Get the TLS certificate used by the peer + * + * \param self the TLS connection instance + * \param certSize[OUT] the certificate size in bytes + * + * \return address of the certificate buffer + */ +PAL_API uint8_t* +TLSConnection_getPeerCertificate(TLSConnection self, int* certSize); -typedef void (*TLSConfiguration_EventHandler)(void* parameter, TLSConfiguration_EventLevel eventLevel, int eventCode, const char* message); +/** + * \brief Get the TLS version used by the connection + * + * \param self the TLS connection instance + * + * \return TLS version + */ +PAL_API TLSConfigVersion +TLSConnection_getTLSVersion(TLSConnection self); + +typedef void (*TLSConfiguration_EventHandler)(void* parameter, TLSEventLevel eventLevel, int eventCode, const char* message, TLSConnection con); /** * \brief Set the security event handler + * + * \param handler the security event callback handler + * \param parameter user provided parameter to be passed to the callback handler */ PAL_API void TLSConfiguration_setEventHandler(TLSConfiguration self, TLSConfiguration_EventHandler handler, void* parameter); @@ -209,15 +266,6 @@ TLSConfiguration_addCACertificateFromFile(TLSConfiguration self, const char* fil PAL_API void TLSConfiguration_setRenegotiationTime(TLSConfiguration self, int timeInMs); -typedef enum { - TLS_VERSION_NOT_SELECTED = 0, - TLS_VERSION_SSL_3_0 = 3, - TLS_VERSION_TLS_1_0 = 4, - TLS_VERSION_TLS_1_1 = 5, - TLS_VERSION_TLS_1_2 = 6, - TLS_VERSION_TLS_1_3 = 7 -} TLSConfigVersion; - /** * \brief Set minimal allowed TLS version to use */ @@ -249,6 +297,12 @@ TLSConfiguration_addCRL(TLSConfiguration self, uint8_t* crl, int crlLen); PAL_API bool TLSConfiguration_addCRLFromFile(TLSConfiguration self, const char* filename); +/** + * \brief Removes any CRL (certificate revocation list) currently in use + */ +PAL_API void +TLSConfiguration_resetCRL(TLSConfiguration self); + /** * Release all resource allocated by the TLSConfiguration instance * diff --git a/hal/memory/lib_memory.c b/hal/memory/lib_memory.c index adeb105a..74fde5b4 100644 --- a/hal/memory/lib_memory.c +++ b/hal/memory/lib_memory.c @@ -38,7 +38,6 @@ Memory_malloc(size_t size) return memory; } - void* Memory_calloc(size_t nmemb, size_t size) { @@ -50,7 +49,6 @@ Memory_calloc(size_t nmemb, size_t size) return memory; } - void * Memory_realloc(void *ptr, size_t size) { @@ -67,4 +65,3 @@ Memory_free(void* memb) { free(memb); } - diff --git a/hal/tls/mbedtls/tls_mbedtls.c b/hal/tls/mbedtls/tls_mbedtls.c index 65c96538..e2f152d0 100644 --- a/hal/tls/mbedtls/tls_mbedtls.c +++ b/hal/tls/mbedtls/tls_mbedtls.c @@ -105,10 +105,10 @@ struct sTLSSocket { }; static void -raiseSecurityEvent(TLSConfiguration config, TLSConfiguration_EventLevel eventCategory, int eventCode, const char* message) +raiseSecurityEvent(TLSConfiguration config, TLSEventLevel eventCategory, int eventCode, const char* message, TLSSocket socket) { if (config->eventHandler) { - config->eventHandler(config->eventHandlerParameter, eventCategory, eventCode, message); + config->eventHandler(config->eventHandlerParameter, eventCategory, eventCode, message, (TLSConnection)socket); } } @@ -174,7 +174,7 @@ verifyCertificate (void* parameter, mbedtls_x509_crt *crt, int certificate_depth if (certMatches) *flags = 0; else { - raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED, "Incident: Certificate not configured"); + raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED, "Alarm: certificate validation: trusted individual certificate not available", self); *flags |= MBEDTLS_X509_BADCERT_OTHER; return 1; @@ -214,7 +214,7 @@ TLSConfiguration_setupComplete(TLSConfiguration self) int ret = mbedtls_ssl_conf_own_cert( &(self->conf), &(self->ownCertificate), &(self->ownKey)); if (ret != 0) { - DEBUG_PRINT("TLS", "mbedtls_ssl_conf_own_cert returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_ssl_conf_own_cert returned -0x%x\n", -ret); return false; } } @@ -265,7 +265,7 @@ TLSConfiguration_create() mbedtls_ssl_conf_authmode(&(self->conf), MBEDTLS_SSL_VERIFY_REQUIRED); - mbedtls_ssl_conf_renegotiation(&(self->conf), MBEDTLS_SSL_RENEGOTIATION_ENABLED); + mbedtls_ssl_conf_renegotiation(&(self->conf), MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION); /* static int hashes[] = {3,4,5,6,7,8,0}; */ /* mbedtls_ssl_conf_sig_hashes(&(self->conf), hashes); */ @@ -349,7 +349,7 @@ TLSConfiguration_setOwnCertificate(TLSConfiguration self, uint8_t* certificate, int ret = mbedtls_x509_crt_parse(&(self->ownCertificate), certificate, certLen); if (ret != 0) - DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse returned -0x%x\n", -ret); return (ret == 0); } @@ -360,7 +360,7 @@ TLSConfiguration_setOwnCertificateFromFile(TLSConfiguration self, const char* fi int ret = mbedtls_x509_crt_parse_file(&(self->ownCertificate), filename); if (ret != 0) - DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse_file returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse_file returned -0x%x\n", -ret); return (ret == 0); } @@ -371,7 +371,7 @@ TLSConfiguration_setOwnKey(TLSConfiguration self, uint8_t* key, int keyLen, cons int ret = mbedtls_pk_parse_key(&(self->ownKey), key, keyLen, (const uint8_t*) keyPassword, (keyPassword == NULL) ? 0 : strlen(keyPassword)); if (ret != 0) - DEBUG_PRINT("TLS", "mbedtls_pk_parse_key returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_pk_parse_key returned -0x%x\n", -ret); return (ret == 0); } @@ -382,7 +382,7 @@ TLSConfiguration_setOwnKeyFromFile(TLSConfiguration self, const char* filename, int ret = mbedtls_pk_parse_keyfile(&(self->ownKey), filename, keyPassword); if (ret != 0) - DEBUG_PRINT("TLS", "mbedtls_pk_parse_keyfile returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_pk_parse_keyfile returned -0x%x\n", -ret); return (ret == 0); } @@ -427,7 +427,7 @@ TLSConfiguration_addCACertificate(TLSConfiguration self, uint8_t* certificate, i int ret = mbedtls_x509_crt_parse(&(self->cacerts), certificate, certLen); if (ret != 0) { - DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse returned -0x%x\n", -ret); return false; } @@ -440,21 +440,41 @@ TLSConfiguration_addCACertificateFromFile(TLSConfiguration self, const char* fil int ret = mbedtls_x509_crt_parse_file(&(self->cacerts), filename); if (ret != 0) - DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_x509_crt_parse returned -0x%x\n", -ret); return (ret == 0); } +static void +udpatedCRL(TLSConfiguration self) +{ + self->crlUpdated = Hal_getTimeInMs(); + + /* We need to clean-up resumption cache (if enabled) to make sure we renegotiate as CRL may have changed data */ + if (self->useSessionResumption == false) + return; + + if (self->conf.endpoint == MBEDTLS_SSL_IS_SERVER) + { + mbedtls_ssl_cache_entry *cur = self->cache.chain; + + while (cur) { + cur->timestamp = 0; + cur = cur->next; + } + } +} + bool TLSConfiguration_addCRL(TLSConfiguration self, uint8_t* crl, int crlLen) { int ret = mbedtls_x509_crl_parse(&(self->crl), crl, crlLen); if (ret != 0) { - DEBUG_PRINT("TLS", "mbedtls_x509_crl_parse returned %d\n", ret); + DEBUG_PRINT("TLS", "mbedtls_x509_crl_parse returned -0x%x\n", -ret); } else { - self->crlUpdated = Hal_getTimeInMs(); + udpatedCRL(self); } return (ret == 0); @@ -469,12 +489,20 @@ TLSConfiguration_addCRLFromFile(TLSConfiguration self, const char* filename) DEBUG_PRINT("TLS", "mbedtls_x509_crl_parse_file returned %d\n", ret); } else { - self->crlUpdated = Hal_getTimeInMs(); + udpatedCRL(self); } return (ret == 0); } +void +TLSConfiguration_resetCRL(TLSConfiguration self) +{ + mbedtls_x509_crl_free(&(self->crl)); + mbedtls_x509_crl_init(&(self->crl)); + self->crlUpdated = Hal_getTimeInMs(); +} + void TLSConfiguration_setRenegotiationTime(TLSConfiguration self, int timeInMs) { @@ -518,63 +546,72 @@ TLSConfiguration_destroy(TLSConfiguration self) } static void -createSecurityEvents(TLSConfiguration config, int ret, uint32_t flags) +createSecurityEvents(TLSConfiguration config, int ret, uint32_t flags, TLSSocket socket) { if (config->eventHandler == NULL) return; switch (ret) { + case MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG: + raiseSecurityEvent(config, TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_NO_CIPHER, "Alarm: Algorithm not supported", socket); + break; + + case MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN: + raiseSecurityEvent(config, TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_NO_CIPHER, "Alarm: no matching TLS ciphers", socket); + break; case MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE: - raiseSecurityEvent(config, TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_ALGO_NOT_SUPPORTED, "Incident: Algorithm not supported"); + raiseSecurityEvent(config, TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_ALGO_NOT_SUPPORTED, "Alarm: Algorithm not supported", socket); break; case MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION: - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_UNSECURE_COMMUNICATION, "Incident: Unsecure communication"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_UNSECURE_COMMUNICATION, "Alarm: Unsecure communication", socket); break; case MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE: - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_UNAVAILABLE, "Incident: Certificate unavailable"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_UNAVAILABLE, "Alarm: certificate unavailable", socket); break; case MBEDTLS_ERR_SSL_BAD_HS_CERTIFICATE: - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_BAD_CERT, "Incident: Bad certificate"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_BAD_CERT, "Alarm: Bad certificate", socket); break; case MBEDTLS_ERR_SSL_CERTIFICATE_TOO_LARGE: - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_SIZE_EXCEEDED, "Incident: TLS certificate size exceeded"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_SIZE_EXCEEDED, "Alarm: TLS certificate size exceeded", socket); break; case MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED: - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_VALIDATION_FAILED, "Incident: certificate validation: certificate signature could not be validated"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_VALIDATION_FAILED, "Alarm: certificate validation: certificate signature could not be validated", socket); break; case MBEDTLS_ERR_SSL_CERTIFICATE_REQUIRED: - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_REQUIRED, "Incident: Certificate required"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_REQUIRED, "Alarm: Certificate required", socket); break; case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: { if (flags & MBEDTLS_X509_BADCERT_EXPIRED) { - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_EXPIRED, "Incident: Certificate expired"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_EXPIRED, "Alarm: expired certificate", socket); } else if (flags & MBEDTLS_X509_BADCERT_REVOKED) { - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_REVOKED, "Incident: Certificate revoked"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_REVOKED, "Alarm: revoked certificate", socket); } else if (flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED) { - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_NOT_TRUSTED, "Incident: Certificate validation: CA certificate not available"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_NOT_TRUSTED, "Alarm: Certificate validation: CA certificate not available", socket); } else if (flags & MBEDTLS_X509_BADCERT_OTHER) { - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED, "Incident: Certificate not configured"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED, "Alarm: Certificate not configured", socket); } - else { - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_VALIDATION_FAILED, "Incident: Certificate verification failed"); + else if (flags & MBEDTLS_X509_BADCERT_BAD_KEY) { + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED, "Alarm: Insufficient key length", socket); } + + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_CERT_VALIDATION_FAILED, "Alarm: Certificate verification failed", socket); } break; default: - raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_HANDSHAKE_FAILED_UNKNOWN_REASON, "Incident: handshake failed for unknown reason"); + raiseSecurityEvent(config,TLS_SEC_EVT_INCIDENT, TLS_EVENT_CODE_ALM_HANDSHAKE_FAILED_UNKNOWN_REASON, "Alarm: handshake failed for unknown reason", socket); break; } } @@ -749,11 +786,11 @@ TLSSocket_create(Socket socket, TLSConfiguration configuration, bool storeClient { if( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { - DEBUG_PRINT("TLS", "handshake failed - mbedtls_ssl_handshake --> %d\n\n", ret ); + DEBUG_PRINT("TLS", "handshake failed - mbedtls_ssl_handshake returned -0x%x\n", -ret ); uint32_t flags = mbedtls_ssl_get_verify_result(&(self->ssl)); - createSecurityEvents(configuration, ret, flags); + createSecurityEvents(configuration, ret, flags, self); mbedtls_ssl_free(&(self->ssl)); @@ -793,7 +830,7 @@ TLSSocket_create(Socket socket, TLSConfiguration configuration, bool storeClient self->lastRenegotiationTime = Hal_getTimeInMs(); if (getTLSVersion(self->ssl.major_ver, self->ssl.minor_ver) < TLS_VERSION_TLS_1_2) { - raiseSecurityEvent(configuration, TLS_SEC_EVT_WARNING, TLS_EVENT_CODE_WRN_INSECURE_TLS_VERSION, "Warning: Insecure TLS version"); + raiseSecurityEvent(configuration, TLS_SEC_EVT_WARNING, TLS_EVENT_CODE_WRN_INSECURE_TLS_VERSION, "Warning: Insecure TLS version", self); } } @@ -817,7 +854,7 @@ TLSSocket_performHandshake(TLSSocket self) if (ret == 0) { if (getTLSVersion(self->ssl.major_ver, self->ssl.minor_ver) < TLS_VERSION_TLS_1_2) { - raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_WARNING, TLS_EVENT_CODE_WRN_INSECURE_TLS_VERSION, "Warning: Insecure TLS version"); + raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_WARNING, TLS_EVENT_CODE_WRN_INSECURE_TLS_VERSION, "Warning: Insecure TLS version", self); } return true; @@ -828,38 +865,59 @@ TLSSocket_performHandshake(TLSSocket self) if (self->tlsConfig->eventHandler) { uint32_t flags = mbedtls_ssl_get_verify_result(&(self->ssl)); - createSecurityEvents(self->tlsConfig, ret, flags); + createSecurityEvents(self->tlsConfig, ret, flags, self); } return false; } } -int -TLSSocket_read(TLSSocket self, uint8_t* buf, int size) +static void +checkForCRLUpdate(TLSSocket self) { - if (self->crlUpdated != self->tlsConfig->crlUpdated) { - DEBUG_PRINT("TLS", "CRL updated -> refresh CA chain\n"); + if (self->crlUpdated == self->tlsConfig->crlUpdated) + return; + + DEBUG_PRINT("TLS", "CRL updated -> refresh CA chain\n"); + + mbedtls_ssl_conf_ca_chain( &(self->conf), &( self->tlsConfig->cacerts), &( self->tlsConfig->crl) ); - mbedtls_ssl_conf_ca_chain( &(self->conf), &( self->tlsConfig->cacerts), &( self->tlsConfig->crl) ); + self->crlUpdated = self->tlsConfig->crlUpdated; + + /* IEC TS 62351-100-3 Conformance test 6.2.6 requires that upon CRL update a TLS renegotiation should occur */ + self->lastRenegotiationTime = 0; +} - self->crlUpdated = self->tlsConfig->crlUpdated; +/* true = renegotiation is not needed or it is successfull, false = Failed */ +static bool +startRenegotiationIfRequired(TLSSocket self) +{ + if (self->tlsConfig->renegotiationTimeInMs <= 0) + return true; + + if (Hal_getTimeInMs() <= self->lastRenegotiationTime + self->tlsConfig->renegotiationTimeInMs) + return true; + + raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INFO, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "Info: session renegotiation started", self); + + if (TLSSocket_performHandshake(self) == false) { + DEBUG_PRINT("TLS", " renegotiation failed\n"); + return false; } - if (self->tlsConfig->renegotiationTimeInMs > 0) { - if (Hal_getTimeInMs() > self->lastRenegotiationTime + self->tlsConfig->renegotiationTimeInMs) { + DEBUG_PRINT("TLS", " started renegotiation\n"); + self->lastRenegotiationTime = Hal_getTimeInMs(); + + return true; +} - raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INFO, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "Info: session renegotiation started"); +int +TLSSocket_read(TLSSocket self, uint8_t* buf, int size) +{ + checkForCRLUpdate(self); - if (TLSSocket_performHandshake(self) == false) { - DEBUG_PRINT("TLS", " renegotiation failed\n"); - return -1; - } - else { - DEBUG_PRINT("TLS", " started renegotiation\n"); - self->lastRenegotiationTime = Hal_getTimeInMs(); - } - } + if (startRenegotiationIfRequired(self) == false) { + return -1; } int ret = mbedtls_ssl_read(&(self->ssl), buf, size); @@ -885,7 +943,7 @@ TLSSocket_read(TLSSocket self, uint8_t* buf, int size) { uint32_t flags = mbedtls_ssl_get_verify_result(&(self->ssl)); - createSecurityEvents(self->tlsConfig, ret, flags); + createSecurityEvents(self->tlsConfig, ret, flags, self); } return -1; @@ -901,28 +959,10 @@ TLSSocket_write(TLSSocket self, uint8_t* buf, int size) int ret; int len = size; - if (self->crlUpdated != self->tlsConfig->crlUpdated) { - DEBUG_PRINT("TLS", "CRL updated -> refresh CA chain\n"); - - mbedtls_ssl_conf_ca_chain( &(self->conf), &( self->tlsConfig->cacerts), &( self->tlsConfig->crl) ); + checkForCRLUpdate(self); - self->crlUpdated = self->tlsConfig->crlUpdated; - } - - if (self->tlsConfig->renegotiationTimeInMs > 0) { - if (Hal_getTimeInMs() > self->lastRenegotiationTime + self->tlsConfig->renegotiationTimeInMs) { - - raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INFO, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "Info: session renegotiation started"); - - if (TLSSocket_performHandshake(self) == false) { - DEBUG_PRINT("TLS", " renegotiation failed\n"); - return -1; - } - else { - DEBUG_PRINT("TLS", " started renegotiation\n"); - self->lastRenegotiationTime = Hal_getTimeInMs(); - } - } + if (startRenegotiationIfRequired(self) == false) { + return -1; } while ((ret = mbedtls_ssl_write(&(self->ssl), buf, len)) <= 0) @@ -956,7 +996,7 @@ TLSSocket_close(TLSSocket self) { 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 -0x%x\n", -ret); break; } } @@ -970,3 +1010,54 @@ TLSSocket_close(TLSSocket self) GLOBAL_FREEMEM(self); } + +char* +TLSConnection_getPeerAddress(TLSConnection self, char* peerAddrBuf) +{ + TLSSocket socket = (TLSSocket)self; + + if (peerAddrBuf == NULL) { + peerAddrBuf = (char*)GLOBAL_MALLOC(61); + } + + if (peerAddrBuf) + return Socket_getPeerAddressStatic(socket->socket, peerAddrBuf); + else + return NULL; +} + +uint8_t* +TLSConnection_getPeerCertificate(TLSConnection self, int* certSize) +{ + TLSSocket socket = (TLSSocket)self; + + return TLSSocket_getPeerCertificate(socket, certSize); +} + +TLSConfigVersion +TLSConnection_getTLSVersion(TLSConnection self) +{ + TLSSocket socket = (TLSSocket)self; + + return getTLSVersion(socket->ssl.major_ver, socket->ssl.minor_ver); +} + +const char* +TLSConfigVersion_toString(TLSConfigVersion version) +{ + switch (version) + { + case TLS_VERSION_SSL_3_0: + return "SSL 3.0"; + case TLS_VERSION_TLS_1_0: + return "TLS 1.0"; + case TLS_VERSION_TLS_1_1: + return "TLS 1.1"; + case TLS_VERSION_TLS_1_2: + return "TLS 1.2"; + case TLS_VERSION_TLS_1_3: + return "TLS 1.3"; + default: + return "unknown TLS version"; + } +} diff --git a/pyiec61850/examples/rcbSubscriptionExample.py b/pyiec61850/examples/rcbSubscriptionExample.py new file mode 100644 index 00000000..d4d05aa5 --- /dev/null +++ b/pyiec61850/examples/rcbSubscriptionExample.py @@ -0,0 +1,260 @@ +#!/usr/bin/python + +''' +Example of RCB subscription, with the Python wrapper + +This example is intended to be used with server_example_basic_io. + +Usage: + 'sudo python3 ./pyiec61850/examples/rcbSubscriptionExample.py' + or + 'sudo python3 ./pyiec61850/examples/rcbSubscriptionExample.py localhost 102' + or + 'python3 ./pyiec61850/examples/rcbSubscriptionExample.py localhost 8102' + + +Swig generates 2 wrapped objects: + - a generic RCB handler + - a generic RCB subscriber + +The user needs to: + - create his specific RCB handler in Python, + for processing the received RCB as he wants + - create his specific RCB subscriber in Python, + with his registering parameters + - connect his handler to his subscriber with a composition relationship +''' + +import time +import sys +import iec61850 + + +def open_connection(ip_address, mms_port): + '''Open the connection with the IED''' + l_connection = iec61850.IedConnection_create() + l_error = iec61850.IedConnection_connect(l_connection, ip_address, mms_port) + return l_error, l_connection + + +def close_connection(i_connection): + '''Close the connection with the IED''' + iec61850.IedConnection_close(i_connection) + + +def destroy_connection(i_connection): + '''Destroy (free) the Connection object''' + iec61850.IedConnection_destroy(i_connection) + + +class PyRCBHandler(iec61850.RCBHandler): + '''Class processing the received RCB''' + + def __init__(self): + iec61850.RCBHandler.__init__(self) + + def trigger(self): + '''Method for triggering the handler and processing the last received RCB. + In these example, we only print some attributs and data of the RCB''' + + # the following section is the application part of the Swig C subthread: + # we must catch the Python exceptions, otherwise it will crash. + try: + l_rcb_ref = iec61850.ClientReport_getRcbReference(self._client_report) + print(f"\nNew received RCB: {l_rcb_ref}") + + if iec61850.ClientReport_hasDataSetName(self._client_report): + l_dataset_name = iec61850.ClientReport_getDataSetName(self._client_report) + print(f"\tDataSet name: {l_dataset_name}") + + print(f"\tReport id: {iec61850.ClientReport_getRptId(self._client_report)}") + + if iec61850.ClientReport_hasSeqNum(self._client_report): + l_seq_num = iec61850.ClientReport_getSeqNum(self._client_report) + print(f"\tSequence num: {l_seq_num}") + + if iec61850.ClientReport_hasSubSeqNum(self._client_report): + l_sub_seq_num = iec61850.ClientReport_getSubSeqNum(self._client_report) + print(f"\tSub-sequence num: {l_sub_seq_num}") + + if iec61850.ClientReport_hasTimestamp(self._client_report): + l_timestamp_millisec =iec61850.ClientReport_getTimestamp(self._client_report) + print(f"\tTimestamp in millsec: {l_timestamp_millisec}") + + mms_value_array = iec61850.ClientReport_getDataSetValues(self._client_report) + mms_value_array_size = iec61850.MmsValue_getArraySize(mms_value_array) + print(f"\tDataSet size: {mms_value_array_size}") + + for mms_value_index in range(mms_value_array_size): + mms_value = iec61850.MmsValue_getElement(mms_value_array, mms_value_index) + mms_value_type = iec61850.MmsValue_getTypeString(mms_value) + + if mms_value_type == "boolean": + print(f"\tMMS value: {iec61850.MmsValue_getBoolean(mms_value)}") + elif mms_value_type == "float": + print(f"\tMMS value: {iec61850.MmsValue_toFloat(mms_value)}") + else: + print("\tMMS value: other type") + + l_reason = iec61850.ClientReport_getReasonForInclusion(self._client_report, + mms_value_index) + l_reason_str = iec61850.ReasonForInclusion_getValueAsString(l_reason) + print(f"\tReason for inclusion: {l_reason_str}") + + except RuntimeError as l_error: + print(f"Runtime Error (in subscriber thread): {l_error}") + except AssertionError as l_error: + print(f"Assertion Error (in subscriber thread): {l_error}") + except Exception as l_exception: + print(f"Exception (in subscriber thread): {l_exception}") + + +class PyRCBSubscriber: + '''Class representing a RCB subscriber in Python, + and that owns the RCB handler in Python for processing the received RCB''' + + def __init__(self): + self._libiec61850_rcb_client = None + self._internal_rcb_handler = PyRCBHandler() + self._wrapped_rcb_subscriber = iec61850.RCBSubscriber() # generic RCB subscriber + self._connection = None # do not destroy it + self._libiec61850_error_code = iec61850.IED_ERROR_OK + + def __del__(self): + if self._connection is not None: + self.destroy() + + + def subscribe(self, i_connection, i_report_control_block_ref): + '''Select the subscription parameters and create the RCB subscription''' + + # preconditions + assert iec61850.IedConnection_getState(i_connection) == iec61850.IED_STATE_CONNECTED, \ + "error: Not connected" + assert i_report_control_block_ref, "error: the reference of the ReportControlBlock is empty" + assert self._libiec61850_rcb_client is None, "error: the RCB client is already created" + + self._connection = i_connection + + # Like the usual RCB subscription with the C API: + # read data set + print(f"RCBSubscription: create subscription for: '{i_report_control_block_ref}'") + l_return_value = iec61850.pyWrap_IedConnection_getRCBValues(self._connection, + i_report_control_block_ref, + None) + if isinstance(l_return_value, int): + self._libiec61850_error_code = l_return_value + else: + [self._libiec61850_rcb_client, self._libiec61850_error_code] = l_return_value + + if self._libiec61850_error_code != iec61850.IED_ERROR_OK: + raise RuntimeError('IEC61850 error') + + + # Specific instructions with Python: + # Initialize the generic and wrapped 'subscriber' + l_client_report_control_block_id = \ + iec61850.ClientReportControlBlock_getRptId(self._libiec61850_rcb_client) + self._wrapped_rcb_subscriber.setIedConnection(self._connection) + self._wrapped_rcb_subscriber.setRcbReference(i_report_control_block_ref) + self._wrapped_rcb_subscriber.setRcbRptId(l_client_report_control_block_id) + + # Specific instructions with Python: + # Connect the specific callback/handler + self._internal_rcb_handler.thisown = 0 # the following subscriber will be the owner of this handler + self._wrapped_rcb_subscriber.setEventHandler(self._internal_rcb_handler) + + + # Like the usual RCB subscription, same feature but with a specific Python object + # Install handler for reports + l_registering_status = self._wrapped_rcb_subscriber.subscribe() + assert l_registering_status is True, "Error: Failed to register the RCBSubscriber" + + # Like the usual RCB subscription with the C API: + # Set trigger options and enable report + l_trigger_options = iec61850.TRG_OPT_DATA_UPDATE | \ + iec61850.TRG_OPT_INTEGRITY | \ + iec61850.TRG_OPT_GI | \ + iec61850.TRG_OPT_DATA_CHANGED | \ + iec61850.TRG_OPT_QUALITY_CHANGED + l_rcb_attributes = iec61850.RCB_ELEMENT_RPT_ENA | iec61850.RCB_ELEMENT_TRG_OPS + + iec61850.ClientReportControlBlock_setTrgOps(self._libiec61850_rcb_client, + l_trigger_options) + + iec61850.ClientReportControlBlock_setRptEna(self._libiec61850_rcb_client, True) + + self._libiec61850_error_code = \ + iec61850.pyWrap_IedConnection_setRCBValues(self._connection, + self._libiec61850_rcb_client, + l_rcb_attributes, + True) + + # Check subscription status + if self._libiec61850_error_code != iec61850.IED_ERROR_OK: + raise RuntimeError('IEC61850 error') + + + def destroy(self): + '''Stop the RCB subscription and destroy the internal objects''' + + if self._libiec61850_rcb_client and \ + iec61850.IedConnection_getState(self._connection) == iec61850.IED_STATE_CONNECTED: + # Disable reporting + iec61850.ClientReportControlBlock_setRptEna(self._libiec61850_rcb_client, False) + self._libiec61850_error_code = \ + iec61850.pyWrap_IedConnection_setRCBValues(self._connection, + self._libiec61850_rcb_client, + iec61850.RCB_ELEMENT_RPT_ENA, + True) + # Check the 'disable reporting' command result + if self._libiec61850_error_code != iec61850.IED_ERROR_OK: + raise RuntimeError('IEC61850 error') + + # Destroy the libiec61850 objects + if self._libiec61850_rcb_client: + iec61850.ClientReportControlBlock_destroy(self._libiec61850_rcb_client) + self._libiec61850_rcb_client = None + + # Destroy the RCB subscriber + if self._wrapped_rcb_subscriber: + del self._wrapped_rcb_subscriber + self._wrapped_rcb_subscriber = None + + +# MAIN +L_RCBREF_STATUS = "simpleIOGenericIO/LLN0.RP.EventsRCB01" +L_RCBREF_MEASUREMENTS = "simpleIOGenericIO/LLN0.BR.Measurements01" + +HOSTNAME = "localhost" +PORT = 102 +if len(sys.argv) > 1: + HOSTNAME = sys.argv[1] +if len(sys.argv) > 2: + PORT = int(sys.argv[2]) + +[error, con] = open_connection(HOSTNAME, PORT) + +if error == iec61850.IED_ERROR_OK: + try: + rcb_subscriber_1 = PyRCBSubscriber() + rcb_subscriber_1.subscribe(con, L_RCBREF_STATUS) + + rcb_subscriber_2 = PyRCBSubscriber() + rcb_subscriber_2.subscribe(con, L_RCBREF_MEASUREMENTS) + + time.sleep(3) + + rcb_subscriber_1.destroy() + rcb_subscriber_2.destroy() + + except RuntimeError as caught_exception: + print(f"exception: {caught_exception}") + except AssertionError as caught_exception: + print(f"exception: {caught_exception}") + + close_connection(con) +else: + print("Connection error") + +destroy_connection(con) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 65b3ca1a..50e6d4c5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ if(WITH_MBEDTLS) include_directories( ${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls - ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.16/include + ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.28/include ) endif(WITH_MBEDTLS) diff --git a/src/common/string_utilities.c b/src/common/string_utilities.c index 41fd1192..abf9a04c 100644 --- a/src/common/string_utilities.c +++ b/src/common/string_utilities.c @@ -197,6 +197,9 @@ StringUtils_copyStringMax(char* dest, int maxBufferSize, const char* str1) { char* res = dest; + if (maxBufferSize < 1) + return NULL; + if (dest == NULL) res = (char*)GLOBAL_MALLOC(maxBufferSize); diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index 11b172c2..ff5101cc 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -234,13 +234,22 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) case 0x84: /* BIT STRING */ if (MmsValue_getType(value) == MMS_BIT_STRING) { int padding = buffer[bufPos]; - int bitStringLength = (8 * (elementLength - 1)) - padding; - if (bitStringLength == value->value.bitString.size) { - memcpy(value->value.bitString.buf, buffer + bufPos + 1, - elementLength - 1); + + if (padding > 7) { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: invalid bit-string (padding not plausible)\n"); + + pe = GOOSE_PARSE_ERROR_INVALID_PADDING; } else { - pe = GOOSE_PARSE_ERROR_LENGTH_MISMATCH; + int bitStringLength = (8 * (elementLength - 1)) - padding; + if (bitStringLength == value->value.bitString.size) { + memcpy(value->value.bitString.buf, buffer + bufPos + 1, + elementLength - 1); + } + else { + pe = GOOSE_PARSE_ERROR_LENGTH_MISMATCH; + } } } else { @@ -376,7 +385,7 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; } - if ( pe != GOOSE_PARSE_ERROR_NO_ERROR ) { + if (pe != GOOSE_PARSE_ERROR_NO_ERROR) { break; /* from while */ } @@ -386,14 +395,16 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) } if (elementIndex <= maxIndex) { - pe = GOOSE_PARSE_ERROR_UNDERFLOW; + if (pe == GOOSE_PARSE_ERROR_NO_ERROR) { + pe = GOOSE_PARSE_ERROR_UNDERFLOW; + } } if (DEBUG_GOOSE_SUBSCRIBER) { - switch ( pe ) { + switch (pe) { case GOOSE_PARSE_ERROR_UNKNOWN_TAG: printf("GOOSE_SUBSCRIBER: Found unkown tag %02x!\n", tag); - break; + break; case GOOSE_PARSE_ERROR_TAGDECODE: printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); break; @@ -412,6 +423,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) case GOOSE_PARSE_ERROR_LENGTH_MISMATCH: printf("GOOSE_SUBSCRIBER: Message contains value of wrong length!\n"); break; + case GOOSE_PARSE_ERROR_INVALID_PADDING: + printf("GOOSE_SUBSCRIBER: Malformed message: invalid padding!\n"); default: break; } @@ -524,7 +537,16 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt case 0x83: /* boolean */ if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found boolean\n"); - value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); + + if (elementLength > 0) { + value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); + } + else { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: invalid length for boolean\n"); + + goto exit_with_error; + } break; diff --git a/src/goose/goose_subscriber.h b/src/goose/goose_subscriber.h index 1c44158c..ca986633 100644 --- a/src/goose/goose_subscriber.h +++ b/src/goose/goose_subscriber.h @@ -47,6 +47,7 @@ typedef enum GOOSE_PARSE_ERROR_UNDERFLOW, GOOSE_PARSE_ERROR_TYPE_MISMATCH, GOOSE_PARSE_ERROR_LENGTH_MISMATCH, + GOOSE_PARSE_ERROR_INVALID_PADDING } GooseParseError; typedef struct sGooseSubscriber* GooseSubscriber; diff --git a/src/iec61850/client/client_control.c b/src/iec61850/client/client_control.c index caa07148..ee9ec9e9 100644 --- a/src/iec61850/client/client_control.c +++ b/src/iec61850/client/client_control.c @@ -29,7 +29,7 @@ #include "mms_client_connection.h" #include "ied_connection_private.h" -#if _MSC_VER +#ifdef _MSC_VER #define snprintf _snprintf #endif diff --git a/src/iec61850/client/client_report.c b/src/iec61850/client/client_report.c index fbaa74c2..af12d26a 100644 --- a/src/iec61850/client/client_report.c +++ b/src/iec61850/client/client_report.c @@ -139,7 +139,7 @@ ClientReport_getRptId(ClientReport self) ReasonForInclusion ClientReport_getReasonForInclusion(ClientReport self, int elementIndex) { - if (self->reasonForInclusion != NULL) + if ((self->reasonForInclusion != NULL) && (elementIndex < self->dataSetSize) && (elementIndex >= 0)) return self->reasonForInclusion[elementIndex]; else return IEC61850_REASON_NOT_INCLUDED; diff --git a/src/iec61850/client/client_report_control.c b/src/iec61850/client/client_report_control.c index e126d570..6affd50e 100644 --- a/src/iec61850/client/client_report_control.c +++ b/src/iec61850/client/client_report_control.c @@ -808,8 +808,9 @@ writeVariableHandler(uint32_t invokeId, void* parameter, MmsError mmsError, MmsD handler(param->originalInvokeId, call->callbackParameter, err); releaseWriteCall(self, call, param); - } + goto exit_function; + } param->currentItemId = LinkedList_getNext(param->currentItemId); @@ -829,6 +830,7 @@ writeVariableHandler(uint32_t invokeId, void* parameter, MmsError mmsError, MmsD MmsConnection_writeVariableAsync(self->connection, &(call->invokeId), &writeError, param->domainId, itemId, value, writeVariableHandler, self); if (writeError != MMS_ERROR_NONE) { + handler(param->originalInvokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(writeError)); releaseWriteCall(self, call, param); @@ -839,6 +841,10 @@ writeVariableHandler(uint32_t invokeId, void* parameter, MmsError mmsError, MmsD if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } + +exit_function: + + return; } uint32_t diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 9ab48163..bc8e9ff6 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -105,6 +105,9 @@ iedConnection_mapMmsErrorToIedError(MmsError mmsError) case MMS_ERROR_DEFINITION_OBJECT_UNDEFINED: return IED_ERROR_OBJECT_UNDEFINED; + case MMS_ERROR_ACCESS_TEMPORARILY_UNAVAILABLE: + return IED_ERROR_TEMPORARILY_UNAVAILABLE; + default: return IED_ERROR_UNKNOWN; } @@ -648,6 +651,15 @@ IedConnection_tick(IedConnection self) return MmsConnection_tick(self->connection); } +void +IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, int localPort) +{ + MmsConnection connection = self->connection; + IsoConnectionParameters isoP = MmsConnection_getIsoConnectionParameters(connection); + + IsoConnectionParameters_setLocalTcpParameters(isoP, localIpAddress, localPort); +} + void IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs) { @@ -1745,7 +1757,7 @@ IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error LinkedList_destroy(logicalDeviceNames); } else - *error = iedConnection_mapMmsErrorToIedError(mmsError); + if (error) *error = iedConnection_mapMmsErrorToIedError(mmsError); } LinkedList /**/ @@ -3294,7 +3306,18 @@ uint32_t IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, const char* dataSetReference, LinkedList /* char* */ dataSetElements, IedConnection_GenericServiceHandler handler, void* parameter) { - uint32_t invokeId = 0; + MmsError mmsError = MMS_ERROR_NONE; + + IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); + + if (call == NULL) { + *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; + goto exit_function; + } + + call->callback = handler; + call->callbackParameter = parameter; + call->invokeId = 0; char domainIdBuffer[65]; char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1]; @@ -3338,19 +3361,6 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons isAssociationSpecific = true; } - MmsError mmsError; - - IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - - if (call == NULL) { - *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; - goto exit_function; - } - - call->callback = handler; - call->callbackParameter = parameter; - call->invokeId = 0; - LinkedList dataSetEntries = LinkedList_create(); LinkedList dataSetElement = LinkedList_getNext(dataSetElements); @@ -3379,8 +3389,6 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons &mmsError, domainId, itemId, dataSetEntries, createDataSetAsyncHandler, self); } - invokeId = call->invokeId; - *error = iedConnection_mapMmsErrorToIedError(mmsError); cleanup_list: @@ -3388,7 +3396,15 @@ cleanup_list: LinkedList_destroyDeep(dataSetEntries, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy); exit_function: - return invokeId; + + if (*error != IED_ERROR_OK) { + iedConnection_releaseOutstandingCall(self, call); + + return 0; + } + else { + return call->invokeId; + } } LinkedList /* */ @@ -3513,6 +3529,8 @@ getDataSetDirectoryAsyncHandler(uint32_t invokeId, void* parameter, MmsError mms if (handler) handler(call->invokeId, call->callbackParameter, err, dataSetMembers, deletable); + + iedConnection_releaseOutstandingCall(self, call); } if (specs) @@ -3523,7 +3541,7 @@ uint32_t IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error, const char* dataSetReference, IedConnection_GetDataSetDirectoryHandler handler, void* parameter) { - uint32_t invokeId = 0; + MmsError mmsError = MMS_ERROR_NONE; IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); @@ -3578,9 +3596,6 @@ IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error isAssociationSpecific = true; } - MmsError mmsError = MMS_ERROR_NONE; - - if (isAssociationSpecific) MmsConnection_readNamedVariableListDirectoryAssociationSpecificAsync(self->connection, &(call->invokeId), &mmsError, itemId, getDataSetDirectoryAsyncHandler, self); @@ -3590,7 +3605,15 @@ IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error *error = iedConnection_mapMmsErrorToIedError(mmsError); exit_function: - return invokeId; + + if (*error != IED_ERROR_OK) { + iedConnection_releaseOutstandingCall(self, call); + + return 0; + } + else { + return call->invokeId; + } } ClientDataSet diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 852f2192..c3a174fe 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -231,6 +231,17 @@ IedConnection_createWithTlsSupport(TLSConfiguration tlsConfig); LIB61850_API void IedConnection_destroy(IedConnection self); +/** +* \brief Set the local IP address and port to be used by the client +* +* NOTE: This function is optional. When not used the OS decides what IP address and TCP port to use. +* +* \param self IedConnection instance +* \param localIpAddress the local IP address or hostname as C string +* \param localPort the local TCP port to use. When < 1 the OS will chose the TCP port to use. +*/ +LIB61850_API void +IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, int localPort); /** * \brief set the connect timeout in ms diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index c2765c45..9a364656 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -241,25 +241,6 @@ typedef enum { * @{ */ -#if (CONFIG_PROVIDE_OLD_FC_DEFINES == 1) -#define ST IEC61850_FC_ST -#define MX IEC61850_FC_MX -#define SP IEC61850_FC_SP -#define SV IEC61850_FC_SV -#define CF IEC61850_FC_CF -#define DC IEC61850_FC_DC -#define SG IEC61850_FC_SG -#define SE IEC61850_FC_SE -#define SR IEC61850_FC_SR -#define OR IEC61850_FC_OR -#define BL IEC61850_FC_BL -#define EX IEC61850_FC_EX -#define CO IEC61850_FC_CO -#define ALL IEC61850_FC_ALL -#define NONE IEC61850_FC_NONE -#endif /* (CONFIG_PROVIDE_OLD_FC_DEFINES == 1) */ - - /** FCs (Functional constraints) according to IEC 61850-7-2 */ typedef enum eFunctionalConstraint { /** Status information */ diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index 32f6ff04..693dc7f4 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -395,6 +395,12 @@ LIB61850_API LogControlBlock* LogControlBlock_create(const char* name, LogicalNode* parent, const char* dataSetName, const char* logRef, uint8_t trgOps, uint32_t intgPd, bool logEna, bool reasonCode); +LIB61850_API const char* +LogControlBlock_getName(LogControlBlock* self); + +LIB61850_API LogicalNode* +LogControlBlock_getParent(LogControlBlock* self); + /** * \brief create a log (used by the IEC 61850 log service) * diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 750f47ac..a7ea685e 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -121,44 +121,6 @@ typedef enum { IEC61850_CURRENCY = 30, IEC61850_OPTFLDS = 31, /* bit-string(10) */ IEC61850_TRGOPS = 32 /* bit-string(6) */ - - -#if (CONFIG_IEC61850_USE_COMPAT_TYPE_DECLARATIONS == 1) - , - BOOLEAN = 0,/* int */ - INT8 = 1, /* int8_t */ - INT16 = 2, /* int16_t */ - INT32 = 3, /* int32_t */ - INT64 = 4, /* int64_t */ - INT128 = 5, /* no native mapping! */ - INT8U = 6, /* uint8_t */ - INT16U = 7, /* uint16_t */ - INT24U = 8, /* uint32_t */ - INT32U = 9, /* uint32_t */ - FLOAT32 = 10, /* float */ - FLOAT64 = 11, /* double */ - ENUMERATED = 12, - OCTET_STRING_64 = 13, - OCTET_STRING_6 = 14, - OCTET_STRING_8 = 15, - VISIBLE_STRING_32 = 16, - VISIBLE_STRING_64 = 17, - VISIBLE_STRING_65 = 18, - VISIBLE_STRING_129 = 19, - VISIBLE_STRING_255 = 20, - UNICODE_STRING_255 = 21, - TIMESTAMP = 22, - QUALITY = 23, - CHECK = 24, - CODEDENUM = 25, - GENERIC_BITSTRING = 26, - CONSTRUCTED = 27, - ENTRY_TIME = 28, - PHYCOMADDR = 29, - CURRENCY = 30 - OPTFLDS = 31, - TRGOPS = 32 -#endif } DataAttributeType; typedef enum { diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index b0097e3f..3db3235e 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-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -42,6 +42,13 @@ extern "C" { #include "iso_connection_parameters.h" #include "iec61850_config_file_parser.h" +#define IEC61850_REPORTSETTINGS_RPT_ID 1 +#define IEC61850_REPORTSETTINGS_BUF_TIME 2 +#define IEC61850_REPORTSETTINGS_DATSET 4 +#define IEC61850_REPORTSETTINGS_TRG_OPS 8 +#define IEC61850_REPORTSETTINGS_OPT_FIELDS 16 +#define IEC61850_REPORTSETTINGS_INTG_PD 32 + /** * \brief Configuration object to configure IEC 61850 stack features */ @@ -99,6 +106,9 @@ struct sIedServerConfig /** integrity report start times will by synchronized with straight numbers (default: false) */ bool syncIntegrityReportTimes; + + /** for each configurable ReportSetting there is a separate flag (default: Dyn = enable write for all) */ + uint8_t reportSettingsWritable; }; /** @@ -388,6 +398,27 @@ IedServerConfig_useIntegratedGoosePublisher(IedServerConfig self, bool enable); LIB61850_API bool IedServerConfig_isLogServiceEnabled(IedServerConfig self); +/** + * \brief Make a configurable report setting writeable or read-only + * + * \note Can be used to implement some of Services\ReportSettings options + * + * \param[in] setting one of IEC61850_REPORTSETTINGS_RPT_ID, _BUF_TIME, _DATSET, _TRG_OPS, _OPT_FIELDS, _INTG_PD + * \param[in] isDyn true, when setting is writable ("Dyn") or false, when read-only + */ +LIB61850_API void +IedServerConfig_setReportSetting(IedServerConfig self, uint8_t setting, bool isDyn); + +/** + * \brief Check if a configurable report setting is writable or read-only + * + * \param[in] setting one of IEC61850_REPORTSETTINGS_RPT_ID, _BUF_TIME, _DATSET, _TRG_OPS, _OPT_FIELDS, _INTG_PD + * + * \return isDyn true, when setting is writable ("Dyn") or false, when read-only + */ +LIB61850_API bool +IedServerConfig_getReportSetting(IedServerConfig self, uint8_t setting); + /** * An opaque handle for an IED server instance */ @@ -687,6 +718,21 @@ IedServer_setGooseInterfaceIdEx(IedServer self, LogicalNode* ln, const char* gcb LIB61850_API void IedServer_useGooseVlanTag(IedServer self, LogicalNode* ln, const char* gcbName, bool useVlanTag); +/** + * \brief Set the time quality for all timestamps internally generated by this IedServer instance + * + * You can call this function during the initialization of the server or whenever a time quality + * flag has to be updated (on clock failure or change of time synchronization state). + * + * \param self the instance of IedServer to operate on. + * \param leapSecondKnown set/unset leap seconds known flag + * \param clockFailure set/unset clock failure flag + * \param clockNotSynchronized set/unset clock not synchronized flag + * \param subsecondPrecision set the subsecond precision (number of significant bits of the fractionOfSecond part of the time stamp) + */ +LIB61850_API void +IedServer_setTimeQuality(IedServer self, bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision); + /**@}*/ /** @@ -710,8 +756,6 @@ IedServer_useGooseVlanTag(IedServer self, LogicalNode* ln, const char* gcbName, LIB61850_API void IedServer_setAuthenticator(IedServer self, AcseAuthenticator authenticator, void* authenticatorParameter); - - /** * \brief get the peer address of this connection as string * @@ -770,6 +814,14 @@ typedef void (*IedConnectionIndicationHandler) (IedServer self, ClientConnection LIB61850_API void IedServer_setConnectionIndicationHandler(IedServer self, IedConnectionIndicationHandler handler, void* parameter); +/** + * \brief Ignore all requests from clients + * + * \param self the instance of IedServer to configure. + * \param enable when true all requests from clients will be ignored + */ +void +IedServer_ignoreClientRequests(IedServer self, bool enable); /**@}*/ @@ -1344,6 +1396,26 @@ ControlAction_getOrIdent(ControlAction self, int* orIdentSize); LIB61850_API int ControlAction_getCtlNum(ControlAction self); +/** + * \brief Gets the synchroCheck bit provided by the client + * + * \param self the control action instance + * + * \return the synchroCheck bit + */ +LIB61850_API bool +ControlAction_getSynchroCheck(ControlAction self); + +/** + * \brief Gets the interlockCheck bit provided by the client + * + * \param self the control action instance + * + * \return the interlockCheck bit + */ +LIB61850_API bool +ControlAction_getInterlockCheck(ControlAction self); + /** * \brief Check if the control callback is called by a select or operate command * @@ -1820,6 +1892,121 @@ typedef MmsDataAccessError LIB61850_API void IedServer_setReadAccessHandler(IedServer self, ReadAccessHandler handler, void* parameter); +/** + * \brief Callback that is called in case of RCB access to give the user the opportunity to block or allow the operation + * + * \note This callback is called before the IedServer_RCBEventHandler and only in case of operations (RCB_EVENT_GET_PARAMETER, RCB_EVENT_SET_PARAMETER, RCB_EVENT_ENABLE + * + * \param parameter user provided parameter + * \param rcb affected report control block + * \param connection client connection that is involved + * \param operation one of the following operation event types: RCB_EVENT_GET_PARAMETER, RCB_EVENT_SET_PARAMETER + */ +typedef bool +(*IedServer_RCBAccessHandler) (void* parameter, ReportControlBlock* rcb, ClientConnection connection, IedServer_RCBEventType operation); + +/** + * \brief Set a handler to control read and write access to report control blocks (RCBs) + * + * \param self the instance of IedServer to operate on. + * \param handler the event handler to be used + * \param parameter a user provided parameter that is passed to the handler. + */ +LIB61850_API void +IedServer_setRCBAccessHandler(IedServer self, IedServer_RCBAccessHandler handler, void* parameter); + +typedef enum { + LCB_EVENT_GET_PARAMETER, + LCB_EVENT_SET_PARAMETER +} IedServer_LCBEventType; + +/** + * \brief Callback that is called in case of LCB access to give the user the opportunity to block or allow the operation + * + * + * \param parameter user provided parameter + * \param lcb affected log control block + * \param connection client connection that is involved + * \param operation one of the following operation event types: LCB_EVENT_GET_PARAMETER, LCB_EVENT_SET_PARAMETER + */ +typedef bool +(*IedServer_LCBAccessHandler) (void* parameter, LogControlBlock* lcb, ClientConnection connection, IedServer_LCBEventType operation); + +/** + * \brief Set a handler to control read and write access to log control blocks (LCBs) + * + * \param self the instance of IedServer to operate on. + * \param handler the event handler to be used + * \param parameter a user provided parameter that is passed to the handler. + */ +LIB61850_API void +IedServer_setLCBAccessHandler(IedServer self, IedServer_LCBAccessHandler handler, void* parameter); + +/** + * \brief Callback that is called when the client is trying to read log data + * + * \param parameter user provided parameter + * \param logRef object reference of the log + * \param connection client connection that is involved + * + * \return true to allow read log data, false to deny + */ +typedef bool +(*IedServer_LogAccessHandler) (void* parameter, const char* logRef, ClientConnection connection); + +/** + * \brief Set a handler control access to a log (read log data) + * + * \param handler the callback handler to be used + * \param parameter a user provided parameter that is passed to the handler. + */ +LIB61850_API void +IedServer_setLogAccessHandler(IedServer self, IedServer_LogAccessHandler handler, void* parameter); + +typedef enum { + DATASET_CREATE, + DATASET_DELETE, + DATASET_READ, + DATASET_WRITE, + DATASET_GET_DIRECTORY +} IedServer_DataSetOperation; + +/** + * \brief Callback that is called when the client is calling a dataset operation (create, delete, read, write, list directory) + * + * \note This callback is called before the IedServer_RCBEventHandler and only in case of operations (RCB_EVENT_GET_PARAMETER, RCB_EVENT_SET_PARAMETER, RCB_EVENT_ENABLE + * + * \param parameter user provided parameter + * \param connection client connection that is involved + * \param operation one of the following operation types: DATASET_CREATE, DATASET_DELETE, DATASET_READ, DATASET_WRITE, DATASET_GET_DIRECTORY + * + * \return true to allow operation, false to deny operation + */ +typedef bool +(*IedServer_DataSetAccessHandler) (void* parameter, ClientConnection connection, IedServer_DataSetOperation operation, const char* datasetRef); + +/** + * \brief Set a handler to control access to a dataset (create, delete, read, write, list directory) + * + * \param handler the callback handler to be used + * \param parameter a user provided parameter that is passed to the handler. + */ +LIB61850_API void +IedServer_setDataSetAccessHandler(IedServer self, IedServer_DataSetAccessHandler handler, void* parameter); + +typedef enum { + DIRECTORY_CAT_LD_LIST, + DIRECTORY_CAT_DATA_LIST, + DIRECTORY_CAT_DATASET_LIST, + DIRECTORY_CAT_LOG_LIST +} IedServer_DirectoryCategory; + +typedef bool +(*IedServer_DirectoryAccessHandler) (void* parameter, ClientConnection connection, IedServer_DirectoryCategory category, LogicalDevice* logicalDevice); + +LIB61850_API void +IedServer_setDirectoryAccessHandler(IedServer self, IedServer_DirectoryAccessHandler handler, void* parameter); + /**@}*/ /**@}*/ diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index 14a694b1..d5555659 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-2018 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -50,6 +50,7 @@ struct sIedServer bool enableBRCBResvTms; bool enableOwnerForRCB; bool syncIntegrityReportTimes; + uint8_t rcbSettingsWritable; #endif #if (CONFIG_MMS_THREADLESS_STACK != 1) @@ -78,6 +79,8 @@ struct sIedServer uint8_t edition; + uint8_t timeQuality; /* user settable time quality for internally updated times */ + bool running; }; diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 7684739a..82ef712b 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -121,7 +121,7 @@ LIB61850_INTERNAL void Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs); LIB61850_INTERNAL MmsValue* -LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig); +LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, MmsServerConnection connection); LIB61850_INTERNAL MmsDataAccessError LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index 3e896495..51714f11 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -325,7 +325,10 @@ struct sMmsMapping { /* flag indicates if data model is locked --> prevents reports to be sent */ bool isModelLocked; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore isModelLockedMutex; +#endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ IedServer iedServer; @@ -334,6 +337,21 @@ struct sMmsMapping { IedServer_RCBEventHandler rcbEventHandler; void* rcbEventHandlerParameter; + + IedServer_RCBAccessHandler rcbAccessHandler; + void* rcbAccessHandlerParameter; + + IedServer_LCBAccessHandler lcbAccessHandler; + void* lcbAccessHandlerParameter; + + IedServer_LogAccessHandler logAccessHandler; + void* logAccessHandlerParameter; + + IedServer_DataSetAccessHandler dataSetAccessHandler; + void* dataSetAccessHandlerParameter; + + IedServer_DirectoryAccessHandler directoryAccessHandler; + void* directoryAccessHandlerParameter; }; #endif /* MMS_MAPPING_INTERNAL_H_ */ diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index a4e3433f..558343c2 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -140,7 +140,7 @@ LIB61850_INTERNAL MmsDataAccessError Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value, MmsServerConnection connection); -LIB61850_INTERNAL void +LIB61850_INTERNAL bool ReportControl_readAccess(ReportControl* rc, MmsMapping* mmsMapping, MmsServerConnection connection, char* elementName); LIB61850_INTERNAL void diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 70527812..27d40624 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-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -580,12 +580,19 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB; self->enableOwnerForRCB = serverConfiguration->enableOwnerForRCB; self->syncIntegrityReportTimes = serverConfiguration->syncIntegrityReportTimes; + self->rcbSettingsWritable = serverConfiguration->reportSettingsWritable; } else { self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->enableOwnerForRCB = false; self->syncIntegrityReportTimes = false; + self->rcbSettingsWritable = IEC61850_REPORTSETTINGS_RPT_ID + + IEC61850_REPORTSETTINGS_BUF_TIME + + IEC61850_REPORTSETTINGS_DATSET + + IEC61850_REPORTSETTINGS_TRG_OPS + + IEC61850_REPORTSETTINGS_OPT_FIELDS + + IEC61850_REPORTSETTINGS_INTG_PD; #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) self->enableBRCBResvTms = true; #else @@ -659,6 +666,7 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio } #endif + IedServer_setTimeQuality(self, true, false, false, 10); } else { IedServer_destroy(self); @@ -688,6 +696,27 @@ IedServer_setRCBEventHandler(IedServer self, IedServer_RCBEventHandler handler, self->mmsMapping->rcbEventHandlerParameter = parameter; } +void +IedServer_setRCBAccessHandler(IedServer self, IedServer_RCBAccessHandler handler, void* parameter) +{ + self->mmsMapping->rcbAccessHandler = handler; + self->mmsMapping->rcbAccessHandlerParameter = parameter; +} + +void +IedServer_setLCBAccessHandler(IedServer self, IedServer_LCBAccessHandler handler, void* parameter) +{ + self->mmsMapping->lcbAccessHandler = handler; + self->mmsMapping->lcbAccessHandlerParameter = parameter; +} + +void +IedServer_setLogAccessHandler(IedServer self, IedServer_LogAccessHandler handler, void* parameter) +{ + self->mmsMapping->logAccessHandler = handler; + self->mmsMapping->logAccessHandlerParameter = parameter; +} + void IedServer_destroy(IedServer self) { @@ -910,11 +939,15 @@ IedServer_lockDataModel(IedServer self) { MmsServer_lockModel(self->mmsServer); +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->mmsMapping->isModelLockedMutex); +#endif self->mmsMapping->isModelLocked = true; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->mmsMapping->isModelLockedMutex); +#endif } void @@ -928,13 +961,17 @@ IedServer_unlockDataModel(IedServer self) /* check if reports have to be sent! */ Reporting_processReportEventsAfterUnlock(self->mmsMapping); +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->mmsMapping->isModelLockedMutex); +#endif MmsServer_unlockModel(self->mmsServer); self->mmsMapping->isModelLocked = false; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->mmsMapping->isModelLockedMutex); +#endif } #if (CONFIG_IEC61850_CONTROL_SERVICE == 1) @@ -1479,7 +1516,7 @@ IedServer_updateUTCTimeAttributeValue(IedServer self, DataAttribute* dataAttribu #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->dataModelLock); #endif - MmsValue_setUtcTimeMs(dataAttribute->mmsValue, value); + MmsValue_setUtcTimeMsEx(dataAttribute->mmsValue, value, self->timeQuality); #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->dataModelLock); #endif @@ -1882,7 +1919,6 @@ private_IedServer_removeClientConnection(IedServer self, ClientConnection client #endif } - void IedServer_setGooseInterfaceId(IedServer self, const char* interfaceId) { @@ -1890,3 +1926,44 @@ IedServer_setGooseInterfaceId(IedServer self, const char* interfaceId) self->mmsMapping->gooseInterfaceId = StringUtils_copyString(interfaceId); #endif } + +void +IedServer_setTimeQuality(IedServer self, bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision) +{ + uint8_t timeQuality = 0; + + if (clockNotSynchronized) + timeQuality += 0x20; + + if (clockFailure) + timeQuality += 0x40; + + if (leapSecondKnown) + timeQuality += 0x80; + + timeQuality += (subsecondPrecision & 0x1f); + + self->timeQuality = timeQuality; +} + +void +IedServer_ignoreClientRequests(IedServer self, bool enable) +{ + if (self->mmsServer) { + MmsServer_ignoreClientRequests(self->mmsServer, enable); + } +} + +void +IedServer_setDataSetAccessHandler(IedServer self, IedServer_DataSetAccessHandler handler, void* parameter) +{ + self->mmsMapping->dataSetAccessHandler = handler; + self->mmsMapping->dataSetAccessHandlerParameter = parameter; +} + +void +IedServer_setDirectoryAccessHandler(IedServer self, IedServer_DirectoryAccessHandler handler, void* parameter) +{ + self->mmsMapping->directoryAccessHandler = handler; + self->mmsMapping->directoryAccessHandlerParameter = parameter; +} diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index fdc38f8e..673f58b1 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -1,7 +1,7 @@ /* * ied_server_config.c * - * Copyright 2018-2022 Michael Zillgith + * Copyright 2018-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -59,6 +59,12 @@ IedServerConfig_create() self->enableResvTmsForBRCB = true; self->enableOwnerForRCB = false; self->syncIntegrityReportTimes = false; + self->reportSettingsWritable = IEC61850_REPORTSETTINGS_RPT_ID + + IEC61850_REPORTSETTINGS_BUF_TIME + + IEC61850_REPORTSETTINGS_DATSET + + IEC61850_REPORTSETTINGS_TRG_OPS + + IEC61850_REPORTSETTINGS_OPT_FIELDS + + IEC61850_REPORTSETTINGS_INTG_PD; } return self; @@ -264,3 +270,34 @@ IedServerConfig_getSyncIntegrityReportTimes(IedServerConfig self) { return self->syncIntegrityReportTimes; } + +static void +configureSetting(IedServerConfig self, uint8_t flags, uint8_t setting, bool value) +{ + if (flags & setting) + { + if (value) { + self->reportSettingsWritable |= setting; + } + else { + self->reportSettingsWritable &= ~setting; + } + } +} + +void +IedServerConfig_setReportSetting(IedServerConfig self, uint8_t setting, bool isDyn) +{ + configureSetting(self, setting, IEC61850_REPORTSETTINGS_RPT_ID, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_BUF_TIME, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_DATSET, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_TRG_OPS, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_OPT_FIELDS, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_INTG_PD, isDyn); +} + +bool +IedServerConfig_getReportSetting(IedServerConfig self, uint8_t setting) +{ + return (self->reportSettingsWritable & setting); +} diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index dc2ae540..de2b6f89 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -258,7 +258,7 @@ copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject MmsValue_update(trkInst->ctlNum->mmsValue, controlObject->ctlNum); if (trkInst->operTm) - MmsValue_setUtcTimeMs(trkInst->operTm->mmsValue, controlObject->operateTime); + MmsValue_setUtcTimeMsEx(trkInst->operTm->mmsValue, controlObject->operateTime, self->iedServer->timeQuality); if (trkInst->respAddCause) MmsValue_update(trkInst->respAddCause->mmsValue, controlObject->addCause); @@ -394,7 +394,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, ControlObject* controlObject MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType); if (trkInst->t) - MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs()); + MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality); if (trkInst->errorCode) MmsValue_setInt32(trkInst->errorCode->mmsValue, errVal); @@ -590,9 +590,7 @@ setOpOk(ControlObject* self, bool value, uint64_t currentTimeInMs) if (self->tOpOk) { MmsValue* timestamp = self->tOpOk->mmsValue; - MmsValue_setUtcTimeMs(timestamp, currentTimeInMs); - - /* TODO update time quality */ + MmsValue_setUtcTimeMsEx(timestamp, currentTimeInMs, self->iedServer->timeQuality); } self->pendingEvents |= PENDING_EVENT_OP_OK_TRUE; @@ -862,6 +860,7 @@ executeStateMachine: MmsValue* operTm = getOperParameterOperTime(controlObject->oper); MmsValue_setUtcTime(operTm, 0); + MmsValue_setUtcTimeQuality(operTm, self->iedServer->timeQuality); } else { MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId, @@ -1660,8 +1659,6 @@ ControlObject_sendCommandTerminationNegative(ControlObject* self) MmsServerConnection_sendInformationReportListOfVariables(self->mmsConnection, &_varSpecList, &_values, false); } /* ControlObject_sendCommandTerminationNegative() */ - - void ControlObject_sendLastApplError(ControlObject* self, MmsServerConnection connection, char* ctlVariable, int error, ControlAddCause addCause, MmsValue* ctlNum, MmsValue* origin, bool handlerMode) @@ -1875,7 +1872,7 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia if (DEBUG_IED_SERVER) printf("IED_SERVER: select not applicable for control model %u\n", controlObject->ctlModel); - value = controlObject->sbo; + value = &emptyString; } } @@ -2008,7 +2005,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari } if (controlObject->ctlModel == CONTROL_MODEL_STATUS_ONLY) { - indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; + indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto free_and_return; } @@ -2035,7 +2032,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari MmsValue* test = getOperParameterTest(value); if (checkValidityOfOriginParameter(origin) == false) { - indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, ADD_CAUSE_SELECT_FAILED, ctlNum, origin, true); @@ -2142,7 +2139,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari } } else { - indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; } } else { @@ -2163,12 +2160,12 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if ((ctlVal == NULL) || (test == NULL) || (ctlNum == NULL) || (origin == NULL) || (check == NULL) || (timeParameter == NULL)) { - indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto free_and_return; } if (checkValidityOfOriginParameter(origin) == false) { - indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; ControlObject_sendLastApplError(controlObject, connection, "Oper", CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS, @@ -2224,7 +2221,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari (controlObject->testMode == testCondition) ) == false) { - indication = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; ControlObject_sendLastApplError(controlObject, connection, "Oper", CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS, @@ -2372,7 +2369,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari MmsValue* origin = getCancelParameterOrigin(value); if ((ctlNum == NULL) || (origin == NULL)) { - indication = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; if (DEBUG_IED_SERVER) printf("IED_SERVER: Invalid cancel message!\n"); goto free_and_return; @@ -2501,6 +2498,22 @@ ControlAction_getCtlNum(ControlAction self) return -1; } +bool +ControlAction_getSynchroCheck(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + return (bool)(controlObject->synchroCheck); +} + +bool +ControlAction_getInterlockCheck(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + return (bool)(controlObject->interlockCheck); +} + bool ControlAction_isSelect(ControlAction self) { diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 6fc8f7b4..c55a1ff4 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -1,7 +1,7 @@ /* * logging.c * - * Copyright 2016-2022 Michael Zillgith + * Copyright 2016-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -43,6 +43,8 @@ #if (CONFIG_IEC61850_LOG_SERVICE == 1) +static MmsValue objectAccessDenied = {MMS_DATA_ACCESS_ERROR, false, {DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED}}; + LogInstance* LogInstance_create(LogicalNode* parentLN, const char* name) { @@ -418,7 +420,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, LogControl* logControl, IEC6 MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType); if (trkInst->t) - MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs()); + MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality); if (trkInst->errorCode) MmsValue_setInt32(trkInst->errorCode->mmsValue, @@ -520,6 +522,19 @@ LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* doma if (logControl == NULL) { return DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; } + else + { + if (self->lcbAccessHandler) + { + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); + + if (self->lcbAccessHandler(self->lcbAccessHandlerParameter, logControl->logControlBlock, clientConnection, LCB_EVENT_SET_PARAMETER) == false) { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + goto exit_function; + } + } + } if (strcmp(varName, "LogEna") == 0) { bool logEna = MmsValue_getBoolean(value); @@ -699,7 +714,7 @@ exit_function: } MmsValue* -LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig) +LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, MmsServerConnection connection) { MmsValue* value = NULL; @@ -723,27 +738,41 @@ LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* varName = MmsMapping_getNextNameElement(objectName); - if (varName != NULL) + if (varName) *(varName - 1) = 0; LogControl* logControl = lookupLogControl(self, domain, lnName, objectName); - if (logControl != NULL) { + if (logControl) + { + bool allowAccess = true; + + if (self->lcbAccessHandler) + { + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); - updateLogStatusInLCB(logControl); + if (self->lcbAccessHandler(self->lcbAccessHandlerParameter, logControl->logControlBlock, clientConnection, LCB_EVENT_GET_PARAMETER) == false) { + allowAccess = false; - if (varName != NULL) { - value = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName); + value = &objectAccessDenied; + } } - else { - value = logControl->mmsValue; + + if (allowAccess) { + updateLogStatusInLCB(logControl); + + if (varName) { + value = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName); + } + else { + value = logControl->mmsValue; + } } } return value; } - static char* createDataSetReferenceForDefaultDataSet(LogControlBlock* lcb, LogControl* logControl) { diff --git a/src/iec61850/server/mms_mapping/mms_goose.c b/src/iec61850/server/mms_mapping/mms_goose.c index a5352b00..5aab2585 100644 --- a/src/iec61850/server/mms_mapping/mms_goose.c +++ b/src/iec61850/server/mms_mapping/mms_goose.c @@ -1,7 +1,7 @@ /* * mms_goose.c * - * Copyright 2013-2021 Michael Zillgith + * Copyright 2013-2022 Michael Zillgith * * This file is part of libIEC61850. * @@ -231,7 +231,7 @@ updateGenericTrackingObjectValues(MmsGooseControlBlock gc, IEC61850_ServiceType MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType); if (trkInst->t) - MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs()); + MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), gc->mmsMapping->iedServer->timeQuality); if (trkInst->errorCode) MmsValue_setInt32(trkInst->errorCode->mmsValue, diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 3247d4b1..2d76f39c 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-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -25,6 +25,7 @@ #include "mms_mapping.h" #include "mms_mapping_internal.h" #include "mms_server_internal.h" +#include "mms_value_internal.h" #include "stack_config.h" #include "mms_goose.h" @@ -68,6 +69,8 @@ typedef struct uint64_t reservationTimeout; } SettingGroup; +static MmsValue objectAccessDenied = {MMS_DATA_ACCESS_ERROR, false, {DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED}}; + #if (CONFIG_IEC61850_CONTROL_SERVICE == 1) bool @@ -136,7 +139,7 @@ createNamedVariableFromDataAttribute(DataAttribute* attribute) sizeof(MmsVariableSpecification)); namedVariable = namedVariable->typeSpec.array.elementTypeSpec; - if (attribute->firstChild && ((DataAttribute*)(attribute->firstChild))->type != IEC61850_CONSTRUCTED) { + if (attribute->type != IEC61850_CONSTRUCTED) { isBasicArray = true; } } @@ -157,7 +160,8 @@ createNamedVariableFromDataAttribute(DataAttribute* attribute) DataAttribute* subDataAttribute = (DataAttribute*) attribute->firstChild; int i = 0; - while (subDataAttribute != NULL) { + while (subDataAttribute) + { namedVariable->typeSpec.structure.elements[i] = createNamedVariableFromDataAttribute(subDataAttribute); @@ -693,7 +697,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, SettingGroupControlBlock* sg MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType); if (trkInst->t) - MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs()); + MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality); if (trkInst->errorCode) MmsValue_setInt32(trkInst->errorCode->mmsValue, @@ -840,7 +844,7 @@ MmsMapping_changeActiveSettingGroup(MmsMapping* self, SettingGroupControlBlock* MmsValue* lActTm = MmsValue_getElement(sg->sgcbMmsValues, 4); MmsValue_setUint8(actSg, sgcb->actSG); - MmsValue_setUtcTimeMs(lActTm, Hal_getTimeInMs()); + MmsValue_setUtcTimeMsEx(lActTm, Hal_getTimeInMs(), self->iedServer->timeQuality); #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) copySGCBValuesToTrackingObject(self, sgcb); @@ -2720,7 +2724,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, MmsValue* lActTm = MmsValue_getElement(sg->sgcbMmsValues, 4); MmsValue_setUint8(actSg, sg->sgcb->actSG); - MmsValue_setUtcTimeMs(lActTm, Hal_getTimeInMs()); + MmsValue_setUtcTimeMsEx(lActTm, Hal_getTimeInMs(), self->iedServer->timeQuality); } else retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; @@ -3112,7 +3116,7 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo #if (CONFIG_IEC61850_LOG_SERVICE == 1) /* LOG control block - LG */ if (isLogControlBlock(separator)) { - retValue = LIBIEC61850_LOG_SVC_readAccessControlBlock(self, domain, variableId); + retValue = LIBIEC61850_LOG_SVC_readAccessControlBlock(self, domain, variableId, connection); goto exit_function; } #endif @@ -3155,31 +3159,35 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo continue; if (strlen(rc->name) == variableIdLen) { - if (strncmp(variableId, rc->name, variableIdLen) == 0) { - + if (strncmp(variableId, rc->name, variableIdLen) == 0) + { char* elementName = MmsMapping_getNextNameElement(reportName); - ReportControl_readAccess(rc, self, connection, elementName); - MmsValue* value = NULL; + if (ReportControl_readAccess(rc, self, connection, elementName)) + { #if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_wait(rc->rcbValuesLock); + Semaphore_wait(rc->rcbValuesLock); #endif - if (elementName != NULL) - value = ReportControl_getRCBValue(rc, elementName); - else - value = rc->rcbValues; + if (elementName != NULL) + value = ReportControl_getRCBValue(rc, elementName); + else + value = rc->rcbValues; - if (value) { - value = MmsValue_clone(value); - MmsValue_setDeletableRecursive(value); - } + if (value) { + value = MmsValue_clone(value); + MmsValue_setDeletableRecursive(value); + } #if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_post(rc->rcbValuesLock); + Semaphore_post(rc->rcbValuesLock); #endif + } + else { + value = &objectAccessDenied; + } retValue = value; @@ -3221,6 +3229,46 @@ unselectControlsForConnection(MmsMapping* self, MmsServerConnection connection) } #endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */ +static bool +mmsGetNameListHandler(void* parameter, MmsGetNameListType nameListType, MmsDomain* domain, MmsServerConnection connection) +{ + MmsMapping* self = (MmsMapping*) parameter; + + bool allowAccess = true; + + if (self->directoryAccessHandler) { + + LogicalDevice* ld = NULL; + + IedServer_DirectoryCategory category = DIRECTORY_CAT_DATA_LIST; + + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); + + if (domain) { + ld = IedModel_getDevice(self->model, MmsDomain_getName(domain)); + + if (ld == NULL) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: mmsGetNameListHandler -> LD not found!\n"); + } + } + + /* convert type to category */ + if (nameListType == MMS_GETNAMELIST_DATA) + category = DIRECTORY_CAT_DATA_LIST; + else if (nameListType == MMS_GETNAMELIST_DATASETS) + category = DIRECTORY_CAT_DATASET_LIST; + else if (nameListType == MMS_GETNAMELIST_DOMAINS) + category = DIRECTORY_CAT_LD_LIST; + else if (nameListType == MMS_GETNAMELIST_JOURNALS) + category = DIRECTORY_CAT_LOG_LIST; + + allowAccess = self->directoryAccessHandler(self->directoryAccessHandlerParameter, clientConnection, category, ld); + } + + return allowAccess; +} + static void /* is called by MMS server layer and runs in the connection handling thread */ mmsConnectionHandler(void* parameter, MmsServerConnection connection, MmsServerEvent event) { @@ -3317,18 +3365,16 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS { return DATA_ACCESS_ERROR_SUCCESS; } - else { - + else + { StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) variableId, separator - variableId); LogicalNode* ln = LogicalDevice_getLogicalNode(ld, str); - if (ln != NULL) { - - + if (ln != NULL) + { char* doStart = strchr(separator + 1, '$'); - if (doStart != NULL) { char* doEnd = strchr(doStart + 1, '$'); @@ -3361,6 +3407,13 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS } } } + else { + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, + connection); + + return self->readAccessHandler(ld, ln, NULL, fc, clientConnection, + self->readAccessHandlerParameter); + } } } } @@ -3373,21 +3426,58 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS return DATA_ACCESS_ERROR_SUCCESS; } +static bool +checkDataSetAccess(MmsMapping* self, MmsServerConnection connection, MmsVariableListType listType, MmsDomain* domain, char* listName, IedServer_DataSetOperation operation) +{ + bool accessGranted = true; + + if (self->dataSetAccessHandler) { + + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); + + char dataSetRef[130]; + dataSetRef[0] = 0; + + if (listType == MMS_ASSOCIATION_SPECIFIC) { + dataSetRef[0] = '@'; + StringUtils_copyStringToBuffer(dataSetRef + 1, listName); + } + else if (listType == MMS_VMD_SPECIFIC) { + StringUtils_copyStringToBuffer(dataSetRef, listName); + } + else if (listType == MMS_DOMAIN_SPECIFIC) { + StringUtils_appendString(dataSetRef, 129, domain->domainName); + StringUtils_appendString(dataSetRef, 129, "/"); + StringUtils_appendString(dataSetRef, 129, listName); + } + + accessGranted = self->dataSetAccessHandler(self->dataSetAccessHandlerParameter, clientConnection, operation, dataSetRef); + } + + return accessGranted; +} + static MmsError -variableListChangedHandler (void* parameter, bool create, MmsVariableListType listType, MmsDomain* domain, +variableListAccessHandler (void* parameter, MmsVariableListAccessType accessType, MmsVariableListType listType, MmsDomain* domain, char* listName, MmsServerConnection connection) { MmsError allow = MMS_ERROR_NONE; - (void)connection; + MmsMapping* self = (MmsMapping*) parameter; /* TODO add log message */ #if (DEBUG_IED_SERVER == 1) - if (create) + if (accessType == MMS_VARLIST_CREATE) printf("IED_SERVER: create data set "); - else + else if (accessType == MMS_VARLIST_DELETE) printf("IED_SERVER: delete data set "); + else if (accessType == MMS_VARLIST_READ) + printf("IED_SERVER: read data set "); + else if (accessType == MMS_VARLIST_WRITE) + printf("IED_SERVER: write data set "); + else if (accessType == MMS_VARLIST_READ) + printf("IED_SERVER: get directory of data set "); switch (listType) { case MMS_VMD_SPECIFIC: @@ -3404,120 +3494,172 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li printf("specific (name=%s)\n", listName); #endif /* (DEBUG_IED_SERVER == 1) */ - MmsMapping* self = (MmsMapping*) parameter; + if (accessType == MMS_VARLIST_CREATE) { - if (create) { - if (listType == MMS_DOMAIN_SPECIFIC) { - /* check if LN exists - otherwise reject request (to fulfill test case sDsN1c) */ + if (checkDataSetAccess(self, connection, listType, domain, listName, DATASET_CREATE)) { - allow = MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT; + if (listType == MMS_DOMAIN_SPECIFIC) { + /* check if LN exists - otherwise reject request (to fulfill test case sDsN1c) */ - IedModel* model = self->model; + allow = MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT; - LogicalDevice* ld = IedModel_getDevice(model, domain->domainName); + IedModel* model = self->model; - if (ld != NULL) { + LogicalDevice* ld = IedModel_getDevice(model, domain->domainName); - char lnName[129]; + if (ld != NULL) { - char* separator = strchr(listName, '$'); + char lnName[129]; - if (separator != NULL) { - int lnNameLen = separator - listName; + char* separator = strchr(listName, '$'); - memcpy(lnName, listName, lnNameLen); - lnName[lnNameLen] = 0; + if (separator != NULL) { + int lnNameLen = separator - listName; - if (LogicalDevice_getLogicalNode(ld, lnName) != NULL) - allow = MMS_ERROR_NONE; - } + memcpy(lnName, listName, lnNameLen); + lnName[lnNameLen] = 0; - } + if (LogicalDevice_getLogicalNode(ld, lnName) != NULL) + allow = MMS_ERROR_NONE; + } + } + } + } + else { + allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; } } - else { - /* Check if data set is referenced in a report */ + else if (accessType == MMS_VARLIST_DELETE) { - LinkedList rcElement = self->reportControls; + if (checkDataSetAccess(self, connection, listType, domain, listName, DATASET_DELETE)) { + /* Check if data set is referenced in a report */ - while ((rcElement = LinkedList_getNext(rcElement)) != NULL) { - ReportControl* rc = (ReportControl*) rcElement->data; + LinkedList rcElement = self->reportControls; - if (rc->isDynamicDataSet) { - if (rc->dataSet != NULL) { + while ((rcElement = LinkedList_getNext(rcElement)) != NULL) { + ReportControl* rc = (ReportControl*) rcElement->data; - if (listType == MMS_DOMAIN_SPECIFIC) { - if (rc->dataSet->logicalDeviceName != NULL) { - if (strcmp(rc->dataSet->name, listName) == 0) { - if (strcmp(rc->dataSet->logicalDeviceName, MmsDomain_getName(domain) + strlen(self->model->name)) == 0) { - allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; - break; + if (rc->isDynamicDataSet) { + if (rc->dataSet != NULL) { + + if (listType == MMS_DOMAIN_SPECIFIC) { + if (rc->dataSet->logicalDeviceName != NULL) { + if (strcmp(rc->dataSet->name, listName) == 0) { + if (strcmp(rc->dataSet->logicalDeviceName, MmsDomain_getName(domain) + strlen(self->model->name)) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } } } } - } - else if (listType == MMS_VMD_SPECIFIC) { - if (rc->dataSet->logicalDeviceName == NULL) { - if (strcmp(rc->dataSet->name, listName) == 0) { - allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; - break; + else if (listType == MMS_VMD_SPECIFIC) { + if (rc->dataSet->logicalDeviceName == NULL) { + if (strcmp(rc->dataSet->name, listName) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } } } - } - else if (listType == MMS_ASSOCIATION_SPECIFIC) { - if (rc->dataSet->logicalDeviceName == NULL) { - if (strcmp(rc->dataSet->name, listName) == 0) { - allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; - break; + else if (listType == MMS_ASSOCIATION_SPECIFIC) { + if (rc->dataSet->logicalDeviceName == NULL) { + if (strcmp(rc->dataSet->name, listName) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } } } - } + } } } - } - #if (CONFIG_IEC61850_LOG_SERVICE == 1) - /* check if data set is referenced in a log control block*/ - LinkedList logElement = self->logControls; + /* check if data set is referenced in a log control block*/ + LinkedList logElement = self->logControls; - while ((logElement = LinkedList_getNext(logElement)) != NULL) { - LogControl* lc = (LogControl*) logElement->data; + while ((logElement = LinkedList_getNext(logElement)) != NULL) { + LogControl* lc = (LogControl*) logElement->data; - if (lc->isDynamicDataSet) { - if (lc->dataSet != NULL) { + if (lc->isDynamicDataSet) { + if (lc->dataSet != NULL) { - if (listType == MMS_DOMAIN_SPECIFIC) { - if (lc->dataSet->logicalDeviceName != NULL) { - if (strcmp(lc->dataSet->name, listName) == 0) { - if (strcmp(lc->dataSet->logicalDeviceName, MmsDomain_getName(domain) + strlen(self->model->name)) == 0) { - allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; - break; + if (listType == MMS_DOMAIN_SPECIFIC) { + if (lc->dataSet->logicalDeviceName != NULL) { + if (strcmp(lc->dataSet->name, listName) == 0) { + if (strcmp(lc->dataSet->logicalDeviceName, MmsDomain_getName(domain) + strlen(self->model->name)) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } } } } - } - else if (listType == MMS_VMD_SPECIFIC) { - if (lc->dataSet->logicalDeviceName == NULL) { - if (strcmp(lc->dataSet->name, listName) == 0) { - allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; - break; + else if (listType == MMS_VMD_SPECIFIC) { + if (lc->dataSet->logicalDeviceName == NULL) { + if (strcmp(lc->dataSet->name, listName) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } } } - } + } } } - } #endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + + } + else { + allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + } + } + else if (accessType == MMS_VARLIST_READ) + { + if (checkDataSetAccess(self, connection, listType, domain, listName, DATASET_READ) == false) { + allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + } + } + else if (accessType == MMS_VARLIST_WRITE) + { + if (checkDataSetAccess(self, connection, listType, domain, listName, DATASET_WRITE) == false) { + allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + } + } + else if (accessType == MMS_VARLIST_GET_DIRECTORY) { + if (checkDataSetAccess(self, connection, listType, domain, listName, DATASET_GET_DIRECTORY) == false) { + allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + } } return allow; } +#if (CONFIG_IEC61850_LOG_SERVICE == 1) +static bool +mmsReadJournalHandler(void* parameter, MmsDomain* domain, const char* logName, MmsServerConnection connection) +{ + bool allowAccess = true; + + MmsMapping* self = (MmsMapping*)parameter; + + if (self->logAccessHandler) { + char logReference[130]; + logReference[0] = 0; + + StringUtils_appendString(logReference, 130, MmsDomain_getName(domain)); + StringUtils_appendString(logReference, 130, "/"); + StringUtils_appendString(logReference, 130, logName); + + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); + + allowAccess = self->logAccessHandler(self->logAccessHandlerParameter, logReference, clientConnection); + } + + return allowAccess; +} +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + void MmsMapping_installHandlers(MmsMapping* self) { @@ -3525,7 +3667,12 @@ MmsMapping_installHandlers(MmsMapping* self) MmsServer_installWriteHandler(self->mmsServer, mmsWriteHandler, (void*) self); MmsServer_installReadAccessHandler(self->mmsServer, mmsReadAccessHandler, (void*) self); MmsServer_installConnectionHandler(self->mmsServer, mmsConnectionHandler, (void*) self); - MmsServer_installVariableListChangedHandler(self->mmsServer, variableListChangedHandler, (void*) self); + MmsServer_installVariableListAccessHandler(self->mmsServer, variableListAccessHandler, (void*) self); + MmsServer_installGetNameListHandler(self->mmsServer, mmsGetNameListHandler, (void*) self); + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsServer_installReadJournalHandler(self->mmsServer, mmsReadJournalHandler, (void*) self); +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ } void @@ -3716,7 +3863,9 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) { LinkedList element = self->reportControls; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->isModelLockedMutex); +#endif bool modelLocked = self->isModelLocked; @@ -3732,8 +3881,7 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) continue; break; case REPORT_CONTROL_VALUE_CHANGED: - if (((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) && - ((rc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)) + if ((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) continue; break; case REPORT_CONTROL_QUALITY_CHANGED: @@ -3754,7 +3902,9 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) Reporting_processReportEventsAfterUnlock(self); } +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->isModelLockedMutex); +#endif } #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ @@ -3775,13 +3925,17 @@ MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value) if (DataSet_isMemberValue(dataSet, value, NULL)) { MmsGooseControlBlock_setStateChangePending(gcb); +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->isModelLockedMutex); +#endif if (self->isModelLocked == false) { MmsGooseControlBlock_publishNewState(gcb); } +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->isModelLockedMutex); +#endif } } } diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index d75bf137..9cbbb4f2 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-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -579,7 +579,7 @@ updateGenericTrackingObjectValues(MmsMapping* self, ReportControl* rc, IEC61850_ MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType); if (trkInst->t) - MmsValue_setUtcTimeMs(trkInst->t->mmsValue, Hal_getTimeInMs()); + MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), self->iedServer->timeQuality); if (trkInst->errorCode) MmsValue_setInt32(trkInst->errorCode->mmsValue, @@ -673,6 +673,43 @@ createDataSetValuesShadowBuffer(ReportControl* rc) } } +static bool +checkIfClientHasAccessToDataSetEntries(MmsMapping* mapping, MmsServerConnection connection, MmsNamedVariableList mmsVariableList) +{ + bool accessAllowed = true; + + if (connection) { + + LinkedList entryElem = LinkedList_getNext(mmsVariableList->listOfVariables); + + while (entryElem) { + MmsNamedVariableListEntry entry = (MmsNamedVariableListEntry)LinkedList_getData(entryElem); + + MmsValue* entryValue = mmsServer_getValue(mapping->mmsServer, entry->domain, entry->variableName, connection, true); + + if (entryValue) { + + if (MmsValue_getType(entryValue) == MMS_DATA_ACCESS_ERROR) { + accessAllowed = false; + } + + MmsValue_deleteConditional(entryValue); + } + else { + accessAllowed = false; + } + + if (accessAllowed == false) + break; + + entryElem = LinkedList_getNext(entryElem); + } + + } + + return accessAllowed; +} + static bool updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, MmsServerConnection connection) { @@ -680,6 +717,8 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, MmsValue* dataSetValue; + bool isUsedDataSetDynamic = rc->isDynamicDataSet; + if (newDatSet != NULL) { if (strcmp(MmsValue_toString(newDatSet), "") == 0) { success = true; @@ -751,20 +790,32 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, } } - if (rc->isDynamicDataSet) { - if (rc->dataSet && dataSetChanged) { - deleteDataSetValuesShadowBuffer(rc); - MmsMapping_freeDynamicallyCreatedDataSet(rc->dataSet); - rc->isDynamicDataSet = false; - rc->dataSet = NULL; - } - } - - if (dataSetValue && dataSetChanged) { + if (dataSetValue) { const char* dataSetName = MmsValue_toString(dataSetValue); DataSet* dataSet = IedModel_lookupDataSet(mapping->model, dataSetName); + if (dataSet) { + + char domainNameBuf[130]; + + MmsMapping_getMmsDomainFromObjectReference(dataSetName, domainNameBuf); + + MmsDomain* dsDomain = MmsDevice_getDomain(mapping->mmsDevice, domainNameBuf); + + if (dsDomain) { + MmsNamedVariableList namedVariableList = MmsDomain_getNamedVariableList(dsDomain, dataSet->name); + + if (namedVariableList) { + if (checkIfClientHasAccessToDataSetEntries(mapping, connection, namedVariableList) == false) { + goto exit_function; + } + } + + } + + } + #if (MMS_DYNAMIC_DATA_SETS == 1) if (dataSet == NULL) { dataSet = MmsMapping_getDomainSpecificDataSet(mapping, dataSetName); @@ -779,19 +830,28 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, MmsNamedVariableList mmsVariableList = MmsServerConnection_getNamedVariableList(connection, dataSetName + 1); - if (mmsVariableList != NULL) + if (mmsVariableList) { + if (checkIfClientHasAccessToDataSetEntries(mapping, connection, mmsVariableList) == false) { + goto exit_function; + } + dataSet = MmsMapping_createDataSetByNamedVariableList(mapping, mmsVariableList); + } } } - } /* check for VMD specific data set */ else if (dataSetName[0] == '/') { MmsNamedVariableList mmsVariableList = MmsDevice_getNamedVariableListWithName(mapping->mmsDevice, dataSetName + 1); - if (mmsVariableList != NULL) + if (mmsVariableList) { + if (checkIfClientHasAccessToDataSetEntries(mapping, connection, mmsVariableList) == false) { + goto exit_function; + } + dataSet = MmsMapping_createDataSetByNamedVariableList(mapping, mmsVariableList); + } } } @@ -810,11 +870,20 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ - if (dataSetChanged == true) { + if (rc->dataSet && rc->dataSet != dataSet) + dataSetChanged = true; + + if (dataSetChanged) { /* delete pending event and create buffer for new data set */ deleteDataSetValuesShadowBuffer(rc); + if (isUsedDataSetDynamic) { + if (rc->dataSet) { + MmsMapping_freeDynamicallyCreatedDataSet(rc->dataSet); + } + } + rc->dataSet = dataSet; createDataSetValuesShadowBuffer(rc); @@ -830,7 +899,6 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, GLOBAL_FREEMEM(rc->inclusionFlags); rc->inclusionFlags = (uint8_t*) GLOBAL_CALLOC(dataSet->elementCount, sizeof(uint8_t)); - } success = true; @@ -862,7 +930,6 @@ createDataSetReferenceForDefaultDataSet(ReportControlBlock* rcb, ReportControl* return dataSetReference; } - static MmsValue* createOptFlds(ReportControlBlock* reportControlBlock) { @@ -1697,21 +1764,35 @@ checkReservationTimeout(MmsMapping* self, ReportControl* rc) } } -void +bool ReportControl_readAccess(ReportControl* rc, MmsMapping* mmsMapping, MmsServerConnection connection, char* elementName) { - (void)elementName; + bool accessAllowed = true; + MmsDataAccessError accessError = DATA_ACCESS_ERROR_SUCCESS; /* check reservation timeout */ if (rc->buffered) { checkReservationTimeout(mmsMapping, rc); } - if (mmsMapping->rcbEventHandler) { - ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(mmsMapping->iedServer, connection); + ClientConnection clientConnection = NULL; - mmsMapping->rcbEventHandler(mmsMapping->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_GET_PARAMETER, elementName, DATA_ACCESS_ERROR_SUCCESS); + if (mmsMapping->rcbAccessHandler || mmsMapping->rcbEventHandler) { + clientConnection = private_IedServer_getClientConnectionByHandle(mmsMapping->iedServer, connection); } + + if (mmsMapping->rcbAccessHandler) { + if (mmsMapping->rcbAccessHandler(mmsMapping->rcbAccessHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_GET_PARAMETER) == false) { + accessError = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + accessAllowed = false; + } + } + + if (mmsMapping->rcbEventHandler) { + mmsMapping->rcbEventHandler(mmsMapping->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_GET_PARAMETER, elementName, accessError); + } + + return accessAllowed; } static bool @@ -1809,6 +1890,15 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); + /* check if write access to RCB is allowed on this connection */ + if (self->rcbAccessHandler) { + if (self->rcbAccessHandler(self->rcbAccessHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_SET_PARAMETER) == false) { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + + goto exit_function_only_tracking; + } + } + /* check reservation timeout for buffered RCBs */ if (rc->buffered) { @@ -1923,6 +2013,9 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (updateReportDataset(self, rc, NULL, connection)) { if (rc->reserved == false) { + + rc->resvTms = RESV_TMS_IMPLICIT_VALUE; + reserveRcb(rc, connection); if (self->rcbEventHandler) { @@ -2102,6 +2195,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "DatSet") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_DATSET)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2149,6 +2248,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "IntgPd") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_INTG_PD)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2196,6 +2301,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "TrgOps") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_TRG_OPS)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2269,6 +2380,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme else if (strcmp(elementName, "BufTm") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_BUF_TIME)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2302,6 +2419,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "RptID") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_RPT_ID)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2409,6 +2532,14 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme goto exit_function; } + else if (strcmp(elementName, "OptFlds") == 0) { + + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_OPT_FIELDS)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + } #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); @@ -2459,7 +2590,6 @@ exit_function: } } - } else if (rc->resvTms == -1) { if (rc->reserved == false) { @@ -2491,6 +2621,8 @@ exit_function: ReportControl_unlockNotify(rc); +exit_function_only_tracking: + #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) if (rc->buffered) updateGenericTrackingObjectValues(self, rc, IEC61850_SERVICE_TYPE_SET_BRCB_VALUES, retVal); @@ -3339,6 +3471,8 @@ sendNextReportEntrySegment(ReportControl* self) MmsValue* subSeqNum = self->subSeqVal; + int numberOfAddedElements = 0; + for (i = 0; i < self->dataSet->elementCount; i++) { if ((report->flags > 0) || MmsValue_getBitStringBit(inclusionField, i)) { @@ -3420,6 +3554,8 @@ sendNextReportEntrySegment(ReportControl* self) MmsValue_setBitStringBit(self->inclusionField, i, true); + numberOfAddedElements++; + accessResultSize += elementSize; estimatedSegmentSize += elementSize; } @@ -3460,11 +3596,16 @@ sendNextReportEntrySegment(ReportControl* self) uint32_t informationReportSize = 1 + informationReportContentSize + BerEncoder_determineLengthSize(informationReportContentSize); uint32_t completeMessageSize = 1 + informationReportSize + BerEncoder_determineLengthSize(informationReportSize); - if ((int) completeMessageSize > maxMmsPduSize) { + if (((int) completeMessageSize > maxMmsPduSize) || (numberOfAddedElements == 0)) { if (DEBUG_IED_SERVER) - printf("IED_SERVER: report message too large %u (max = %i) -> skip message!\n", completeMessageSize, maxMmsPduSize); + printf("IED_SERVER: MMS PDU size too small to encode report data (max PDU size = %i) -> skip message!\n", maxMmsPduSize); - goto exit_function; + self->startIndexForNextSegment = 0; + segmented = false; + moreFollows = false; + sentSuccess = true; + + goto exit_remove_report; } /* encode the report message */ @@ -3688,6 +3829,8 @@ sendNextReportEntrySegment(ReportControl* self) self->startIndexForNextSegment = maxIndex; } +exit_remove_report: + if (segmented == false) { assert(self->reportBuffer->nextToTransmit != self->reportBuffer->nextToTransmit->next); @@ -3880,7 +4023,9 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) void Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->isModelLockedMutex); +#endif if (self->isModelLocked == false) { @@ -3897,7 +4042,9 @@ Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs) } } +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->isModelLockedMutex); +#endif } /* diff --git a/src/iec61850/server/model/cdc.c b/src/iec61850/server/model/cdc.c index 13967de6..122fa8bd 100644 --- a/src/iec61850/server/model/cdc.c +++ b/src/iec61850/server/model/cdc.c @@ -676,7 +676,7 @@ addAnalogControls(DataObject* parent, uint32_t controlOptions, bool isIntegerNot CAC_AnalogueValue_create("ctlVal", (ModelNode*) cancel, IEC61850_FC_CO, 0, isIntegerNotFloat); - addCommonOperateElements(cancel, isTimeActivated, true); + addCommonOperateElements(cancel, isTimeActivated, false); } } diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 18933c46..790c9be3 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -347,6 +347,18 @@ LogControlBlock_create(const char* name, LogicalNode* parent, const char* dataSe return self; } +const char* +LogControlBlock_getName(LogControlBlock* self) +{ + return self->name; +} + +LogicalNode* +LogControlBlock_getParent(LogControlBlock* self) +{ + return self->parent; +} + static void LogicalNode_addReportControlBlock(LogicalNode* self, ReportControlBlock* rcb) { @@ -676,7 +688,7 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type self->triggerOptions = triggerOptions; self->sAddr = sAddr; - if ((arrayElements > 0) && (type != IEC61850_CONSTRUCTED)) { + if (arrayElements > 0) { int i; for (i = 0; i < arrayElements; i++) { @@ -691,7 +703,7 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type arrayElement->fc = fc; arrayElement->firstChild = NULL; arrayElement->mmsValue = NULL; - arrayElement->parent = parent; + arrayElement->parent = (ModelNode*)self; arrayElement->sibling = NULL; arrayElement->triggerOptions = triggerOptions; arrayElement->sAddr = sAddr; diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 9d660ba5..375d62f9 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -649,106 +649,110 @@ ModelNode_getChildCount(ModelNode* modelNode) { ModelNode* ModelNode_getChild(ModelNode* self, const char* name) { - /* check for element separator */ - const char* separator = strchr(name, '.'); + /* check for element separator */ + const char* separator = strchr(name, '.'); - /* allow first character to be "." */ - if (separator == name) - name++; + /* allow first character to be "." */ + if (separator == name) + name++; - /* check for array separator */ - const char* arraySeparator = strchr(name, '('); + /* check for array separator */ + const char* arraySeparator = strchr(name, '('); - if (arraySeparator) { + if (arraySeparator) { - const char* arraySeparator2 = strchr(arraySeparator, ')'); + const char* arraySeparator2 = strchr(arraySeparator, ')'); - if (arraySeparator2) { - int idx = (int) strtol(arraySeparator + 1, NULL, 10); + if (arraySeparator2) { + int idx = (int) strtol(arraySeparator + 1, NULL, 10); - ModelNode* arrayNode = NULL; + ModelNode* arrayNode = NULL; - if (name == arraySeparator) { - arrayNode = ModelNode_getChildWithIdx(self, idx); - } - else { - char nameCopy[65]; + if (name == arraySeparator) { + arrayNode = ModelNode_getChildWithIdx(self, idx); + } + else { + char nameCopy[65]; - const char* pos = name; + const char* pos = name; - int cpyIdx = 0; + int cpyIdx = 0; - while (pos < arraySeparator) { - nameCopy[cpyIdx] = *pos; - cpyIdx++; - pos++; - } + while (pos < arraySeparator) { + nameCopy[cpyIdx] = *pos; + cpyIdx++; + pos++; + } - nameCopy[cpyIdx] = 0; + nameCopy[cpyIdx] = 0; - ModelNode* childNode = ModelNode_getChild(self, nameCopy); + ModelNode* childNode = ModelNode_getChild(self, nameCopy); - if (childNode) { - arrayNode = ModelNode_getChildWithIdx(childNode, idx); - } - else - return NULL; - } + if (childNode) { + arrayNode = ModelNode_getChildWithIdx(childNode, idx); + } + else + return NULL; + } - if (arrayNode) { + if (arrayNode) { - if (*(arraySeparator2 + 1) == 0) { - return arrayNode; - } - else { - if (*(arraySeparator2 + 1) == '.') - return ModelNode_getChild(arrayNode, arraySeparator2 + 2); - else - return ModelNode_getChild(arrayNode, arraySeparator2 + 1); - } + if (*(arraySeparator2 + 1) == 0) { + return arrayNode; + } + else { + if (*(arraySeparator2 + 1) == '.') + return ModelNode_getChild(arrayNode, arraySeparator2 + 2); + else + return ModelNode_getChild(arrayNode, arraySeparator2 + 1); + } - } - else - return NULL; + } + else + return NULL; + } + else { + /* invalid name */ + return NULL; + } - } - else { - /* invalid name */ - return NULL; - } + } - } + int nameElementLength = 0; - int nameElementLength = 0; + if (separator != NULL) + nameElementLength = (separator - name); + else + nameElementLength = strlen(name); - if (separator != NULL) - nameElementLength = (separator - name); - else - nameElementLength = strlen(name); + ModelNode* nextNode = self->firstChild; - ModelNode* nextNode = self->firstChild; + ModelNode* matchingNode = NULL; - ModelNode* matchingNode = NULL; + while (nextNode) { - while (nextNode) { - int nodeNameLen = strlen(nextNode->name); + if (nextNode->name == NULL) { + break; /* is an array element */ + } - if (nodeNameLen == nameElementLength) { + int nodeNameLen = strlen(nextNode->name); - if (memcmp(nextNode->name, name, nodeNameLen) == 0) { - matchingNode = nextNode; - break; - } - } + if (nodeNameLen == nameElementLength) { - nextNode = nextNode->sibling; - } + if (memcmp(nextNode->name, name, nodeNameLen) == 0) { + matchingNode = nextNode; + break; + } + } - if ((separator != NULL) && (matchingNode != NULL)) { - return ModelNode_getChild(matchingNode, separator + 1); - } - else - return matchingNode; + nextNode = nextNode->sibling; + } + + if ((separator != NULL) && (matchingNode != NULL)) { + return ModelNode_getChild(matchingNode, separator + 1); + } + else + return matchingNode; } ModelNode* diff --git a/src/mms/asn1/asn1_ber_primitive_value.c b/src/mms/asn1/asn1_ber_primitive_value.c index 0dbd15bf..55cf626b 100644 --- a/src/mms/asn1/asn1_ber_primitive_value.c +++ b/src/mms/asn1/asn1_ber_primitive_value.c @@ -1,24 +1,24 @@ /* * asn1_ber_primitive_value.c * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2022 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * 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. + * 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 . + * 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. + * See COPYING file for the complete license text. */ #include "libiec61850_platform_includes.h" diff --git a/src/mms/asn1/ber_decode.c b/src/mms/asn1/ber_decode.c index 12078610..8a4866a8 100644 --- a/src/mms/asn1/ber_decode.c +++ b/src/mms/asn1/ber_decode.c @@ -1,7 +1,7 @@ /* * ber_decoder.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2022 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/src/mms/asn1/ber_encoder.c b/src/mms/asn1/ber_encoder.c index 63566f38..53a5d178 100644 --- a/src/mms/asn1/ber_encoder.c +++ b/src/mms/asn1/ber_encoder.c @@ -1,7 +1,7 @@ /* * ber_encoder.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2022 Michael Zillgith * * This file is part of libIEC61850. * @@ -356,6 +356,28 @@ BerEncoder_UInt32determineEncodedSize(uint32_t value) return size; } +int +BerEncoder_Int32determineEncodedSize(int32_t value) +{ + uint8_t* valueArray = (uint8_t*) &value; + uint8_t valueBuffer[5]; + + valueBuffer[0] = 0; + + int i; + for (i = 0; i < 4; i++) { + valueBuffer[i + 1] = valueArray[i]; + } + +#if (ORDER_LITTLE_ENDIAN == 1) + BerEncoder_revertByteOrder(valueBuffer + 1, 4); +#endif + + int size = BerEncoder_compressInteger(valueBuffer, 5); + + return size; +} + int BerEncoder_determineLengthSize(uint32_t length) { @@ -457,7 +479,6 @@ BerEncoder_encodeOIDToBuffer(const char* oidString, uint8_t* buffer, int maxBufL requiredBytes--; } } - } return encodedBytes; diff --git a/src/mms/asn1/ber_integer.c b/src/mms/asn1/ber_integer.c index 9a7e8030..01281e3f 100644 --- a/src/mms/asn1/ber_integer.c +++ b/src/mms/asn1/ber_integer.c @@ -1,7 +1,7 @@ /* * ber_integer.c * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2022 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/src/mms/inc/iso_connection_parameters.h b/src/mms/inc/iso_connection_parameters.h index cdd04bcb..df413579 100644 --- a/src/mms/inc/iso_connection_parameters.h +++ b/src/mms/inc/iso_connection_parameters.h @@ -1,7 +1,7 @@ /* * iso_connection_parameters.h * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -24,6 +24,10 @@ #ifndef ISO_CONNECTION_PARAMETERS_H_ #define ISO_CONNECTION_PARAMETERS_H_ +#ifndef CONFIG_MMS_SUPPORT_TLS +#define CONFIG_MMS_SUPPORT_TLS 0 +#endif + #ifdef __cplusplus extern "C" { #endif @@ -145,6 +149,9 @@ struct sIsoConnectionParameters const char* hostname; int tcpPort; + const char* localIpAddress; + int localTcpPort; + uint8_t remoteApTitle[10]; int remoteApTitleLen; int remoteAEQualifier; @@ -215,6 +222,20 @@ IsoConnectionParameters_setAcseAuthenticationParameter(IsoConnectionParameters s LIB61850_API void IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const char* hostname, int tcpPort); +/** +* \brief Set Local TCP parameters (FOR LIBRARY INTERNAL USE) +* +* NOTE: This function used internally by the MMS Client library. When using the MMS or IEC 61850 API +* there should be no reason for the user to call this function +* +* \param self the IsoConnectionParameters instance +* \param localIpAddress the hostname of local IP address of the server +* \param localTcpPort the local TCP port number of the server +*/ +LIB61850_API void +IsoConnectionParameters_setLocalTcpParameters(IsoConnectionParameters self, const char* localIpAddress, int localTcpPort); + + /** * \brief set the remote AP-Title and AE-Qualifier * diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 70bedc28..86f12c9b 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -648,6 +648,23 @@ MmsConnection_writeVariableAsync(MmsConnection self, uint32_t* usedInvokeId, Mms MmsConnection_WriteVariableHandler handler, void* parameter); +/** + * \brief Write a single variable to the server (using component alternate access) + * + * \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 written + * \param itemId name of the variable to be written + * \param componentId the name of the variable component + * \param value value of the variable to be written + * + * \return when successful, the data access error value returned by the server + */ +LIB61850_API MmsDataAccessError +MmsConnection_writeVariableComponent(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, + const char* componentId, MmsValue* value); + /** * \brief Write a single array element with a component to an array type variable * @@ -672,6 +689,11 @@ MmsConnection_writeSingleArrayElementWithComponentAsync(MmsConnection self, uint uint32_t arrayIndex, const char* componentId, MmsValue* value, MmsConnection_WriteVariableHandler handler, void* parameter); +LIB61850_API void +MmsConnection_writeVariableComponentAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, + const char* domainId, const char* itemId, const char* componentId, MmsValue* value, + MmsConnection_WriteVariableHandler handler, void* parameter); + /** * \brief Write a single array element or a sub array to an array type variable * diff --git a/src/mms/inc/mms_server.h b/src/mms/inc/mms_server.h index 7e6118d0..15248789 100644 --- a/src/mms/inc/mms_server.h +++ b/src/mms/inc/mms_server.h @@ -1,7 +1,7 @@ /* * mms_server.h * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -57,30 +57,70 @@ MmsServer_setLocalIpAddress(MmsServer self, const char* localIpAddress); LIB61850_INTERNAL bool MmsServer_isRunning(MmsServer self); +typedef enum { + MMS_VARLIST_CREATE, + MMS_VARLIST_DELETE, + MMS_VARLIST_READ, + MMS_VARLIST_WRITE, + MMS_VARLIST_GET_DIRECTORY +} MmsVariableListAccessType; + /** - * \brief callback handler that is called whenever a named variable list changes + * \brief callback handler that is called for each named variable list access * * \param parameter a user provided parameter - * \param create if true the the request if a request to create a new variable list, false is a delete request + * \param accessType the kind of access (create, delete, read, write, get directory) * \param listType the type (scope) of the named variable list (either domain, association or VMD specific) * \param domain the MMS domain the list is belonging to (is NULL for association or VMD specific lists!) * \param listName the name - * \param connection client connection that requests the creation of deletion of the variable list + * \param connection client connection that is accessing the named variable list * * \return MMS_ERROR_NONE if the request is accepted, otherwise the MmsError value that has to be sent back to the client */ -typedef MmsError (*MmsNamedVariableListChangedHandler)(void* parameter, bool create, MmsVariableListType listType, MmsDomain* domain, +typedef MmsError (*MmsNamedVariableListAccessHandler)(void* parameter, MmsVariableListAccessType accessType, MmsVariableListType listType, MmsDomain* domain, char* listName, MmsServerConnection connection); /** - * \brief Install callback handler that is called when a named variable list changes (is created or deleted) + * \brief Install callback handler that is called when a named variable list is accessed by a client * * \param self the MmsServer instance to operate on * \param handler the callback handler function * \param parameter user provided parameter that is passed to the callback handler */ LIB61850_INTERNAL void -MmsServer_installVariableListChangedHandler(MmsServer self, MmsNamedVariableListChangedHandler handler, void* parameter); +MmsServer_installVariableListAccessHandler(MmsServer self, MmsNamedVariableListAccessHandler handler, void* parameter); + +/** + * \brief callback handler that is called for each received read journal request + * + * \param parameter a user provided parameter + * \param domain the MMS domain the journal is belonging to + * \param logName the name of the journal + * \param connection client connection that is accessing the journal + */ +typedef bool (*MmsReadJournalHandler)(void* parameter, MmsDomain* domain, const char* logName, MmsServerConnection connection); + +/** + * \brief Install callback handler that is called when a journal is accessed by a client + * + * \param self the MmsServer instance to operate on + * \param handler the callback handler function + * \param parameter user provided parameter that is passed to the callback handler + */ +LIB61850_INTERNAL void +MmsServer_installReadJournalHandler(MmsServer self, MmsReadJournalHandler handler, void* parameter); + +typedef enum { + MMS_GETNAMELIST_DOMAINS, + MMS_GETNAMELIST_JOURNALS, + MMS_GETNAMELIST_DATASETS, + MMS_GETNAMELIST_DATA +} MmsGetNameListType; + +typedef bool (*MmsGetNameListHandler)(void* parameter, MmsGetNameListType nameListType, MmsDomain* domain, MmsServerConnection connection); + +LIB61850_INTERNAL void +MmsServer_installGetNameListHandler(MmsServer self, MmsGetNameListHandler handler, void* parameter); /** * \brief ObtainFile service callback handler @@ -370,6 +410,9 @@ MmsServerConnection_getLocalAddress(MmsServerConnection self); LIB61850_INTERNAL void* MmsServerConnection_getSecurityToken(MmsServerConnection self); +LIB61850_INTERNAL void +MmsServer_ignoreClientRequests(MmsServer self, bool enable);; + /**@}*/ #ifdef __cplusplus diff --git a/src/mms/inc/mms_value.h b/src/mms/inc/mms_value.h index e67bccb2..206345c4 100644 --- a/src/mms/inc/mms_value.h +++ b/src/mms/inc/mms_value.h @@ -498,12 +498,30 @@ MmsValue_getUtcTimeInMsWithUs(const MmsValue* self, uint32_t* usec); * bit 0-4 = subsecond time accuracy (number of significant bits of subsecond time) * * \param self MmsValue instance to operate on. Has to be of a type MMS_UTCTIME. - * * \param timeQuality the byte representing the time quality */ LIB61850_API void MmsValue_setUtcTimeQuality(MmsValue* self, uint8_t timeQuality); +/** + * \brief Update an MmsValue object of type MMS_UTCTIME with a millisecond time. + * + * Meaning of the bits in the timeQuality byte: + * + * bit 7 = leapSecondsKnown + * bit 6 = clockFailure + * bit 5 = clockNotSynchronized + * bit 0-4 = subsecond time accuracy (number of significant bits of subsecond time) + * + * \param self MmsValue instance to operate on. Has to be of a type MMS_UTCTIME. + * \param timeval the new value in milliseconds since epoch (1970/01/01 00:00 UTC) + * \param timeQuality the byte representing the time quality + * + * \return the updated MmsValue instance + */ +LIB61850_API MmsValue* +MmsValue_setUtcTimeMsEx(MmsValue* self, uint64_t timeval, uint8_t timeQuality); + /** * \brief get the TimeQuality byte of the UtcTime * diff --git a/src/mms/inc_private/cotp.h b/src/mms/inc_private/cotp.h index b7877cc8..3cbe2681 100644 --- a/src/mms/inc_private/cotp.h +++ b/src/mms/inc_private/cotp.h @@ -114,4 +114,7 @@ CotpConnection_getRemoteRef(CotpConnection* self); LIB61850_INTERNAL int CotpConnection_getLocalRef(CotpConnection* self); +LIB61850_INTERNAL void +CotpConnection_flushBuffer(CotpConnection* self); + #endif /* COTP_H_ */ diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 57243230..aa0e60f3 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -272,6 +272,11 @@ mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const int startIndex, int elementCount, MmsValue* value, ByteBuffer* writeBuffer); +LIB61850_INTERNAL int +mmsClient_createWriteRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component, + MmsValue* value, + ByteBuffer* writeBuffer); + LIB61850_INTERNAL int mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, uint32_t arrayIndex, const char* component, diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 780e1d74..ec70d359 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -123,8 +123,14 @@ struct sMmsServer { MmsConnectionHandler connectionHandler; void* connectionHandlerParameter; - MmsNamedVariableListChangedHandler variableListChangedHandler; /* TODO this is only required if dynamic data sets are supported! */ - void* variableListChangedHandlerParameter; + MmsNamedVariableListAccessHandler variableListAccessHandler; + void* variableListAccessHandlerParameter; + + MmsReadJournalHandler readJournalHandler; + void* readJournalHandlerParameter; + + MmsGetNameListHandler getNameListHandler; + void* getNameListHandlerParameter; AcseAuthenticator authenticator; void* authenticatorParameter; @@ -135,7 +141,8 @@ struct sMmsServer { Map openConnections; Map valueCaches; - bool isLocked; + + bool blockRequests; ByteBuffer* transmitBuffer; /* global buffer for encoding reports, delayed responses... */ #if (CONFIG_MMS_THREADLESS_STACK != 1) @@ -420,7 +427,7 @@ mmsServer_createMmsWriteResponse(MmsServerConnection connection, uint32_t invokeId, ByteBuffer* response, int numberOfItems, MmsDataAccessError* accessResults); LIB61850_INTERNAL MmsError -mmsServer_callVariableListChangedHandler(bool create, MmsVariableListType listType, MmsDomain* domain, +mmsServer_callVariableListChangedHandler(MmsVariableListAccessType accessType, MmsVariableListType listType, MmsDomain* domain, char* listName, MmsServerConnection connection); #endif /* MMS_SERVER_INTERNAL_H_ */ diff --git a/src/mms/inc_private/mms_server_libinternal.h b/src/mms/inc_private/mms_server_libinternal.h index ac16e445..444989c9 100644 --- a/src/mms/inc_private/mms_server_libinternal.h +++ b/src/mms/inc_private/mms_server_libinternal.h @@ -1,7 +1,7 @@ /* * mms_server_libinternal.h * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -196,7 +196,7 @@ MmsServer_getConnectionCounter(MmsServer self); LIB61850_INTERNAL void MmsServer_stopListeningThreadless(MmsServer self); -LIB61850_INTERNAL const char* +LIB61850_INTERNAL const char* MmsServer_getFilesystemBasepath(MmsServer self); #endif /* MMS_SERVER_LIBINTERNAL_H_ */ diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c index cb58e98f..4f37176b 100644 --- a/src/mms/iso_client/iso_client_connection.c +++ b/src/mms/iso_client/iso_client_connection.c @@ -692,8 +692,13 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim /* set timeout for connect */ self->nextReadTimeout = Hal_getTimeInMs() + connectTimeoutInMs; + /* Connect to Local Ip Address*/ + if (self->parameters->localIpAddress) { + Socket_bind(self->socket, self->parameters->localIpAddress, self->parameters->localTcpPort); + } + if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) { - + Socket_destroy(self->socket); self->socket = NULL; @@ -704,9 +709,9 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim success = false; } - + Semaphore_post(self->tickMutex); - + return success; } diff --git a/src/mms/iso_common/iso_connection_parameters.c b/src/mms/iso_common/iso_connection_parameters.c index 3fdf32c9..aa5334b3 100644 --- a/src/mms/iso_common/iso_connection_parameters.c +++ b/src/mms/iso_common/iso_connection_parameters.c @@ -104,6 +104,18 @@ IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const cha self->tcpPort = tcpPort; } +void +IsoConnectionParameters_setLocalTcpParameters(IsoConnectionParameters self, const char* localIpAddress, int localTcpPort) +{ + if (self) { + if (localIpAddress) { + self->localIpAddress = strdup(localIpAddress); + self->localTcpPort = localTcpPort; + } + } +} + + void IsoConnectionParameters_setRemoteApTitle(IsoConnectionParameters self, const char* apTitle, int aeQualifier) { diff --git a/src/mms/iso_cotp/cotp.c b/src/mms/iso_cotp/cotp.c index f57e05bc..2b4e43e6 100644 --- a/src/mms/iso_cotp/cotp.c +++ b/src/mms/iso_cotp/cotp.c @@ -5,7 +5,7 @@ * * Partial implementation of the ISO 8073 COTP (ISO TP0) protocol for MMS. * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -174,6 +174,38 @@ writeToSocket(CotpConnection* self, uint8_t* buf, int size) #endif } +static bool +flushBuffer(CotpConnection* self) +{ + if (self->socketExtensionBufferFill > 0) { + + int sentBytes = writeToSocket(self, self->socketExtensionBuffer, self->socketExtensionBufferFill); + + if (sentBytes > 0) { + + if (sentBytes != self->socketExtensionBufferFill) { + int target = 0; + int i; + uint8_t* buf = self->socketExtensionBuffer; + + for (i = sentBytes; i < self->socketExtensionBufferFill; i++) { + buf[target++] = buf[i]; + } + + self->socketExtensionBufferFill = self->socketExtensionBufferFill - sentBytes; + } + else { + self->socketExtensionBufferFill = 0; + } + } + else if (sentBytes == -1) { + return false; + } + } + + return true; +} + static bool sendBuffer(CotpConnection* self) { @@ -182,7 +214,15 @@ sendBuffer(CotpConnection* self) bool retVal = false; - int sentBytes = writeToSocket(self, buffer, remainingSize); + if (flushBuffer(self) == false) { + goto exit_function; + } + + int sentBytes = 0; + + if (self->socketExtensionBufferFill == 0) { + sentBytes = writeToSocket(self, buffer, remainingSize); + } if (sentBytes == -1) goto exit_function; @@ -215,33 +255,6 @@ exit_function: return retVal; } -static void -flushBuffer(CotpConnection* self) -{ - if (self->socketExtensionBufferFill > 0) { - - int sentBytes = writeToSocket(self, self->socketExtensionBuffer, self->socketExtensionBufferFill); - - if (sentBytes > 0) { - - if (sentBytes != self->socketExtensionBufferFill) { - int target = 0; - int i; - uint8_t* buf = self->socketExtensionBuffer; - - for (i = sentBytes; i < self->socketExtensionBufferFill; i++) { - buf[target++] = buf[i]; - } - - self->socketExtensionBufferFill = self->socketExtensionBufferFill - sentBytes; - } - else { - self->socketExtensionBufferFill = 0; - } - } - } -} - CotpIndication CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) { @@ -262,7 +275,9 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) int totalSize = (fragments * (COTP_DATA_HEADER_SIZE + 4)) + payload->length; /* try to flush extension buffer */ - flushBuffer(self); + if (flushBuffer(self) == false) { + return COTP_ERROR; + } /* check if totalSize will fit in extension buffer */ if (self->socketExtensionBuffer) { @@ -281,7 +296,7 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) int currentChainIndex = 0; if (DEBUG_COTP) - printf("\nCOTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); + printf("COTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); uint8_t* buffer = self->writeBuffer->buffer; @@ -307,7 +322,7 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) if (currentChainIndex >= currentChain->partLength) { currentChain = currentChain->nextPart; if (DEBUG_COTP) - printf("\nCOTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); + printf("COTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); currentChainIndex = 0; } @@ -756,6 +771,13 @@ readFromSocket(CotpConnection* self, uint8_t* buf, int size) #endif } +void +CotpConnection_flushBuffer(CotpConnection* self) +{ + if (self->socketExtensionBufferFill > 0) + flushBuffer(self); +} + TpktState CotpConnection_readToTpktBuffer(CotpConnection* self) { @@ -765,6 +787,14 @@ CotpConnection_readToTpktBuffer(CotpConnection* self) assert (bufferSize > 4); + if (self->socketExtensionBufferFill > 0) { + if (flushBuffer(self) == false) + goto exit_error; + + if (self->socketExtensionBufferFill > 0) + goto exit_waiting; + } + int readBytes; if (bufPos < 4) { diff --git a/src/mms/iso_mms/asn1c/asn_internal.h b/src/mms/iso_mms/asn1c/asn_internal.h index 493b4336..5d6910d8 100644 --- a/src/mms/iso_mms/asn1c/asn_internal.h +++ b/src/mms/iso_mms/asn1c/asn_internal.h @@ -11,6 +11,8 @@ #include "asn_application.h" /* Application-visible API */ +#define EMIT_ASN_DEBUG 0 + #include "lib_memory.h" #ifndef __NO_ASSERT_H__ /* Include assert.h only for internal use. */ diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index de7db996..a9763c08 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -354,10 +354,6 @@ sendMessage(MmsConnection self, ByteBuffer* message) } #endif /* (CONFIG_MMS_RAW_MESSAGE_LOGGING == 1) */ -#if (CONFIG_MMS_COLLECT_STATISTICS == 1) - self->statAplMessagesSent++; -#endif - IsoClientConnection_sendMessage(self->isoClient, message); } @@ -4383,6 +4379,69 @@ exit_function: return; } +MmsDataAccessError +MmsConnection_writeVariableComponent(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, + const char* componentId, MmsValue* value) +{ + struct writeVariableParameters parameter; + + MmsError err = MMS_ERROR_NONE; + + parameter.waitForResponse = Semaphore_create(1); + parameter.err = MMS_ERROR_NONE; + parameter.accessError = DATA_ACCESS_ERROR_SUCCESS; + + Semaphore_wait(parameter.waitForResponse); + + MmsConnection_writeVariableComponentAsync(self, NULL, &err, domainId, itemId, componentId, value, writeVariableHandler, ¶meter); + + if (err == MMS_ERROR_NONE) { + Semaphore_wait(parameter.waitForResponse); + + err = parameter.err; + } + + Semaphore_destroy(parameter.waitForResponse); + + if (mmsError) + *mmsError = err; + + return parameter.accessError; +} + +void +MmsConnection_writeVariableComponentAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, + const char* domainId, const char* itemId, const char* componentId, MmsValue* value, + MmsConnection_WriteVariableHandler handler, void* parameter) +{ + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (mmsError) + *mmsError = MMS_ERROR_CONNECTION_LOST; + goto exit_function; + } + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + if (usedInvokeId) + *usedInvokeId = invokeId; + + mmsClient_createWriteRequestComponent(invokeId, domainId, itemId, componentId, value, payload); + + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, intParam); + + if (mmsError) + *mmsError = err; + +exit_function: + return; +} + struct writeMultipleVariablesParameter { Semaphore sem; diff --git a/src/mms/iso_mms/client/mms_client_write.c b/src/mms/iso_mms/client/mms_client_write.c index e99a6e1b..fc2b3d62 100644 --- a/src/mms/iso_mms/client/mms_client_write.c +++ b/src/mms/iso_mms/client/mms_client_write.c @@ -529,6 +529,63 @@ mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const return rval.encoded; } +int +mmsClient_createWriteRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component, + MmsValue* value, + ByteBuffer* writeBuffer) +{ + MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); + + mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present = + ConfirmedServiceRequest_PR_write; + WriteRequest_t* request = + &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write); + + /* Create list of variable specifications */ + request->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable; + request->variableAccessSpecification.choice.listOfVariable.list.count = 1; + request->variableAccessSpecification.choice.listOfVariable.list.array = + (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); + + ListOfVariableSeq_t* variableIdentifier = createNewDomainVariableSpecification(domainId, itemId); + + request->variableAccessSpecification.choice.listOfVariable.list.array[0] = variableIdentifier; + + variableIdentifier->alternateAccess = mmsClient_createAlternateAccessComponent(component); + + /* Create list of typed data values */ + request->listOfData.list.count = 1; + request->listOfData.list.size = 1; + request->listOfData.list.array = (Data_t**) GLOBAL_CALLOC(1, sizeof(struct Data*)); + request->listOfData.list.array[0] = mmsMsg_createBasicDataElement(value); + + /* Encode complete ASN1 structure */ + + asn_enc_rval_t rval; + + rval = der_encode(&asn_DEF_MmsPdu, mmsPdu, + (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer); + + /* Free ASN structure */ + mmsClient_deleteAlternateAccess(variableIdentifier->alternateAccess); + request->variableAccessSpecification.choice.listOfVariable.list.count = 0; + + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array[0]); + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array); + request->variableAccessSpecification.choice.listOfVariable.list.array = 0; + + request->listOfData.list.count = 0; + + deleteDataElement(request->listOfData.list.array[0]); + + GLOBAL_FREEMEM(request->listOfData.list.array); + request->listOfData.list.array = 0; + + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + + return rval.encoded; +} + int mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, uint32_t arrayIndex, const char* component, diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 5d80a630..549619a8 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -763,8 +763,8 @@ MmsValue_setUtcTime(MmsValue* self, uint32_t timeval) return self; } -MmsValue* -MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval) +static void +setUtcTimeMs(MmsValue* self, uint64_t timeval, uint8_t timeQuality) { uint32_t timeval32 = (timeval / 1000LL); @@ -786,7 +786,21 @@ MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval) valueArray[6] = (fractionOfSecond & 0xff); /* encode time quality */ - valueArray[7] = 0x0a; /* 10 bit sub-second time accuracy */ + valueArray[7] = timeQuality; +} + +MmsValue* +MmsValue_setUtcTimeMs(MmsValue* self, uint64_t timeval) +{ + setUtcTimeMs(self, timeval, 0x0a); /* set quality as 10 bit sub-second time accuracy */ + + return self; +} + +MmsValue* +MmsValue_setUtcTimeMsEx(MmsValue* self, uint64_t timeval, uint8_t timeQuality) +{ + setUtcTimeMs(self, timeval, timeQuality); return self; } @@ -2191,7 +2205,7 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) const char* currentStr = MmsValue_printToBuffer((const MmsValue*) MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos); - bufPos += strlen(currentStr); + bufPos += strnlen(currentStr, bufferSize - bufPos); if (bufPos >= bufferSize) break; @@ -2226,9 +2240,13 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) int size = MmsValue_getBitStringSize(self); /* fill buffer with zeros */ - if (size > bufferSize) { + if (size + 1 > bufferSize) { memset(buffer, 0, bufferSize); - break; + + size = bufferSize - 1; + + if (size < 1) + break; } int i; 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 005aa4c6..2170f8c1 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -1,7 +1,7 @@ /* * mms_get_namelist_service.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -190,20 +190,29 @@ getJournalListDomainSpecific(MmsServerConnection connection, char* domainName) MmsDomain* domain = MmsDevice_getDomain(device, domainName); - if (domain != NULL) { - nameList = LinkedList_create(); + if (domain) { - if (domain->journals != NULL) { + bool allowAccess = true; - LinkedList journalList = domain->journals; + if (connection->server->getNameListHandler) { + allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_JOURNALS, domain, connection); + } - while ((journalList = LinkedList_getNext(journalList)) != NULL) { + if (allowAccess) { + nameList = LinkedList_create(); - MmsJournal journal = (MmsJournal) LinkedList_getData(journalList); + if (domain->journals != NULL) { - LinkedList_add(nameList, (void*) journal->name); - } + LinkedList journalList = domain->journals; + + while ((journalList = LinkedList_getNext(journalList)) != NULL) { + + MmsJournal journal = (MmsJournal) LinkedList_getData(journalList); + LinkedList_add(nameList, (void*) journal->name); + } + + } } } @@ -219,46 +228,56 @@ getNameListDomainSpecific(MmsServerConnection connection, char* domainName) MmsDomain* domain = MmsDevice_getDomain(device, domainName); - if (domain != NULL) { - nameList = LinkedList_create(); - MmsVariableSpecification** variables = domain->namedVariables; + if (domain) { - int i; + bool allowAccess = true; - LinkedList element = nameList; + if (connection->server->getNameListHandler) { + allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_DATASETS, domain, connection); + } + + if (allowAccess) { + nameList = LinkedList_create(); + MmsVariableSpecification** variables = domain->namedVariables; + + int i; + + LinkedList element = nameList; #if (CONFIG_MMS_SORT_NAME_LIST == 1) - int* index = (int*) GLOBAL_MALLOC(sizeof(int) * domain->namedVariablesCount); + int* index = (int*) GLOBAL_MALLOC(sizeof(int) * domain->namedVariablesCount); - for (i = 0; i < domain->namedVariablesCount; i++) - index[i] = i; + for (i = 0; i < domain->namedVariablesCount; i++) + index[i] = i; - sortIndex(index, domain->namedVariablesCount, domain->namedVariables); + sortIndex(index, domain->namedVariablesCount, domain->namedVariables); #endif /* (CONFIG_MMS_SORT_NAME_LIST == 1) */ - for (i = 0; i < domain->namedVariablesCount; i++) { + for (i = 0; i < domain->namedVariablesCount; i++) { #if (CONFIG_MMS_SORT_NAME_LIST == 1) - element = LinkedList_insertAfter(element, StringUtils_copyString(variables[index[i]]->name)); + element = LinkedList_insertAfter(element, StringUtils_copyString(variables[index[i]]->name)); #else - element = LinkedList_insertAfter(element, StringUtils_copyString(variables[i]->name)); + element = LinkedList_insertAfter(element, StringUtils_copyString(variables[i]->name)); #endif #if (CONFIG_MMS_SUPPORT_FLATTED_NAME_SPACE == 1) #if (CONFIG_MMS_SORT_NAME_LIST == 1) - char* prefix = variables[index[i]]->name; - element = addSubNamedVaribleNamesToList(element, prefix, variables[index[i]]); + char* prefix = variables[index[i]]->name; + element = addSubNamedVaribleNamesToList(element, prefix, variables[index[i]]); #else - char* prefix = variables[i]->name; - element = addSubNamedVaribleNamesToList(element, prefix, variables[i]); + char* prefix = variables[i]->name; + element = addSubNamedVaribleNamesToList(element, prefix, variables[i]); #endif /* (CONFIG_MMS_SORT_NAME_LIST == 1) */ #endif /* (CONFIG_MMS_SUPPORT_FLATTED_NAME_SPACE == 1) */ - } + } #if (CONFIG_MMS_SORT_NAME_LIST == 1) - GLOBAL_FREEMEM(index); + GLOBAL_FREEMEM(index); #endif + + } } return nameList; @@ -293,10 +312,19 @@ getNamedVariableListsDomainSpecific(MmsServerConnection connection, char* domain MmsDomain* domain = MmsDevice_getDomain(device, domainName); - if (domain != NULL) { - LinkedList variableLists = MmsDomain_getNamedVariableLists(domain); + if (domain) { + + bool allowAccess = true; - nameList = createStringsFromNamedVariableList(variableLists); + if (connection->server->getNameListHandler) { + allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_DATASETS, domain, connection); + } + + if (allowAccess) { + LinkedList variableLists = MmsDomain_getNamedVariableLists(domain); + + nameList = createStringsFromNamedVariableList(variableLists); + } } return nameList; @@ -613,38 +641,79 @@ mmsServer_handleGetNameListRequest( if (objectClass == OBJECT_CLASS_DOMAIN) { - LinkedList nameList = getDomainNames(connection); + bool allowAccess = true; + + if (connection->server->getNameListHandler) { + allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_DOMAINS, NULL, connection); + } + + if (allowAccess) { + LinkedList nameList = getDomainNames(connection); #if (CONFIG_MMS_SORT_NAME_LIST == 1) - StringUtils_sortList(nameList); + StringUtils_sortList(nameList); #endif - createNameListResponse(connection, invokeId, nameList, response, continueAfterId); + createNameListResponse(connection, invokeId, nameList, response, continueAfterId); - LinkedList_destroyStatic(nameList); + LinkedList_destroyStatic(nameList); + } + else { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + } } #if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) else if (objectClass == OBJECT_CLASS_NAMED_VARIABLE) { - LinkedList nameList = getNameListVMDSpecific(connection); - createNameListResponse(connection, invokeId, nameList, response, continueAfterId); + bool allowAccess = true; + + if (connection->server->getNameListHandler) { + allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_DATA, NULL, connection); + } + + if (allowAccess) { + LinkedList nameList = getNameListVMDSpecific(connection); + +#if (CONFIG_MMS_SORT_NAME_LIST == 1) + StringUtils_sortList(nameList); +#endif + + createNameListResponse(connection, invokeId, nameList, response, continueAfterId); - LinkedList_destroyStatic(nameList); + LinkedList_destroyStatic(nameList); + } + else { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + } } #endif /* (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) */ #if (MMS_DATA_SET_SERVICE == 1) else if (objectClass == OBJECT_CLASS_NAMED_VARIABLE_LIST) { - LinkedList nameList = getNamedVariableListsVMDSpecific(connection); + + bool allowAccess = true; + + if (connection->server->getNameListHandler) { + allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_DATASETS, NULL, connection); + } + + if (allowAccess) { + + LinkedList nameList = getNamedVariableListsVMDSpecific(connection); #if (CONFIG_MMS_SORT_NAME_LIST == 1) - StringUtils_sortList(nameList); + StringUtils_sortList(nameList); #endif - createNameListResponse(connection, invokeId, nameList, response, continueAfterId); + createNameListResponse(connection, invokeId, nameList, response, continueAfterId); - LinkedList_destroy(nameList); + LinkedList_destroy(nameList); + + } + else { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + } } #endif /* (MMS_DATA_SET_SERVICE == 1) */ diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index 22980f76..0aadde40 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -462,6 +462,20 @@ mmsServer_handleReadJournalRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: readJournal - read journal %s ...\n", mmsJournal->name); + MmsServer mmsServer = connection->server; + + if (mmsServer->readJournalHandler) + { + if (mmsServer->readJournalHandler(mmsServer->readJournalHandlerParameter, mmsDomain, logName, connection) == false) + { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + + /* TODO log access error */ + + return; + } + } + struct sJournalEncoder encoder; encoder.buffer = response->buffer; diff --git a/src/mms/iso_mms/server/mms_named_variable_list.c b/src/mms/iso_mms/server/mms_named_variable_list.c index 31694780..3c5ea5c3 100644 --- a/src/mms/iso_mms/server/mms_named_variable_list.c +++ b/src/mms/iso_mms/server/mms_named_variable_list.c @@ -3,22 +3,22 @@ * * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * 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. + * 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 . + * 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. + * See COPYING file for the complete license text. */ #include "libiec61850_platform_includes.h" @@ -51,7 +51,6 @@ MmsNamedVariableListEntry_destroy(MmsNamedVariableListEntry self) GLOBAL_FREEMEM(self); } - MmsDomain* MmsNamedVariableListEntry_getDomain(MmsNamedVariableListEntry self) { @@ -120,5 +119,3 @@ MmsNamedVariableList_destroy(MmsNamedVariableList self) GLOBAL_FREEMEM(self->name); GLOBAL_FREEMEM(self); } - - 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 3a27061c..85f1b960 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 @@ -1,7 +1,7 @@ /* * mms_named_variable_list_service.c * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -49,67 +49,67 @@ #endif MmsError -mmsServer_callVariableListChangedHandler(bool create, MmsVariableListType listType, MmsDomain* domain, - char* listName, MmsServerConnection connection) +mmsServer_callVariableListChangedHandler(MmsVariableListAccessType accessType, MmsVariableListType listType, MmsDomain* domain, + char* listName, MmsServerConnection connection) { - MmsServer self = connection->server; + MmsServer self = connection->server; - if (self->variableListChangedHandler != NULL) { - if (DEBUG_MMS_SERVER) - printf("MMS_SERVER: call MmsNamedVariableListChangedHandler for new list %s\n", listName); + if (self->variableListAccessHandler != NULL) { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: call MmsNamedVariableListAccessHandler for new list %s\n", listName); - return self->variableListChangedHandler(self->variableListChangedHandlerParameter, - create, listType, domain, listName, connection); - } - else - return MMS_ERROR_NONE; + return self->variableListAccessHandler(self->variableListAccessHandlerParameter, + accessType, listType, domain, listName, connection); + } + else + return MMS_ERROR_NONE; } static void createDeleteNamedVariableListResponse(uint32_t invokeId, ByteBuffer* response, - uint32_t numberMatched, uint32_t numberDeleted) + uint32_t numberMatched, uint32_t numberDeleted) { - uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId) + 2; + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId) + 2; - uint32_t numberMatchedSize = - 2 + BerEncoder_UInt32determineEncodedSize(numberMatched); + uint32_t numberMatchedSize = + 2 + BerEncoder_UInt32determineEncodedSize(numberMatched); - uint32_t numberDeletedSize = - 2 + BerEncoder_UInt32determineEncodedSize(numberDeleted); + uint32_t numberDeletedSize = + 2 + BerEncoder_UInt32determineEncodedSize(numberDeleted); - uint32_t deleteNVLSize = 2 + numberMatchedSize + numberDeletedSize; + uint32_t deleteNVLSize = 2 + numberMatchedSize + numberDeletedSize; - uint32_t confirmedResponsePDUSize = invokeIdSize + deleteNVLSize; + uint32_t confirmedResponsePDUSize = invokeIdSize + deleteNVLSize; - int bufPos = 0; - uint8_t* buffer = response->buffer; + int bufPos = 0; + uint8_t* buffer = response->buffer; - bufPos = BerEncoder_encodeTL(0xa1, confirmedResponsePDUSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0xa1, confirmedResponsePDUSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x02, invokeIdSize - 2, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize - 2, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0xad, numberMatchedSize + numberDeletedSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0xad, numberMatchedSize + numberDeletedSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x80, numberMatchedSize - 2, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(numberMatched, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x80, numberMatchedSize - 2, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(numberMatched, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x81, numberDeletedSize - 2, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(numberDeleted, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x81, numberDeletedSize - 2, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(numberDeleted, buffer, bufPos); - response->size = bufPos; + response->size = bufPos; } static void /* Confirmed service error (ServiceError) */ createServiceErrorDeleteVariableLists(uint32_t invokeId, ByteBuffer* response, - MmsError errorType, uint32_t numberDeleted) + MmsError errorType, uint32_t numberDeleted) { - uint8_t buffer[8]; + uint8_t buffer[8]; - int size = BerEncoder_encodeUInt32WithTL(0x86, numberDeleted, buffer, 0); + int size = BerEncoder_encodeUInt32WithTL(0x86, numberDeleted, buffer, 0); - mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, - buffer, size); + mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, + buffer, size); } void @@ -118,38 +118,38 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, uint32_t invokeId, ByteBuffer* response) { - (void)bufPos; - - DeleteNamedVariableListRequest_t* request = NULL; - MmsPdu_t* mmsPdu = NULL; - - asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu, (void**) &mmsPdu, buffer, maxBufPos); - - if (rval.code != RC_OK) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); - goto exit_function; - } - - if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) && - (mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present - == ConfirmedServiceRequest_PR_deleteNamedVariableList)) - { - request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList); - } - else { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); - goto exit_function; - } + (void)bufPos; + + DeleteNamedVariableListRequest_t* request = NULL; + MmsPdu_t* mmsPdu = NULL; + + asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu, (void**) &mmsPdu, buffer, maxBufPos); + + if (rval.code != RC_OK) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + goto exit_function; + } + + if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) && + (mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present + == ConfirmedServiceRequest_PR_deleteNamedVariableList)) + { + request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList); + } + else { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + goto exit_function; + } long scopeOfDelete = DeleteNamedVariableListRequest__scopeOfDelete_specific; if (request->scopeOfDelete) - asn_INTEGER2long(request->scopeOfDelete, &scopeOfDelete); + asn_INTEGER2long(request->scopeOfDelete, &scopeOfDelete); MmsDevice* device = MmsServer_getDevice(connection->server); if (scopeOfDelete == DeleteNamedVariableListRequest__scopeOfDelete_specific) { - MmsError serviceError = MMS_ERROR_NONE; + MmsError serviceError = MMS_ERROR_NONE; int numberMatched = 0; int numberDeleted = 0; @@ -160,86 +160,86 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, for (i = 0; i < numberItems; i++) { if (request->listOfVariableListName->list.array[i]->present == ObjectName_PR_domainspecific) { - char domainName[65]; - char listName[65]; + char domainName[65]; + char listName[65]; - mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.domainspecific.domainId, - domainName, 65); + mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.domainspecific.domainId, + domainName, 65); - mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.domainspecific.itemId, - listName, 65); + mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.domainspecific.itemId, + listName, 65); - MmsDomain* domain = MmsDevice_getDomain(device, domainName); + MmsDomain* domain = MmsDevice_getDomain(device, domainName); - if (domain != NULL) { + if (domain != NULL) { - MmsNamedVariableList variableList = MmsDomain_getNamedVariableList(domain, listName); + MmsNamedVariableList variableList = MmsDomain_getNamedVariableList(domain, listName); - if (variableList != NULL) { - numberMatched++; + if (variableList != NULL) { + numberMatched++; - if (MmsNamedVariableList_isDeletable(variableList)) { + if (MmsNamedVariableList_isDeletable(variableList)) { - MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_DOMAIN_SPECIFIC, domain, listName, connection); + MmsError deleteError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_DELETE, MMS_DOMAIN_SPECIFIC, domain, listName, connection); - if (deleteError == MMS_ERROR_NONE) { - MmsDomain_deleteNamedVariableList(domain, listName); - numberDeleted++; - } - else - serviceError = deleteError; - } - } - } + if (deleteError == MMS_ERROR_NONE) { + MmsDomain_deleteNamedVariableList(domain, listName); + numberDeleted++; + } + else + serviceError = deleteError; + } + } + } } else if (request->listOfVariableListName->list.array[i]->present == ObjectName_PR_aaspecific) { - char listName[65]; + char listName[65]; - mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.aaspecific, - listName, 65); + mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.aaspecific, + listName, 65); MmsNamedVariableList variableList = MmsServerConnection_getNamedVariableList(connection, listName); if (variableList != NULL) { numberMatched++; - MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection); + MmsError deleteError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_DELETE, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection); if (deleteError == MMS_ERROR_NONE) { - numberDeleted++; - MmsServerConnection_deleteNamedVariableList(connection, listName); + numberDeleted++; + MmsServerConnection_deleteNamedVariableList(connection, listName); } else - serviceError = deleteError; + serviceError = deleteError; } } else if (request->listOfVariableListName->list.array[i]->present == ObjectName_PR_vmdspecific) { - char listName[65]; + char listName[65]; - mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.vmdspecific, - listName, 65); + mmsMsg_copyAsn1IdentifierToStringBuffer(request->listOfVariableListName->list.array[i]->choice.vmdspecific, + listName, 65); - MmsNamedVariableList variableList = mmsServer_getNamedVariableListWithName(device->namedVariableLists, listName); + MmsNamedVariableList variableList = mmsServer_getNamedVariableListWithName(device->namedVariableLists, listName); - if (variableList != NULL) { - numberMatched++; + if (variableList != NULL) { + numberMatched++; - MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_VMD_SPECIFIC, NULL, listName, connection); + MmsError deleteError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_DELETE, MMS_VMD_SPECIFIC, NULL, listName, connection); - if (deleteError == MMS_ERROR_NONE) { - numberDeleted++; - mmsServer_deleteVariableList(device->namedVariableLists, listName); - } - else - serviceError = deleteError; - } + if (deleteError == MMS_ERROR_NONE) { + numberDeleted++; + mmsServer_deleteVariableList(device->namedVariableLists, listName); + } + else + serviceError = deleteError; + } } } if (serviceError == MMS_ERROR_NONE) - createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted); + createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted); else - createServiceErrorDeleteVariableLists(invokeId, response, serviceError, numberDeleted); + createServiceErrorDeleteVariableLists(invokeId, response, serviceError, numberDeleted); } else { mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); @@ -247,61 +247,61 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, exit_function: - asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); - return; + return; } static void createDefineNamedVariableListResponse(uint32_t invokeId, ByteBuffer* response) { - uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize((uint32_t) invokeId) + 2; + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize((uint32_t) invokeId) + 2; - uint32_t confirmedResponsePDUSize = 2 + invokeIdSize; + uint32_t confirmedResponsePDUSize = 2 + invokeIdSize; - int bufPos = 0; - uint8_t* buffer = response->buffer; + int bufPos = 0; + uint8_t* buffer = response->buffer; - bufPos = BerEncoder_encodeTL(0xa1, confirmedResponsePDUSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0xa1, confirmedResponsePDUSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x02, invokeIdSize - 2, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize - 2, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x8b, 0, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x8b, 0, buffer, bufPos); - response->size = bufPos; + response->size = bufPos; } static bool checkIfVariableExists(MmsDevice* device, MmsAccessSpecifier* accessSpecifier) { - (void)device; + (void)device; - if (accessSpecifier->domain == NULL) - return false; + if (accessSpecifier->domain == NULL) + return false; - MmsVariableSpecification* variableSpec = - MmsDomain_getNamedVariable(accessSpecifier->domain, accessSpecifier->variableName); + MmsVariableSpecification* variableSpec = + MmsDomain_getNamedVariable(accessSpecifier->domain, accessSpecifier->variableName); - if (variableSpec == NULL) - return false; + if (variableSpec == NULL) + return false; - if (accessSpecifier->arrayIndex != -1) { - if (variableSpec->type != MMS_ARRAY) - return false; + if (accessSpecifier->arrayIndex != -1) { + if (variableSpec->type != MMS_ARRAY) + return false; - if (accessSpecifier->arrayIndex >= variableSpec->typeSpec.array.elementCount) - return false; + if (accessSpecifier->arrayIndex >= variableSpec->typeSpec.array.elementCount) + return false; - if (accessSpecifier->componentName != NULL) { - variableSpec = variableSpec->typeSpec.array.elementTypeSpec; + if (accessSpecifier->componentName != NULL) { + variableSpec = variableSpec->typeSpec.array.elementTypeSpec; - if (MmsVariableSpecification_getNamedVariableRecursive(variableSpec, accessSpecifier->componentName) == NULL) - return false; - } - } + if (MmsVariableSpecification_getNamedVariableRecursive(variableSpec, accessSpecifier->componentName) == NULL) + return false; + } + } - return true; + return true; } static MmsNamedVariableList @@ -309,20 +309,20 @@ createNamedVariableList(MmsServer server, MmsDomain* domain, MmsDevice* device, DefineNamedVariableListRequest_t* request, char* variableListName, MmsError* mmsError) { - MmsNamedVariableList namedVariableList = NULL; + MmsNamedVariableList namedVariableList = NULL; int variableCount = request->listOfVariable.list.count; if (DEBUG_MMS_SERVER) - printf("MMS_SERVER: create-named-variable-list (%i variable(s) | max=%i)\n", variableCount, server->maxDataSetEntries); + printf("MMS_SERVER: create-named-variable-list (%i variable(s) | max=%i)\n", variableCount, server->maxDataSetEntries); #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; + *mmsError = MMS_ERROR_DEFINITION_OTHER; + goto exit_function; } namedVariableList = MmsNamedVariableList_create(domain, variableListName, true); @@ -342,8 +342,8 @@ createNamedVariableList(MmsServer server, MmsDomain* domain, MmsDevice* device, if (request->listOfVariable.list.array[i]->alternateAccess->list.count != 1) { - if (DEBUG_MMS_SERVER) - printf("MMS_SERVER: create-named-variable list - only one alternate access specification allowed!\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: create-named-variable list - only one alternate access specification allowed!\n"); MmsNamedVariableList_destroy(namedVariableList); namedVariableList = NULL; @@ -355,31 +355,31 @@ createNamedVariableList(MmsServer server, MmsDomain* domain, MmsDevice* device, request->listOfVariable.list.array[i]->alternateAccess->list.array[0]; if ((alternateAccess->present == AlternateAccess__Member_PR_unnamed) - && (alternateAccess->choice.unnamed->present == AlternateAccessSelection_PR_selectAlternateAccess) - && (alternateAccess->choice.unnamed->choice.selectAlternateAccess.accessSelection.present == - AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_index)) + && (alternateAccess->choice.unnamed->present == AlternateAccessSelection_PR_selectAlternateAccess) + && (alternateAccess->choice.unnamed->choice.selectAlternateAccess.accessSelection.present == + AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_index)) { asn_INTEGER2long(&(alternateAccess->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.index), &arrayIndex); if (alternateAccess->choice.unnamed->choice.selectAlternateAccess.alternateAccess) { - componentNameBuf[0] = 0; + componentNameBuf[0] = 0; - componentName = mmsMsg_getComponentNameFromAlternateAccess( - alternateAccess->choice.unnamed->choice.selectAlternateAccess.alternateAccess, - componentNameBuf, 0); + componentName = mmsMsg_getComponentNameFromAlternateAccess( + alternateAccess->choice.unnamed->choice.selectAlternateAccess.alternateAccess, + componentNameBuf, 0); + } + else { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: create-named-variable-list - component specification is missing!\n"); } - else { - if (DEBUG_MMS_SERVER) - printf("MMS_SERVER: create-named-variable-list - component specification is missing!\n"); - } } else if ((alternateAccess->present == AlternateAccess__Member_PR_unnamed) - && (alternateAccess->choice.unnamed->present == AlternateAccessSelection_PR_selectAccess) - && (alternateAccess->choice.unnamed->choice.selectAccess.present == AlternateAccessSelection__selectAccess_PR_index) - ) + && (alternateAccess->choice.unnamed->present == AlternateAccessSelection_PR_selectAccess) + && (alternateAccess->choice.unnamed->choice.selectAccess.present == AlternateAccessSelection__selectAccess_PR_index) + ) { - asn_INTEGER2long(&(alternateAccess->choice.unnamed->choice.selectAccess.choice.index), &arrayIndex); + asn_INTEGER2long(&(alternateAccess->choice.unnamed->choice.selectAccess.choice.index), &arrayIndex); } else { MmsNamedVariableList_destroy(namedVariableList); @@ -392,16 +392,16 @@ createNamedVariableList(MmsServer server, MmsDomain* domain, MmsDevice* device, if (varSpec->present == VariableSpecification_PR_name) { - char variableName[65]; - char domainId[65]; + char variableName[65]; + char domainId[65]; - StringUtils_createStringFromBufferInBuffer(variableName, - varSpec->choice.name.choice.domainspecific.itemId.buf, - varSpec->choice.name.choice.domainspecific.itemId.size); + StringUtils_createStringFromBufferInBuffer(variableName, + varSpec->choice.name.choice.domainspecific.itemId.buf, + varSpec->choice.name.choice.domainspecific.itemId.size); - StringUtils_createStringFromBufferInBuffer(domainId, - varSpec->choice.name.choice.domainspecific.domainId.buf, - varSpec->choice.name.choice.domainspecific.domainId.size); + StringUtils_createStringFromBufferInBuffer(domainId, + varSpec->choice.name.choice.domainspecific.domainId.buf, + varSpec->choice.name.choice.domainspecific.domainId.size); MmsDomain* elementDomain = MmsDevice_getDomain(device, domainId); @@ -413,24 +413,24 @@ createNamedVariableList(MmsServer server, MmsDomain* domain, MmsDevice* device, accessSpecifier.componentName = componentName; if (DEBUG_MMS_SERVER) - printf("MMS SERVER: add named variable list entry: %s/%s(%li)%s\n", MmsDomain_getName(elementDomain), variableName, arrayIndex, componentName); + printf("MMS SERVER: add named variable list entry: %s/%s(%li)%s\n", MmsDomain_getName(elementDomain), variableName, arrayIndex, componentName); /* check if element exists */ if (checkIfVariableExists(device, &accessSpecifier) == true) { - MmsNamedVariableListEntry variable = - MmsNamedVariableListEntry_create(accessSpecifier); + MmsNamedVariableListEntry variable = + MmsNamedVariableListEntry_create(accessSpecifier); - MmsNamedVariableList_addVariable(namedVariableList, variable); + MmsNamedVariableList_addVariable(namedVariableList, variable); } else { - if (DEBUG_MMS_SERVER) - printf("MMS_SERVER: failed - variable does not exist!\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: failed - variable does not exist!\n"); - MmsNamedVariableList_destroy(namedVariableList); - namedVariableList = NULL; - i = variableCount; /* exit loop after freeing loop variables */ - *mmsError = MMS_ERROR_DEFINITION_OBJECT_UNDEFINED; + MmsNamedVariableList_destroy(namedVariableList); + namedVariableList = NULL; + i = variableCount; /* exit loop after freeing loop variables */ + *mmsError = MMS_ERROR_DEFINITION_OBJECT_UNDEFINED; } } else { @@ -453,7 +453,7 @@ mmsServer_handleDefineNamedVariableListRequest( uint32_t invokeId, ByteBuffer* response) { - (void)bufPos; + (void)bufPos; DefineNamedVariableListRequest_t* request = 0; @@ -462,177 +462,177 @@ mmsServer_handleDefineNamedVariableListRequest( asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu, (void**) &mmsPdu, buffer, maxBufPos); if (rval.code != RC_OK) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); - goto exit_free_struct; + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + goto exit_free_struct; } - if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) && - (mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present - == ConfirmedServiceRequest_PR_defineNamedVariableList)) - { - request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.defineNamedVariableList); - } - else { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); - goto exit_free_struct; - } + if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) && + (mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present + == ConfirmedServiceRequest_PR_defineNamedVariableList)) + { + request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.defineNamedVariableList); + } + else { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + goto exit_free_struct; + } - MmsDevice* device = MmsServer_getDevice(connection->server); + MmsDevice* device = MmsServer_getDevice(connection->server); - if (request->variableListName.present == ObjectName_PR_domainspecific) { + if (request->variableListName.present == ObjectName_PR_domainspecific) { - char domainName[65]; + char domainName[65]; - if (request->variableListName.choice.domainspecific.domainId.size > 64) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); - goto exit_free_struct; - } + if (request->variableListName.choice.domainspecific.domainId.size > 64) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + goto exit_free_struct; + } - StringUtils_createStringFromBufferInBuffer(domainName, - request->variableListName.choice.domainspecific.domainId.buf, - request->variableListName.choice.domainspecific.domainId.size); + StringUtils_createStringFromBufferInBuffer(domainName, + request->variableListName.choice.domainspecific.domainId.buf, + request->variableListName.choice.domainspecific.domainId.size); - MmsDomain* domain = MmsDevice_getDomain(device, domainName); + MmsDomain* domain = MmsDevice_getDomain(device, domainName); - if (domain == NULL) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); - goto exit_free_struct; - } + if (domain == NULL) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + goto exit_free_struct; + } #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) - if (LinkedList_size(domain->namedVariableLists) < connection->server->maxDomainSpecificDataSets) { + if (LinkedList_size(domain->namedVariableLists) < connection->server->maxDomainSpecificDataSets) { #else - if (LinkedList_size(domain->namedVariableLists) < CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS) { + 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) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); - goto exit_free_struct; - } - - StringUtils_createStringFromBufferInBuffer(variableListName, - request->variableListName.choice.domainspecific.itemId.buf, - request->variableListName.choice.domainspecific.itemId.size); - - if (MmsDomain_getNamedVariableList(domain, variableListName) != NULL) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); - } - else { - MmsError mmsError; - - MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, domain, device, - request, variableListName, &mmsError); - - if (namedVariableList != NULL) { - - mmsError = mmsServer_callVariableListChangedHandler(true, MMS_DOMAIN_SPECIFIC, domain, variableListName, connection); - - if (mmsError == MMS_ERROR_NONE) { - MmsDomain_addNamedVariableList(domain, namedVariableList); - createDefineNamedVariableListResponse(invokeId, response); - } - else { - MmsNamedVariableList_destroy(namedVariableList); - mmsMsg_createServiceErrorPdu(invokeId, response, mmsError); - } - } - else - mmsMsg_createServiceErrorPdu(invokeId, response, mmsError); - } + char variableListName[65]; + + if (request->variableListName.choice.domainspecific.itemId.size > 64) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + goto exit_free_struct; + } + + StringUtils_createStringFromBufferInBuffer(variableListName, + request->variableListName.choice.domainspecific.itemId.buf, + request->variableListName.choice.domainspecific.itemId.size); + + if (MmsDomain_getNamedVariableList(domain, variableListName) != NULL) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + } + else { + MmsError mmsError; + + MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, domain, device, + request, variableListName, &mmsError); + + if (namedVariableList != NULL) { + + mmsError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_CREATE, MMS_DOMAIN_SPECIFIC, domain, variableListName, connection); + + if (mmsError == MMS_ERROR_NONE) { + MmsDomain_addNamedVariableList(domain, namedVariableList); + createDefineNamedVariableListResponse(invokeId, response); + } + else { + MmsNamedVariableList_destroy(namedVariableList); + mmsMsg_createServiceErrorPdu(invokeId, response, mmsError); + } + } + else + mmsMsg_createServiceErrorPdu(invokeId, response, mmsError); + } } else - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); } 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) { + if (LinkedList_size(connection->namedVariableLists) < connection->server->maxAssociationSpecificDataSets) { #else - if (LinkedList_size(connection->namedVariableLists) < CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS) { + if (LinkedList_size(connection->namedVariableLists) < CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS) { #endif - char variableListName[65]; - - if (request->variableListName.choice.aaspecific.size > 64) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); - goto exit_free_struct; - } - - StringUtils_createStringFromBufferInBuffer(variableListName, - request->variableListName.choice.aaspecific.buf, - request->variableListName.choice.aaspecific.size); - - if (MmsServerConnection_getNamedVariableList(connection, variableListName) != NULL) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); - } - else { - MmsError mmsError; - - MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, NULL, device, - request, variableListName, &mmsError); - - if (namedVariableList != NULL) { - - if (mmsServer_callVariableListChangedHandler(true, MMS_ASSOCIATION_SPECIFIC, NULL, variableListName, connection) == MMS_ERROR_NONE) { - MmsServerConnection_addNamedVariableList(connection, namedVariableList); - createDefineNamedVariableListResponse(invokeId, response); - } - else { - MmsNamedVariableList_destroy(namedVariableList); - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); - } - - } - else - mmsMsg_createServiceErrorPdu(invokeId, response, mmsError); - } - } - else - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); + char variableListName[65]; + + if (request->variableListName.choice.aaspecific.size > 64) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + goto exit_free_struct; + } + + StringUtils_createStringFromBufferInBuffer(variableListName, + request->variableListName.choice.aaspecific.buf, + request->variableListName.choice.aaspecific.size); + + if (MmsServerConnection_getNamedVariableList(connection, variableListName) != NULL) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + } + else { + MmsError mmsError; + + MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, NULL, device, + request, variableListName, &mmsError); + + if (namedVariableList != NULL) { + + if (mmsServer_callVariableListChangedHandler(MMS_VARLIST_CREATE, MMS_ASSOCIATION_SPECIFIC, NULL, variableListName, connection) == MMS_ERROR_NONE) { + MmsServerConnection_addNamedVariableList(connection, namedVariableList); + createDefineNamedVariableListResponse(invokeId, response); + } + else { + MmsNamedVariableList_destroy(namedVariableList); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + } + + } + else + mmsMsg_createServiceErrorPdu(invokeId, response, mmsError); + } + } + else + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); } else if (request->variableListName.present == ObjectName_PR_vmdspecific) { - LinkedList vmdScopeNVLs = MmsDevice_getNamedVariableLists(connection->server->device); + LinkedList vmdScopeNVLs = MmsDevice_getNamedVariableLists(connection->server->device); - if (LinkedList_size(vmdScopeNVLs) < CONFIG_MMS_MAX_NUMBER_OF_VMD_SPECIFIC_DATA_SETS) { + if (LinkedList_size(vmdScopeNVLs) < CONFIG_MMS_MAX_NUMBER_OF_VMD_SPECIFIC_DATA_SETS) { - char variableListName[65]; + char variableListName[65]; - if (request->variableListName.choice.vmdspecific.size > 64) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); - goto exit_free_struct; - } + if (request->variableListName.choice.vmdspecific.size > 64) { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + goto exit_free_struct; + } - StringUtils_createStringFromBufferInBuffer(variableListName, - request->variableListName.choice.vmdspecific.buf, - request->variableListName.choice.vmdspecific.size); + StringUtils_createStringFromBufferInBuffer(variableListName, + request->variableListName.choice.vmdspecific.buf, + request->variableListName.choice.vmdspecific.size); - if (mmsServer_getNamedVariableListWithName(MmsDevice_getNamedVariableLists(connection->server->device), variableListName) != NULL) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); - } - else { - MmsError mmsError; + if (mmsServer_getNamedVariableListWithName(MmsDevice_getNamedVariableLists(connection->server->device), variableListName) != NULL) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + } + else { + MmsError mmsError; - MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, NULL, device, - request, variableListName, &mmsError); + MmsNamedVariableList namedVariableList = createNamedVariableList(connection->server, NULL, device, + request, variableListName, &mmsError); - if (namedVariableList != NULL) { - if (mmsServer_callVariableListChangedHandler(true, MMS_VMD_SPECIFIC, NULL, variableListName, connection) - == MMS_ERROR_NONE) { - LinkedList_add(vmdScopeNVLs, (void*) namedVariableList); + if (namedVariableList != NULL) { + if (mmsServer_callVariableListChangedHandler(MMS_VARLIST_CREATE, MMS_VMD_SPECIFIC, NULL, variableListName, connection) + == MMS_ERROR_NONE) { + LinkedList_add(vmdScopeNVLs, (void*) namedVariableList); - createDefineNamedVariableListResponse(invokeId, response); - } - else { - MmsNamedVariableList_destroy(namedVariableList); - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); - } + createDefineNamedVariableListResponse(invokeId, response); + } + else { + MmsNamedVariableList_destroy(namedVariableList); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + } - } - } - } + } + } + } } else mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED); @@ -640,7 +640,7 @@ mmsServer_handleDefineNamedVariableListRequest( exit_free_struct: asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); - return; + return; } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ @@ -670,7 +670,7 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, varListResponse->listOfVariable.list.size = variableCount; varListResponse->listOfVariable.list.array = (struct GetNamedVariableListAttributesResponse__listOfVariable__Member**) - GLOBAL_CALLOC(variableCount, sizeof(void*)); + GLOBAL_CALLOC(variableCount, sizeof(void*)); LinkedList variable = LinkedList_getNext(variables); @@ -679,7 +679,7 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, MmsNamedVariableListEntry variableEntry = (MmsNamedVariableListEntry) variable->data; varListResponse->listOfVariable.list.array[i] = (struct GetNamedVariableListAttributesResponse__listOfVariable__Member*) - GLOBAL_CALLOC(1, sizeof(struct GetNamedVariableListAttributesResponse__listOfVariable__Member)); + GLOBAL_CALLOC(1, sizeof(struct GetNamedVariableListAttributesResponse__listOfVariable__Member)); varListResponse->listOfVariable.list.array[i]->variableSpecification.present = VariableSpecification_PR_name; @@ -702,12 +702,12 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, domainspecific.itemId.size = strlen(variableEntry->variableName); if (variableEntry->arrayIndex != -1) { - varListResponse->listOfVariable.list.array[i]->alternateAccess = - mmsClient_createAlternateAccessIndexComponent(variableEntry->arrayIndex, variableEntry->componentName); + varListResponse->listOfVariable.list.array[i]->alternateAccess = + mmsClient_createAlternateAccessIndexComponent(variableEntry->arrayIndex, variableEntry->componentName); } else if (variableEntry->componentName) { - varListResponse->listOfVariable.list.array[i]->alternateAccess = - mmsClient_createAlternateAccessComponent(variableEntry->componentName); + varListResponse->listOfVariable.list.array[i]->alternateAccess = + mmsClient_createAlternateAccessComponent(variableEntry->componentName); } variable = LinkedList_getNext(variable); @@ -718,9 +718,9 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); if (res.encoded == -1) - return false; + return false; else - return true; + return true; } void @@ -736,44 +736,53 @@ mmsServer_handleGetNamedVariableListAttributesRequest( (void**) &request, buffer + bufPos, maxBufPos - bufPos); if (rval.code != RC_OK) { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); - goto exit_function; + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + goto exit_function; } if (request->present == ObjectName_PR_domainspecific) { - char domainName[65]; - char itemName[65]; + char domainName[65]; + char itemName[65]; - if ((request->choice.domainspecific.domainId.size > 64) || - (request->choice.domainspecific.itemId.size > 64)) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); - goto exit_function; - } + if ((request->choice.domainspecific.domainId.size > 64) || + (request->choice.domainspecific.itemId.size > 64)) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + goto exit_function; + } - StringUtils_createStringFromBufferInBuffer(domainName, request->choice.domainspecific.domainId.buf, - request->choice.domainspecific.domainId.size); + StringUtils_createStringFromBufferInBuffer(domainName, request->choice.domainspecific.domainId.buf, + request->choice.domainspecific.domainId.size); - StringUtils_createStringFromBufferInBuffer(itemName, request->choice.domainspecific.itemId.buf, - request->choice.domainspecific.itemId.size); + StringUtils_createStringFromBufferInBuffer(itemName, request->choice.domainspecific.itemId.buf, + request->choice.domainspecific.itemId.size); MmsDevice* mmsDevice = MmsServer_getDevice(connection->server); MmsDomain* domain = MmsDevice_getDomain(mmsDevice, domainName); if (domain != NULL) { - MmsNamedVariableList variableList = - MmsDomain_getNamedVariableList(domain, itemName); + MmsNamedVariableList varList = MmsDomain_getNamedVariableList(domain, itemName); - if (variableList != NULL) { + if (varList) { - if (createGetNamedVariableListAttributesResponse(invokeId, response, variableList) == false) { + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_DOMAIN_SPECIFIC, domain, varList->name, connection); - /* encoding failed - probably because buffer size is too small for message */ - ByteBuffer_setSize(response, 0); + if (accessError == MMS_ERROR_NONE) { + if (createGetNamedVariableListAttributesResponse(invokeId, response, varList) == false) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + /* encoding failed - probably because buffer size is too small for message */ + ByteBuffer_setSize(response, 0); + + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + } + } + else { + if (DEBUG_MMS_SERVER) printf("MMS get named variable list attributes: variable list %s access error: %i\n", varList->name, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); } + } else mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); @@ -785,43 +794,77 @@ mmsServer_handleGetNamedVariableListAttributesRequest( #if (MMS_DYNAMIC_DATA_SETS == 1) else if (request->present == ObjectName_PR_aaspecific) { - char listName[65]; + char listName[65]; - if (request->choice.aaspecific.size > 64) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); - goto exit_function; - } + if (request->choice.aaspecific.size > 64) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + goto exit_function; + } + + StringUtils_createStringFromBufferInBuffer(listName, request->choice.aaspecific.buf, + request->choice.aaspecific.size); + + MmsNamedVariableList varList = MmsServerConnection_getNamedVariableList(connection, listName); + + if (varList) { + + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_ASSOCIATION_SPECIFIC, NULL, varList->name, connection); - StringUtils_createStringFromBufferInBuffer(listName, request->choice.aaspecific.buf, - request->choice.aaspecific.size); + if (accessError == MMS_ERROR_NONE) { + if (createGetNamedVariableListAttributesResponse(invokeId, response, varList) == false) { - MmsNamedVariableList varList = MmsServerConnection_getNamedVariableList(connection, listName); + /* encoding failed - probably because buffer size is too small for message */ + ByteBuffer_setSize(response, 0); - if (varList != NULL) - createGetNamedVariableListAttributesResponse(invokeId, response, varList); - else - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + } + } + else { + if (DEBUG_MMS_SERVER) printf("MMS get named variable list attributes: variable list %s access error: %i\n", varList->name, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } + } + else + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ else if (request->present == ObjectName_PR_vmdspecific) { - char listName[65]; + char listName[65]; + + if (request->choice.vmdspecific.size > 64) { + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + goto exit_function; + } + + StringUtils_createStringFromBufferInBuffer(listName, request->choice.vmdspecific.buf, + request->choice.vmdspecific.size); + + MmsDevice* mmsDevice = MmsServer_getDevice(connection->server); - if (request->choice.vmdspecific.size > 64) { - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); - goto exit_function; - } + MmsNamedVariableList varList = mmsServer_getNamedVariableListWithName(mmsDevice->namedVariableLists, listName); - StringUtils_createStringFromBufferInBuffer(listName, request->choice.vmdspecific.buf, - request->choice.vmdspecific.size); + if (varList) { - MmsDevice* mmsDevice = MmsServer_getDevice(connection->server); + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_VMD_SPECIFIC, NULL, varList->name, connection); - MmsNamedVariableList varList = mmsServer_getNamedVariableListWithName(mmsDevice->namedVariableLists, listName); + if (accessError == MMS_ERROR_NONE) { + if (createGetNamedVariableListAttributesResponse(invokeId, response, varList) == false) { - if (varList != NULL) - createGetNamedVariableListAttributesResponse(invokeId, response, varList); - else - mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + /* encoding failed - probably because buffer size is too small for message */ + ByteBuffer_setSize(response, 0); + + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + } + } + else { + if (DEBUG_MMS_SERVER) printf("MMS get named variable list attributes: variable list %s access error: %i\n", varList->name, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } + } + else + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } else { mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index 2638bc0b..2b33dc41 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-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -695,13 +695,9 @@ createNamedVariableListResponse(MmsServerConnection connection, MmsNamedVariable LinkedList /**/ values = LinkedList_create(); LinkedList variables = MmsNamedVariableList_getVariableList(namedList); - int variableCount = LinkedList_size(variables); - - int i; - LinkedList variable = LinkedList_getNext(variables); - for (i = 0; i < variableCount; i++) { + while (variable) { MmsNamedVariableListEntry variableListEntry = (MmsNamedVariableListEntry) variable->data; @@ -768,9 +764,21 @@ handleReadNamedVariableListRequest( else { MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr); - if (namedList != NULL) { - createNamedVariableListResponse(connection, namedList, invokeId, response, isSpecWithResult(read), - &accessSpec); + if (namedList) + { + + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_READ, MMS_DOMAIN_SPECIFIC, domain, namedList->name, connection); + + if (accessError == MMS_ERROR_NONE) { + createNamedVariableListResponse(connection, namedList, invokeId, response, isSpecWithResult(read), + &accessSpec); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS read: named variable list %s access error: %i\n", nameIdStr, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } + } else { if (DEBUG_MMS_SERVER) printf("MMS read: named variable list %s not found!\n", nameIdStr); @@ -791,14 +799,24 @@ handleReadNamedVariableListRequest( mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { - VarAccessSpec accessSpec; + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_READ, MMS_VMD_SPECIFIC, NULL, namedList->name, connection); + + if (accessError == MMS_ERROR_NONE) { + VarAccessSpec accessSpec; - accessSpec.isNamedVariableList = true; - accessSpec.specific = 0; - accessSpec.domainId = NULL; - accessSpec.itemId = listName; + accessSpec.isNamedVariableList = true; + accessSpec.specific = 0; + accessSpec.domainId = NULL; + accessSpec.itemId = listName; + + createNamedVariableListResponse(connection, namedList, invokeId, response, isSpecWithResult(read), &accessSpec); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS read: VMD specific named variable list %s access error: %i\n", listName, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } - createNamedVariableListResponse(connection, namedList, invokeId, response, isSpecWithResult(read), &accessSpec); } } #if (MMS_DYNAMIC_DATA_SETS == 1) @@ -815,14 +833,25 @@ handleReadNamedVariableListRequest( if (namedList == NULL) mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { - VarAccessSpec accessSpec; - accessSpec.isNamedVariableList = true; - accessSpec.specific = 2; - accessSpec.domainId = NULL; - accessSpec.itemId = listName; + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_READ, MMS_ASSOCIATION_SPECIFIC, NULL, namedList->name, connection); + + if (accessError == MMS_ERROR_NONE) { - createNamedVariableListResponse(connection, namedList, invokeId, response, isSpecWithResult(read), &accessSpec); + VarAccessSpec accessSpec; + + accessSpec.isNamedVariableList = true; + accessSpec.specific = 2; + accessSpec.domainId = NULL; + accessSpec.itemId = listName; + + createNamedVariableListResponse(connection, namedList, invokeId, response, isSpecWithResult(read), &accessSpec); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS read: association specific named variable list %s access error: %i\n", listName, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } } } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 3905b52d..b9d4c798 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -1,7 +1,7 @@ /* * mms_server.c * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -95,13 +95,13 @@ MmsServer_create(MmsDevice* device, TLSConfiguration tlsConfiguration) if (self->valueCaches == NULL) goto exit_error; - self->isLocked = false; - self->transmitBuffer = ByteBuffer_create(NULL, CONFIG_MMS_MAXIMUM_PDU_SIZE); if (self->transmitBuffer == NULL) goto exit_error; + self->blockRequests = false; + #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) self->fileServiceEnabled = true; self->dynamicVariableListServiceEnabled = true; @@ -359,10 +359,24 @@ MmsServer_installConnectionHandler(MmsServer self, MmsConnectionHandler connecti } void -MmsServer_installVariableListChangedHandler(MmsServer self, MmsNamedVariableListChangedHandler handler, void* parameter) +MmsServer_installVariableListAccessHandler(MmsServer self, MmsNamedVariableListAccessHandler handler, void* parameter) +{ + self->variableListAccessHandler = handler; + self->variableListAccessHandlerParameter = parameter; +} + +void +MmsServer_installReadJournalHandler(MmsServer self, MmsReadJournalHandler handler, void* parameter) { - self->variableListChangedHandler = handler; - self->variableListChangedHandlerParameter = parameter; + self->readJournalHandler = handler; + self->readJournalHandlerParameter = parameter; +} + +void +MmsServer_installGetNameListHandler(MmsServer self, MmsGetNameListHandler handler, void* parameter) +{ + self->getNameListHandler = handler; + self->getNameListHandlerParameter = parameter; } void @@ -565,7 +579,6 @@ exit_function: return value; } - MmsDevice* MmsServer_getDevice(MmsServer self) { @@ -820,4 +833,8 @@ MmsServer_getFilesystemBasepath(MmsServer self) #endif } - +void +MmsServer_ignoreClientRequests(MmsServer self, bool enable) +{ + self->blockRequests = enable; +} diff --git a/src/mms/iso_mms/server/mms_server_common.c b/src/mms/iso_mms/server/mms_server_common.c index 62f9e0d5..455a26ec 100644 --- a/src/mms/iso_mms/server/mms_server_common.c +++ b/src/mms/iso_mms/server/mms_server_common.c @@ -391,7 +391,7 @@ mmsServer_getNamedVariableListWithName(LinkedList namedVariableLists, const char LinkedList element = LinkedList_getNext(namedVariableLists); - while (element != NULL) { + while (element) { MmsNamedVariableList varList = (MmsNamedVariableList) element->data; if (strcmp(MmsNamedVariableList_getName(varList), variableListName) == 0) { @@ -405,14 +405,13 @@ mmsServer_getNamedVariableListWithName(LinkedList namedVariableLists, const char return variableList; } - void mmsServer_deleteVariableList(LinkedList namedVariableLists, char* variableListName) { LinkedList previousElement = namedVariableLists; LinkedList element = LinkedList_getNext(namedVariableLists); - while (element != NULL ) { + while (element) { MmsNamedVariableList varList = (MmsNamedVariableList) element->data; if (strcmp(MmsNamedVariableList_getName(varList), variableListName) @@ -428,5 +427,3 @@ mmsServer_deleteVariableList(LinkedList namedVariableLists, char* variableListNa element = LinkedList_getNext(element); } } - - diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index b14ecc3a..4f914081 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -665,6 +665,9 @@ handleConfirmedResponsePdu( static inline void MmsServerConnection_parseMessage(MmsServerConnection self, ByteBuffer* message, ByteBuffer* response) { + if (self->server->blockRequests) + return; + uint8_t* buffer = message->buffer; if (message->size < 2) diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index 11306e4d..096bc30f 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -1,7 +1,7 @@ /* * mms_write_service.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -430,8 +430,19 @@ handleWriteNamedVariableListRequest( else { MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr); - if (namedList != NULL) { - createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + if (namedList) { + + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_WRITE, MMS_DOMAIN_SPECIFIC, domain, namedList->name, connection); + + if (accessError == MMS_ERROR_NONE) { + createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS write: named variable list %s access error: %i\n", nameIdStr, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } + } else { if (DEBUG_MMS_SERVER) printf("MMS write: named variable list %s not found!\n", nameIdStr); @@ -448,8 +459,19 @@ handleWriteNamedVariableListRequest( MmsNamedVariableList namedList = mmsServer_getNamedVariableListWithName(connection->server->device->namedVariableLists, listName); - if (namedList != NULL) { - createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + if (namedList) { + + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_WRITE, MMS_VMD_SPECIFIC, NULL, namedList->name, connection); + + if (accessError == MMS_ERROR_NONE) { + createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS write: vmd specific named variable list %s access error: %i\n", namedList->name, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } + } else { if (DEBUG_MMS_SERVER) printf("MMS write: vmd specific named variable list %s not found!\n", listName); @@ -465,8 +487,19 @@ handleWriteNamedVariableListRequest( MmsNamedVariableList namedList = MmsServerConnection_getNamedVariableList(connection, listName); - if (namedList != NULL) { - createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + if (namedList) { + + MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_WRITE, MMS_ASSOCIATION_SPECIFIC, NULL, namedList->name, connection); + + if (accessError == MMS_ERROR_NONE) { + createWriteNamedVariableListResponse(connection, writeRequest, invokeId, namedList, response); + } + else { + if (DEBUG_MMS_SERVER) printf("MMS write: association specific named variable list %s access error: %i\n", namedList->name, accessError); + + mmsMsg_createServiceErrorPdu(invokeId, response, accessError); + } + } else { if (DEBUG_MMS_SERVER) printf("MMS write: association specific named variable list %s not found!\n", listName); @@ -479,6 +512,58 @@ handleWriteNamedVariableListRequest( } +static MmsVariableSpecification* +getComponent(MmsServerConnection connection, MmsDomain* domain, AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable, char* variableName) +{ + MmsVariableSpecification* retValue = NULL; + + if (mmsServer_isComponentAccess(alternateAccess)) { + Identifier_t component = + alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component; + + if (component.size > 129) + goto exit_function; + + if (namedVariable->type == MMS_STRUCTURE) { + + int i; + + for (i = 0; i < namedVariable->typeSpec.structure.elementCount; i++) { + + if ((int) strlen(namedVariable->typeSpec.structure.elements[i]->name) + == component.size) { + if (!strncmp(namedVariable->typeSpec.structure.elements[i]->name, + (char*) component.buf, component.size)) + { + if (strlen(variableName) + component.size < 199) { + + StringUtils_appendString(variableName, 200, "$"); + + /* here we need strncat because component.buf is not null terminated! */ + strncat(variableName, (const char*)component.buf, (size_t)component.size); + + if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess + != NULL) { + retValue = + getComponent(connection, domain, + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess, + namedVariable->typeSpec.structure.elements[i], + variableName); + } + else { + retValue = namedVariable->typeSpec.structure.elements[i]; + } + } + } + } + } + } + } + +exit_function: + return retValue; +} + void mmsServer_handleWriteRequest( MmsServerConnection connection, @@ -603,15 +688,21 @@ mmsServer_handleWriteRequest( AlternateAccess_t* alternateAccess = varSpec->alternateAccess; if (alternateAccess != NULL) { - if (variable->type != MMS_ARRAY) { + + if ((variable->type == MMS_STRUCTURE) && (mmsServer_isComponentAccess(alternateAccess) == false)) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; } - if (!mmsServer_isIndexAccess(alternateAccess)) { + if ((variable->type == MMS_ARRAY) && (mmsServer_isIndexAccess(alternateAccess) == false)) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; continue; } + + if (variable->type != MMS_ARRAY && variable->type != MMS_STRUCTURE) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + continue; + } } Data_t* dataElement = writeRequest->listOfData.list.array[i]; @@ -628,72 +719,84 @@ mmsServer_handleWriteRequest( if (domain == NULL) domain = (MmsDomain*) device; - MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); - - if (cachedArray == NULL) { - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; - goto end_of_main_loop; - } - - int index = mmsServer_getLowIndex(alternateAccess); - int numberOfElements = mmsServer_getNumberOfElements(alternateAccess); - - if (numberOfElements == 0) { /* select single array element with index */ - - MmsValue* elementValue = MmsValue_getElement(cachedArray, index); + if (mmsServer_isIndexAccess(alternateAccess)) { + MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); - if (elementValue == NULL) { + if (cachedArray == NULL) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; goto end_of_main_loop; } - if (mmsServer_isAccessToArrayComponent(alternateAccess)) { - MmsVariableSpecification* namedVariable = MmsDomain_getNamedVariable(domain, nameIdStr); + int index = mmsServer_getLowIndex(alternateAccess); + int numberOfElements = mmsServer_getNumberOfElements(alternateAccess); - if (namedVariable) { - elementValue = mmsServer_getComponentOfArrayElement(alternateAccess, namedVariable, elementValue); - } + if (numberOfElements == 0) { /* select single array element with index */ + + MmsValue* elementValue = MmsValue_getElement(cachedArray, index); - if ((namedVariable == NULL) || (elementValue == NULL)) { - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; + if (elementValue == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; goto end_of_main_loop; } - } - if (MmsValue_update(elementValue, value) == false) { - accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; - goto end_of_main_loop; - } - } - else { /* select sub-array with start-index and number-of-elements */ + if (mmsServer_isAccessToArrayComponent(alternateAccess)) { + MmsVariableSpecification* namedVariable = MmsDomain_getNamedVariable(domain, nameIdStr); - if (MmsValue_getType(value) != MMS_ARRAY) { - accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; - goto end_of_main_loop; - } + if (namedVariable) { + elementValue = mmsServer_getComponentOfArrayElement(alternateAccess, namedVariable, elementValue); + } - int elementNo; - - for (elementNo = 0; elementNo < numberOfElements; elementNo++) { - MmsValue* newElement = MmsValue_getElement(value, elementNo); - MmsValue* elementValue = MmsValue_getElement(cachedArray, index++); + if ((namedVariable == NULL) || (elementValue == NULL)) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; + goto end_of_main_loop; + } + } - if ((elementValue == NULL) || (newElement == NULL) ) { + if (MmsValue_update(elementValue, value) == false) { accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; goto end_of_main_loop; } + } + else { /* select sub-array with start-index and number-of-elements */ - if (MmsValue_update(elementValue, newElement) == false) { + if (MmsValue_getType(value) != MMS_ARRAY) { accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; goto end_of_main_loop; } + int elementNo; + + for (elementNo = 0; elementNo < numberOfElements; elementNo++) { + MmsValue* newElement = MmsValue_getElement(value, elementNo); + MmsValue* elementValue = MmsValue_getElement(cachedArray, index++); + + if ((elementValue == NULL) || (newElement == NULL) ) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + + if (MmsValue_update(elementValue, newElement) == false) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + } } - } - accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; - goto end_of_main_loop; + accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; + goto end_of_main_loop; + } + else if (mmsServer_isComponentAccess(alternateAccess)) { + variable = getComponent(connection, domain, alternateAccess, variable, nameIdStr); + if (variable == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; + goto end_of_main_loop; + } + } + else { + accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; + goto end_of_main_loop; + } } /* Check for correct type */ @@ -723,7 +826,7 @@ mmsServer_handleWriteRequest( goto exit_function; } - exit_function: +exit_function: MmsServer_unlockModel(connection->server); diff --git a/src/mms/iso_server/iso_connection.c b/src/mms/iso_server/iso_connection.c index 7f997a9b..795b0636 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-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -160,6 +160,8 @@ IsoConnection_removeFromHandleSet(const IsoConnection self, HandleSet handles) void IsoConnection_callTickHandler(IsoConnection self) { + CotpConnection_flushBuffer(self->cotpConnection); + if (self->tickHandler) { self->tickHandler(self->handlerParameter); } @@ -171,10 +173,7 @@ IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread) #if (CONFIG_MMS_SINGLE_THREADED != 1) if (isSingleThread == false) { - /* call tick handler */ - if (self->tickHandler) { - self->tickHandler(self->handlerParameter); - } + IsoConnection_callTickHandler(self); if (Handleset_waitReady(self->handleSet, 10) < 1) goto exit_function; diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index 62c43ebb..a2daa6ad 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -455,7 +455,6 @@ exit_function: return success; } - /** used by single and multi-threaded versions * * \param isSingleThread when true server is running in single thread or non-thread mode diff --git a/src/r_session/r_session_crypto_mbedtls.c b/src/r_session/r_session_crypto_mbedtls.c index 0fe577cb..761ebd59 100644 --- a/src/r_session/r_session_crypto_mbedtls.c +++ b/src/r_session/r_session_crypto_mbedtls.c @@ -3,7 +3,7 @@ * * Implementation of RSessionCrypto interface using mbedtls * - * Copyright 2013-2021 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/third_party/mbedtls/README b/third_party/mbedtls/README index 04597eb4..b6171f07 100644 --- a/third_party/mbedtls/README +++ b/third_party/mbedtls/README @@ -1,9 +1,9 @@ README ------ -For TLS support with mbedtls download the source tarball of version 2.16.x and extract here in the subfolder +For TLS support with mbedtls download the source tarball of version 2.28.x and extract here in the subfolder -mbedtls-2.16 +mbedtls-2.28 -After extracting of the archive you may have to rename the folder to match the exact name "mbedtls-2.16". Otherwise the build system will not find the library. +After extracting of the archive you may have to rename the folder to match the exact name "mbedtls-2.28". Otherwise the build system will not find the library.