Merge branch 'v1.6_develop' into v1.6_develop_rgoose_sntp

v1.6_develop_rgoose_sntp
Michael Zillgith 2 years ago
commit da62fe854f

@ -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

@ -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

@ -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'

@ -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

@ -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

@ -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

@ -775,7 +775,10 @@ namespace IEC61850
Dispose (true);
}
~ControlObject()
{
Dispose (false);
}
}
}

@ -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);
}
/// <summary>
/// Set the local IP address and port to be used by the client
/// </summary>
/// <param name="localIpAddress">the local IP address or hostname</param>
/// <param name="localPort">the local TCP port to use. When 0 the OS will chose the TCP port to use.</param>
public void SetLocalAddress(string localIpAddress, int localPort)
{
IedConnection_setLocalAddress(connection, localIpAddress, localPort);
}
/// <summary>
/// Set the local IP address to be used by the client
/// </summary>
/// <param name="localIpAddress">the local IP address or hostname</param>
public void SetLocalAddress(string localIpAddress)
{
IedConnection_setLocalAddress(connection, localIpAddress, 0);
}
/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
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<MmsDataAccessError>();
accessResultList.Add(dataAccessError);
element = LinkedList_getNext(element);

@ -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
}
}
/// <summary>
/// Set the time quality for all timestamps internally generated by this IedServer instance
/// </summary>
/// <param name="leapSecondKnown">set/unset leap seconds known flag</param>
/// <param name="clockFailure">set/unset clock failure flag</param>
/// <param name="clockNotSynchronized">set/unset clock not synchronized flag</param>
/// <param name="subsecondPrecision">set the subsecond precision (number of significant bits of the fractionOfSecond part of the time stamp)</param>
public void SetTimeQuality(bool leapSecondKnown, bool clockFailure, bool clockNotSynchronized, int subsecondPrecision)
{
IedServer_setTimeQuality(self, leapSecondKnown, clockFailure, clockNotSynchronized, subsecondPrecision);
}
}
}

@ -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;
/// </summary>
namespace IEC61850
{
namespace TLS
{
/// <summary>
/// A container for TLS configuration and certificates.
/// </summary>
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;
}
}
}
}
}
}
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;
}
}
/// <summary>
/// TLS version used by the connection
/// </summary>
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");
}
}
}
}
/// <summary>
/// Peer IP address and TCP port of the TLS connection
/// </summary>
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");
}
}
}
}
/// <summary>
/// TLS certificate used by the peer
/// </summary>
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");
}
}
}
}
}
/// <summary>
/// TLS security event handler
/// </summary>
/// <param name="parameter">user provided context paramter to be passed to the handler</param>
/// <param name="eventLevel">severity level of the event</param>
/// <param name="eventCode">code to identify the event type</param>
/// <param name="message">text message describing the event</param>
/// <param name="connection">TLS connection that caused the event</param>
public delegate void TLSEventHandler(object parameter, TLSEventLevel eventLevel, TLSEventCode eventCode, string message, TLSConnection connection);
/// <summary>
/// A container for TLS configuration and certificates.
/// </summary>
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;
}
}
}
}
}
}

@ -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)

@ -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;
}

@ -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);
}

@ -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;
}

@ -102,7 +102,3 @@ main(int argc, char **argv)
return 0;
}

@ -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
)

@ -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

@ -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 <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#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() */

File diff suppressed because it is too large Load Diff

@ -0,0 +1,311 @@
/*
* static_model.h
*
* automatically generated from simpleIO_direct_control.cid
*/
#ifndef STATIC_MODEL_H_
#define STATIC_MODEL_H_
#include <stdlib.h>
#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_ */

@ -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");

@ -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");

@ -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");

@ -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");

@ -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");

@ -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");

@ -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");

@ -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");

@ -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");

@ -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);

@ -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) {

@ -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

@ -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")

@ -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;

@ -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

@ -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
*

@ -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);
}

@ -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";
}
}

@ -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)

@ -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)

@ -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);

@ -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;

@ -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;

@ -29,7 +29,7 @@
#include "mms_client_connection.h"
#include "ied_connection_private.h"
#if _MSC_VER
#ifdef _MSC_VER
#define snprintf _snprintf
#endif

@ -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;

@ -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

@ -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 /*<char*>*/
@ -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 /* <char*> */
@ -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

@ -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

@ -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 */

@ -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)
*

@ -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 {

@ -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);
/**@}*/
/**@}*/

@ -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;
};

@ -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,

@ -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_ */

@ -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

@ -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;
}

@ -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);
}

@ -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)
{

@ -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)
{

@ -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,

@ -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
}
}
}

@ -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
}
/*

@ -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);
}
}

@ -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;

@ -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*

@ -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 <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
* See COPYING file for the complete license text.
*/
#include "libiec61850_platform_includes.h"

@ -1,7 +1,7 @@
/*
* ber_decoder.c
*
* Copyright 2013 Michael Zillgith
* Copyright 2013-2022 Michael Zillgith
*
* This file is part of libIEC61850.
*

@ -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;

@ -1,7 +1,7 @@
/*
* ber_integer.c
*
* Copyright 2013-2020 Michael Zillgith
* Copyright 2013-2022 Michael Zillgith
*
* This file is part of libIEC61850.
*

@ -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
*

@ -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
*

@ -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

@ -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
*

@ -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_ */

@ -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,

@ -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_ */

@ -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_ */

@ -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;
}

@ -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)
{

@ -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) {

@ -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. */

@ -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, &parameter);
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;

@ -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,

@ -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;

@ -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) */

@ -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;

@ -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 <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* 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);
}

File diff suppressed because it is too large Load Diff

@ -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 /*<MmsValue>*/ 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) */

@ -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;
}

@ -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);
}
}

@ -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)

@ -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);

@ -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;

@ -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

@ -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.
*

@ -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.

Loading…
Cancel
Save