Merge branch 'v1.6_develop' of bitbucket.com:mz-automation/libiec61850 into v1.6_develop

pull/521/head
Michael Zillgith 1 year ago
commit 2866a52c88

@ -170,7 +170,7 @@
/* allow application to set server identity (for MMS identity service) at runtime */
#define CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY 1
/* Force memory alignment - required for some platforms (required more memory for buffered reporting) */
/* Force memory alignment - required for some platforms (requires more memory for buffered reporting) */
#define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1
/* overwrite default results for MMS identify service */
@ -240,10 +240,24 @@
/* enable to configure MmsServer at runtime */
#define CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME 1
/* Define the default number of the maximum outstanding calls allowed by the caller (client) */
#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING 5
/* Define the default number of the maximum outstanding calls allowed by the calling endpoint (server) */
#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED 5
/************************************************************************************
* Check configuration for consistency - DO NOT MODIFY THIS PART!
************************************************************************************/
#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING < 1)
#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING must be greater than 0!"
#endif
#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED < 1)
#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED must be greater than 0!"
#endif
#if (MMS_JOURNAL_SERVICE != 1)
#if (CONFIG_IEC61850_LOG_SERVICE == 1)

@ -160,7 +160,7 @@
/* allow application to set server identity (for MMS identity service) at runtime */
#define CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY 1
/* Force memory alignment - required for some platforms (required more memory for buffered reporting) */
/* Force memory alignment - required for some platforms (requires more memory for buffered reporting) */
#define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1
/* default results for MMS identify service */
@ -229,10 +229,24 @@
/* enable to configure MmsServer at runtime */
#define CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME 1
/* Define the default number of the maximum outstanding calls allowed by the caller (client) */
#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING 5
/* Define the default number of the maximum outstanding calls allowed by the calling endpoint (server) */
#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED 5
/************************************************************************************
* Check configuration for consistency - DO NOT MODIFY THIS PART!
************************************************************************************/
#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING < 1)
#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING must be greater than 0!"
#endif
#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED < 1)
#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED must be greater than 0!"
#endif
#if (MMS_JOURNAL_SERVICE != 1)
#if (CONFIG_IEC61850_LOG_SERVICE == 1)

@ -431,6 +431,9 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern UInt32 IedConnection_getRequestTimeout(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedConnection_setMaxOutstandingCalls(IntPtr self, int calling, int called);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedConnection_setTimeQuality(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool leapSecondKnown, [MarshalAs(UnmanagedType.I1)] bool clockFailure, [MarshalAs(UnmanagedType.I1)] bool clockNotSynchronized, int subsecondPrecision);
@ -559,6 +562,12 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedConnection_installStateChangedHandler(IntPtr connection, InternalStateChangedHandler handler, IntPtr parameter);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedServer_ignoreReadAccess(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool ignore);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedServer_ignoreClientRequests(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool ignore);
/*********************
* Async functions
*********************/
@ -815,6 +824,16 @@ namespace IEC61850
}
}
/// <summary>
/// Set the maximum number outstanding calls allowed for this connection
/// </summary>
/// <param name="calling">the maximum outstanding calls allowed by the caller (client)</param>
/// <param name="called">the maximum outstanding calls allowed by the called endpoint (server)</param>
public void SetMaxOutstandingCalls(int calling, int called)
{
IedConnection_setMaxOutstandingCalls(connection, calling, called);
}
/// <summary>
/// Gets or sets the maximum size if a PDU (has to be set before calling connect!).
/// </summary>
@ -1175,6 +1194,9 @@ namespace IEC61850
private static List<MmsJournalEntry> WrapNativeLogQueryResult(IntPtr linkedList)
{
if (linkedList == IntPtr.Zero)
return null;
List<MmsJournalEntry> journalEntries = new List<MmsJournalEntry>();
IntPtr element = LinkedList_getNext(linkedList);
@ -1945,6 +1967,24 @@ namespace IEC61850
}
}
/// <summary>
/// Ignore all MMS requests from clients (for testing purposes)
/// </summary>
/// <param name="ignore">when true all requests from clients will be ignored</param>
public void IgnoreClientRequests(bool ignore)
{
IedServer_ignoreClientRequests(connection, ignore);
}
/// <summary>
/// Temporarily ignore read requests (for testing purposes)
/// </summary>
/// <param name="ignore">true to ignore read requests, false to handle read requests.</param>
public void IgnoreReadAccess(bool ignore)
{
IedServer_ignoreReadAccess(connection, ignore);
}
/// <summary>
/// Read the values of a data set (GetDataSetValues service).
/// </summary>
@ -2238,22 +2278,27 @@ namespace IEC61850
GetDataSetDirectoryHandler handler = callbackInfo.Item1;
object handlerParameter = callbackInfo.Item2;
IntPtr element = LinkedList_getNext(dataSetDirectory);
handle.Free();
List<string> newList = new List<string>();
List<string> newList = null;
while (element != IntPtr.Zero)
if (dataSetDirectory != IntPtr.Zero)
{
string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element));
newList = new List<string>();
newList.Add(dataObject);
IntPtr element = LinkedList_getNext(dataSetDirectory);
element = LinkedList_getNext(element);
}
while (element != IntPtr.Zero)
{
string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element));
LinkedList_destroy(dataSetDirectory);
newList.Add(dataObject);
element = LinkedList_getNext(element);
}
LinkedList_destroy(dataSetDirectory);
}
handler.Invoke(invokeId, handlerParameter, (IedClientError)err, newList, isDeletable);
}
@ -2428,11 +2473,9 @@ namespace IEC61850
dataSet = new DataSet(nativeDataSet);
}
handler(invokeId, handlerParameter, clientError, dataSet);
}
public delegate void ReadDataSetHandler(UInt32 invokeId,object parameter,IedClientError err,DataSet dataSet);
/// <summary>
@ -2566,7 +2609,6 @@ namespace IEC61850
{
handler(invokeId, handlerParameter, clientError, null, moreFollows);
}
}
/// <summary>
@ -2632,7 +2674,6 @@ namespace IEC61850
return GetLogicalDeviceDataSetsAsync(null, ldName, continueAfter, handler, parameter);
}
public UInt32 GetLogicalDeviceDataSetsAsync(List<string> result, string ldName, string continueAfter, GetNameListHandler handler, object parameter)
{
int error;
@ -2977,6 +3018,19 @@ namespace IEC61850
IED_STATE_CLOSING = 3
}
public static class IedClientErrorExtension
{
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr IedClientError_toString(int err);
public static string ToString(this IedClientError err)
{
string stringVal = Marshal.PtrToStringAnsi(IedClientError_toString((int)err));
return stringVal;
}
}
/// <summary>
/// Error codes for client side functions
/// </summary>
@ -3062,6 +3116,9 @@ namespace IEC61850
/** Received an invalid response message from the server */
IED_ERROR_MALFORMED_MESSAGE = 34,
/** Service was not executed because required resource is still in use */
IED_ERROR_OBJECT_CONSTRAINT_CONFLICT = 35,
/** Service not implemented */
IED_ERROR_SERVICE_NOT_IMPLEMENTED = 98,

@ -1,7 +1,7 @@
/*
* IEC61850CommonAPI.cs
*
* Copyright 2014-2017 Michael Zillgith
* Copyright 2014-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -454,6 +454,9 @@ namespace IEC61850
SetByMmsUtcTime (mmsUtcTime);
}
/// <summary>
/// Initializes a new instance of the <see cref="IEC61850CommonAPI.Timestamp"/> class.
/// </summary>
public Timestamp()
{
self = Timestamp_create ();
@ -461,6 +464,19 @@ namespace IEC61850
responsibleForDeletion = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="IEC61850CommonAPI.Timestamp"/> class.
/// </summary>
public Timestamp(Timestamp other) : this()
{
SetTimeInSeconds (other.GetTimeInSeconds ());
SetSubsecondPrecision (other.GetSubsecondPrecision ());
SetFractionOfSecondPart (other.GetFractionOfSecondPart ());
SetLeapSecondKnow(other.IsLeapSecondKnown());
SetClockFailure(other.HasClockFailure());
SetClockNotSynchronized(other.IsClockNotSynchronized());
}
public Timestamp(byte[] value)
{
self = Timestamp_createFromByteArray (value);

@ -1874,6 +1874,9 @@ namespace IEC61850
[return: MarshalAs(UnmanagedType.I1)]
static extern bool ControlAction_getInterlockCheck(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr ControlAction_getT(IntPtr self);
private IntPtr self;
private IedServer.ControlHandlerInfo info;
private IedServer iedServer;
@ -2003,6 +2006,18 @@ namespace IEC61850
{
return ControlAction_getInterlockCheck(self);
}
/// <summary>
/// Gets the time (paramter T) of the control action
/// </summary>
public Timestamp GetT()
{
IntPtr tPtr = ControlAction_getT(self);
Timestamp t = new Timestamp(tPtr, false);
return new Timestamp(t);
}
}
public delegate void GoCBEventHandler(MmsGooseControlBlock goCB, int cbEvent, object parameter);
@ -2247,6 +2262,10 @@ namespace IEC61850
static extern void IedServer_handleWriteAccessForComplexAttribute(IntPtr self, IntPtr dataAttribute,
InternalWriteAccessHandler handler, IntPtr parameter);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedServer_handleWriteAccessForDataObject(IntPtr self, IntPtr dataObject, int fc,
InternalWriteAccessHandler handler, IntPtr parameter);
public delegate void ConnectionIndicationHandler(IedServer iedServer, ClientConnection clientConnection, bool connected, object parameter);
private ConnectionIndicationHandler connectionHandler = null;
@ -2713,12 +2732,27 @@ namespace IEC61850
}
}
private void AddHandlerInfoForDataObjectRecursive(DataObject dataObject, FunctionalConstraint fc, WriteAccessHandler handler, object parameter, InternalWriteAccessHandler internalHandler)
{
foreach (ModelNode child in dataObject.GetChildren())
{
if (child is DataAttribute && (child as DataAttribute).FC == fc)
{
AddHandlerInfoForDataAttributeRecursive(child as DataAttribute, handler, parameter, internalHandler);
}
else if (child is DataObject)
{
AddHandlerInfoForDataObjectRecursive(child as DataObject, fc, handler, parameter, internalHandler);
}
}
}
/// <summary>
/// Install a WriteAccessHandler for a data attribute and for all sub data attributes
/// </summary>
/// This instructs the server to monitor write attempts by MMS clients to specific
/// data attributes.If a client tries to write to the monitored data attribute the
/// handler is invoked.The handler can decide if the write access will be allowed
/// data attributes. If a client tries to write to the monitored data attribute the
/// handler is invoked. The handler can decide if the write access will be allowed
/// or denied.If a WriteAccessHandler is set for a specific data attribute - the
/// default write access policy will not be performed for that data attribute.
/// <remarks>
@ -2738,6 +2772,27 @@ namespace IEC61850
IedServer_handleWriteAccessForComplexAttribute(self, dataAttr.self, internalHandler, IntPtr.Zero);
}
/// <summary>
/// Install a WriteAccessHandler for a data object and for all sub data objects and sub data attributes that have the same functional constraint
/// </summary>
/// This instructs the server to monitor write attempts by MMS clients to specific
/// data attributes. If a client tries to write to the monitored data attribute the
/// handler is invoked. The handler can decide if the write access will be allowed
/// or denied. If a WriteAccessHandler is set the
/// default write access policy will not be performed for the matching data attributes.
/// <param name="dataObject">the data object to monitor</param>
/// <param name="fc">the functional constraint (FC) to monitor</param>
/// <param name="handler">the callback function that is invoked if a client tries to write to a monitored data attribute that is a child of the data object.</param>
/// <param name="parameter">a user provided parameter that is passed to the WriteAccessHandler when called.</param>
public void HandleWriteAccessForDataObject(DataObject dataObj, FunctionalConstraint fc, WriteAccessHandler handler, object parameter)
{
InternalWriteAccessHandler internalHandler = new InternalWriteAccessHandler(WriteAccessHandlerImpl);
AddHandlerInfoForDataObjectRecursive(dataObj, fc, handler, parameter, internalHandler);
IedServer_handleWriteAccessForDataObject(self, dataObj.self, (int)fc, internalHandler, IntPtr.Zero);
}
/// <summary>
/// Set the defualt write access policy for a specific FC. The default policy is applied when no handler is installed for a data attribute
/// </summary>

@ -36,7 +36,7 @@ namespace example2
}
catch (IedConnectionException e)
{
Console.WriteLine("IED connection excepion: " + e.Message);
Console.WriteLine("IED connection exception: " + e.Message + " err: " + e.GetIedClientError().ToString());
}
// release all resources - do NOT use the object after this call!!

@ -93,7 +93,7 @@ SCL.xsd">
</DataSet>
<SampledValueControl name="MSVCB01" datSet="PhsMeas1"
smvID="xxxxMUnn01" smpRate="80" nofASDU="1" confRev="1">
smvID="xxxxMUnn01" smpRate="80" nofASDU="1" confRev="1" smpMod="SmpPerPeriod">
<SmvOpts refreshTime="false" sampleSynchronized="true"
security="false" dataRef="false" />

@ -23,6 +23,38 @@ sigint_handler(int signalId)
running = 0;
}
static const char*
ACSIClassToStr(ACSIClass acsiClass)
{
switch (acsiClass)
{
case ACSI_CLASS_BRCB:
return "BRCB";
case ACSI_CLASS_URCB:
return "URCB";
case ACSI_CLASS_GoCB:
return "GoCB";
case ACSI_CLASS_SGCB:
return "SGCB";
case ACSI_CLASS_LCB:
return "LCB";
case ACSI_CLASS_GsCB:
return "GsCB";
case ACSI_CLASS_LOG:
return "log";
case ACSI_CLASS_DATA_SET:
return "dataset";
case ACSI_CLASS_DATA_OBJECT:
return "data-object";
case ACSI_CLASS_MSVCB:
return "MSVCB";
case ACSI_CLASS_USVCB:
return "USVCB";
default:
return "unknown";
}
}
static ControlHandlerResult
controlHandlerForBinaryOutput(ControlAction action, void* parameter, MmsValue* value, bool test)
{
@ -65,7 +97,6 @@ controlHandlerForBinaryOutput(ControlAction action, void* parameter, MmsValue* v
return CONTROL_RESULT_OK;
}
static void
connectionHandler (IedServer self, ClientConnection connection, bool connected, void* parameter)
{
@ -79,43 +110,41 @@ connectionHandler (IedServer self, ClientConnection connection, bool connected,
* 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)
controlBlockAccessHandler(void* parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice* ld, LogicalNode* ln, const char* objectName, const char* subObjectName, IedServer_ControlBlockAccessType accessType)
{
printf("RCB: %s access: %s\n", ReportControlBlock_getName(rcb), operation == RCB_EVENT_GET_PARAMETER ? "READ" : "WRITE");
printf("%s %s access %s/%s.%s.%s\n", ACSIClassToStr(acsiClass), accessType == IEC61850_CB_ACCESS_TYPE_WRITE ? "write" : "read", ld->name, ln->name, objectName, subObjectName);
if (operation == RCB_EVENT_GET_PARAMETER) {
return true;
/* allow only read access to LCBs */
if (acsiClass == ACSI_CLASS_LCB) {
if (accessType == IEC61850_CB_ACCESS_TYPE_READ)
return true;
else
return false;
}
else {
/* change to false to disallow write access to control block */
return true;
/* allow only read access to BRCBs */
if (acsiClass == ACSI_CLASS_BRCB) {
if (accessType == IEC61850_CB_ACCESS_TYPE_READ)
return true;
else
return false;
}
}
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;
}
/* to all other control blocks allow read and write access */
return true;
}
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("RCB: %s event: %i\n", ReportControlBlock_getName(rcb), event);
printf(" param: %s\n", parameterName);
printf(" result: %i\n", serviceError);
}
if (event == RCB_EVENT_ENABLE) {
printf("RCB: %s event: %i\n", ReportControlBlock_getName(rcb), event);
char* rptId = ReportControlBlock_getRptID(rcb);
printf(" rptID: %s\n", rptId);
char* dataSet = ReportControlBlock_getDataSet(rcb);
@ -137,15 +166,39 @@ dataSetAccessHandler(void* parameter, ClientConnection connection, IedServer_Dat
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);
printf("Read access to %s/%s.%s\n", ld->name, ln->name, dataObject ? dataObject->name : "-");
if (!strcmp(ln->name, "GGIO1") && !strcmp(dataObject->name, "AnIn1")) {
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
if (dataObject == NULL) {
if (!strcmp(ln->name, "GGIO1")) {
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
}
else {
if (!strcmp(ln->name, "GGIO1") && !strcmp(dataObject->name, "AnIn1")) {
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
}
return DATA_ACCESS_ERROR_SUCCESS;
}
static bool
listObjectsAccessHandler(void* parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice* ld, LogicalNode* ln, const char* objectName, const char* subObjectName, FunctionalConstraint fc)
{
if (subObjectName)
printf("list objects access[2] to %s/%s.%s.%s [acsi-class: %s(%i)] [FC=%s]\n", ld->name, ln ? ln->name : "-", objectName, subObjectName, ACSIClassToStr(acsiClass), acsiClass, FunctionalConstraint_toString(fc));
else
printf("list objects access[2] to %s/%s.%s [acsi-class: %s(%i)] [FC=%s]\n", ld->name, ln ? ln->name : "-", objectName, ACSIClassToStr(acsiClass), acsiClass, FunctionalConstraint_toString(fc));
// if (acsiClass == ACSI_CLASS_BRCB) {
// return true;
// }
// return false;
return true;
}
static bool
directoryAccessHandler(void* parameter, ClientConnection connection, IedServer_DirectoryCategory category, LogicalDevice* logicalDevice)
{
@ -231,15 +284,12 @@ main(int argc, char** argv)
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);
/* Install handler to control access to control blocks (RCBs, LCBs, GoCBs, SVCBs, SGCBs)*/
IedServer_setControlBlockAccessHandler(iedServer, controlBlockAccessHandler, 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.
@ -258,6 +308,9 @@ main(int argc, char** argv)
IedServer_setDirectoryAccessHandler(iedServer, directoryAccessHandler, NULL);
/* control visibility of data objects in directory (get-name-list) and variable description (get-variable-access-attributes) services */
IedServer_setListObjectsAccessHandler(iedServer, listObjectsAccessHandler, NULL);
/* MMS server will be instructed to start listening for client connections. */
IedServer_start(iedServer, tcpPort);

@ -1,7 +1,7 @@
/*
* static_model.c
*
* automatically generated from simpleIO_direct_control.cid
* automatically generated from ../../examples/server_example_basic_io/simpleIO_direct_control.cid
*/
#include "static_model.h"
@ -2169,7 +2169,15 @@ ReportControlBlock iedModel_GenericIO_LLN0_report9 = {&iedModel_GenericIO_LLN0,
extern LogControlBlock iedModel_GenericIO_LLN0_lcb0;
extern LogControlBlock iedModel_GenericIO_LLN0_lcb1;
LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, &iedModel_GenericIO_LLN0_lcb1};
LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", NULL, NULL, 3, 0, true, true, NULL};
extern Log iedModel_GenericIO_LLN0_log0;
extern Log iedModel_GenericIO_LLN0_log1;
Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1};
Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL};
IedModel iedModel = {
@ -2180,8 +2188,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
&iedModel_GenericIO_LLN0_lcb0,
&iedModel_GenericIO_LLN0_log0,
initializeValues
};

@ -115,26 +115,62 @@ 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)
static const char*
ACSIClassToStr(ACSIClass acsiClass)
{
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;
switch (acsiClass)
{
case ACSI_CLASS_BRCB:
return "BRCB";
case ACSI_CLASS_URCB:
return "URCB";
case ACSI_CLASS_GoCB:
return "GoCB";
case ACSI_CLASS_SGCB:
return "SGCB";
case ACSI_CLASS_LCB:
return "LCB";
case ACSI_CLASS_GsCB:
return "GsCB";
case ACSI_CLASS_LOG:
return "log";
case ACSI_CLASS_DATA_SET:
return "dataset";
case ACSI_CLASS_DATA_OBJECT:
return "data-object";
case ACSI_CLASS_MSVCB:
return "MSVCB";
case ACSI_CLASS_USVCB:
return "USVCB";
default:
return "unknown";
}
else {
return false;
}
bool
controlBlockAccessHandler(void* parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice* ld, LogicalNode* ln, const char* objectName, const char* subObjectName, IedServer_ControlBlockAccessType accessType)
{
printf("Access to %s %s/%s.%s\n", ACSIClassToStr(acsiClass), ld->name, ln ? ln->name : "-", objectName);
if (acsiClass == ACSI_CLASS_LCB) {
if (accessType == IEC61850_CB_ACCESS_TYPE_READ)
return true;
else
return false;
}
return true;
}
static bool
logAccessHandler(void* parameter, const char* logName, ClientConnection connection)
listObjectsAccessHandler(void* parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice* ld, LogicalNode* ln, const char* objectName, const char* subObjectName, FunctionalConstraint fc)
{
printf("Access to log %s from %s\n", logName, ClientConnection_getPeerAddress(connection));
if (subObjectName)
printf("list objects access[2] to %s/%s.%s.%s [acsi-class: %s(%i)] [FC=%s]\n", ld->name, ln ? ln->name : "-", objectName, subObjectName, ACSIClassToStr(acsiClass), acsiClass, FunctionalConstraint_toString(fc));
else
printf("list objects access[2] to %s/%s.%s [acsi-class: %s(%i)] [FC=%s]\n", ld->name, ln ? ln->name : "-", objectName, ACSIClassToStr(acsiClass), acsiClass, FunctionalConstraint_toString(fc));
return false;
return true;
}
int
@ -169,9 +205,7 @@ main(int argc, char** argv)
IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL);
IedServer_setLCBAccessHandler(iedServer, lcbAccessHandler, NULL);
IedServer_setLogAccessHandler(iedServer, logAccessHandler, NULL);
IedServer_setControlBlockAccessHandler(iedServer, controlBlockAccessHandler, NULL);
LogStorage statusLog = SqliteLogStorage_createInstance("log_status.db");
@ -204,6 +238,7 @@ main(int argc, char** argv)
LogStorage_getEntries(statusLog, 0, Hal_getTimeInMs(), entryCallback, (LogEntryDataCallback) entryDataCallback, NULL);
#endif
IedServer_setListObjectsAccessHandler(iedServer, listObjectsAccessHandler, NULL);
/* MMS server will be instructed to start listening to client connections. */
IedServer_start(iedServer, tcpPort);

@ -1,7 +1,7 @@
/*
* ethernet_linux.c
*
* Copyright 2013-2022 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -57,7 +57,8 @@ EthernetHandleSet_new(void)
{
EthernetHandleSet result = (EthernetHandleSet) GLOBAL_MALLOC(sizeof(struct sEthernetHandleSet));
if (result != NULL) {
if (result != NULL)
{
result->handles = NULL;
result->nhandles = 0;
}
@ -68,8 +69,8 @@ EthernetHandleSet_new(void)
void
EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock)
{
if (self != NULL && sock != NULL) {
if (self != NULL && sock != NULL)
{
int i = self->nhandles++;
self->handles = realloc(self->handles, self->nhandles * sizeof(struct pollfd));
@ -82,12 +83,14 @@ EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock)
void
EthernetHandleSet_removeSocket(EthernetHandleSet self, const EthernetSocket sock)
{
if ((self != NULL) && (sock != NULL)) {
if ((self != NULL) && (sock != NULL))
{
int i;
for (i = 0; i < self->nhandles; i++) {
if (self->handles[i].fd == sock->rawSocket) {
for (i = 0; i < self->nhandles; i++)
{
if (self->handles[i].fd == sock->rawSocket)
{
memmove(&self->handles[i], &self->handles[i+1], sizeof(struct pollfd) * (self->nhandles - i - 1));
self->nhandles--;
return;
@ -128,7 +131,8 @@ getInterfaceIndex(int sock, const char* deviceName)
strncpy(ifr.ifr_name, deviceName, IFNAMSIZ - 1);
if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) {
if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Failed to get interface index");
return -1;
@ -157,7 +161,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr)
int i;
for(i = 0; i < 6; i++ )
for (i = 0; i < 6; i++ )
{
addr[i] = (unsigned char)buffer.ifr_hwaddr.sa_data[i];
}
@ -166,106 +170,112 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr)
EthernetSocket
Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress)
{
EthernetSocket ethernetSocket = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket));
EthernetSocket self = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket));
if (ethernetSocket) {
ethernetSocket->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (self)
{
self->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (ethernetSocket->rawSocket == -1) {
if (self->rawSocket == -1)
{
if (DEBUG_SOCKET)
printf("Error creating raw socket!\n");
GLOBAL_FREEMEM(ethernetSocket);
GLOBAL_FREEMEM(self);
return NULL;
}
ethernetSocket->socketAddress.sll_family = PF_PACKET;
ethernetSocket->socketAddress.sll_protocol = htons(ETH_P_ALL);
self->socketAddress.sll_family = PF_PACKET;
self->socketAddress.sll_protocol = htons(ETH_P_ALL);
int ifcIdx = getInterfaceIndex(ethernetSocket->rawSocket, interfaceId);
int ifcIdx = getInterfaceIndex(self->rawSocket, interfaceId);
if (ifcIdx == -1) {
Ethernet_destroySocket(ethernetSocket);
if (ifcIdx == -1)
{
Ethernet_destroySocket(self);
return NULL;
}
ethernetSocket->socketAddress.sll_ifindex = ifcIdx;
self->socketAddress.sll_ifindex = ifcIdx;
ethernetSocket->socketAddress.sll_hatype = ARPHRD_ETHER;
ethernetSocket->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST;
self->socketAddress.sll_hatype = ARPHRD_ETHER;
self->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST;
ethernetSocket->socketAddress.sll_halen = ETH_ALEN;
self->socketAddress.sll_halen = ETH_ALEN;
memset(ethernetSocket->socketAddress.sll_addr, 0, 8);
memset(self->socketAddress.sll_addr, 0, 8);
if (destAddress != NULL)
memcpy(ethernetSocket->socketAddress.sll_addr, destAddress, 6);
memcpy(self->socketAddress.sll_addr, destAddress, 6);
self->isBind = false;
ethernetSocket->isBind = false;
Ethernet_setMode(self, ETHERNET_SOCKET_MODE_PROMISC);
}
return ethernetSocket;
return self;
}
void
Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode)
Ethernet_setMode(EthernetSocket self, EthernetSocketMode mode)
{
if (ethSocket) {
if (mode == ETHERNET_SOCKET_MODE_PROMISC) {
if (self)
{
if (mode == ETHERNET_SOCKET_MODE_PROMISC)
{
struct ifreq ifr;
if (ioctl (ethSocket->rawSocket, SIOCGIFFLAGS, &ifr) == -1)
if (ioctl (self->rawSocket, SIOCGIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Problem getting device flags");
return;
}
ifr.ifr_flags |= IFF_PROMISC;
if (ioctl (ethSocket->rawSocket, SIOCSIFFLAGS, &ifr) == -1)
if (ioctl (self->rawSocket, SIOCSIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Setting device to promiscuous mode failed");
return;
}
}
else if (mode == ETHERNET_SOCKET_MODE_ALL_MULTICAST) {
else if (mode == ETHERNET_SOCKET_MODE_ALL_MULTICAST)
{
struct ifreq ifr;
if (ioctl (ethSocket->rawSocket, SIOCGIFFLAGS, &ifr) == -1)
if (ioctl (self->rawSocket, SIOCGIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Problem getting device flags");
return;
}
ifr.ifr_flags |= IFF_ALLMULTI;
if (ioctl (ethSocket->rawSocket, SIOCSIFFLAGS, &ifr) == -1)
if (ioctl (self->rawSocket, SIOCSIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Setting device to promiscuous mode failed");
return;
}
}
else if (mode == ETHERNET_SOCKET_MODE_HOST_ONLY) {
ethSocket->socketAddress.sll_pkttype = PACKET_HOST;
else if (mode == ETHERNET_SOCKET_MODE_HOST_ONLY)
{
self->socketAddress.sll_pkttype = PACKET_HOST;
}
else if (mode == ETHERNET_SOCKET_MODE_MULTICAST) {
ethSocket->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST;
else if (mode == ETHERNET_SOCKET_MODE_MULTICAST)
{
self->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST;
}
}
}
void
Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress)
Ethernet_addMulticastAddress(EthernetSocket self, uint8_t* multicastAddress)
{
struct packet_mreq mreq;
memset(&mreq, 0, sizeof(struct packet_mreq));
mreq.mr_ifindex = ethSocket->socketAddress.sll_ifindex;
mreq.mr_ifindex = self->socketAddress.sll_ifindex;
mreq.mr_alen = ETH_ALEN;
mreq.mr_type = PACKET_MR_MULTICAST;
mreq.mr_address[0] = multicastAddress[0];
@ -275,17 +285,17 @@ Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress
mreq.mr_address[4] = multicastAddress[4];
mreq.mr_address[5] = multicastAddress[5];
int res = setsockopt(ethSocket->rawSocket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
int res = setsockopt(self->rawSocket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
if (res != 0) {
if (res != 0)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Setting multicast address failed");
}
}
void
Ethernet_setProtocolFilter(EthernetSocket ethSocket, uint16_t etherType)
Ethernet_setProtocolFilter(EthernetSocket self, uint16_t etherType)
{
if (etherType == 0x88b8)
{
@ -303,22 +313,22 @@ Ethernet_setProtocolFilter(EthernetSocket ethSocket, uint16_t etherType)
fprog.len = sizeof(filter) / sizeof(*filter);
fprog.filter = filter;
if (setsockopt(ethSocket->rawSocket, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) == -1)
if (setsockopt(self->rawSocket, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) == -1)
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Applying filter failed");
}
else
{
ethSocket->socketAddress.sll_protocol = htons(etherType);
self->socketAddress.sll_protocol = htons(etherType);
}
}
/* non-blocking receive */
int
Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize)
{
if (self->isBind == false) {
if (self->isBind == false)
{
if (bind(self->rawSocket, (struct sockaddr*) &self->socketAddress, sizeof(self->socketAddress)) == 0)
self->isBind = true;
else
@ -329,17 +339,20 @@ Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize)
}
void
Ethernet_sendPacket(EthernetSocket ethSocket, uint8_t* buffer, int packetSize)
Ethernet_sendPacket(EthernetSocket self, uint8_t* buffer, int packetSize)
{
sendto(ethSocket->rawSocket, buffer, packetSize,
0, (struct sockaddr*) &(ethSocket->socketAddress), sizeof(ethSocket->socketAddress));
sendto(self->rawSocket, buffer, packetSize,
0, (struct sockaddr*) &(self->socketAddress), sizeof(self->socketAddress));
}
void
Ethernet_destroySocket(EthernetSocket ethSocket)
Ethernet_destroySocket(EthernetSocket self)
{
close(ethSocket->rawSocket);
GLOBAL_FREEMEM(ethSocket);
if (self)
{
close(self->rawSocket);
GLOBAL_FREEMEM(self);
}
}
bool
@ -347,4 +360,3 @@ Ethernet_isSupported()
{
return true;
}

@ -53,13 +53,13 @@ FileSystem_openFile(char* fileName, bool readWrite)
int
FileSystem_readFile(FileHandle handle, uint8_t* buffer, int maxSize)
{
return fread(buffer, maxSize, 1, (FILE*) handle);
return fread(buffer, 1, maxSize, (FILE*) handle);
}
int
FileSystem_writeFile(FileHandle handle, uint8_t* buffer, int size)
{
return fwrite(buffer, size, 1, (FILE*) handle);
return fwrite(buffer, 1, size, (FILE*) handle);
}
void

@ -62,13 +62,13 @@ FileSystem_openFile(char* fileName, bool readWrite)
int
FileSystem_readFile(FileHandle handle, uint8_t* buffer, int maxSize)
{
return fread(buffer, maxSize, 1, (FILE*) handle);
return fread(buffer, 1, maxSize, (FILE*) handle);
}
int
FileSystem_writeFile(FileHandle handle, uint8_t* buffer, int size)
{
return fwrite(buffer, size, 1, (FILE*) handle);
return fwrite(buffer, 1, size, (FILE*) handle);
}
void

@ -816,6 +816,7 @@ int
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize)
{
struct sockaddr_storage remoteAddress;
memset(&remoteAddress, 0, sizeof(struct sockaddr_storage));
socklen_t structSize = sizeof(struct sockaddr_storage);
int result = recvfrom(self->fd, msg, msgSize, MSG_DONTWAIT, (struct sockaddr*)&remoteAddress, &structSize);

@ -749,6 +749,7 @@ int
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize)
{
struct sockaddr_storage remoteAddress;
memset(&remoteAddress, 0, sizeof(struct sockaddr_storage));
socklen_t structSize = sizeof(struct sockaddr_storage);
int result = recvfrom(self->fd, (char*) msg, msgSize, 0, (struct sockaddr*)&remoteAddress, &structSize);

@ -37,9 +37,9 @@
#endif
#if (CONFIG_DEBUG_TLS == 1)
#define DEBUG_PRINT(appId, fmt, ...) fprintf(stderr, "%s: " fmt, appId, ## __VA_ARGS__);
#define DEBUG_PRINT(appId, fmt, ...) fprintf(stderr, "%s: " fmt, appId, ## __VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...) {do {} while(0);}
#define DEBUG_PRINT(fmt, ...) do {} while(0)
#endif
@ -265,7 +265,7 @@ TLSConfiguration_create()
mbedtls_ssl_conf_authmode(&(self->conf), MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_renegotiation(&(self->conf), MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION);
mbedtls_ssl_conf_renegotiation(&(self->conf), MBEDTLS_SSL_RENEGOTIATION_ENABLED);
/* static int hashes[] = {3,4,5,6,7,8,0}; */
/* mbedtls_ssl_conf_sig_hashes(&(self->conf), hashes); */
@ -297,7 +297,7 @@ TLSConfiguration_create()
void
TLSConfiguration_setClientMode(TLSConfiguration self)
{
self->conf.endpoint = MBEDTLS_SSL_IS_CLIENT;
mbedtls_ssl_conf_endpoint(&(self->conf), MBEDTLS_SSL_IS_CLIENT);
}
void
@ -852,20 +852,26 @@ TLSSocket_performHandshake(TLSSocket self)
{
int ret = mbedtls_ssl_renegotiate(&(self->ssl));
if (ret == 0) {
if (ret == 0 || ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ||
ret == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS || ret == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) {
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", self);
}
DEBUG_PRINT("TLS", "TLSSocket_performHandshake Success -> ret=%i\n", ret);
raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INFO, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "TLS session renegotiation completed", self);
return true;
}
else {
DEBUG_PRINT("TLS", "TLSSocket_performHandshake failed -> ret=%i\n", ret);
if (self->tlsConfig->eventHandler) {
uint32_t flags = mbedtls_ssl_get_verify_result(&(self->ssl));
raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_WARNING, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "Alarm: TLS session renegotiation failed", self);
createSecurityEvents(self->tlsConfig, ret, flags, self);
/* mbedtls_ssl_renegotiate mandates to reset the ssl session in case of errors */
ret = mbedtls_ssl_session_reset(&(self->ssl));
if (ret != 0) {
DEBUG_PRINT("TLS", "mbedtls_ssl_session_reset failed -> ret: -0x%X\n", -ret);
}
return false;
@ -898,16 +904,10 @@ startRenegotiationIfRequired(TLSSocket self)
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");
if (TLSSocket_performHandshake(self) == false)
return false;
}
DEBUG_PRINT("TLS", " started renegotiation\n");
self->lastRenegotiationTime = Hal_getTimeInMs();
return true;
}
@ -920,22 +920,30 @@ TLSSocket_read(TLSSocket self, uint8_t* buf, int size)
return -1;
}
int ret = mbedtls_ssl_read(&(self->ssl), buf, size);
if ((ret == MBEDTLS_ERR_SSL_WANT_READ) || (ret == MBEDTLS_ERR_SSL_WANT_WRITE))
return 0;
int len = 0;
while (len < size) {
int ret = mbedtls_ssl_read(&(self->ssl), (buf + len), (size - len));
if (ret > 0) {
len += ret;
continue;
}
if (ret < 0) {
switch (ret) {
case 0: // falling through
case MBEDTLS_ERR_SSL_WANT_READ:
case MBEDTLS_ERR_SSL_WANT_WRITE:
case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS:
case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS:
// Known "good" cases indicating the read is done
return len;
switch (ret)
{
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
DEBUG_PRINT("TLS", " connection was closed gracefully\n");
return -1;
break;
case MBEDTLS_ERR_NET_CONN_RESET:
DEBUG_PRINT("TLS", " connection was reset by peer\n");
return -1;
break;
default:
DEBUG_PRINT("TLS", " mbedtls_ssl_read returned -0x%x\n", -ret);
@ -945,19 +953,23 @@ TLSSocket_read(TLSSocket self, uint8_t* buf, int size)
createSecurityEvents(self->tlsConfig, ret, flags, self);
}
}
return -1;
int reset_err = mbedtls_ssl_session_reset(&(self->ssl));
if (0 != reset_err) {
DEBUG_PRINT("TLS", "mbedtls_ssl_session_reset failed -0x%X\n", -reset_err);
}
return ret;
}
return ret;
return len;
}
int
TLSSocket_write(TLSSocket self, uint8_t* buf, int size)
{
int ret;
int len = size;
int len = 0;
checkForCRLUpdate(self);
@ -965,22 +977,26 @@ TLSSocket_write(TLSSocket self, uint8_t* buf, int size)
return -1;
}
while ((ret = mbedtls_ssl_write(&(self->ssl), buf, len)) <= 0)
while (len < size)
{
if (ret == MBEDTLS_ERR_NET_CONN_RESET)
{
DEBUG_PRINT("TLS", "peer closed the connection\n");
return -1;
int ret = mbedtls_ssl_write(&(self->ssl), (buf + len), (size -len));
if ((ret == MBEDTLS_ERR_SSL_WANT_READ) || (ret == MBEDTLS_ERR_SSL_WANT_WRITE) ||
(ret == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS) || (ret == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS)) {
continue;
}
if ((ret != MBEDTLS_ERR_SSL_WANT_READ) && (ret != MBEDTLS_ERR_SSL_WANT_WRITE))
{
DEBUG_PRINT("TLS", "mbedtls_ssl_write returned %d\n", ret);
if (ret < 0) {
DEBUG_PRINT("TLS", "mbedtls_ssl_write returned -0x%X\n", -ret);
if (0 != (ret = mbedtls_ssl_session_reset(&(self->ssl)))) {
DEBUG_PRINT("TLS", "mbedtls_ssl_session_reset failed -0x%X\n", -ret);
}
return -1;
}
}
len = ret;
len += ret;
}
return len;
}
@ -1008,6 +1024,7 @@ TLSSocket_close(TLSSocket self)
if (self->peerCert)
GLOBAL_FREEMEM(self->peerCert);
GLOBAL_FREEMEM(self);
}

@ -1,18 +1,22 @@
# The SWIG functions/macros used in this module, swig_add_module and swig_add_library
# are not available in CMake versions earlier than 3.8
# cmake_minimum_required(VERSION 3.8)
cmake_minimum_required(VERSION 3.12)
cmake_policy(SET CMP0078 NEW)
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14.0")
cmake_policy(SET CMP0086 NEW)
endif()
find_package(SWIG REQUIRED)
include(${SWIG_USE_FILE})
find_package(PythonInterp ${BUILD_PYTHON_VERSION} REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT REQUIRED)
find_package(Python COMPONENTS Interpreter Development REQUIRED)
include_directories(${PYTHON_INCLUDE_PATH})
include_directories(${Python_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_SWIG_FLAGS "")
set_property(SOURCE iec61850.i PROPERTY CPLUSPLUS ON)
set_property(SOURCE iec61850.i PROPERTY SWIG_MODULE_NAME pyiec61850)
if(WIN32)
set(LIBS iec61850 ws2_32)
@ -20,25 +24,21 @@ else()
set(LIBS iec61850-shared)
endif()
if(${CMAKE_VERSION} VERSION_LESS 3.8)
swig_add_module(iec61850 python iec61850.i)
else()
swig_add_library(iec61850
LANGUAGE python
SOURCES iec61850.i
)
endif()
swig_add_library(pyiec61850
LANGUAGE python
SOURCES iec61850.i
)
swig_link_libraries(iec61850 ${PYTHON_LIBRARIES} ${LIBS})
swig_link_libraries(pyiec61850 ${LIBS})
# Finding python modules install path
execute_process(
COMMAND ${PYTHON_EXECUTABLE} -c
"from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib())"
COMMAND ${Python_EXECUTABLE} -c
"from sysconfig import get_path; import sys; sys.stdout.write(get_path('platlib'))"
OUTPUT_VARIABLE PYTHON_SITE_DIR
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/iec61850.py DESTINATION ${PYTHON_SITE_DIR})
install(TARGETS _iec61850 LIBRARY DESTINATION ${PYTHON_SITE_DIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyiec61850.py DESTINATION ${PYTHON_SITE_DIR})
install(TARGETS pyiec61850 LIBRARY DESTINATION ${PYTHON_SITE_DIR})
add_test(test_pyiec61850 ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/test_pyiec61850.py)
add_test(test_pyiec61850 ${Python_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/test_pyiec61850.py)

@ -0,0 +1,95 @@
# Python wrapper for libIEC61850
## Building
Before building you should install SWIG and Python
(see the '[Setup development environment on Linux](#setup-development-environment-on-linux-ubuntu)' section for help).
To build the Python bindings you have to turn on the BUILD\_PYTHON\_BINDINGS flag in CMake from cmake-gui or in command line:
```sh
$ mkdir build && cd build
$ cmake -DBUILD_PYTHON_BINDINGS=ON ..
```
Then compile the library and install it:
```sh
$ make
$ sudo make install
```
(Eventually, update your ld cache with: `sudo ldconfig`)
CMake and SWIG will automatically detect your Python version and install the Python library in Python library directories.
For running the integrated tests:
```sh
$ make test
```
pyiec61850 library is to be imported calling:
```python
import pyiec61850 as iec61850
```
## Client tutorial
The Python bindings works similarly to the basic C library. However there are some differences:
* a specific function is to be called to cast variables from one type to another
* arguments passed by pointer are to be removed from arguments and append to the return list
For example to create a connection, call:
```python
con = iec61850.IedConnection_create()
error = iec61850.IedConnection_connect(con, "localhost", 102)
if (error == iec61850.IED_ERROR_OK):
# Do some work
iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)
```
To iterate over a list of logical devices, the code becomes:
```python
[deviceList, error] = iec61850.IedConnection_getLogicalDeviceList(con)
device = iec61850.LinkedList_getNext(deviceList)
while device:
print("LD: %s" % iec61850.toCharP(device.data))
[logicalNodes, error] = iec61850.IedConnection_getLogicalDeviceDirectory(
con, iec61850.toCharP(device.data))
device = iec61850.LinkedList_getNext(device)
iec61850.LinkedList_destroy(deviceList)
```
Reading and writing operations can be performed using this syntax:
```python
[floatValue, error] = iec61850.IedConnection_readFloatValue(con,
"simpleIOGenericIO/GGIO1.AnIn1.mag.f", iec61850.IEC61850_FC_MX)
err = iec61850.IedConnection_writeFloatValue(con,
"simpleIOGenericIO/GGIO1.AnIn1.mag.f", iec61850.IEC61850_FC_MX, 10.0)
```
## Appendix
## Setup development environment on Linux Ubuntu
_[Tested on Ubuntu 20.04 LTS, Ubuntu 22.04 LTS, Ubuntu 23.10]_
Here the minimum required packages for compiling libiec61850 and the Python
wrapper (without TLS, SQlite, ...):
```sh
$ sudo apt-get update
$ sudo apt-get install g++ cmake swig git python3 python3-all-dev
```
## Setup development environment on Linux Alpine
_[Tested on Alpine 3.18]_
Here the minimum required packages for compiling libiec61850 and the Python
wrapper (without TLS, SQlite, ...):
```sh
$ apk update
$ apk add git g++ swig make cmake python3 python3-dev linux-headers
```

@ -1,6 +1,6 @@
#!/usr/bin/python
import os,sys
import iec61850
import pyiec61850 as iec61850
if __name__=="__main__":
hostname = "localhost";
tcpPort = 102

@ -27,7 +27,7 @@ The user needs to:
import time
import sys
import iec61850
import pyiec61850 as iec61850
def open_connection(ip_address, mms_port):

@ -1,5 +1,5 @@
/* File : iec61850.i */
%module(directors="1") iec61850
%module(directors="1") pyiec61850
%ignore ControlObjectClient_setTestMode(ControlObjectClient self);
%ignore CDA_OperBoolean(ModelNode* parent, bool isTImeActivated);
%ignore LogicalNode_hasBufferedReports(LogicalNode* node);

@ -6,7 +6,7 @@ import traceback
import signal
import sys
sys.path.append('.')
import iec61850
import pyiec61850 as iec61850
def signal_handler(signal, frame):
global running
running =0

@ -1,50 +0,0 @@
# Building
Before building you should install swig and python.
To build python bindings you have to turn on the BUILD\_PYTHON\_BINDINGS flag in CMake from cmake-gui or in command line:
```sh
$ cmake -DBUILD_PYTHON_BINDINGS=ON .
```
Then compile the library and install it. CMake and swig will automatically detect your python version and install the python library in python library directories.
pyiec61850 library is to be imported calling
```python
import iec61850
```
# Client tutorial
The python bindings works similarly to the basic C library. However there are some differences:
* a specific function is to be called to cast variables from one type to another
* arguments passed by pointer are to be removed from arguments and append to the return list
For example to create a connection, call:
```python
con = iec61850.IedConnection_create()
error = iec61850.IedConnection_connect(con, "localhost", 102)
if (error == iec61850.IED_ERROR_OK):
# Do some work
iec61850.IedConnection_close(con)
iec61850.IedConnection_destroy(con)
```
To iterate over a list of logical devices, the code becomes:
```python
[deviceList, error] = iec61850.IedConnection_getLogicalDeviceList(con)
device = iec61850.LinkedList_getNext(deviceList)
while device:
print("LD: %s" % iec61850.toCharP(device.data))
[logicalNodes, error] = iec61850.IedConnection_getLogicalDeviceDirectory(
con, iec61850.toCharP(device.data))
device = iec61850.LinkedList_getNext(device)
iec61850.LinkedList_destroy(deviceList)
```
Reading and writing operations can be performed using this syntax:
```python
[floatValue, error] = iec61850.IedConnection_readFloatValue(con,
"simpleIOGenericIO/GGIO1.AnIn1.mag.f", iec61850.IEC61850_FC_MX)
err = iec61850.IedConnection_writeFloatValue(con,
"simpleIOGenericIO/GGIO1.AnIn1.mag.f", iec61850.IEC61850_FC_MX, 10.0)
```

@ -40,6 +40,19 @@ StringUtils_copyStringMax(char* dest, int maxBufferSize, const char* str1);
LIB61850_INTERNAL char*
StringUtils_copyStringToBuffer(const char* string, char* buffer);
/**
* \brief Copy string to buffer and replace characters
*
* NOTE: str should be a 0 terminated string. The terminating 0 is also copied.
*
* \param str the source string to copy
* \param buffer the destination buffer
* \param oldChar the character that has to be replaced while copying
* \param newChar the replacement character
*/
LIB61850_INTERNAL char*
StringUtils_copyStringToBufferAndReplace(const char* str, char* buffer, char oldChar, char newChar);
LIB61850_INTERNAL char*
StringUtils_copySubString(char* startPos, char* endPos);

@ -1,7 +1,7 @@
/*
* string_utilities.c
*
* Copyright 2013 Michael Zillgith
* Copyright 2013-2023 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -65,18 +65,39 @@ StringUtils_copyStringToBuffer(const char* string, char* buffer)
return buffer;
}
char*
StringUtils_copyStringToBufferAndReplace(const char* str, char* buffer, char oldChar, char newChar)
{
int i = 0;
while (true)
{
if (str[i] == oldChar)
buffer[i] = newChar;
else
buffer[i] = str[i];
if (str[i] == 0)
break;
i++;
}
return buffer;
}
char*
StringUtils_createStringFromBuffer(const uint8_t* buf, int size)
{
char* newStr = (char*) GLOBAL_MALLOC(size + 1);
char* newStr = (char*) GLOBAL_MALLOC(size + 1);
if (newStr) {
memcpy(newStr, buf, size);
newStr[size] = 0;
}
if (newStr) {
memcpy(newStr, buf, size);
newStr[size] = 0;
}
return newStr;
return newStr;
}
char*
@ -408,11 +429,11 @@ getCharWeight(int c)
{
static bool initialized = false;
static char lookupTable[LT_MAX_CHARS + 1];
static const char* charOrder = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz$_0123456789";
if (!initialized) {
int ltIndex;
int weight = 1;
const char* charOrder = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz$_0123456789";
for (ltIndex = 1; ltIndex < LT_MAX_CHARS; ltIndex++) {
if (strchr(charOrder, ltIndex)) continue;

@ -1,7 +1,7 @@
/*
* goose_publisher.c
*
* Copyright 2013-2022 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -65,18 +65,19 @@ GoosePublisher_createEx(CommParameters* parameters, const char* interfaceID, boo
{
GoosePublisher self = (GoosePublisher) GLOBAL_CALLOC(1, sizeof(struct sGoosePublisher));
if (self) {
if (prepareGooseBuffer(self, parameters, interfaceID, useVlanTag)) {
if (self)
{
if (prepareGooseBuffer(self, parameters, interfaceID, useVlanTag))
{
self->timestamp = MmsValue_newUtcTimeByMsTime(Hal_getTimeInMs());
GoosePublisher_reset(self);
}
else {
else
{
GoosePublisher_destroy(self);
self = NULL;
}
}
return self;
@ -91,20 +92,21 @@ GoosePublisher_create(CommParameters* parameters, const char* interfaceID)
void
GoosePublisher_destroy(GoosePublisher self)
{
if (self) {
if (self)
{
if (self->ethernetSocket) {
Ethernet_destroySocket(self->ethernetSocket);
}
MmsValue_delete(self->timestamp);
if (self->goID != NULL)
if (self->goID)
GLOBAL_FREEMEM(self->goID);
if (self->goCBRef != NULL)
if (self->goCBRef)
GLOBAL_FREEMEM(self->goCBRef);
if (self->dataSetRef != NULL)
if (self->dataSetRef)
GLOBAL_FREEMEM(self->dataSetRef);
if (self->buffer)
@ -117,18 +119,27 @@ GoosePublisher_destroy(GoosePublisher self)
void
GoosePublisher_setGoID(GoosePublisher self, char* goID)
{
if (self->goID)
GLOBAL_FREEMEM(self->goID);
self->goID = StringUtils_copyString(goID);
}
void
GoosePublisher_setGoCbRef(GoosePublisher self, char* goCbRef)
{
if (self->goCBRef)
GLOBAL_FREEMEM(self->goCBRef);
self->goCBRef = StringUtils_copyString(goCbRef);
}
void
GoosePublisher_setDataSetRef(GoosePublisher self, char* dataSetRef)
{
if (self->dataSetRef)
GLOBAL_FREEMEM(self->dataSetRef);
self->dataSetRef = StringUtils_copyString(dataSetRef);
}
@ -197,7 +208,7 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char*
{
uint8_t srcAddr[6];
if (interfaceID != NULL)
if (interfaceID)
Ethernet_getInterfaceMACAddress(interfaceID, srcAddr);
else
Ethernet_getInterfaceMACAddress(CONFIG_ETHERNET_INTERFACE_ID, srcAddr);
@ -209,71 +220,84 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char*
uint16_t vlanId;
uint16_t appId;
if (parameters == NULL) {
dstAddr = defaultDstAddr;
priority = CONFIG_GOOSE_DEFAULT_PRIORITY;
vlanId = CONFIG_GOOSE_DEFAULT_VLAN_ID;
appId = CONFIG_GOOSE_DEFAULT_APPID;
}
else {
if (parameters)
{
dstAddr = parameters->dstAddress;
priority = parameters->vlanPriority;
vlanId = parameters->vlanId;
appId = parameters->appId;
}
else
{
dstAddr = defaultDstAddr;
priority = CONFIG_GOOSE_DEFAULT_PRIORITY;
vlanId = CONFIG_GOOSE_DEFAULT_VLAN_ID;
appId = CONFIG_GOOSE_DEFAULT_APPID;
}
if (interfaceID != NULL)
if (interfaceID)
self->ethernetSocket = Ethernet_createSocket(interfaceID, dstAddr);
else
self->ethernetSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, dstAddr);
if (self->ethernetSocket) {
if (self->ethernetSocket)
{
self->buffer = (uint8_t*) GLOBAL_MALLOC(GOOSE_MAX_MESSAGE_SIZE);
memcpy(self->buffer, dstAddr, 6);
memcpy(self->buffer + 6, srcAddr, 6);
if (self->buffer)
{
memcpy(self->buffer, dstAddr, 6);
memcpy(self->buffer + 6, srcAddr, 6);
int bufPos = 12;
int bufPos = 12;
if (useVlanTags) {
/* Priority tag - IEEE 802.1Q */
self->buffer[bufPos++] = 0x81;
self->buffer[bufPos++] = 0x00;
if (useVlanTags)
{
/* Priority tag - IEEE 802.1Q */
self->buffer[bufPos++] = 0x81;
self->buffer[bufPos++] = 0x00;
uint8_t tci1 = priority << 5;
tci1 += vlanId / 256;
uint8_t tci1 = priority << 5;
tci1 += vlanId / 256;
uint8_t tci2 = vlanId % 256;
uint8_t tci2 = vlanId % 256;
self->buffer[bufPos++] = tci1; /* Priority + VLAN-ID */
self->buffer[bufPos++] = tci2; /* VLAN-ID */
}
self->buffer[bufPos++] = tci1; /* Priority + VLAN-ID */
self->buffer[bufPos++] = tci2; /* VLAN-ID */
}
/* EtherType GOOSE */
self->buffer[bufPos++] = 0x88;
self->buffer[bufPos++] = 0xB8;
/* EtherType GOOSE */
self->buffer[bufPos++] = 0x88;
self->buffer[bufPos++] = 0xB8;
/* APPID */
self->buffer[bufPos++] = appId / 256;
self->buffer[bufPos++] = appId % 256;
/* APPID */
self->buffer[bufPos++] = appId / 256;
self->buffer[bufPos++] = appId % 256;
self->lengthField = bufPos;
self->lengthField = bufPos;
/* Length */
self->buffer[bufPos++] = 0x00;
self->buffer[bufPos++] = 0x08;
/* Length */
self->buffer[bufPos++] = 0x00;
self->buffer[bufPos++] = 0x08;
/* Reserved1 */
self->buffer[bufPos++] = 0x00;
self->buffer[bufPos++] = 0x00;
/* Reserved1 */
self->buffer[bufPos++] = 0x00;
self->buffer[bufPos++] = 0x00;
/* Reserved2 */
self->buffer[bufPos++] = 0x00;
self->buffer[bufPos++] = 0x00;
/* Reserved2 */
self->buffer[bufPos++] = 0x00;
self->buffer[bufPos++] = 0x00;
self->payloadStart = bufPos;
self->payloadStart = bufPos;
return true;
return true;
}
else
{
if (DEBUG_GOOSE_PUBLISHER)
printf("GOOSE_PUBLISHER: Failed to allocate buffer\n");
return false;
}
}
else {
return false;
@ -281,8 +305,8 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char*
}
static int32_t
createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffer, size_t maxPayloadSize) {
createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffer, size_t maxPayloadSize)
{
/* Step 1 - calculate length fields */
uint32_t goosePduLength = 0;
@ -294,7 +318,7 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe
goosePduLength += BerEncoder_determineEncodedStringSize(self->dataSetRef);
if (self->goID != NULL)
if (self->goID)
goosePduLength += BerEncoder_determineEncodedStringSize(self->goID);
else
goosePduLength += BerEncoder_determineEncodedStringSize(self->goCBRef);
@ -317,13 +341,16 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe
LinkedList element = LinkedList_getNext(dataSetValues);
while (element != NULL) {
while (element)
{
MmsValue* dataSetEntry = (MmsValue*) element->data;
if (dataSetEntry) {
if (dataSetEntry)
{
dataSetSize += MmsValue_encodeMmsData(dataSetEntry, NULL, 0, false);
}
else {
else
{
/* TODO encode MMS NULL */
if (DEBUG_GOOSE_PUBLISHER)
printf("GOOSE_PUBLISHER: NULL value in data set!\n");
@ -358,7 +385,7 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe
bufPos = BerEncoder_encodeStringWithTag(0x82, self->dataSetRef, buffer, bufPos);
/* Encode goID */
if (self->goID != NULL)
if (self->goID)
bufPos = BerEncoder_encodeStringWithTag(0x83, self->goID, buffer, bufPos);
else
bufPos = BerEncoder_encodeStringWithTag(0x83, self->goCBRef, buffer, bufPos);
@ -390,7 +417,8 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe
/* Encode data set entries */
element = LinkedList_getNext(dataSetValues);
while (element != NULL) {
while (element)
{
MmsValue* dataSetEntry = (MmsValue*) element->data;
if (dataSetEntry) {
@ -444,7 +472,7 @@ GoosePublisher_publishAndDump(GoosePublisher self, LinkedList dataSet, char *msg
int rc = GoosePublisher_publish(self, dataSet);
if (rc == 0)
{
{
int copied = self->payloadStart + self->payloadLength;
if (bufSize < copied)

@ -170,7 +170,7 @@ GoosePublisher_setStNum(GoosePublisher self, uint32_t stNum);
* NOTE: Only for testing! The sequence number is increase manually whenever \ref GoosePublisher_publish is called.
*
* \param self GoosePublisher instance
* \param stNum the state number of the next GOOSE message to send
* \param sqNum the sequence number of the next GOOSE message to send
*/
LIB61850_API void
GoosePublisher_setSqNum(GoosePublisher self, uint32_t sqNum);

@ -1,7 +1,7 @@
/*
* goose_receiver.c
*
* Copyright 2014-2022 Michael Zillgith
* Copyright 2014-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -63,7 +63,8 @@ GooseReceiver_createEx(uint8_t* buffer)
{
GooseReceiver self = (GooseReceiver) GLOBAL_MALLOC(sizeof(struct sGooseReceiver));
if (self != NULL) {
if (self != NULL)
{
self->running = false;
self->stop = false;
self->interfaceId = NULL;
@ -124,9 +125,20 @@ static void
createNewStringFromBufferElement(MmsValue* value, uint8_t* bufferSrc, int elementLength)
{
value->value.visibleString.buf = (char*) GLOBAL_MALLOC(elementLength + 1);
memcpy(value->value.visibleString.buf, bufferSrc, elementLength);
value->value.visibleString.buf[elementLength] = 0;
value->value.visibleString.size = elementLength;
if (value->value.visibleString.buf)
{
memcpy(value->value.visibleString.buf, bufferSrc, elementLength);
value->value.visibleString.buf[elementLength] = 0;
value->value.visibleString.size = elementLength;
}
else
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: failed to allocate memory for visible string\n");
value->value.visibleString.size = 0;
}
}
static GooseParseError
@ -141,24 +153,28 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
GooseParseError pe = GOOSE_PARSE_ERROR_NO_ERROR;
uint8_t tag;
while (bufPos < allDataLength) {
while (bufPos < allDataLength)
{
tag = buffer[bufPos++];
if (elementIndex > maxIndex) {
if (elementIndex > maxIndex)
{
pe = GOOSE_PARSE_ERROR_OVERFLOW;
break; /* from while */
}
MmsValue* value = MmsValue_getElement(dataSetValues, elementIndex);
if (value == NULL) {
if (value == NULL)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: type mismatch (element %i not found)\n", elementIndex);
return GOOSE_PARSE_ERROR_TYPE_MISMATCH;
}
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength);
if (bufPos < 0) {
if (bufPos < 0)
{
pe = GOOSE_PARSE_ERROR_TAGDECODE;
break; /* from while */
}
@ -173,7 +189,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
case 0xa1: /* array */
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: found array\n");
if (MmsValue_getType(value) == MMS_ARRAY) {
if (MmsValue_getType(value) == MMS_ARRAY)
{
if (parseAllData(buffer + bufPos, elementLength, value) != GOOSE_PARSE_ERROR_NO_ERROR)
pe = GOOSE_PARSE_ERROR_SUBLEVEL;
}
@ -185,7 +202,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
case 0xa2: /* structure */
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: found structure\n");
if (MmsValue_getType(value) == MMS_STRUCTURE) {
if (MmsValue_getType(value) == MMS_STRUCTURE)
{
if (parseAllData(buffer + bufPos, elementLength, value) != GOOSE_PARSE_ERROR_NO_ERROR)
pe = GOOSE_PARSE_ERROR_SUBLEVEL;
}
@ -208,18 +226,23 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x84: /* BIT STRING */
if (MmsValue_getType(value) == MMS_BIT_STRING) {
if (MmsValue_getType(value) == MMS_BIT_STRING)
{
int padding = buffer[bufPos];
if (padding > 7) {
if (padding > 7)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: invalid bit-string (padding not plausible)\n");
pe = GOOSE_PARSE_ERROR_INVALID_PADDING;
}
else {
else
{
int bitStringLength = (8 * (elementLength - 1)) - padding;
if (bitStringLength == value->value.bitString.size) {
if (bitStringLength == value->value.bitString.size)
{
memcpy(value->value.bitString.buf, buffer + bufPos + 1,
elementLength - 1);
}
@ -234,8 +257,10 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x85: /* integer */
if (MmsValue_getType(value) == MMS_INTEGER) {
if (elementLength <= value->value.integer->maxSize) {
if (MmsValue_getType(value) == MMS_INTEGER)
{
if (elementLength <= value->value.integer->maxSize)
{
value->value.integer->size = elementLength;
memcpy(value->value.integer->octets, buffer + bufPos, elementLength);
}
@ -249,8 +274,10 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x86: /* unsigned integer */
if (MmsValue_getType(value) == MMS_UNSIGNED) {
if (elementLength <= value->value.integer->maxSize) {
if (MmsValue_getType(value) == MMS_UNSIGNED)
{
if (elementLength <= value->value.integer->maxSize)
{
value->value.integer->size = elementLength;
memcpy(value->value.integer->octets, buffer + bufPos, elementLength);
}
@ -264,7 +291,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x87: /* Float */
if (MmsValue_getType(value) == MMS_FLOAT) {
if (MmsValue_getType(value) == MMS_FLOAT)
{
if (elementLength == 9) {
MmsValue_setDouble(value, BerDecoder_decodeDouble(buffer, bufPos));
}
@ -281,15 +309,19 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x89: /* octet string */
if (MmsValue_getType(value) == MMS_OCTET_STRING) {
if (elementLength <= abs(value->value.octetString.maxSize)) {
if (MmsValue_getType(value) == MMS_OCTET_STRING)
{
if (elementLength <= abs(value->value.octetString.maxSize))
{
value->value.octetString.size = elementLength;
memcpy(value->value.octetString.buf, buffer + bufPos, elementLength);
}
else {
else
{
uint8_t* newBuf = (uint8_t*)GLOBAL_MALLOC(elementLength);
if (newBuf) {
if (newBuf)
{
memcpy(newBuf, buffer + bufPos, elementLength);
uint8_t* oldBuf = value->value.octetString.buf;
@ -300,7 +332,6 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
GLOBAL_FREEMEM(oldBuf);
}
}
}
else {
@ -309,14 +340,17 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x8a: /* visible string */
if (MmsValue_getType(value) == MMS_VISIBLE_STRING) {
if (value->value.visibleString.buf != NULL) {
if ((int32_t) value->value.visibleString.size >= elementLength) {
if (MmsValue_getType(value) == MMS_VISIBLE_STRING)
{
if (value->value.visibleString.buf != NULL)
{
if ((int32_t) value->value.visibleString.size >= elementLength)
{
memcpy(value->value.visibleString.buf, buffer + bufPos, elementLength);
value->value.visibleString.buf[elementLength] = 0;
}
else {
else
{
GLOBAL_FREEMEM(value->value.visibleString.buf);
createNewStringFromBufferElement(value, buffer + bufPos, elementLength);
@ -324,7 +358,6 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
}
else
createNewStringFromBufferElement(value, buffer + bufPos, elementLength);
}
else {
pe = GOOSE_PARSE_ERROR_TYPE_MISMATCH;
@ -332,7 +365,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x8c: /* binary time */
if (MmsValue_getType(value) == MMS_BINARY_TIME) {
if (MmsValue_getType(value) == MMS_BINARY_TIME)
{
if ((elementLength == 4) || (elementLength == 6)) {
memcpy(value->value.binaryTime.buf, buffer + bufPos, elementLength);
}
@ -343,7 +377,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break;
case 0x91: /* Utctime */
if (elementLength == 8) {
if (elementLength == 8)
{
if (MmsValue_getType(value) == MMS_UTC_TIME) {
MmsValue_setUtcTimeByBuffer(value, buffer + bufPos);
}
@ -370,13 +405,15 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
elementIndex++;
}
if (elementIndex <= maxIndex) {
if (elementIndex <= maxIndex)
{
if (pe == GOOSE_PARSE_ERROR_NO_ERROR) {
pe = GOOSE_PARSE_ERROR_UNDERFLOW;
}
}
if (DEBUG_GOOSE_SUBSCRIBER) {
if (DEBUG_GOOSE_SUBSCRIBER)
{
switch (pe) {
case GOOSE_PARSE_ERROR_UNKNOWN_TAG:
printf("GOOSE_SUBSCRIBER: Found unkown tag %02x!\n", tag);
@ -419,11 +456,14 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
MmsValue* dataSetValues = NULL;
while (bufPos < allDataLength) {
while (bufPos < allDataLength)
{
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength);
if (bufPos < 0) {
if (bufPos < 0)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n");
return 0;
@ -474,11 +514,14 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
elementIndex = 0;
bufPos = 0;
while (bufPos < allDataLength) {
while (bufPos < allDataLength)
{
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength);
if (bufPos < 0) {
if (bufPos < 0)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n");
return 0;
@ -528,22 +571,26 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
case 0x84: /* BIT STRING */
{
if (elementLength > 1) {
if (elementLength > 1)
{
int padding = buffer[bufPos];
int rawBitLength = (elementLength - 1) * 8;
if (padding > 7) {
if (padding > 7)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: invalid bit-string (padding not plausible)\n");
goto exit_with_error;
}
else {
else
{
value = MmsValue_newBitString(rawBitLength - padding);
memcpy(value->value.bitString.buf, buffer + bufPos + 1, elementLength - 1);
}
}
else {
else
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: invalid bit-string\n");
@ -553,13 +600,15 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
break;
case 0x85: /* integer */
if (elementLength > 8) {
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: unsupported integer size(%i)\n", elementLength);
if (elementLength > 8)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: unsupported integer size(%i)\n", elementLength);
goto exit_with_error;
goto exit_with_error;
}
else {
else
{
value = MmsValue_newInteger(elementLength * 8);
memcpy(value->value.integer->octets, buffer + bufPos, elementLength);
value->value.integer->size = elementLength;
@ -568,13 +617,15 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
break;
case 0x86: /* unsigned integer */
if (elementLength > 8) {
if (elementLength > 8)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: unsupported unsigned size(%i)\n", elementLength);
goto exit_with_error;
}
else {
else
{
value = MmsValue_newUnsigned(elementLength * 8);
memcpy(value->value.integer->octets, buffer + bufPos, elementLength);
value->value.integer->size = elementLength;
@ -610,13 +661,16 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
break;
case 0x91: /* Utctime */
if (elementLength == 8) {
if (elementLength == 8)
{
value = MmsValue_newUtcTime(0);
MmsValue_setUtcTimeByBuffer(value, buffer + bufPos);
}
else
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: UTCTime element is of wrong size!\n");
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: UTCTime element is of wrong size!\n");
}
break;
default:
@ -627,7 +681,8 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
bufPos += elementLength;
if (value != NULL) {
if (value != NULL)
{
MmsValue_setElement(dataSetValues, elementIndex, value);
elementIndex++;
}
@ -662,10 +717,12 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
uint32_t numberOfDatSetEntries = 0;
if (buffer[bufPos++] == 0x61) {
if (buffer[bufPos++] == 0x61)
{
int gooseLength;
bufPos = BerDecoder_decodeLength(buffer, &gooseLength, bufPos, apduLength);
if (bufPos < 0) {
if (bufPos < 0)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n");
return 0;
@ -673,12 +730,14 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
int gooseEnd = bufPos + gooseLength;
while (bufPos < gooseEnd) {
while (bufPos < gooseEnd)
{
int elementLength;
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, apduLength);
if (bufPos < 0) {
if (bufPos < 0)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n");
return 0;
@ -696,16 +755,19 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
{
LinkedList element = LinkedList_getNext(self->subscriberList);
while (element != NULL) {
while (element)
{
GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element);
if (subscriber->isObserver)
{
if (elementLength > 129) {
if (elementLength > 129)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: gocbRef too long!\n");
}
else {
else
{
memcpy(subscriber->goCBRef, buffer + bufPos, elementLength);
subscriber->goCBRef[elementLength] = 0;
}
@ -713,8 +775,10 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
matchingSubscriber = subscriber;
break;
}
else if (subscriber->goCBRefLen == elementLength) {
if (memcmp(subscriber->goCBRef, buffer + bufPos, elementLength) == 0) {
else if (subscriber->goCBRefLen == elementLength)
{
if (memcmp(subscriber->goCBRef, buffer + bufPos, elementLength) == 0)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: gocbRef is matching!\n");
matchingSubscriber = subscriber;
@ -744,12 +808,15 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Found dataSet\n");
{
if (matchingSubscriber) {
if (elementLength > 129) {
if (matchingSubscriber)
{
if (elementLength > 129)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: datSet too long!\n");
}
else {
else
{
memcpy(matchingSubscriber->datSet, buffer + bufPos, elementLength);
matchingSubscriber->datSet[elementLength] = 0;
}
@ -761,12 +828,15 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Found goId\n");
{
if (matchingSubscriber) {
if (elementLength > 129) {
if (matchingSubscriber)
{
if (elementLength > 129)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: goId too long!\n");
}
else {
else
{
memcpy(matchingSubscriber->goId, buffer + bufPos, elementLength);
matchingSubscriber->goId[elementLength] = 0;
}
@ -832,15 +902,17 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
bufPos += elementLength;
}
if (matchingSubscriber != NULL) {
if (matchingSubscriber != NULL)
{
matchingSubscriber->timeAllowedToLive = timeAllowedToLive;
matchingSubscriber->ndsCom = ndsCom;
matchingSubscriber->simulation = simulation;
if (matchingSubscriber->dataSetValuesSelfAllocated) {
if (matchingSubscriber->dataSetValuesSelfAllocated)
{
/* when confRev changed replaced old data set */
if ((matchingSubscriber->dataSetValues != NULL) && (matchingSubscriber->confRev != confRev)) {
if ((matchingSubscriber->dataSetValues != NULL) && (matchingSubscriber->confRev != confRev))
{
MmsValue_delete(matchingSubscriber->dataSetValues);
matchingSubscriber->dataSetValues = NULL;
}
@ -850,14 +922,16 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
if (timestampBufPos)
MmsValue_setUtcTimeByBuffer(matchingSubscriber->timestamp, timestampBufPos);
else {
else
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: GOOSE message has no time stamp\n");
MmsValue_setUtcTime(matchingSubscriber->timestamp, 0);
}
if (matchingSubscriber->isObserver && matchingSubscriber->dataSetValues != NULL) {
if (matchingSubscriber->isObserver && matchingSubscriber->dataSetValues != NULL)
{
MmsValue_delete(matchingSubscriber->dataSetValues);
matchingSubscriber->dataSetValues = NULL;
}
@ -866,7 +940,8 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
if (matchingSubscriber->dataSetValues == NULL)
matchingSubscriber->dataSetValues = parseAllDataUnknownValue(matchingSubscriber, dataSetBufferAddress, dataSetBufferLength, false);
else {
else
{
GooseParseError parseError = parseAllData(dataSetBufferAddress, dataSetBufferLength, matchingSubscriber->dataSetValues);
if (parseError != GOOSE_PARSE_ERROR_NO_ERROR) {
@ -876,7 +951,8 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength)
matchingSubscriber->parseError = parseError;
}
if (matchingSubscriber->stNum == stNum) {
if (matchingSubscriber->stNum == stNum)
{
if (matchingSubscriber->sqNum >= sqNum) {
isValid = false;
}
@ -920,13 +996,18 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes)
uint8_t priority = 0;
uint16_t vlanId = 0;
bool vlanSet = false;
/* check for VLAN tag */
if ((buffer[bufPos] == 0x81) && (buffer[bufPos + 1] == 0x00)) {
if ((buffer[bufPos] == 0x81) && (buffer[bufPos + 1] == 0x00))
{
priority = buffer[bufPos + 2] & 0xF8 >> 5;
vlanId = ((buffer[bufPos + 2] & 0x07) << 8) + buffer[bufPos + 3];
vlanSet = true;
bufPos += 4; /* skip VLAN tag */
headerLength += 4;
if (numbytes < (22 + 4))
return;
}
/* check for GOOSE Ethertype */
@ -956,13 +1037,22 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes)
int apduLength = length - 8;
if (numbytes < length + headerLength) {
if (apduLength < 0)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Invalid length field\n");
return;
}
if (numbytes < length + headerLength)
{
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: Invalid PDU size\n");
return;
}
if (DEBUG_GOOSE_SUBSCRIBER) {
if (DEBUG_GOOSE_SUBSCRIBER)
{
printf("GOOSE_SUBSCRIBER: GOOSE message:\nGOOSE_SUBSCRIBER: ----------------\n");
printf("GOOSE_SUBSCRIBER: DST-MAC: %02x:%02x:%02x:%02x:%02X:%02X\n",
dstMac[0], dstMac[1], dstMac[2], dstMac[3], dstMac[4], dstMac[5]);
@ -974,7 +1064,8 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes)
/* check if there is an interested subscriber */
LinkedList element = LinkedList_getNext(self->subscriberList);
while (element != NULL) {
while (element)
{
GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element);
if (subscriber->isObserver)
@ -990,7 +1081,8 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes)
}
if (((subscriber->appId == -1) || (subscriber->appId == appId)) &&
(!subscriber->dstMacSet || (memcmp(subscriber->dstMac, dstMac,6) == 0))) {
(!subscriber->dstMacSet || (memcmp(subscriber->dstMac, dstMac,6) == 0)))
{
subscriberFound = true;
break;
}
@ -1014,9 +1106,12 @@ gooseReceiverLoop(void *threadParameter)
EthernetHandleSet handleSet = EthernetHandleSet_new();
EthernetHandleSet_addSocket(handleSet, self->ethSocket);
if (self->running) {
bool running = true;
while (self->running) {
if (running)
{
while (running)
{
switch (EthernetHandleSet_waitReady(handleSet, 100))
{
case -1:
@ -1030,6 +1125,8 @@ gooseReceiverLoop(void *threadParameter)
}
if (self->stop)
break;
running = self->running;
}
GooseReceiver_stopThreadless(self);
@ -1046,7 +1143,8 @@ void
GooseReceiver_start(GooseReceiver self)
{
#if (CONFIG_MMS_THREADLESS_STACK == 0)
if (GooseReceiver_startThreadless(self)) {
if (GooseReceiver_startThreadless(self))
{
self->thread = Thread_create((ThreadExecutionFunction) gooseReceiverLoop, (void*) self, false);
if (self->thread != NULL) {
@ -1086,7 +1184,8 @@ GooseReceiver_stop(GooseReceiver self)
void
GooseReceiver_destroy(GooseReceiver self)
{
if (self) {
if (self)
{
#if (CONFIG_MMS_THREADLESS_STACK == 0)
if ((self->thread != NULL) && (GooseReceiver_isRunning(self)))
GooseReceiver_stop(self);
@ -1114,7 +1213,8 @@ GooseReceiver_startThreadless(GooseReceiver self)
else
self->ethSocket = Ethernet_createSocket(self->interfaceId, NULL);
if (self->ethSocket != NULL) {
if (self->ethSocket != NULL)
{
Ethernet_setProtocolFilter(self->ethSocket, ETH_P_GOOSE);
/* set multicast addresses for subscribers */
@ -1122,14 +1222,17 @@ GooseReceiver_startThreadless(GooseReceiver self)
LinkedList element = LinkedList_getNext(self->subscriberList);
while (element != NULL) {
while (element != NULL)
{
GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element);
if (subscriber->dstMacSet == false) {
if (subscriber->dstMacSet == false)
{
/* no destination MAC address defined -> we have to switch to all multicast mode */
Ethernet_setMode(self->ethSocket, ETHERNET_SOCKET_MODE_ALL_MULTICAST);
}
else {
else
{
Ethernet_addMulticastAddress(self->ethSocket, subscriber->dstMac);
}
@ -1159,7 +1262,8 @@ GooseReceiver_tick(GooseReceiver self)
{
int packetSize = Ethernet_receivePacket(self->ethSocket, self->buffer, ETH_BUFFER_LENGTH);
if (packetSize > 0) {
if (packetSize > 0)
{
parseGooseMessage(self, self->buffer, packetSize);
return true;
}

@ -1,7 +1,7 @@
/*
* goose_subscriber.c
*
* Copyright 2013-2022 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -40,7 +40,8 @@ GooseSubscriber_create(char* goCbRef, MmsValue* dataSetValues)
{
GooseSubscriber self = (GooseSubscriber) GLOBAL_CALLOC(1, sizeof(struct sGooseSubscriber));
if (self) {
if (self)
{
StringUtils_copyStringMax(self->goCBRef, 130, goCbRef);
self->goCBRefLen = strlen(goCbRef);
@ -97,7 +98,8 @@ GooseSubscriber_setAppId(GooseSubscriber self, uint16_t appId)
void
GooseSubscriber_destroy(GooseSubscriber self)
{
if (self) {
if (self)
{
MmsValue_delete(self->timestamp);
if (self->dataSetValuesSelfAllocated)
@ -120,19 +122,19 @@ GooseSubscriber_getAppId(GooseSubscriber self)
return self->appId;
}
char *
char*
GooseSubscriber_getGoId(GooseSubscriber self)
{
return self->goId;
}
char *
char*
GooseSubscriber_getGoCbRef(GooseSubscriber self)
{
return self->goCBRef;
}
char *
char*
GooseSubscriber_getDataSet(GooseSubscriber self)
{
return self->datSet;

@ -3,7 +3,7 @@
*
* Client implementation for IEC 61850 reporting.
*
* Copyright 2013-2022 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -283,14 +283,27 @@ lookupReportHandler(IedConnection self, const char* rcbReference)
return NULL;
}
static void
uninstallReportHandler(IedConnection self, const char* rcbReference)
{
ClientReport report = lookupReportHandler(self, rcbReference);
if (report != NULL) {
LinkedList_remove(self->enabledReports, report);
ClientReport_destroy(report);
}
}
void
IedConnection_installReportHandler(IedConnection self, const char* rcbReference, const char* rptId, ReportCallbackFunction handler,
void* handlerParameter)
{
Semaphore_wait(self->reportHandlerMutex);
ClientReport report = lookupReportHandler(self, rcbReference);
if (report != NULL) {
IedConnection_uninstallReportHandler(self, rcbReference);
uninstallReportHandler(self, rcbReference);
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: Removed existing report callback handler for %s\n", rcbReference);
@ -306,8 +319,8 @@ IedConnection_installReportHandler(IedConnection self, const char* rcbReference,
else
report->rptId = NULL;
Semaphore_wait(self->reportHandlerMutex);
LinkedList_add(self->enabledReports, report);
Semaphore_post(self->reportHandlerMutex);
if (DEBUG_IED_CLIENT)
@ -319,12 +332,7 @@ IedConnection_uninstallReportHandler(IedConnection self, const char* rcbReferenc
{
Semaphore_wait(self->reportHandlerMutex);
ClientReport report = lookupReportHandler(self, rcbReference);
if (report != NULL) {
LinkedList_remove(self->enabledReports, report);
ClientReport_destroy(report);
}
uninstallReportHandler(self, rcbReference);
Semaphore_post(self->reportHandlerMutex);
}
@ -367,6 +375,8 @@ IedConnection_triggerGIReport(IedConnection self, IedClientError* error, const c
void
iedConnection_handleReport(IedConnection self, MmsValue* value)
{
Semaphore_wait(self->reportHandlerMutex);
MmsValue* rptIdValue = MmsValue_getElement(value, 0);
if ((rptIdValue == NULL) || (MmsValue_getType(rptIdValue) != MMS_VISIBLE_STRING)) {
@ -769,15 +779,14 @@ iedConnection_handleReport(IedConnection self, MmsValue* value)
matchingReport->reasonForInclusion[i] = IEC61850_REASON_NOT_INCLUDED;
}
}
Semaphore_wait(self->reportHandlerMutex);
if (matchingReport->callback != NULL)
matchingReport->callback(matchingReport->callbackParameter, matchingReport);
exit_function:
Semaphore_post(self->reportHandlerMutex);
exit_function:
return;
}

@ -528,39 +528,44 @@ readObjectHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV
IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId);
if (call) {
if (call)
{
IedConnection_GetRCBValuesHandler handler = (IedConnection_GetRCBValuesHandler) call->callback;
ClientReportControlBlock updateRcb = (ClientReportControlBlock) call->specificParameter;
char* rcbReference = (char*) call->specificParameter2.pointer;
if (err != MMS_ERROR_NONE) {
if (err != MMS_ERROR_NONE)
{
handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), NULL);
}
else {
if (value == NULL) {
else
{
if (value == NULL)
{
handler(invokeId, call->callbackParameter, IED_ERROR_OBJECT_DOES_NOT_EXIST, NULL);
}
else {
if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) {
else
{
if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR)
{
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: getRCBValues returned data-access-error!\n");
handler(invokeId, call->callbackParameter, iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)), NULL);
}
else {
else
{
ClientReportControlBlock returnRcb = updateRcb;
if (returnRcb == NULL)
returnRcb = ClientReportControlBlock_create(rcbReference);
if (clientReportControlBlock_updateValues(returnRcb, value)) {
if (clientReportControlBlock_updateValues(returnRcb, value))
{
handler(invokeId, call->callbackParameter, IED_ERROR_OK, returnRcb);
}
else {
else
{
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: getRCBValues returned wrong type!\n");
@ -569,19 +574,18 @@ readObjectHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV
if (updateRcb == NULL)
ClientReportControlBlock_destroy(returnRcb);
}
}
MmsValue_delete(value);
}
}
GLOBAL_FREEMEM(rcbReference);
iedConnection_releaseOutstandingCall(self, call);
}
else {
else
{
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: internal error - no matching outstanding call!\n");
}
@ -598,7 +602,8 @@ IedConnection_getRCBValuesAsync(IedConnection self, IedClientError* error, const
char* domainName = MmsMapping_getMmsDomainFromObjectReference(rcbReference, domainId);
if (domainName == NULL) {
if (domainName == NULL)
{
*error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT;
return 0;
}
@ -608,7 +613,8 @@ IedConnection_getRCBValuesAsync(IedConnection self, IedClientError* error, const
IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self);
if (call == NULL) {
if (call == NULL)
{
*error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED;
return 0;
}
@ -627,7 +633,8 @@ IedConnection_getRCBValuesAsync(IedConnection self, IedClientError* error, const
*error = iedConnection_mapMmsErrorToIedError(err);
if (err != MMS_ERROR_NONE) {
if (err != MMS_ERROR_NONE)
{
GLOBAL_FREEMEM(call->specificParameter2.pointer);
iedConnection_releaseOutstandingCall(self, call);
return 0;

File diff suppressed because it is too large Load Diff

@ -768,7 +768,6 @@ MmsMapping_ObjectReferenceToVariableAccessSpec(char* objectReference)
return accessSpec;
}
static int
getNumberOfDigits(int value)
{

@ -1,7 +1,7 @@
/*
* iec61850_client.h
*
* Copyright 2013-2021 Michael Zillgith
* Copyright 2013-2023 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -162,6 +162,9 @@ typedef enum {
/** Received an invalid response message from the server */
IED_ERROR_MALFORMED_MESSAGE = 34,
/** Service was not executed because required resource is still in use */
IED_ERROR_OBJECT_CONSTRAINT_CONFLICT = 35,
/** Service not implemented */
IED_ERROR_SERVICE_NOT_IMPLEMENTED = 98,
@ -169,6 +172,14 @@ typedef enum {
IED_ERROR_UNKNOWN = 99
} IedClientError;
/**
* \brief Convert error value to string
*
* \return string constant representing the error
*/
LIB61850_API const char*
IedClientError_toString(IedClientError err);
/**************************************************
* Connection creation and destruction
**************************************************/
@ -255,6 +266,16 @@ IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, in
LIB61850_API void
IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs);
/**
* \brief Set the maximum number outstanding calls allowed for this connection
*
* \param self the connection object
* \param calling the maximum outstanding calls allowed by the caller (client)
* \param called the maximum outstanding calls allowed by the called endpoint (server)
*/
LIB61850_API void
IedConnection_setMaxOutstandingCalls(IedConnection self, int calling, int called);
/**
* \brief set the request timeout in ms
*
@ -1151,15 +1172,6 @@ typedef int ReasonForInclusion;
/** the reason for inclusion is unknown (e.g. report is not configured to include reason-for-inclusion) */
#define IEC61850_REASON_UNKNOWN 32
#define REASON_NOT_INCLUDED IEC61850_REASON_NOT_INCLUDED
#define REASON_DATA_CHANGE IEC61850_REASON_DATA_CHANGE
#define REASON_QUALITY_CHANGE IEC61850_REASON_QUALITY_CHANGE
#define REASON_DATA_UPDATE IEC61850_REASON_DATA_UPDATE
#define REASON_INTEGRITY IEC61850_REASON_INTEGRITY
#define REASON_GI IEC61850_REASON_GI
#define REASON_UNKNOWN IEC61850_REASON_UNKNOWN
/* Element encoding mask values for ClientReportControlBlock */
/** include the report ID into the setRCB request */
@ -1256,9 +1268,11 @@ typedef void (*ReportCallbackFunction) (void* parameter, ClientReport report);
* Otherwise the internal data structures storing the received data set values will not be updated
* correctly.
*
* When replacing a report handler you only have to call this function. There is no separate call to
* \note Replacing a report handler you only have to call this function. There is no separate call to
* IedConnection_uninstallReportHandler() required.
*
* \note Do not call this function inside of the ReportCallbackFunction. Doing so will cause a deadlock.
*
* \param self the connection object
* \param rcbReference object reference of the report control block
* \param rptId a string that identifies the report. If the rptId is not available then the
@ -1273,6 +1287,8 @@ IedConnection_installReportHandler(IedConnection self, const char* rcbReference,
/**
* \brief uninstall a report handler function for the specified report control block (RCB)
*
* \note Do not call this function inside of the ReportCallbackFunction. Doing so will cause a deadlock.
*
* \param self the connection object
* \param rcbReference object reference of the report control block
*/
@ -2467,20 +2483,6 @@ IedConnection_getServerDirectory(IedConnection self, IedClientError* error, bool
LIB61850_API LinkedList /*<char*>*/
IedConnection_getLogicalDeviceDirectory(IedConnection self, IedClientError* error, const char* logicalDeviceName);
typedef enum {
ACSI_CLASS_DATA_OBJECT,
ACSI_CLASS_DATA_SET,
ACSI_CLASS_BRCB,
ACSI_CLASS_URCB,
ACSI_CLASS_LCB,
ACSI_CLASS_LOG,
ACSI_CLASS_SGCB,
ACSI_CLASS_GoCB,
ACSI_CLASS_GsCB,
ACSI_CLASS_MSVCB,
ACSI_CLASS_USVCB
} ACSIClass;
/**
* \brief returns a list of all MMS variables that are children of the given logical node
*

@ -55,6 +55,21 @@ typedef struct {
uint8_t dstAddress[6];
} PhyComAddress;
/** IEC 61850 ACSI classes */
typedef enum {
ACSI_CLASS_DATA_OBJECT,
ACSI_CLASS_DATA_SET,
ACSI_CLASS_BRCB,
ACSI_CLASS_URCB,
ACSI_CLASS_LCB,
ACSI_CLASS_LOG,
ACSI_CLASS_SGCB,
ACSI_CLASS_GoCB,
ACSI_CLASS_GsCB,
ACSI_CLASS_MSVCB,
ACSI_CLASS_USVCB
} ACSIClass;
/**
* \brief Control model (represented by "ctlModel" attribute)
*/

@ -3,7 +3,7 @@
*
* IEC 61850 server API for libiec61850.
*
* Copyright 2013-2023 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -815,7 +815,9 @@ LIB61850_API void
IedServer_setConnectionIndicationHandler(IedServer self, IedConnectionIndicationHandler handler, void* parameter);
/**
* \brief Ignore all requests from clients
* \brief Ignore all requests from clients (for testing purposes)
*
* NOTE: This function will block all client requests on MMS layer
*
* \param self the instance of IedServer to configure.
* \param enable when true all requests from clients will be ignored
@ -1447,7 +1449,7 @@ LIB61850_API DataObject*
ControlAction_getControlObject(ControlAction self);
/**
* \brief Gets the time of the control, if it's a timeActivatedControl, returns 0, if it's not.
* \brief Gets the time of the control (attribute "operTm"), if it's a timeActivatedControl, returns 0, if it's not.
*
* \param self the control action instance
*
@ -1456,6 +1458,16 @@ ControlAction_getControlObject(ControlAction self);
LIB61850_API uint64_t
ControlAction_getControlTime(ControlAction self);
/**
* \brief Gets the time (attribute "T") of the last received control action (Oper or Select)
*
* \param self the control action instance
*
* \return the time of the last received control action
*/
LIB61850_API Timestamp*
ControlAction_getT(ControlAction self);
/**
* \brief Control model callback to perform the static tests (optional).
*
@ -1845,6 +1857,19 @@ LIB61850_API void
IedServer_handleWriteAccessForComplexAttribute(IedServer self, DataAttribute* dataAttribute,
WriteAccessHandler handler, void* parameter);
/**
* \brief Install a WriteAccessHandler for all data attributes of a data object with a specific FC
*
* \param self the instance of IedServer to operate on.
* \param dataObject the data object to monitor
* \param fc the functional constraint to monitor
* \param handler the callback function that is invoked if a client tries to write to
* the monitored data attribute.
* \param parameter a user provided parameter that is passed to the WriteAccessHandler when called.
*/
LIB61850_API void
IedServer_handleWriteAccessForDataObject(IedServer self, DataObject* dataObject, FunctionalConstraint fc, WriteAccessHandler handler, void* parameter);
typedef enum {
ACCESS_POLICY_ALLOW,
ACCESS_POLICY_DENY
@ -1892,120 +1917,119 @@ typedef MmsDataAccessError
LIB61850_API void
IedServer_setReadAccessHandler(IedServer self, ReadAccessHandler handler, void* parameter);
typedef enum {
DATASET_CREATE,
DATASET_DELETE,
DATASET_READ,
DATASET_WRITE,
DATASET_GET_DIRECTORY
} IedServer_DataSetOperation;
/**
* \brief Callback that is called in case of RCB access to give the user the opportunity to block or allow the operation
* \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 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
* \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_RCBAccessHandler) (void* parameter, ReportControlBlock* rcb, ClientConnection connection, IedServer_RCBEventType operation);
(*IedServer_DataSetAccessHandler) (void* parameter, ClientConnection connection, IedServer_DataSetOperation operation, const char* datasetRef);
/**
* \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
* \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_setRCBAccessHandler(IedServer self, IedServer_RCBAccessHandler handler, void* parameter);
IedServer_setDataSetAccessHandler(IedServer self, IedServer_DataSetAccessHandler handler, void* parameter);
typedef enum {
LCB_EVENT_GET_PARAMETER,
LCB_EVENT_SET_PARAMETER
} IedServer_LCBEventType;
DIRECTORY_CAT_LD_LIST,
DIRECTORY_CAT_DATA_LIST,
DIRECTORY_CAT_DATASET_LIST,
DIRECTORY_CAT_LOG_LIST
} IedServer_DirectoryCategory;
/**
* \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);
(*IedServer_DirectoryAccessHandler) (void* parameter, ClientConnection connection, IedServer_DirectoryCategory category, LogicalDevice* logicalDevice);
/**
* \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);
IedServer_setDirectoryAccessHandler(IedServer self, IedServer_DirectoryAccessHandler handler, void* parameter);
/**
* \brief Callback that is called when the client is trying to read log data
*
* \brief Callback that is called when a client is invoking a list objects service
*
* This callback can be used to control the list object access to specific objects and is called for each object that are subject to a client request.
*
* \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
* \param acsiClass the ACSI class of the object
* \param ld the logical device of the object
* \param ln the logical node of the object
* \param objectName the name of the object (e.g. data object name, data set name, log name, RCB name, ...)
* \param subObjectName the name of a sub element of an object or NULL
* \param fc the functional constraint of the object of IEC61850_FC_NONE when the object has no FC.
*
* \return true to include the object in the service response, otherwise false
*/
typedef bool
(*IedServer_LogAccessHandler) (void* parameter, const char* logRef, ClientConnection connection);
(*IedServer_ListObjectsAccessHandler)(void* parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice* ld, LogicalNode* ln, const char* objectName, const char* subObjectName, FunctionalConstraint fc);
/**
* \brief Set a handler control access to a log (read log data)
*
* \brief Set a handler to control which objects are return by the list objects services
*
* \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);
IedServer_setListObjectsAccessHandler(IedServer self, IedServer_ListObjectsAccessHandler handler, void* parameter);
typedef enum {
DATASET_CREATE,
DATASET_DELETE,
DATASET_READ,
DATASET_WRITE,
DATASET_GET_DIRECTORY
} IedServer_DataSetOperation;
IEC61850_CB_ACCESS_TYPE_READ,
IEC61850_CB_ACCESS_TYPE_WRITE
} IedServer_ControlBlockAccessType;
/**
* \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
*
* \brief Callback that is called when a client is invoking a read or write service to a control block or log
*
* This callback can be used to control the read and write access to control blocks and logs (SGCB, LCBs, URCBs, BRCBs, GoCBs, SVCBs, logs)
*
* \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
* \param acsiClass the ACSI class of the object
* \param ld the logical device of the object
* \param ln the logical node of the object
* \param objectName the name of the object (e.g. data object name, data set name, log name, RCB name, ...)
* \param subObjectName the name of a sub element of an object or NULL
* \param accessType access type (read=IEC61850_CB_ACCESS_TYPE_READ or write=IEC61850_CB_ACCESS_TYPE_WRITE)
*
* \return true to include the object in the service response, otherwise false
*/
typedef bool
(*IedServer_DataSetAccessHandler) (void* parameter, ClientConnection connection, IedServer_DataSetOperation operation, const char* datasetRef);
(*IedServer_ControlBlockAccessHandler)(void* parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice* ld, LogicalNode* ln, const char* objectName, const char* subObjectName, IedServer_ControlBlockAccessType accessType);
/**
* \brief Set a handler to control access to a dataset (create, delete, read, write, list directory)
*
* \brief Set a handler to control read and write access to control blocks and logs
*
* \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);
IedServer_setControlBlockAccessHandler(IedServer self, IedServer_ControlBlockAccessHandler handler, void* parameter);
/**
* \brief Temporarily ignore read requests (for testing purposes)
*
* \param self the instance of IedServer to operate on.
* \param ignore true to ignore read requests, false to handle read requests.
*/
LIB61850_API void
IedServer_setDirectoryAccessHandler(IedServer self, IedServer_DirectoryAccessHandler handler, void* parameter);
IedServer_ignoreReadAccess(IedServer self, bool ignore);
/**@}*/

@ -1,7 +1,7 @@
/*
* control.h
*
* Copyright 2013-2019 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -90,6 +90,8 @@ struct sControlObject
MmsValue* origin;
MmsValue* timestamp;
Timestamp T;
MmsValue* ctlNumSt;
MmsValue* originSt;

@ -1,7 +1,7 @@
/*
* ied_connection_private.h
*
* Copyright 2013-2022 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -34,14 +34,16 @@
typedef struct sIedConnectionOutstandingCall* IedConnectionOutstandingCall;
struct sIedConnectionOutstandingCall {
struct sIedConnectionOutstandingCall
{
bool used;
uint32_t invokeId;
void* callback;
void* callbackParameter;
void* specificParameter; /* function/service specific parameter */
union {
union
{
void* pointer;
struct {
uint32_t originalInvokeId;
@ -69,6 +71,7 @@ struct sIedConnection
Semaphore outstandingCallsLock;
IedConnectionOutstandingCall outstandingCalls;
int maxOutstandingCalled;
IedConnectionClosedHandler connectionCloseHandler;
void* connectionClosedParameter;
@ -81,7 +84,8 @@ struct sIedConnection
uint8_t timeQuality;
};
struct sClientReportControlBlock {
struct sClientReportControlBlock
{
char* objectReference;
bool isBuffered;

@ -81,10 +81,11 @@ struct sIedServer
uint8_t timeQuality; /* user settable time quality for internally updated times */
bool ignoreReadAccess; /* when true don't answer read request (for test purposes) */
bool running;
};
LIB61850_INTERNAL IEC61850_ServiceError
private_IedServer_convertMmsDataAccessErrorToServiceError(MmsDataAccessError mmsError);

@ -124,7 +124,7 @@ LIB61850_INTERNAL MmsValue*
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,
LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig,
MmsValue* value, MmsServerConnection connection);
#endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ */

@ -149,7 +149,7 @@ LIB61850_INTERNAL void
MmsMapping_installReadAccessHandler(MmsMapping* self, ReadAccessHandler handler, void* paramter);
LIB61850_INTERNAL MmsDataAccessError
Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig,
MmsValue* value, MmsServerConnection connection);
LIB61850_INTERNAL MmsValue*

@ -338,20 +338,17 @@ 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;
IedServer_ListObjectsAccessHandler listObjectsAccessHandler;
void* listObjectsAccessHandlerParameter;
IedServer_ControlBlockAccessHandler controlBlockAccessHandler;
void* controlBlockAccessHandlerParameter;
};
#endif /* MMS_MAPPING_INTERNAL_H_ */

@ -38,10 +38,10 @@ LIBIEC61850_SV_createSVControlBlocks(MmsMapping* self, MmsDomain* domain,
LogicalNode* logicalNode, int svCount, bool unicast);
LIB61850_INTERNAL MmsValue*
LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig);
LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig , MmsServerConnection connection);
LIB61850_INTERNAL MmsDataAccessError
LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig,
MmsValue* value, MmsServerConnection connection);
LIB61850_INTERNAL void

@ -126,7 +126,7 @@ LIB61850_INTERNAL void
ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag, bool modelLocked);
LIB61850_INTERNAL MmsValue*
ReportControl_getRCBValue(ReportControl* rc, char* elementName);
ReportControl_getRCBValue(ReportControl* rc, const char* elementName);
LIB61850_INTERNAL MmsVariableSpecification*
Reporting_createMmsBufferedRCBs(MmsMapping* self, MmsDomain* domain,
@ -137,7 +137,7 @@ Reporting_createMmsUnbufferedRCBs(MmsMapping* self, MmsDomain* domain,
LogicalNode* logicalNode, int reportsCount);
LIB61850_INTERNAL MmsDataAccessError
Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value,
Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, const char* elementName, MmsValue* value,
MmsServerConnection connection);
LIB61850_INTERNAL bool

@ -1,7 +1,7 @@
/*
* client_connection.c
*
* Copyright 2013-2022 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -32,8 +32,8 @@
#include "libiec61850_platform_includes.h"
struct sClientConnection {
struct sClientConnection
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore tasksCountMutex;
#endif
@ -47,7 +47,8 @@ private_ClientConnection_create(void* serverConnectionHandle)
{
ClientConnection self = (ClientConnection) GLOBAL_MALLOC(sizeof(struct sClientConnection));
if (self) {
if (self)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
self->tasksCountMutex = Semaphore_create(1);
#endif
@ -62,7 +63,8 @@ private_ClientConnection_create(void* serverConnectionHandle)
void
private_ClientConnection_destroy(ClientConnection self)
{
if (self) {
if (self)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_destroy(self->tasksCountMutex);
#endif
@ -123,7 +125,6 @@ private_ClientConnection_getServerConnectionHandle(ClientConnection self)
return self->serverConnectionHandle;
}
const char*
ClientConnection_getPeerAddress(ClientConnection self)
{

@ -540,17 +540,18 @@ updateDataSetsWithCachedValues(IedServer self)
}
}
IedServer
IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguration, IedServerConfig serverConfiguration)
{
IedServer self = (IedServer) GLOBAL_CALLOC(1, sizeof(struct sIedServer));
if (self) {
if (self)
{
self->model = dataModel;
self->running = false;
self->localIpAddress = NULL;
self->ignoreReadAccess = false;
#if (CONFIG_IEC61850_EDITION_1 == 1)
self->edition = IEC_61850_EDITION_1;
@ -561,7 +562,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1)
self->logServiceEnabled = true;
if (serverConfiguration) {
if (serverConfiguration)
{
self->logServiceEnabled = serverConfiguration->enableLogService;
self->edition = serverConfiguration->edition;
}
@ -574,7 +576,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
#endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */
#if (CONFIG_IEC61850_REPORT_SERVICE == 1)
if (serverConfiguration) {
if (serverConfiguration)
{
self->reportBufferSizeBRCBs = serverConfiguration->reportBufferSize;
self->reportBufferSizeURCBs = serverConfiguration->reportBufferSizeURCBs;
self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB;
@ -582,7 +585,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
self->syncIntegrityReportTimes = serverConfiguration->syncIntegrityReportTimes;
self->rcbSettingsWritable = serverConfiguration->reportSettingsWritable;
}
else {
else
{
self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE;
self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE;
self->enableOwnerForRCB = false;
@ -602,11 +606,13 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
#endif
#if (CONFIG_IEC61850_SETTING_GROUPS == 1)
if (serverConfiguration) {
if (serverConfiguration)
{
self->enableEditSG = serverConfiguration->enableEditSG;
self->hasSGCBResvTms = serverConfiguration->enableResvTmsForSGCB;
}
else {
else
{
self->enableEditSG = true;
self->hasSGCBResvTms = true;
}
@ -614,14 +620,15 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
self->mmsMapping = MmsMapping_create(dataModel, self);
if (self->mmsMapping) {
if (self->mmsMapping)
{
self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping);
self->mmsServer = MmsServer_create(self->mmsDevice, tlsConfiguration);
#if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1)
if (serverConfiguration) {
if (serverConfiguration)
{
MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService);
MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService);
MmsServer_setMaxAssociationSpecificDataSets(self->mmsServer, serverConfiguration->maxAssociationSpecificDataSets);
@ -668,7 +675,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
IedServer_setTimeQuality(self, true, false, false, 10);
}
else {
else
{
IedServer_destroy(self);
self = NULL;
}
@ -696,27 +704,6 @@ 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)
{
@ -1700,6 +1687,38 @@ IedServer_handleWriteAccessForComplexAttribute(IedServer self, DataAttribute* da
}
}
void
IedServer_handleWriteAccessForDataObject(IedServer self, DataObject* dataObject, FunctionalConstraint fc, WriteAccessHandler handler, void* parameter)
{
if (dataObject == NULL) {
if (DEBUG_IED_SERVER)
printf("IED_SERVER: IedServer_handlerWriteAccessForDataObject - dataObject == NULL!\n");
}
else
{
ModelNode* childElement = dataObject->firstChild;
while (childElement)
{
if (childElement->modelType == DataAttributeModelType)
{
DataAttribute* dataAttribute = (DataAttribute*) childElement;
if (dataAttribute->fc == fc)
{
IedServer_handleWriteAccessForComplexAttribute(self, dataAttribute, handler, parameter);
}
}
else if (childElement->modelType == DataObjectModelType)
{
IedServer_handleWriteAccessForDataObject(self, (DataObject*) childElement, fc, handler, parameter);
}
childElement = childElement->sibling;
}
}
}
void
IedServer_setReadAccessHandler(IedServer self, ReadAccessHandler handler, void* parameter)
{
@ -1967,3 +1986,23 @@ IedServer_setDirectoryAccessHandler(IedServer self, IedServer_DirectoryAccessHan
self->mmsMapping->directoryAccessHandler = handler;
self->mmsMapping->directoryAccessHandlerParameter = parameter;
}
void
IedServer_setListObjectsAccessHandler(IedServer self, IedServer_ListObjectsAccessHandler handler, void* parameter)
{
self->mmsMapping->listObjectsAccessHandler = handler;
self->mmsMapping->listObjectsAccessHandlerParameter = parameter;
}
void
IedServer_setControlBlockAccessHandler(IedServer self, IedServer_ControlBlockAccessHandler handler, void* parameter)
{
self->mmsMapping->controlBlockAccessHandler = handler;
self->mmsMapping->controlBlockAccessHandlerParameter = parameter;
}
void
IedServer_ignoreReadAccess(IedServer self, bool ignore)
{
self->ignoreReadAccess = ignore;
}

File diff suppressed because it is too large Load Diff

@ -395,7 +395,6 @@ updateLogStatusInLCB(LogControl* self)
}
}
static void
freeDynamicDataSet(LogControl* self)
{
@ -483,7 +482,7 @@ copyLCBValuesToTrackingObject(MmsMapping* self, LogControl* logControl)
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
MmsDataAccessError
LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig,
MmsValue* value, MmsServerConnection connection)
{
(void)connection;
@ -524,13 +523,28 @@ LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* doma
}
else
{
if (self->lcbAccessHandler)
{
if (self->controlBlockAccessHandler) {
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;
LogicalDevice* ld = IedModel_getDevice(self->model, domain->domainName);
if (ld) {
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, lnName);
if (ln) {
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, ACSI_CLASS_LCB, ld, ln, logControl->logControlBlock->name, varName, IEC61850_CB_ACCESS_TYPE_WRITE) == false) {
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
}
else {
retVal = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
}
}
else {
retVal = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
}
if (retVal != DATA_ACCESS_ERROR_SUCCESS) {
goto exit_function;
}
}
@ -747,14 +761,21 @@ LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain,
{
bool allowAccess = true;
if (self->lcbAccessHandler)
{
if (self->controlBlockAccessHandler) {
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
if (self->lcbAccessHandler(self->lcbAccessHandlerParameter, logControl->logControlBlock, clientConnection, LCB_EVENT_GET_PARAMETER) == false) {
allowAccess = false;
LogicalDevice* ld = IedModel_getDevice(self->model, domain->domainName);
if (ld) {
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, lnName);
value = &objectAccessDenied;
if (ln) {
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, ACSI_CLASS_LCB, ld, ln, logControl->logControlBlock->name, varName, IEC61850_CB_ACCESS_TYPE_READ) == false) {
allowAccess = false;
value = &objectAccessDenied;
}
}
}
}

@ -1,7 +1,7 @@
/*
* mms_mapping.c
*
* Copyright 2013-2023 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -1335,8 +1335,8 @@ checkForServiceTrackingVariables(MmsMapping* self, LogicalNode* logicalNode)
{
ModelNode* modelNode = logicalNode->firstChild;
while (modelNode) {
while (modelNode)
{
if (!strcmp(modelNode->name, "SpcTrk") || !strcmp(modelNode->name, "DpcTrk") ||
!strcmp(modelNode->name, "IncTrk") || !strcmp(modelNode->name, "EncTrk1") ||
!strcmp(modelNode->name, "ApcFTrk") || !strcmp(modelNode->name, "ApcIntTrk") ||
@ -2344,8 +2344,8 @@ lookupGCB(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName)
#endif
static MmsDataAccessError
writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
MmsValue* value)
writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig,
MmsValue* value, MmsServerConnection connection)
{
char variableId[130];
@ -2377,6 +2377,20 @@ writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variable
if (mmsGCB == NULL)
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
/* check if write access to GoCB is allowed on this connection */
if (self->controlBlockAccessHandler)
{
LogicalNode* ln = MmsGooseControlBlock_getLogicalNode(mmsGCB);
LogicalDevice* ld = (LogicalDevice*)ln->parent;
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, ACSI_CLASS_GoCB, ld, ln, MmsGooseControlBlock_getName(mmsGCB), varName, IEC61850_CB_ACCESS_TYPE_WRITE) == false) {
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
}
if (strcmp(varName, "GoEna") == 0) {
if (MmsValue_getType(value) != MMS_BOOLEAN)
return DATA_ACCESS_ERROR_TYPE_INCONSISTENT;
@ -2485,43 +2499,6 @@ writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variable
#endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */
#if 0
static MmsValue*
checkIfValueBelongsToModelNode(DataAttribute* dataAttribute, MmsValue* value, MmsValue* newValue)
{
if (dataAttribute->mmsValue == value)
return newValue;
DataAttribute* child = (DataAttribute*) dataAttribute->firstChild;
while (child != NULL) {
MmsValue* tmpValue = checkIfValueBelongsToModelNode(child, value, newValue);
if (tmpValue != NULL)
return tmpValue;
else
child = (DataAttribute*) child->sibling;
}
if (MmsValue_getType(value) == MMS_STRUCTURE) {
int elementCount = MmsValue_getArraySize(value);
int i;
for (i = 0; i < elementCount; i++) {
MmsValue* childValue = MmsValue_getElement(value, i);
MmsValue* childNewValue = MmsValue_getElement(newValue, i);
MmsValue* tmpValue = checkIfValueBelongsToModelNode(dataAttribute, childValue, childNewValue);
if (tmpValue != NULL)
return tmpValue;
}
}
return NULL;
}
#endif
static FunctionalConstraint
getFunctionalConstraintForWritableNode(char* separator)
{
@ -2591,16 +2568,28 @@ getAccessPolicyForFC(MmsMapping* self, FunctionalConstraint fc)
static MmsDataAccessError
mmsWriteHandler(void* parameter, MmsDomain* domain,
char* variableId, MmsValue* value, MmsServerConnection connection)
const char* variableId, int arrayIdx, const char* componentId, MmsValue* value, MmsServerConnection connection)
{
MmsMapping* self = (MmsMapping*) parameter;
if (DEBUG_IED_SERVER)
printf("IED_SERVER: Write requested %s\n", variableId);
{
if (arrayIdx != -1) {
if (componentId) {
printf("IED_SERVER: Write requested %s(%i).%s\n", variableId, arrayIdx, componentId);
}
else {
printf("IED_SERVER: Write requested %s(%i)\n", variableId, arrayIdx);
}
}
else {
printf("IED_SERVER: Write requested %s\n", variableId);
}
}
/* Access control based on functional constraint */
char* separator = strchr(variableId, '$');
char* separator = (char*)strchr(variableId, '$');
if (separator == NULL)
return DATA_ACCESS_ERROR_INVALID_ADDRESS;
@ -2619,7 +2608,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
/* Goose control block - GO */
if (isGooseControlBlock(separator))
return writeAccessGooseControlBlock(self, domain, variableId, value);
return writeAccessGooseControlBlock(self, domain, variableId, value, connection);
#endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */
@ -2677,7 +2666,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
if (rcNameLen == variableIdLen) {
if (strncmp(variableId, rc->name, variableIdLen) == 0) {
char* elementName = variableId + rcNameLen + 1;
const char* elementName = variableId + rcNameLen + 1;
return Reporting_RCBWriteAccessHandler(self, rc, elementName, value, connection);
}
@ -2699,8 +2688,48 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
if (nextSep != NULL) {
nextSep = strchr(nextSep + 1, '$');
if (nextSep == NULL) {
return DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED;
}
char* nameId = nextSep + 1;
/* check access permissions */
if (self->controlBlockAccessHandler)
{
MmsDataAccessError retVal = DATA_ACCESS_ERROR_SUCCESS;
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
LogicalDevice* ld = IedModel_getDevice(self->model, domain->domainName);
if (ld) {
char lnName[65];
strncpy(lnName, variableId, 64);
lnName[64] = 0;
lnName[lnNameLength] = 0;
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, lnName);
if (ln) {
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, ACSI_CLASS_SGCB, ld, ln, "SGCB", nameId, IEC61850_CB_ACCESS_TYPE_WRITE) == false) {
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
}
else {
retVal = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
}
}
else {
retVal = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
}
if (retVal != DATA_ACCESS_ERROR_SUCCESS) {
return retVal;
}
}
if (strcmp(nameId, "ActSG") == 0) {
SettingGroup* sg = getSettingGroupByMmsDomain(self, domain);
MmsDataAccessError retVal = DATA_ACCESS_ERROR_SUCCESS;
@ -2709,6 +2738,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
uint32_t val = MmsValue_toUint32(value);
if ((val > 0) && (val <= sg->sgcb->numOfSGs)) {
if (val != sg->sgcb->actSG) {
if (sg->actSgChangedHandler) {
@ -2891,13 +2921,19 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
#endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */
/* writable data model elements - SP, SV, CF, DC, BL */
if (fc != IEC61850_FC_NONE) {
if (fc != IEC61850_FC_NONE)
{
MmsValue* cachedValue;
cachedValue = MmsServer_getValueFromCache(self->mmsServer, domain, variableId);
if (cachedValue) {
if (arrayIdx != -1) {
cachedValue = MmsServer_getValueFromCacheEx2(self->mmsServer, domain, variableId, arrayIdx, componentId);
}
else {
cachedValue = MmsServer_getValueFromCache(self->mmsServer, domain, variableId);
}
if (cachedValue)
{
if (!MmsValue_equalTypes(cachedValue, value)) {
return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
}
@ -2910,7 +2946,8 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
printf("IED_SERVER: write to %s policy:%i\n", variableId, nodeAccessPolicy);
#if (CONFIG_IEC61850_SETTING_GROUPS == 1)
if (isFunctionalConstraint("SE", separator)) {
if (isFunctionalConstraint("SE", separator))
{
SettingGroup* sg = getSettingGroupByMmsDomain(self, domain);
if (sg != NULL) {
@ -2927,12 +2964,13 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
/* Call write access handlers */
LinkedList writeHandlerListElement = LinkedList_getNext(self->attributeAccessHandlers);
while (writeHandlerListElement != NULL) {
while (writeHandlerListElement)
{
AttributeAccessHandler* accessHandler = (AttributeAccessHandler*) writeHandlerListElement->data;
DataAttribute* dataAttribute = accessHandler->attribute;
if (dataAttribute->mmsValue == cachedValue) {
if (dataAttribute->mmsValue == cachedValue)
{
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
@ -2940,7 +2978,8 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
accessHandler->handler(dataAttribute, value, clientConnection,
accessHandler->parameter);
if ((handlerResult == DATA_ACCESS_ERROR_SUCCESS) || (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE)) {
if ((handlerResult == DATA_ACCESS_ERROR_SUCCESS) || (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE))
{
handlerFound = true;
if (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE)
@ -2956,14 +2995,14 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
}
/* DENY access if no handler is found and default policy is DENY */
if (!handlerFound) {
if (!handlerFound)
{
if (nodeAccessPolicy == ACCESS_POLICY_DENY)
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
if (updateValue) {
if (updateValue)
{
DataAttribute* da = IedModel_lookupDataAttributeByMmsValue(self->model, cachedValue);
if (da)
@ -3026,7 +3065,7 @@ MmsMapping_installReadAccessHandler(MmsMapping* self, ReadAccessHandler handler,
#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)
static MmsValue*
readAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig)
readAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, MmsServerConnection connection)
{
MmsValue* value = NULL;
@ -3050,13 +3089,28 @@ readAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variableI
char* varName = MmsMapping_getNextNameElement(objectName);
if (varName != NULL)
if (varName)
*(varName - 1) = 0;
MmsGooseControlBlock mmsGCB = lookupGCB(self, domain, lnName, objectName);
if (mmsGCB != NULL) {
if (varName != NULL) {
if (mmsGCB) {
/* check if read access to GoCB is allowed on this connection */
if (self->controlBlockAccessHandler)
{
LogicalNode* ln = MmsGooseControlBlock_getLogicalNode(mmsGCB);
LogicalDevice* ld = (LogicalDevice*)ln->parent;
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, ACSI_CLASS_GoCB, ld, ln, MmsGooseControlBlock_getName(mmsGCB), varName, IEC61850_CB_ACCESS_TYPE_READ) == false) {
return &objectAccessDenied;
}
}
if (varName) {
value = MmsValue_getSubElement(MmsGooseControlBlock_getMmsValues(mmsGCB),
MmsGooseControlBlock_getVariableSpecification(mmsGCB), varName);
}
@ -3070,7 +3124,6 @@ readAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variableI
#endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */
static MmsValue*
mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerConnection connection, bool isDirectAccess)
{
@ -3096,11 +3149,10 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo
}
#endif
#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)
/* GOOSE control blocks - GO */
if (isGooseControlBlock(separator)) {
retValue = readAccessGooseControlBlock(self, domain, variableId);
retValue = readAccessGooseControlBlock(self, domain, variableId, connection);
goto exit_function;
}
#endif
@ -3108,7 +3160,7 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo
#if (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1)
/* Sampled Value control blocks - MS/US */
if (isSampledValueControlBlock(separator)) {
retValue = LIBIEC61850_SV_readAccessSampledValueControlBlock(self, domain, variableId);
retValue = LIBIEC61850_SV_readAccessSampledValueControlBlock(self, domain, variableId, connection);
goto exit_function;
}
#endif
@ -3123,8 +3175,8 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo
#if (CONFIG_IEC61850_REPORT_SERVICE == 1)
/* Report control blocks - BR, RP */
if (isReportControlBlock(separator)) {
if (isReportControlBlock(separator))
{
LinkedList reportControls = self->reportControls;
LinkedList nextElement = reportControls;
@ -3145,11 +3197,12 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo
else
variableIdLen = strlen(variableId);
while ((nextElement = LinkedList_getNext(nextElement)) != NULL) {
while ((nextElement = LinkedList_getNext(nextElement)) != NULL)
{
ReportControl* rc = (ReportControl*) nextElement->data;
if (rc->domain == domain) {
if (rc->domain == domain)
{
int parentLNNameStrLen = strlen(rc->parentLN->name);
if (parentLNNameStrLen != lnNameLength)
@ -3158,7 +3211,8 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo
if (memcmp(rc->parentLN->name, variableId, parentLNNameStrLen) != 0)
continue;
if (strlen(rc->name) == variableIdLen) {
if (strlen(rc->name) == variableIdLen)
{
if (strncmp(variableId, rc->name, variableIdLen) == 0)
{
char* elementName = MmsMapping_getNextNameElement(reportName);
@ -3314,6 +3368,257 @@ mmsConnectionHandler(void* parameter, MmsServerConnection connection, MmsServerE
}
}
static bool
mmsListObjectsAccessHandler(void* parameter, MmsGetNameListType listType, MmsDomain* domain, char* variableId, MmsServerConnection connection)
{
MmsMapping* self = (MmsMapping*) parameter;
if (DEBUG_IED_SERVER)
printf("IED_SERVER: mmsListObjectsAccessHandler: Requested %s\n", variableId);
bool allowAccess = true;
if (listType == MMS_GETNAMELIST_DATASETS)
{
if (self->listObjectsAccessHandler) {
char str[65];
char* ldName = MmsDomain_getName(domain);
LogicalDevice* ld = IedModel_getDevice(self->model, ldName);
LogicalNode* ln = NULL;
char* objectName = variableId;
char* separator = strchr(variableId, '$');
if (separator) {
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) variableId, separator - variableId);
ln = LogicalDevice_getLogicalNode(ld, str);
if (ln) {
objectName = separator + 1;
}
}
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
allowAccess = self->listObjectsAccessHandler(self->listObjectsAccessHandlerParameter, clientConnection, ACSI_CLASS_DATA_SET, ld, ln, objectName, NULL, IEC61850_FC_NONE);
}
return allowAccess;
}
else if (listType == MMS_GETNAMELIST_JOURNALS)
{
if (self->listObjectsAccessHandler) {
char str[65];
char* ldName = MmsDomain_getName(domain);
char* objectName = variableId;
LogicalDevice* ld = IedModel_getDevice(self->model, ldName);
LogicalNode* ln = NULL;
char* separator = strchr(variableId, '$');
if (separator) {
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) variableId, separator - variableId);
ln = LogicalDevice_getLogicalNode(ld, str);
if (ln) {
objectName = separator + 1;
}
}
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
allowAccess = self->listObjectsAccessHandler(self->listObjectsAccessHandlerParameter, clientConnection, ACSI_CLASS_LOG, ld, ln, objectName, NULL, IEC61850_FC_NONE);
}
return allowAccess;
}
if (self->listObjectsAccessHandler)
{
char* separator = strchr(variableId, '$');
char* ldName = MmsDomain_getName(domain);
LogicalDevice* ld = IedModel_getDevice(self->model, ldName);
if (ld)
{
FunctionalConstraint fc = IEC61850_FC_NONE;
if (separator) {
fc = FunctionalConstraint_fromString(separator + 1);
if (fc == IEC61850_FC_BR || fc == IEC61850_FC_US ||
fc == IEC61850_FC_MS || fc == IEC61850_FC_RP ||
fc == IEC61850_FC_LG || fc == IEC61850_FC_GO)
{
char* subObjectName = NULL;
char str[65];
char subObjectBuf[65];
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) variableId, separator - variableId);
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, str);
if (ln) {
char* doStart = strchr(separator + 1, '$');
if (doStart != NULL) {
char* doEnd = strchr(doStart + 1, '$');
if (doEnd == NULL) {
StringUtils_copyStringToBuffer(doStart + 1, str);
}
else {
doEnd--;
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) (doStart + 1), doEnd - doStart);
subObjectName = StringUtils_copyStringToBufferAndReplace(doEnd + 2, subObjectBuf, '$', '.');
}
}
}
ACSIClass acsiClass = ACSI_CLASS_USVCB;
switch (fc)
{
case IEC61850_FC_BR:
acsiClass = ACSI_CLASS_BRCB;
break;
case IEC61850_FC_RP:
acsiClass = ACSI_CLASS_URCB;
break;
case IEC61850_FC_GO:
acsiClass = ACSI_CLASS_GoCB;
break;
case IEC61850_FC_LG:
acsiClass = ACSI_CLASS_LCB;
break;
case IEC61850_FC_MS:
acsiClass = ACSI_CLASS_MSVCB;
break;
default:
break;
}
if (self->listObjectsAccessHandler) {
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
allowAccess = self->listObjectsAccessHandler(self->listObjectsAccessHandlerParameter, clientConnection, acsiClass, ld, ln, str, subObjectName, fc);
}
goto exit_function;
}
else
{
char str[65];
char* subObjectName = NULL;
char subObjectBuf[65];
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) variableId, separator - variableId);
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, str);
if (ln != NULL)
{
char* doStart = strchr(separator + 1, '$');
if (doStart != NULL) {
char* doEnd = strchr(doStart + 1, '$');
if (doEnd == NULL) {
StringUtils_copyStringToBuffer(doStart + 1, str);
}
else {
doEnd--;
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) (doStart + 1), doEnd - doStart);
subObjectName = StringUtils_copyStringToBufferAndReplace(doEnd + 2, subObjectBuf, '$', '.');
}
if (fc == IEC61850_FC_SP) {
if (!strcmp(str, "SGCB")) {
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
allowAccess = self->listObjectsAccessHandler(self->listObjectsAccessHandlerParameter, clientConnection, ACSI_CLASS_SGCB, ld, ln, str, subObjectName, fc);
goto exit_function;
}
}
ModelNode* dobj = ModelNode_getChild((ModelNode*) ln, str);
if (dobj != NULL) {
if (dobj->modelType == DataObjectModelType) {
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
if (self->listObjectsAccessHandler) {
allowAccess = self->listObjectsAccessHandler(self->listObjectsAccessHandlerParameter, clientConnection, ACSI_CLASS_DATA_OBJECT, ld, ln, dobj->name, subObjectName, fc);
}
}
}
}
else {
/* no data object but with FC specified */
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
if (self->listObjectsAccessHandler) {
allowAccess = self->listObjectsAccessHandler(self->listObjectsAccessHandlerParameter, clientConnection, ACSI_CLASS_DATA_OBJECT, ld, ln, NULL, NULL, fc);
}
}
}
}
}
else {
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, variableId);
if (ln) {
/* only LN, no FC specified */
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
if (self->listObjectsAccessHandler) {
allowAccess = self->listObjectsAccessHandler(self->listObjectsAccessHandlerParameter, clientConnection, ACSI_CLASS_DATA_OBJECT, ld, ln, NULL, NULL, fc);
}
}
}
}
else {
/* internal error ? - we should not end up here! */
}
}
exit_function:
return allowAccess;
}
static MmsDataAccessError
mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsServerConnection connection, bool isDirectAccess)
{
@ -3324,15 +3629,26 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS
if (DEBUG_IED_SERVER)
printf("IED_SERVER: mmsReadAccessHandler: Requested %s\n", variableId);
if (self->iedServer->ignoreReadAccess)
{
if (DEBUG_IED_SERVER)
printf("IED_SERVER: mmsReadAccessHandler - ignore request\n");
return DATA_ACCESS_ERROR_NO_RESPONSE;
}
char* separator = strchr(variableId, '$');
#if (CONFIG_IEC61850_SETTING_GROUPS == 1)
if (separator) {
if (isFunctionalConstraint("SE", separator)) {
if (separator)
{
if (isFunctionalConstraint("SE", separator))
{
SettingGroup* sg = getSettingGroupByMmsDomain(self, domain);
if (sg != NULL) {
if (sg != NULL)
{
if (sg->sgcb->editSG == 0)
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
}
@ -3350,13 +3666,12 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS
LogicalDevice* ld = IedModel_getDevice(self->model, ldName);
if (ld != NULL) {
char str[65];
FunctionalConstraint fc;
if (ld != NULL)
{
FunctionalConstraint fc = IEC61850_FC_NONE;
if (separator != NULL) {
if (separator != NULL)
{
fc = FunctionalConstraint_fromString(separator + 1);
if (fc == IEC61850_FC_BR || fc == IEC61850_FC_US ||
@ -3367,6 +3682,8 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS
}
else
{
char str[65];
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) variableId, separator - variableId);
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, str);
@ -3375,30 +3692,45 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS
{
char* doStart = strchr(separator + 1, '$');
if (doStart != NULL) {
if (doStart != NULL)
{
char* doEnd = strchr(doStart + 1, '$');
if (doEnd == NULL) {
if (doEnd == NULL)
{
StringUtils_copyStringToBuffer(doStart + 1, str);
}
else {
else
{
doEnd--;
StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) (doStart + 1), doEnd - doStart);
}
if (fc == IEC61850_FC_SP) {
if (fc == IEC61850_FC_SP)
{
if (!strcmp(str, "SGCB"))
{
if (self->controlBlockAccessHandler)
{
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, ACSI_CLASS_SGCB, ld, ln, str, "", IEC61850_CB_ACCESS_TYPE_READ) == false) {
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
}
return DATA_ACCESS_ERROR_SUCCESS;
}
}
ModelNode* dobj = ModelNode_getChild((ModelNode*) ln, str);
if (dobj != NULL) {
if (dobj->modelType == DataObjectModelType) {
if (dobj != NULL)
{
if (dobj->modelType == DataObjectModelType)
{
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
@ -3407,7 +3739,8 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS
}
}
}
else {
else
{
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer,
connection);
@ -3417,6 +3750,18 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS
}
}
}
else
{
LogicalNode* ln = LogicalDevice_getLogicalNode(ld, variableId);
if (ln != NULL)
{
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
return self->readAccessHandler(ld, ln, NULL, fc, clientConnection,
self->readAccessHandlerParameter);
}
}
}
return DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED;
@ -3643,17 +3988,29 @@ mmsReadJournalHandler(void* parameter, MmsDomain* domain, const char* logName, M
MmsMapping* self = (MmsMapping*)parameter;
if (self->logAccessHandler) {
char logReference[130];
logReference[0] = 0;
if (self->controlBlockAccessHandler) {
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
StringUtils_appendString(logReference, 130, MmsDomain_getName(domain));
StringUtils_appendString(logReference, 130, "/");
StringUtils_appendString(logReference, 130, logName);
LogicalDevice* ld = IedModel_getDevice(self->model, domain->domainName);
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
LogicalNode* ln = NULL;
char str[65];
allowAccess = self->logAccessHandler(self->logAccessHandlerParameter, logReference, clientConnection);
StringUtils_copyStringMax(str, 65, logName);
char* name = str;
char* separator = strchr(str, '$');
if (separator) {
name = separator + 1;
*separator = 0;
ln = LogicalDevice_getLogicalNode(ld, str);
}
allowAccess = self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, ACSI_CLASS_LOG, ld, ln, name, NULL, IEC61850_CB_ACCESS_TYPE_READ);
}
return allowAccess;
@ -3666,6 +4023,7 @@ MmsMapping_installHandlers(MmsMapping* self)
MmsServer_installReadHandler(self->mmsServer, mmsReadHandler, (void*) self);
MmsServer_installWriteHandler(self->mmsServer, mmsWriteHandler, (void*) self);
MmsServer_installReadAccessHandler(self->mmsServer, mmsReadAccessHandler, (void*) self);
MmsServer_installListAccessHandler(self->mmsServer, mmsListObjectsAccessHandler, (void*) self);
MmsServer_installConnectionHandler(self->mmsServer, mmsConnectionHandler, (void*) self);
MmsServer_installVariableListAccessHandler(self->mmsServer, variableListAccessHandler, (void*) self);
MmsServer_installGetNameListHandler(self->mmsServer, mmsGetNameListHandler, (void*) self);
@ -3869,10 +4227,12 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag)
bool modelLocked = self->isModelLocked;
while ((element = LinkedList_getNext(element)) != NULL) {
while ((element = LinkedList_getNext(element)) != NULL)
{
ReportControl* rc = (ReportControl*) element->data;
if (rc->enabled || (rc->buffered && rc->dataSet != NULL)) {
if (rc->enabled || (rc->buffered && rc->dataSet != NULL))
{
int index;
switch (flag) {

@ -1,7 +1,7 @@
/*
* mms_sv.c
*
* Copyright 2015-2022 Michael Zillgith
* Copyright 2015-2023 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -32,6 +32,8 @@
#include "mms_sv.h"
#include "mms_mapping_internal.h"
#include "ied_server_private.h"
#include "mms_value_internal.h"
struct sMmsSampledValueControlBlock {
SVControlBlock* svcb;
@ -50,11 +52,12 @@ struct sMmsSampledValueControlBlock {
MmsValue* svEnaValue;
MmsValue* resvValue;
SVCBEventHandler eventHandler;
void* eventHandlerParameter;
};
static MmsValue objectAccessDenied = {MMS_DATA_ACCESS_ERROR, false, {DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED}};
MmsSampledValueControlBlock
MmsSampledValueControlBlock_create()
{
@ -63,7 +66,6 @@ MmsSampledValueControlBlock_create()
return self;
}
void
MmsSampledValueControlBlock_destroy(MmsSampledValueControlBlock self)
{
@ -105,7 +107,8 @@ MmsSampledValueControlBlock_enable(MmsSampledValueControlBlock self)
if (DEBUG_IED_SERVER)
printf("IED_SERVER: enable SVCB %s\n", self->svcb->name);
self->eventHandler(self->svcb, IEC61850_SVCB_EVENT_ENABLE, self->eventHandlerParameter);
if (self->eventHandler)
self->eventHandler(self->svcb, IEC61850_SVCB_EVENT_ENABLE, self->eventHandlerParameter);
}
static void
@ -117,7 +120,8 @@ MmsSampledValueControlBlock_disable(MmsSampledValueControlBlock self)
if (DEBUG_IED_SERVER)
printf("IED_SERVER: disable SVCB %s\n", self->svcb->name);
self->eventHandler(self->svcb, IEC61850_SVCB_EVENT_DISABLE, self->eventHandlerParameter);
if (self->eventHandler)
self->eventHandler(self->svcb, IEC61850_SVCB_EVENT_DISABLE, self->eventHandlerParameter);
}
static bool
@ -127,7 +131,7 @@ MmsSampledValueControlBlock_isEnabled(MmsSampledValueControlBlock self)
}
MmsDataAccessError
LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig,
MmsValue* value, MmsServerConnection connection)
{
char variableId[130];
@ -150,7 +154,7 @@ LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, ch
char* varName = MmsMapping_getNextNameElement(objectName);
if (varName != NULL)
if (varName)
*(varName - 1) = 0;
else
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
@ -160,7 +164,28 @@ LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, ch
if (mmsSVCB == NULL)
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
if (mmsSVCB->reservedByClient != NULL) {
/* check if write access to SVCB is allowed on this connection */
if (self->controlBlockAccessHandler)
{
ACSIClass acsiClass;
if (mmsSVCB->svcb->isUnicast)
acsiClass = ACSI_CLASS_USVCB;
else
acsiClass = ACSI_CLASS_MSVCB;
LogicalNode* ln = mmsSVCB->logicalNode;
LogicalDevice* ld = (LogicalDevice*)ln->parent;
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, acsiClass, ld, ln, mmsSVCB->svcb->name, varName, IEC61850_CB_ACCESS_TYPE_WRITE) == false) {
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
}
if (mmsSVCB->reservedByClient) {
if (mmsSVCB->reservedByClient != connection)
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
}
@ -209,7 +234,7 @@ LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, ch
}
MmsValue*
LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig)
LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, MmsServerConnection connection)
{
MmsValue* value = NULL;
@ -233,13 +258,35 @@ LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* d
char* varName = MmsMapping_getNextNameElement(objectName);
if (varName != NULL)
if (varName)
*(varName - 1) = 0;
MmsSampledValueControlBlock mmsSVCB = lookupSVCB(self, domain, lnName, objectName);
if (mmsSVCB != NULL) {
if (varName != NULL) {
if (mmsSVCB) {
/* check if read access to SVCB is allowed on this connection */
if (self->controlBlockAccessHandler)
{
ACSIClass acsiClass;
if (mmsSVCB->svcb->isUnicast)
acsiClass = ACSI_CLASS_USVCB;
else
acsiClass = ACSI_CLASS_MSVCB;
LogicalNode* ln = mmsSVCB->logicalNode;
LogicalDevice* ld = (LogicalDevice*)ln->parent;
ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection);
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, acsiClass, ld, ln, mmsSVCB->svcb->name, varName, IEC61850_CB_ACCESS_TYPE_READ) == false) {
return &objectAccessDenied;
}
}
if (varName) {
value = MmsValue_getSubElement(mmsSVCB->mmsValue, mmsSVCB->mmsType, varName);
}
else {

@ -1,7 +1,7 @@
/*
* reporting.c
*
* Copyright 2013-2023 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -185,7 +185,8 @@ purgeBuf(ReportControl* rc)
static void
deleteDataSetValuesShadowBuffer(ReportControl* self)
{
if (self->bufferedDataSetValues != NULL) {
if (self->bufferedDataSetValues != NULL)
{
assert(self->dataSet != NULL);
int dataSetSize = DataSet_getSize(self->dataSet);
@ -252,7 +253,7 @@ ReportControl_destroy(ReportControl* self)
}
MmsValue*
ReportControl_getRCBValue(ReportControl* rc, char* elementName)
ReportControl_getRCBValue(ReportControl* rc, const char* elementName)
{
if (rc->buffered) {
if (strcmp(elementName, "RptID") == 0)
@ -460,7 +461,7 @@ copyRCBValuesToTrackingObject(MmsMapping* self, ReportControl* rc)
}
static void
updateSingleTrackingValue(MmsMapping* self, ReportControl* rc, char* name, MmsValue* newValue)
updateSingleTrackingValue(MmsMapping* self, ReportControl* rc, const char* name, MmsValue* newValue)
{
if (rc->buffered) {
if (self->brcbTrk) {
@ -724,9 +725,23 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet,
success = true;
dataSetValue = NULL;
if (rc->buffered) {
rc->isBuffering = false;
purgeBuf(rc);
if (rc->dataSet) {
if (rc->buffered) {
rc->isBuffering = false;
purgeBuf(rc);
}
/* delete pending events */
deleteDataSetValuesShadowBuffer(rc);
if (isUsedDataSetDynamic) {
if (rc->dataSet) {
MmsMapping_freeDynamicallyCreatedDataSet(rc->dataSet);
}
}
/* release used data set */
rc->dataSet = NULL;
}
}
else
@ -739,7 +754,6 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet,
/* check if old and new data sets are the same */
if (rc->dataSet && dataSetValue) {
const char* dataSetLdName = rc->dataSet->logicalDeviceName;
const char* dataSetName = rc->dataSet->name;
const char* newDataSetName = MmsValue_toString(dataSetValue);
@ -790,7 +804,8 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet,
}
}
if (dataSetValue) {
if (dataSetValue)
{
const char* dataSetName = MmsValue_toString(dataSetValue);
DataSet* dataSet = IedModel_lookupDataSet(mapping->model, dataSetName);
@ -1777,12 +1792,24 @@ ReportControl_readAccess(ReportControl* rc, MmsMapping* mmsMapping, MmsServerCon
ClientConnection clientConnection = NULL;
if (mmsMapping->rcbAccessHandler || mmsMapping->rcbEventHandler) {
if (mmsMapping->controlBlockAccessHandler || 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) {
if (mmsMapping->controlBlockAccessHandler)
{
ACSIClass acsiClass;
if (rc->rcb->buffered)
acsiClass = ACSI_CLASS_BRCB;
else
acsiClass = ACSI_CLASS_URCB;
LogicalNode* ln = rc->rcb->parent;
LogicalDevice* ld = (LogicalDevice*)ln->parent;
if (mmsMapping->controlBlockAccessHandler(mmsMapping->controlBlockAccessHandlerParameter, clientConnection, acsiClass, ld, ln, rc->rcb->name, elementName, IEC61850_CB_ACCESS_TYPE_READ) == false) {
accessError = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
accessAllowed = false;
}
@ -1880,7 +1907,7 @@ reserveRcb(ReportControl* rc, MmsServerConnection connection)
}
MmsDataAccessError
Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value,
Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, const char* elementName, MmsValue* value,
MmsServerConnection connection)
{
MmsDataAccessError retVal = DATA_ACCESS_ERROR_SUCCESS;
@ -1891,8 +1918,20 @@ 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) {
if (self->controlBlockAccessHandler)
{
ACSIClass acsiClass;
if (rc->rcb->buffered)
acsiClass = ACSI_CLASS_BRCB;
else
acsiClass = ACSI_CLASS_URCB;
LogicalNode* ln = rc->rcb->parent;
LogicalDevice* ld = (LogicalDevice*)ln->parent;
if (self->controlBlockAccessHandler(self->controlBlockAccessHandlerParameter, clientConnection, acsiClass, ld, ln, rc->rcb->name, elementName, IEC61850_CB_ACCESS_TYPE_WRITE) == false) {
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
goto exit_function_only_tracking;
@ -2873,13 +2912,14 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
int dataBlockSize = 0;
if (isIntegrity || isGI) {
if (isIntegrity || isGI)
{
DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas;
int i;
for (i = 0; i < inclusionBitStringSize; i++) {
for (i = 0; i < inclusionBitStringSize; i++)
{
/* don't need reason for inclusion in GI or integrity report */
int encodedSize;
@ -2887,7 +2927,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
if (dataSetEntry->value) {
encodedSize = MmsValue_encodeMmsData(dataSetEntry->value, NULL, 0, false);
}
else {
else
{
MmsValue _errVal;
_errVal.type = MMS_DATA_ACCESS_ERROR;
_errVal.value.dataAccessError = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
@ -2902,17 +2943,19 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
bufferEntrySize += MemoryAllocator_getAlignedSize(sizeof(int) + dataBlockSize); /* add aligned_size(LEN + DATA) */
}
else { /* other trigger reason */
else
{
/* other trigger reason */
bufferEntrySize += inclusionFieldSize;
int reasonForInclusionSize = 0;
int i;
for (i = 0; i < inclusionBitStringSize; i++) {
if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) {
for (i = 0; i < inclusionBitStringSize; i++)
{
if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE)
{
reasonForInclusionSize++;
assert(reportControl->bufferedDataSetValues[i] != NULL);
@ -2947,13 +2990,15 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
if (DEBUG_IED_SERVER)
printf("IED_SERVER: number of reports in report buffer: %i\n", buffer->reportsCount);
if (buffer->lastEnqueuedReport == NULL) { /* buffer is empty - we start at the beginning of the memory block */
if (buffer->lastEnqueuedReport == NULL)
{
/* buffer is empty - we start at the beginning of the memory block */
entryBufPos = buffer->memoryBlock;
buffer->oldestReport = (ReportBufferEntry*) entryBufPos;
buffer->nextToTransmit = (ReportBufferEntry*) entryBufPos;
}
else {
else
{
assert(buffer->lastEnqueuedReport != NULL);
assert(buffer->oldestReport != NULL);
@ -2965,12 +3010,16 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
if (DEBUG_IED_SERVER) printf ("IED_SERVER: Last buffer offset: %i\n", (int) ((uint8_t*) buffer->lastEnqueuedReport - buffer->memoryBlock));
if (buffer->lastEnqueuedReport == buffer->oldestReport) { /* --> buffer->reportsCount == 1 */
if (buffer->lastEnqueuedReport == buffer->oldestReport)
{
/* --> buffer->reportsCount == 1 */
assert(buffer->reportsCount == 1);
entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength);
if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */
if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize))
{
/* buffer overflow */
entryBufPos = buffer->memoryBlock;
#if (DEBUG_IED_SERVER == 1)
@ -2984,7 +3033,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
buffer->oldestReport->next = NULL;
buffer->nextToTransmit = NULL;
}
else {
else
{
if (buffer->nextToTransmit == buffer->oldestReport)
buffer->nextToTransmit = buffer->lastEnqueuedReport;
@ -2993,17 +3043,22 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
}
}
else if (buffer->lastEnqueuedReport > buffer->oldestReport) {
else if (buffer->lastEnqueuedReport > buffer->oldestReport)
{
entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength);
if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */
if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize))
{
/* buffer overflow */
entryBufPos = buffer->memoryBlock;
/* remove old reports until enough space for new entry is available */
while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) {
while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport)
{
assert(buffer->oldestReport != NULL);
if (buffer->nextToTransmit == buffer->oldestReport) {
if (buffer->nextToTransmit == buffer->oldestReport)
{
buffer->nextToTransmit = buffer->oldestReport->next;
buffer->isOverflow = true;
overflow = true;
@ -3018,7 +3073,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
buffer->reportsCount--;
if (buffer->oldestReport == NULL) {
if (buffer->oldestReport == NULL)
{
buffer->oldestReport = (ReportBufferEntry*) entryBufPos;
buffer->oldestReport->next = NULL;
break;
@ -3028,14 +3084,18 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
buffer->lastEnqueuedReport->next = (ReportBufferEntry*) entryBufPos;
}
else if (buffer->lastEnqueuedReport < buffer->oldestReport) {
else if (buffer->lastEnqueuedReport < buffer->oldestReport)
{
entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength);
if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */
if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize))
{
/* buffer overflow */
entryBufPos = buffer->memoryBlock;
/* remove older reports in upper buffer part */
while ((uint8_t*) buffer->oldestReport > buffer->memoryBlock) {
while ((uint8_t*) buffer->oldestReport > buffer->memoryBlock)
{
assert(buffer->oldestReport != NULL);
if (buffer->nextToTransmit == buffer->oldestReport) {
@ -3055,13 +3115,15 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
}
/* remove older reports in lower buffer part that will be overwritten by new report */
while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) {
while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport)
{
if (buffer->oldestReport == NULL)
break;
assert(buffer->oldestReport != NULL);
if (buffer->nextToTransmit == buffer->oldestReport) {
if (buffer->nextToTransmit == buffer->oldestReport)
{
buffer->nextToTransmit = buffer->oldestReport->next;
buffer->isOverflow = true;
overflow = true;
@ -3077,15 +3139,17 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
buffer->reportsCount--;
}
}
else {
while (((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) && ((uint8_t*) buffer->oldestReport != buffer->memoryBlock)) {
else
{
while (((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) && ((uint8_t*) buffer->oldestReport != buffer->memoryBlock))
{
if (buffer->oldestReport == NULL)
break;
assert(buffer->oldestReport != NULL);
if (buffer->nextToTransmit == buffer->oldestReport) {
if (buffer->nextToTransmit == buffer->oldestReport)
{
buffer->nextToTransmit = buffer->oldestReport->next;
buffer->isOverflow = true;
overflow = true;
@ -3116,7 +3180,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
entry->timeOfEntry = timeOfEntry;
if (isBuffered) {
if (isBuffered)
{
/* ENTRY_ID is set to system time in ms! */
uint64_t entryId = timeOfEntry;
@ -3135,7 +3200,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
printf(" at pos %p\n", entryStartPos);
#endif
if (reportControl->enabled == false) {
if (reportControl->enabled == false)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(reportControl->rcbValuesLock);
#endif
@ -3162,7 +3228,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
entryBufPos += MemoryAllocator_getAlignedSize(sizeof(ReportBufferEntry));
if (isIntegrity || isGI) {
if (isIntegrity || isGI)
{
DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas;
/* encode LEN */
@ -3172,12 +3239,13 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
/* encode DATA */
int i;
for (i = 0; i < inclusionBitStringSize; i++) {
for (i = 0; i < inclusionBitStringSize; i++)
{
if (dataSetEntry->value) {
entryBufPos += MmsValue_encodeMmsData(dataSetEntry->value, entryBufPos, 0, true);
}
else {
else
{
MmsValue _errVal;
_errVal.type = MMS_DATA_ACCESS_ERROR;
_errVal.value.dataAccessError = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
@ -3187,9 +3255,9 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
dataSetEntry = dataSetEntry->sibling;
}
}
else {
else
{
/* encode inclusion bit string */
inclusionFieldStatic.value.bitString.buf = entryBufPos;
memset(entryBufPos, 0, inclusionFieldSize);
@ -3202,10 +3270,10 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
/* encode DATA */
int i;
for (i = 0; i < inclusionBitStringSize; i++) {
if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) {
for (i = 0; i < inclusionBitStringSize; i++)
{
if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE)
{
/* update inclusion bit string for report entry */
MmsValue_setBitStringBit(inclusionField, i, true);
@ -3213,13 +3281,14 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_
entryBufPos += MmsValue_encodeMmsData(reportControl->bufferedDataSetValues[i], entryBufPos, 0, true);
}
}
/* encode REASON */
for (i = 0; i < inclusionBitStringSize; i++) {
for (i = 0; i < inclusionBitStringSize; i++)
{
if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) {
if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE)
{
*entryBufPos = (uint8_t) reportControl->inclusionFlags[i];
entryBufPos ++;
}
@ -3248,10 +3317,12 @@ exit_function:
Semaphore_post(buffer->lock);
#endif
if (reportControl->server) {
if (reportControl->server)
{
MmsMapping* mmsMapping = reportControl->server->mmsMapping;
if (mmsMapping->rcbEventHandler) {
if (mmsMapping->rcbEventHandler)
{
if (overflow) {
mmsMapping->rcbEventHandler(mmsMapping->rcbEventHandlerParameter, reportControl->rcb, NULL, RCB_EVENT_OVERFLOW, NULL, DATA_ACCESS_ERROR_SUCCESS);
}
@ -3942,11 +4013,12 @@ Reporting_activateBufferedReports(MmsMapping* self)
static void
processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
{
if ((rc->enabled) || (rc->isBuffering)) {
if (rc->triggerOps & TRG_OPT_GI) {
if (rc->gi) {
if ((rc->enabled) || (rc->isBuffering))
{
if (rc->triggerOps & TRG_OPT_GI)
{
if (rc->gi)
{
/* send current events in event buffer before GI report */
if (rc->triggered) {
rc->triggered = false;
@ -3961,12 +4033,12 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
}
}
if (rc->triggerOps & TRG_OPT_INTEGRITY) {
if (rc->intgPd > 0) {
if (currentTimeInMs >= rc->nextIntgReportTime) {
if (rc->triggerOps & TRG_OPT_INTEGRITY)
{
if (rc->intgPd > 0)
{
if (currentTimeInMs >= rc->nextIntgReportTime)
{
/* send current events in event buffer before integrity report */
if (rc->triggered) {
enqueueReport(rc, false, false, currentTimeInMs);
@ -3981,8 +4053,8 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
}
/* check for system time change effects */
if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd)) {
if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd))
{
if (rc->server->syncIntegrityReportTimes) {
rc->nextIntgReportTime = getNextRoundedStartTime(currentTimeInMs, rc->intgPd);
}
@ -3995,9 +4067,11 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
rc->triggered = false;
}
else {
else
{
/* check for system time change effects */
if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd)) {
if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd))
{
if (rc->server->syncIntegrityReportTimes) {
rc->nextIntgReportTime = getNextRoundedStartTime(currentTimeInMs, rc->intgPd);
}
@ -4009,9 +4083,10 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
}
}
if (rc->triggered) {
if (currentTimeInMs >= rc->reportTime) {
if (rc->triggered)
{
if (currentTimeInMs >= rc->reportTime)
{
enqueueReport(rc, false, false, currentTimeInMs);
rc->triggered = false;
@ -4027,11 +4102,12 @@ Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs)
Semaphore_wait(self->isModelLockedMutex);
#endif
if (self->isModelLocked == false) {
if (self->isModelLocked == false)
{
LinkedList element = self->reportControls;
while ((element = LinkedList_getNext(element)) != NULL ) {
while ((element = LinkedList_getNext(element)) != NULL )
{
ReportControl* rc = (ReportControl*) element->data;
ReportControl_lockNotify(rc);
@ -4055,11 +4131,12 @@ Reporting_sendReports(MmsMapping* self, MmsServerConnection connection)
{
LinkedList element = LinkedList_getNext(self->reportControls);
while (element) {
while (element)
{
ReportControl* rc = (ReportControl*) LinkedList_getData(element);
if (rc->clientConnection == connection) {
if (rc->clientConnection == connection)
{
ReportControl_lockNotify(rc);
if (rc->enabled) {
@ -4086,8 +4163,10 @@ static void
copyValuesToReportBuffer(ReportControl* self)
{
int i;
for (i = 0; i < self->dataSet->elementCount; i++) {
if (self->inclusionFlags[i] & REPORT_CONTROL_NOT_UPDATED) {
for (i = 0; i < self->dataSet->elementCount; i++)
{
if (self->inclusionFlags[i] & REPORT_CONTROL_NOT_UPDATED)
{
copySingleValueToReportBuffer(self, i);
/* clear not-updated flag */
@ -4104,13 +4183,14 @@ Reporting_processReportEventsAfterUnlock(MmsMapping* self)
uint64_t currentTime = Hal_getTimeInMs();
while ((element = LinkedList_getNext(element)) != NULL ) {
while ((element = LinkedList_getNext(element)) != NULL )
{
ReportControl* rc = (ReportControl*) element->data;
ReportControl_lockNotify(rc);
if ((rc->enabled) || (rc->isBuffering)) {
if ((rc->enabled) || (rc->isBuffering))
{
if (rc->triggered) {
copyValuesToReportBuffer(rc);
@ -4128,10 +4208,13 @@ ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag,
{
ReportControl_lockNotify(self);
if (self->inclusionFlags[dataSetEntryIndex] & flag) { /* report for this data set entry is already pending (bypass BufTm) */
if (self->inclusionFlags[dataSetEntryIndex] & flag)
{
/* report for this data set entry is already pending (bypass BufTm and send report immediately) */
self->reportTime = Hal_getTimeInMs();
if (modelLocked) {
if (modelLocked)
{
/* buffer all relevant values */
copyValuesToReportBuffer(self);
}
@ -4139,19 +4222,21 @@ ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag,
processEventsForReport(self, self->reportTime);
}
if (modelLocked) {
if (modelLocked)
{
/* set flag to update values when report is to be sent or data model unlocked */
self->inclusionFlags[dataSetEntryIndex] = self->inclusionFlags[dataSetEntryIndex] | flag | REPORT_CONTROL_NOT_UPDATED;
}
else {
else
{
self->inclusionFlags[dataSetEntryIndex] = flag;
/* buffer value for report */
copySingleValueToReportBuffer(self, dataSetEntryIndex);
}
if (self->triggered == false) {
if (self->triggered == false)
{
uint64_t currentTime = Hal_getTimeInMs();
MmsValue_setBinaryTime(self->timeOfEntry, currentTime);
@ -4180,7 +4265,8 @@ ReportControlBlock_getRptEna(ReportControlBlock* self)
char*
ReportControlBlock_getRptID(ReportControlBlock* self)
{
if (self->trgOps & 64) {
if (self->trgOps & 64)
{
ReportControl* rc = (ReportControl*)(self->sibling);
#if (CONFIG_MMS_THREADLESS_STACK != 1)

@ -3,7 +3,7 @@
*
* Helper functions for the dynamic creation of Common Data Classes (CDCs)
*
* Copyright 2014 Michael Zillgith
* Copyright 2014-2023 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -35,7 +35,7 @@ DataAttribute*
CAC_AnalogueValue_create(const char* name, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions,
bool isIntegerNotFloat)
{
DataAttribute* analogeValue = DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
DataAttribute* analogeValue = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
if (isIntegerNotFloat)
DataAttribute_create("i", (ModelNode*) analogeValue, IEC61850_INT32, fc, triggerOptions, 0, 0);
@ -48,7 +48,7 @@ CAC_AnalogueValue_create(const char* name, ModelNode* parent, FunctionalConstrai
DataAttribute*
CAC_ValWithTrans_create(const char* name, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions, bool hasTransientIndicator)
{
DataAttribute* valWithTrans = DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
DataAttribute* valWithTrans = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
DataAttribute_create("posVal", (ModelNode*) valWithTrans, IEC61850_INT8, fc, triggerOptions, 0, 0);
@ -64,7 +64,7 @@ CAC_ValWithTrans_create(const char* name, ModelNode* parent, FunctionalConstrain
DataAttribute*
CAC_Vector_create(const char* name, ModelNode* parent, uint32_t options, FunctionalConstraint fc, uint8_t triggerOptions)
{
DataAttribute* vector = DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
DataAttribute* vector = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
CAC_AnalogueValue_create("mag", (ModelNode*) vector, fc, triggerOptions, false);
@ -77,7 +77,7 @@ CAC_Vector_create(const char* name, ModelNode* parent, uint32_t options, Functio
DataAttribute*
CAC_Point_create(const char* name, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions, bool hasZVal)
{
DataAttribute* point = DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
DataAttribute* point = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);
DataAttribute_create("xVal", (ModelNode*) point, IEC61850_FLOAT32, fc, triggerOptions, 0, 0);
DataAttribute_create("yVal", (ModelNode*) point, IEC61850_FLOAT32, fc, triggerOptions, 0, 0);
@ -91,7 +91,7 @@ CAC_Point_create(const char* name, ModelNode* parent, FunctionalConstraint fc, u
DataAttribute*
CAC_ScaledValueConfig_create(const char* name, ModelNode* parent)
{
DataAttribute* scaling = DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
DataAttribute* scaling = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
DataAttribute_create("scaleFactor", (ModelNode*) scaling, IEC61850_FLOAT32, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
DataAttribute_create("offset", (ModelNode*) scaling, IEC61850_FLOAT32, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
@ -102,7 +102,7 @@ CAC_ScaledValueConfig_create(const char* name, ModelNode* parent)
DataAttribute*
CAC_Unit_create(const char* name, ModelNode* parent, bool hasMagnitude)
{
DataAttribute* unit = DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
DataAttribute* unit = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
DataAttribute_create("SIUnit", (ModelNode*) unit, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
@ -255,7 +255,7 @@ CDC_addStandardOptions(DataObject* dataObject, uint32_t options)
DataObject*
CDC_SPS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newSPS = DataObject_create(dataObjectName, parent, 0);
DataObject* newSPS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_addStatusToDataObject(newSPS, IEC61850_BOOLEAN);
@ -273,7 +273,7 @@ CDC_SPS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_DPS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newDPS = DataObject_create(dataObjectName, parent, 0);
DataObject* newDPS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_addStatusToDataObject(newDPS, IEC61850_CODEDENUM);
@ -291,7 +291,7 @@ CDC_DPS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_INS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newINS = DataObject_create(dataObjectName, parent, 0);
DataObject* newINS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_addStatusToDataObject(newINS, IEC61850_INT32);
@ -310,7 +310,7 @@ CDC_INS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_ENS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newENS = DataObject_create(dataObjectName, parent, 0);
DataObject* newENS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_addStatusToDataObject(newENS, IEC61850_ENUMERATED);
@ -328,7 +328,7 @@ CDC_ENS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_BCR_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newBCR = DataObject_create(dataObjectName, parent, 0);
DataObject* newBCR = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("actVal", (ModelNode*) newBCR, IEC61850_INT64, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
@ -359,7 +359,7 @@ CDC_BCR_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_SEC_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newSEC = DataObject_create(dataObjectName, parent, 0);
DataObject* newSEC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("cnt", (ModelNode*) newSEC, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
DataAttribute_create("sev", (ModelNode*) newSEC, IEC61850_ENUMERATED, IEC61850_FC_ST, 0, 0, 0);
@ -380,7 +380,7 @@ CDC_SEC_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_VSS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newSPS = DataObject_create(dataObjectName, parent, 0);
DataObject* newSPS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_addStatusToDataObject(newSPS, IEC61850_VISIBLE_STRING_255);
@ -403,7 +403,7 @@ CDC_VSS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_MV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat)
{
DataObject* newMV = DataObject_create(dataObjectName, parent, 0);
DataObject* newMV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
if (options & CDC_OPTION_INST_MAG)
CAC_AnalogueValue_create("instMag", (ModelNode*) newMV, IEC61850_FC_MX, 0, isIntegerNotFloat);
@ -432,7 +432,7 @@ CDC_MV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, b
DataObject*
CDC_CMV_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newMV = DataObject_create(dataObjectName, parent, 0);
DataObject* newMV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
if (options & CDC_OPTION_INST_MAG)
CAC_Vector_create("instCVal", (ModelNode*) newMV, options, IEC61850_FC_MX, 0);
@ -465,7 +465,7 @@ CDC_CMV_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_SAV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat)
{
DataObject* newSAV = DataObject_create(dataObjectName, parent, 0);
DataObject* newSAV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CAC_AnalogueValue_create("instMag", (ModelNode*) newSAV, IEC61850_FC_MX, 0, isIntegerNotFloat);
@ -491,7 +491,7 @@ CDC_SAV_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_HST_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint16_t maxPts)
{
DataObject* newHST = DataObject_create(dataObjectName, parent, 0);
DataObject* newHST = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("hstVal", (ModelNode*) newHST, IEC61850_INT32, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED | TRG_OPT_DATA_UPDATE, maxPts, 0);
@ -577,7 +577,7 @@ addCommonControlAttributes(DataObject* dobj, uint32_t controlOptions)
DataObject*
CDC_SPC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
DataObject* newSPC = DataObject_create(dataObjectName, parent, 0);
DataObject* newSPC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addOriginatorAndCtlNumOptions((ModelNode*) newSPC, controlOptions);
@ -613,7 +613,7 @@ CDC_SPC_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_DPC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
DataObject* newDPC = DataObject_create(dataObjectName, parent, 0);
DataObject* newDPC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addOriginatorAndCtlNumOptions((ModelNode*) newDPC, controlOptions);
@ -695,7 +695,7 @@ addControlStatusAttributesForAnalogControl(DataObject* dobj, uint32_t controlOpt
DataObject*
CDC_APC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat)
{
DataObject* newAPC = DataObject_create(dataObjectName, parent, 0);
DataObject* newAPC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addControlStatusAttributesForAnalogControl(newAPC, controlOptions);
@ -728,7 +728,7 @@ CDC_APC_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_INC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
DataObject* newINC = DataObject_create(dataObjectName, parent, 0);
DataObject* newINC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addOriginatorAndCtlNumOptions((ModelNode*) newINC, controlOptions);
@ -764,7 +764,7 @@ CDC_INC_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_ENC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
DataObject* newENC = DataObject_create(dataObjectName, parent, 0);
DataObject* newENC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addOriginatorAndCtlNumOptions((ModelNode*) newENC, controlOptions);
@ -791,7 +791,7 @@ CDC_ENC_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_BSC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool hasTransientIndicator)
{
DataObject* newBSC = DataObject_create(dataObjectName, parent, 0);
DataObject* newBSC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addOriginatorAndCtlNumOptions((ModelNode*) newBSC, controlOptions);
@ -821,7 +821,7 @@ CDC_BSC_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_ISC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool hasTransientIndicator)
{
DataObject* newISC = DataObject_create(dataObjectName, parent, 0);
DataObject* newISC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addOriginatorAndCtlNumOptions((ModelNode*) newISC, controlOptions);
@ -855,7 +855,7 @@ CDC_ISC_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_BAC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat)
{
DataObject* newBAC = DataObject_create(dataObjectName, parent, 0);
DataObject* newBAC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
addControlStatusAttributesForAnalogControl(newBAC, controlOptions);
@ -899,7 +899,7 @@ CDC_BAC_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newLPL = DataObject_create(dataObjectName, parent, 0);
DataObject* newLPL = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("vendor", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
DataAttribute_create("swRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
@ -921,7 +921,7 @@ CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newDPL = DataObject_create(dataObjectName, parent, 0);
DataObject* newDPL = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("vendor", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
@ -949,7 +949,7 @@ CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_ACD_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newACD = DataObject_create(dataObjectName, parent, 0);
DataObject* newACD = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("general", (ModelNode*) newACD, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
DataAttribute_create("dirGeneral", (ModelNode*) newACD, IEC61850_ENUMERATED, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
@ -984,7 +984,7 @@ CDC_ACD_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_ACT_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newACT = DataObject_create(dataObjectName, parent, 0);
DataObject* newACT = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("general", (ModelNode*) newACT, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
@ -1010,7 +1010,7 @@ CDC_ACT_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_WYE_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newWYE = DataObject_create(dataObjectName, parent, 0);
DataObject* newWYE = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
/* TODO check if some options should be masked */
/* TODO take care for GC_1 */
@ -1033,7 +1033,7 @@ CDC_WYE_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_DEL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newDEL = DataObject_create(dataObjectName, parent, 0);
DataObject* newDEL = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
/* TODO check if some options should be masked */
CDC_CMV_create("phsAB", (ModelNode*) newDEL, options);
@ -1052,7 +1052,7 @@ CDC_DEL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_SPG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newSPG = DataObject_create(dataObjectName, parent, 0);
DataObject* newSPG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("setVal", (ModelNode*) newSPG, IEC61850_BOOLEAN, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);
@ -1064,7 +1064,7 @@ CDC_SPG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_VSG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newSPG = DataObject_create(dataObjectName, parent, 0);
DataObject* newSPG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("setVal", (ModelNode*) newSPG, IEC61850_VISIBLE_STRING_255, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);
@ -1077,7 +1077,7 @@ CDC_VSG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_ENG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newENG = DataObject_create(dataObjectName, parent, 0);
DataObject* newENG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("setVal", (ModelNode*) newENG, IEC61850_ENUMERATED, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);
@ -1089,7 +1089,7 @@ CDC_ENG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_ING_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newING = DataObject_create(dataObjectName, parent, 0);
DataObject* newING = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("setVal", (ModelNode*) newING, IEC61850_INT32, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);
@ -1114,7 +1114,7 @@ CDC_ING_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataObject*
CDC_ASG_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat)
{
DataObject* newASG = DataObject_create(dataObjectName, parent, 0);
DataObject* newASG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CAC_AnalogueValue_create("setMag", (ModelNode*) newASG, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);
@ -1145,7 +1145,7 @@ CDC_ASG_create(const char* dataObjectName, ModelNode* parent, uint32_t options,
DataObject*
CDC_SPV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, uint32_t wpOptions, bool hasChaManRs)
{
DataObject* newSPV = DataObject_create(dataObjectName, parent, 0);
DataObject* newSPV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
if (hasChaManRs)
CDC_SPC_create("chaManRs", (ModelNode*) newSPV, 0, CDC_CTL_MODEL_DIRECT_NORMAL);
@ -1196,7 +1196,7 @@ CDC_STV_create(const char* dataObjectName, ModelNode* parent,
(void)controlOptions; /* TODO implement */
(void)wpOptions; /* TODO implement */
DataObject* newSTV = DataObject_create(dataObjectName, parent, 0);
DataObject* newSTV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_INS_create("actSt", (ModelNode*) newSTV, 0);
@ -1218,7 +1218,7 @@ CDC_ALM_create(const char* dataObjectName, ModelNode* parent,
(void)controlOptions; /* TODO implement */
(void)wpOptions; /* TODO implement */
DataObject* newALM = DataObject_create(dataObjectName, parent, 0);
DataObject* newALM = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_SPC_create("almAck", (ModelNode*) newALM, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);
@ -1244,7 +1244,7 @@ CDC_CMD_create(const char* dataObjectName, ModelNode* parent,
(void)hasCmTm; /* TODO implement */
(void)hasCmCt; /* TODO implement */
DataObject* newCMD = DataObject_create(dataObjectName, parent, 0);
DataObject* newCMD = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_INC_create("actSt", (ModelNode*) newCMD, 0, controlOptions);
@ -1272,7 +1272,7 @@ CDC_CTE_create(const char* dataObjectName, ModelNode* parent,
{
(void)controlOptions; /* TODO implement */
DataObject* newCTE = DataObject_create(dataObjectName, parent, 0);
DataObject* newCTE = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_SPC_create("manRs", (ModelNode*) newCTE, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);
@ -1313,7 +1313,7 @@ CDC_TMS_create(const char* dataObjectName, ModelNode* parent,
{
(void)controlOptions; /* TODO implement */
DataObject* newTMS = DataObject_create(dataObjectName, parent, 0);
DataObject* newTMS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);
CDC_SPC_create("manRs", (ModelNode*) newTMS, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);
@ -1344,4 +1344,3 @@ CDC_TMS_create(const char* dataObjectName, ModelNode* parent,
return newTMS;
}

@ -1,7 +1,7 @@
/*
* config_file_parser.c
*
* Copyright 2014-2022 Michael Zillgith
* Copyright 2014-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -28,9 +28,9 @@
#include "libiec61850_platform_includes.h"
#include "stack_config.h"
#define READ_BUFFER_MAX_SIZE 1024
#include <ctype.h>
static uint8_t lineBuffer[READ_BUFFER_MAX_SIZE];
#define READ_BUFFER_MAX_SIZE 1024
static int
readLine(FileHandle fileHandle, uint8_t* buffer, int maxSize)
@ -114,18 +114,132 @@ ConfigFileParser_createModelFromConfigFileEx(const char* filename)
return model;
}
static bool
setValue(char* lineBuffer, DataAttribute* dataAttribute)
{
char* valueIndicator = strchr((char*) lineBuffer, '=');
if (valueIndicator != NULL) {
switch (dataAttribute->type) {
case IEC61850_UNICODE_STRING_255:
{
char* stringStart = valueIndicator + 2;
terminateString(stringStart, '"');
dataAttribute->mmsValue = MmsValue_newMmsString(stringStart);
}
break;
case IEC61850_VISIBLE_STRING_255:
case IEC61850_VISIBLE_STRING_129:
case IEC61850_VISIBLE_STRING_65:
case IEC61850_VISIBLE_STRING_64:
case IEC61850_VISIBLE_STRING_32:
case IEC61850_CURRENCY:
{
char* stringStart = valueIndicator + 2;
terminateString(stringStart, '"');
dataAttribute->mmsValue = MmsValue_newVisibleString(stringStart);
}
break;
case IEC61850_INT8:
case IEC61850_INT16:
case IEC61850_INT32:
case IEC61850_INT64:
case IEC61850_INT128:
case IEC61850_ENUMERATED:
{
int32_t intValue;
if (sscanf(valueIndicator + 1, "%i", &intValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newIntegerFromInt32(intValue);
}
break;
case IEC61850_INT8U:
case IEC61850_INT16U:
case IEC61850_INT24U:
case IEC61850_INT32U:
{
uint32_t uintValue;
if (sscanf(valueIndicator + 1, "%u", &uintValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newUnsignedFromUint32(uintValue);
}
break;
case IEC61850_FLOAT32:
{
float floatValue;
if (sscanf(valueIndicator + 1, "%f", &floatValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newFloat(floatValue);
}
break;
case IEC61850_FLOAT64:
{
double doubleValue;
if (sscanf(valueIndicator + 1, "%lf", &doubleValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newDouble(doubleValue);
}
break;
case IEC61850_BOOLEAN:
{
int boolean;
if (sscanf(valueIndicator + 1, "%i", &boolean) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newBoolean((bool) boolean);
}
break;
case IEC61850_OPTFLDS:
{
int value;
if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newBitString(-10);
MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value);
}
break;
case IEC61850_TRGOPS:
{
int value;
if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newBitString(-6);
MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value);
}
break;
default:
break;
}
}
return true;
exit_error:
return false;
}
IedModel*
ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
{
uint8_t* lineBuffer = (uint8_t*)GLOBAL_MALLOC(READ_BUFFER_MAX_SIZE);
if (lineBuffer == NULL)
goto exit_error;
int bytesRead = 1;
bool stateInModel = false;
int indendation = 0;
bool inArray = false;
bool inArrayElement = false;
IedModel* model = NULL;
LogicalDevice* currentLD = NULL;
LogicalNode* currentLN = NULL;
ModelNode* currentModelNode = NULL;
ModelNode* currentArrayNode = NULL;
DataSet* currentDataSet = NULL;
GSEControlBlock* currentGoCB = NULL;
SVControlBlock* currentSMVCB = NULL;
@ -136,16 +250,30 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
int currentLine = 0;
while (bytesRead > 0) {
while (bytesRead > 0)
{
bytesRead = readLine(fileHandle, lineBuffer, READ_BUFFER_MAX_SIZE);
currentLine++;
if (bytesRead > 0) {
if (bytesRead > 0)
{
lineBuffer[bytesRead] = 0;
if (stateInModel) {
/* trim trailing spaces */
while (bytesRead > 1) {
bytesRead--;
if (isspace(lineBuffer[bytesRead])) {
lineBuffer[bytesRead] = 0;
}
else {
break;
}
}
if (stateInModel)
{
if (StringUtils_startsWith((char*) lineBuffer, "}")) {
if (indendation == 1) {
stateInModel = false;
@ -161,13 +289,25 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
indendation = 3;
}
else if (indendation > 4) {
if (inArrayElement && currentModelNode->parent == currentArrayNode) {
inArrayElement = false;
}
else {
indendation--;
}
if (inArray && currentModelNode == currentArrayNode) {
inArray = false;
}
currentModelNode = currentModelNode->parent;
indendation--;
}
}
else if (indendation == 1) {
if (StringUtils_startsWith((char*) lineBuffer, "LD")) {
else if (indendation == 1)
{
if (StringUtils_startsWith((char*) lineBuffer, "LD"))
{
indendation = 2;
char ldName[65];
@ -178,7 +318,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
terminateString(nameString, ')');
if (ldName[0] != 0) {
if (ldName[0] != 0)
{
terminateString(ldName, ')');
currentLD = LogicalDevice_createEx(nameString, model, ldName);
@ -190,8 +331,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
else
goto exit_error;
}
else if (indendation == 2) {
if (StringUtils_startsWith((char*) lineBuffer, "LN")) {
else if (indendation == 2)
{
if (StringUtils_startsWith((char*) lineBuffer, "LN"))
{
indendation = 3;
if (sscanf((char*) lineBuffer, "LN(%129s)", nameString) < 1)
@ -204,26 +347,35 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
else
goto exit_error;
}
else if (indendation == 3) {
if (StringUtils_startsWith((char*) lineBuffer, "DO")) {
else if (indendation == 3)
{
if (StringUtils_startsWith((char*) lineBuffer, "DO"))
{
indendation = 4;
int arrayElements = 0;
sscanf((char*) lineBuffer, "DO(%129s %i)", nameString, &arrayElements);
if (sscanf((char*)lineBuffer, "DO(%129s %i)", nameString, &arrayElements) != 2) {
goto exit_error;
}
currentModelNode = (ModelNode*)
DataObject_create(nameString, (ModelNode*) currentLN, arrayElements);
}
else if (StringUtils_startsWith((char*) lineBuffer, "DS")) {
else if (StringUtils_startsWith((char*) lineBuffer, "DS"))
{
indendation = 4;
sscanf((char*) lineBuffer, "DS(%129s)", nameString);
if (sscanf((char*)lineBuffer, "DS(%129s)", nameString) != 1) {
goto exit_error;
}
terminateString(nameString, ')');
currentDataSet = DataSet_create(nameString, currentLN);
}
else if (StringUtils_startsWith((char*) lineBuffer, "RC")) {
else if (StringUtils_startsWith((char*) lineBuffer, "RC"))
{
int isBuffered;
uint32_t confRef;
int trgOps;
@ -250,7 +402,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
ReportControlBlock_create(nameString, currentLN, rptId,
(bool) isBuffered, dataSetName, confRef, trgOps, options, bufTm, intgPd);
}
else if (StringUtils_startsWith((char*) lineBuffer, "LC")) {
else if (StringUtils_startsWith((char*) lineBuffer, "LC"))
{
uint32_t trgOps;
uint32_t intgPd;
int logEna;
@ -271,7 +424,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
LogControlBlock_create(nameString, currentLN, dataSet, logRef, trgOps, intgPd, logEna, withReasonCode);
}
else if (StringUtils_startsWith((char*) lineBuffer, "LOG")) {
else if (StringUtils_startsWith((char*) lineBuffer, "LOG"))
{
int matchedItems = sscanf((char*) lineBuffer, "LOG(%129s)", nameString);
if (matchedItems < 1) goto exit_error;
@ -281,7 +435,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
Log_create(nameString, currentLN);
}
else if (StringUtils_startsWith((char*) lineBuffer, "GC")) {
else if (StringUtils_startsWith((char*) lineBuffer, "GC"))
{
uint32_t confRef;
int fixedOffs;
int minTime = -1;
@ -296,9 +451,9 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
nameString3, confRef, fixedOffs, minTime, maxTime);
indendation = 4;
}
else if (StringUtils_startsWith((char*) lineBuffer, "SMVC")) {
else if (StringUtils_startsWith((char*) lineBuffer, "SMVC"))
{
uint32_t confRev;
int smpMod;
int smpRate;
@ -313,10 +468,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
currentSMVCB = SVControlBlock_create(nameString, currentLN, nameString2, nameString3, confRev, smpMod, smpRate, optFlds, (bool) isUnicast);
indendation = 4;
}
#if (CONFIG_IEC61850_SETTING_GROUPS == 1)
else if (StringUtils_startsWith((char*) lineBuffer, "SG")) {
else if (StringUtils_startsWith((char*) lineBuffer, "SG"))
{
if (strcmp(currentLN->name, "LLN0") != 0) {
if (DEBUG_IED_SERVER)
@ -345,8 +500,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
}
}
else if (indendation > 3) {
if (StringUtils_startsWith((char*) lineBuffer, "DO")) {
else if (indendation > 3)
{
if (StringUtils_startsWith((char*) lineBuffer, "DO"))
{
indendation++;
int arrayElements = 0;
@ -356,129 +513,116 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
if (matchedItems != 2) goto exit_error;
currentModelNode = (ModelNode*) DataObject_create(nameString, currentModelNode, arrayElements);
}
else if (StringUtils_startsWith((char*) lineBuffer, "DA")) {
int arrayElements = 0;
if (arrayElements > 0)
{
inArray = true;
currentArrayNode = currentModelNode;
}
}
else if (StringUtils_startsWith((char*) lineBuffer, "["))
{
if (inArray == false) {
goto exit_error;
}
int attributeType = 0;
int functionalConstraint = 0;
int triggerOptions = 0;
uint32_t sAddr = 0;
int arrayIndex;
sscanf((char*) lineBuffer, "DA(%129s %i %i %i %i %u)", nameString, &arrayElements, &attributeType, &functionalConstraint, &triggerOptions, &sAddr);
if (sscanf((char*)lineBuffer, "[%i]", &arrayIndex) != 1) {
goto exit_error;
}
DataAttribute* dataAttribute = DataAttribute_create(nameString, currentModelNode,
(DataAttributeType) attributeType, (FunctionalConstraint) functionalConstraint, triggerOptions, arrayElements, sAddr);
if (arrayIndex < 0) {
goto exit_error;
}
char* valueIndicator = strchr((char*) lineBuffer, '=');
if (currentArrayNode->modelType == DataAttributeModelType)
{
if (StringUtils_endsWith((char*)lineBuffer, ";"))
{
/* array of basic data attribute */
ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex);
if (valueIndicator != NULL) {
switch (dataAttribute->type) {
case IEC61850_UNICODE_STRING_255:
{
char* stringStart = valueIndicator + 2;
terminateString(stringStart, '"');
dataAttribute->mmsValue = MmsValue_newMmsString(stringStart);
}
break;
case IEC61850_VISIBLE_STRING_255:
case IEC61850_VISIBLE_STRING_129:
case IEC61850_VISIBLE_STRING_65:
case IEC61850_VISIBLE_STRING_64:
case IEC61850_VISIBLE_STRING_32:
case IEC61850_CURRENCY:
{
char* stringStart = valueIndicator + 2;
terminateString(stringStart, '"');
dataAttribute->mmsValue = MmsValue_newVisibleString(stringStart);
}
break;
case IEC61850_INT8:
case IEC61850_INT16:
case IEC61850_INT32:
case IEC61850_INT64:
case IEC61850_INT128:
case IEC61850_ENUMERATED:
{
int32_t intValue;
if (sscanf(valueIndicator + 1, "%i", &intValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newIntegerFromInt32(intValue);
if (arrayElementNode) {
setValue((char*)lineBuffer, (DataAttribute*)arrayElementNode);
}
break;
case IEC61850_INT8U:
case IEC61850_INT16U:
case IEC61850_INT24U:
case IEC61850_INT32U:
{
uint32_t uintValue;
if (sscanf(valueIndicator + 1, "%u", &uintValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newUnsignedFromUint32(uintValue);
else {
goto exit_error;
}
break;
case IEC61850_FLOAT32:
{
float floatValue;
if (sscanf(valueIndicator + 1, "%f", &floatValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newFloat(floatValue);
}
break;
}
else if (StringUtils_endsWith((char*)lineBuffer, "{"))
{
/* array of constructed data attribtute */
currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex);
case IEC61850_FLOAT64:
{
double doubleValue;
if (sscanf(valueIndicator + 1, "%lf", &doubleValue) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newDouble(doubleValue);
if (currentModelNode) {
inArrayElement = true;
}
break;
case IEC61850_BOOLEAN:
{
int boolean;
if (sscanf(valueIndicator + 1, "%i", &boolean) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newBoolean((bool) boolean);
else {
goto exit_error;
}
break;
case IEC61850_OPTFLDS:
{
int value;
if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newBitString(-10);
MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value);
}
}
else if (currentArrayNode->modelType == DataObjectModelType)
{
if (StringUtils_endsWith((char*)lineBuffer, "{"))
{
/* array of constructed data attribtute */
currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex);
if (currentModelNode) {
inArrayElement = true;
}
break;
case IEC61850_TRGOPS:
{
int value;
if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error;
dataAttribute->mmsValue = MmsValue_newBitString(-6);
MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value);
else {
goto exit_error;
}
break;
}
else
{
if (DEBUG_IED_SERVER)
printf("Unexpected character at end of line: %s\n", lineBuffer);
goto exit_error;
}
}
}
else if (StringUtils_startsWith((char*) lineBuffer, "DA"))
{
int arrayElements = 0;
int attributeType = 0;
int functionalConstraint = 0;
int triggerOptions = 0;
uint32_t sAddr = 0;
default:
break;
if (sscanf((char*)lineBuffer, "DA(%129s %i %i %i %i %u)", nameString, &arrayElements, &attributeType, &functionalConstraint, &triggerOptions, &sAddr) != 6) {
goto exit_error;
}
}
DataAttribute* dataAttribute = DataAttribute_create(nameString, currentModelNode,
(DataAttributeType) attributeType, (FunctionalConstraint) functionalConstraint, triggerOptions, arrayElements, sAddr);
if (arrayElements > 0)
{
inArray = true;
currentArrayNode = (ModelNode*)dataAttribute;
}
setValue((char*)lineBuffer, dataAttribute);
int lineLength = (int) strlen((char*) lineBuffer);
if (lineBuffer[lineLength - 1] == '{') {
if (lineBuffer[lineLength - 1] == '{')
{
indendation++;
currentModelNode = (ModelNode*) dataAttribute;
}
}
else if (StringUtils_startsWith((char*) lineBuffer, "DE")) {
else if (StringUtils_startsWith((char*) lineBuffer, "DE"))
{
char* start = strchr((char*) lineBuffer, '(');
if (start) {
if (start)
{
start++;
StringUtils_copyStringMax(nameString, 130, start);
@ -491,7 +635,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
/* check for index */
char* sep = strchr(nameString, ' ');
if (sep) {
if (sep)
{
char* indexStr = sep + 1;
*sep = 0;
@ -509,7 +654,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
DataSetEntry_create(currentDataSet, nameString, indexVal, componentVal);
}
}
else if (StringUtils_startsWith((char*) lineBuffer, "PA")) {
else if (StringUtils_startsWith((char*) lineBuffer, "PA"))
{
uint32_t vlanPrio;
uint32_t vlanId;
uint32_t appId;
@ -525,7 +671,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
if (StringUtils_createBufferFromHexString(nameString, (uint8_t*) nameString2) != 6)
goto exit_error;
PhyComAddress* dstAddress =
PhyComAddress_create((uint8_t) vlanPrio, (uint16_t) vlanId, (uint16_t) appId,
(uint8_t*) nameString2);
@ -541,18 +686,20 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
else
goto exit_error;
}
}
else {
if (StringUtils_startsWith((char*) lineBuffer, "MODEL{")) {
else
{
if (StringUtils_startsWith((char*) lineBuffer, "MODEL{"))
{
model = IedModel_create("");
stateInModel = true;
indendation = 1;
}
else if (StringUtils_startsWith((char*) lineBuffer, "MODEL(")) {
sscanf((char*) lineBuffer, "MODEL(%129s)", nameString);
else if (StringUtils_startsWith((char*) lineBuffer, "MODEL("))
{
if (sscanf((char*)lineBuffer, "MODEL(%129s)", nameString) != 1)
goto exit_error;
terminateString(nameString, ')');
model = IedModel_create(nameString);
stateInModel = true;
@ -564,14 +711,18 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
}
}
GLOBAL_FREEMEM(lineBuffer);
return model;
exit_error:
GLOBAL_FREEMEM(lineBuffer);
if (DEBUG_IED_SERVER)
printf("IED_SERVER: error parsing line %i (indentation level = %i)\n", currentLine, indendation);
IedModel_destroy(model);
return NULL;
}

@ -1,7 +1,7 @@
/*
* dynamic_model.c
*
* Copyright 2014-2022 Michael Zillgith
* Copyright 2014-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -75,10 +75,12 @@ IedModel_addDataSet(IedModel* self, DataSet* dataSet)
{
if (self->dataSets == NULL)
self->dataSets = dataSet;
else {
else
{
DataSet* lastDataSet = self->dataSets;
while (lastDataSet != NULL) {
while (lastDataSet != NULL)
{
if (lastDataSet->sibling == NULL) {
lastDataSet->sibling = dataSet;
break;
@ -94,7 +96,8 @@ IedModel_addLogicalDevice(IedModel* self, LogicalDevice* lDevice)
{
if (self->firstChild == NULL)
self->firstChild = lDevice;
else {
else
{
LogicalDevice* sibling = self->firstChild;
while (sibling->sibling != NULL)
@ -109,7 +112,8 @@ IedModel_addLog(IedModel* self, Log* log)
{
if (self->logs == NULL)
self->logs = log;
else {
else
{
Log* lastLog = self->logs;
while (lastLog->sibling != NULL)
@ -124,7 +128,8 @@ IedModel_addLogControlBlock(IedModel* self, LogControlBlock* lcb)
{
if (self->lcbs == NULL)
self->lcbs = lcb;
else {
else
{
LogControlBlock* lastLcb = self->lcbs;
while (lastLcb->sibling != NULL)
@ -139,7 +144,8 @@ IedModel_addReportControlBlock(IedModel* self, ReportControlBlock* rcb)
{
if (self->rcbs == NULL)
self->rcbs = rcb;
else {
else
{
ReportControlBlock* lastRcb = self->rcbs;
while (lastRcb->sibling != NULL)
@ -155,7 +161,8 @@ IedModel_addSettingGroupControlBlock(IedModel* self, SettingGroupControlBlock* s
{
if (self->sgcbs == NULL)
self->sgcbs = sgcb;
else {
else
{
SettingGroupControlBlock* lastSgcb = self->sgcbs;
while (lastSgcb->sibling != NULL)
@ -166,28 +173,46 @@ IedModel_addSettingGroupControlBlock(IedModel* self, SettingGroupControlBlock* s
}
#endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */
static void
IedModel_addGSEControlBlock(IedModel* self, GSEControlBlock* gcb)
{
if (self->gseCBs == NULL)
self->gseCBs = gcb;
else {
else
{
GSEControlBlock* lastGcb = self->gseCBs;
while (lastGcb->sibling != NULL)
while (lastGcb->sibling)
lastGcb = lastGcb->sibling;
lastGcb->sibling = gcb;
}
}
static void
IedModel_addSMVControlBlock(IedModel* self, SVControlBlock* smvcb)
{
if (self->svCBs == NULL) {
self->svCBs = smvcb;
}
else
{
SVControlBlock* lastSvCB = self->svCBs;
while (lastSvCB->sibling)
lastSvCB = lastSvCB->sibling;
lastSvCB->sibling = smvcb;
}
}
LogicalDevice*
LogicalDevice_createEx(const char* inst, IedModel* parent, const char* ldName)
{
LogicalDevice* self = (LogicalDevice*) GLOBAL_CALLOC(1, sizeof(LogicalDevice));
if (self) {
if (self)
{
self->name = StringUtils_copyString(inst);
self->modelType = LogicalDeviceModelType;
self->parent = (ModelNode*) parent;
@ -242,7 +267,8 @@ LogicalNode_create(const char* name, LogicalDevice* parent)
{
LogicalNode* self = (LogicalNode*) GLOBAL_MALLOC(sizeof(LogicalNode));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->parent = (ModelNode*) parent;
self->modelType = LogicalNodeModelType;
@ -296,7 +322,8 @@ Log_create(const char* name, LogicalNode* parent)
{
Log* self = (Log*) GLOBAL_MALLOC(sizeof(Log));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->parent = parent;
self->sibling = NULL;
@ -321,7 +348,8 @@ LogControlBlock_create(const char* name, LogicalNode* parent, const char* dataSe
{
LogControlBlock* self = (LogControlBlock*) GLOBAL_MALLOC(sizeof(LogControlBlock));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->parent = parent;
self->sibling = NULL;
@ -373,7 +401,8 @@ ReportControlBlock_create(const char* name, LogicalNode* parent, const char* rpt
{
ReportControlBlock* self = (ReportControlBlock*) GLOBAL_MALLOC(sizeof(ReportControlBlock));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->parent = parent;
@ -454,7 +483,8 @@ SettingGroupControlBlock_create(LogicalNode* parent, uint8_t actSG, uint8_t numO
SettingGroupControlBlock* self = (SettingGroupControlBlock*) GLOBAL_MALLOC(sizeof(SettingGroupControlBlock));
if (self) {
if (self)
{
self->parent = parent;
self->actSG = actSG;
self->numOfSGs = numOfSGs;
@ -482,7 +512,8 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, const char* appId,
{
GSEControlBlock* self = (GSEControlBlock*) GLOBAL_MALLOC(sizeof(GSEControlBlock));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->parent = parent;
@ -512,13 +543,22 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, const char* appId,
return self;
}
static void
LogicalNode_addSMVControlBlock(LogicalNode* self, SVControlBlock* smvcb)
{
IedModel* model = (IedModel*) self->parent->parent;
IedModel_addSMVControlBlock(model, smvcb);
}
SVControlBlock*
SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, const char* dataSet, uint32_t confRev, uint8_t smpMod,
uint16_t smpRate, uint8_t optFlds, bool isUnicast)
{
SVControlBlock* self = (SVControlBlock*) GLOBAL_MALLOC(sizeof(SVControlBlock));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->parent = parent;
@ -536,6 +576,12 @@ SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, c
self->optFlds = optFlds;
self->isUnicast = isUnicast;
self->dstAddress = NULL;
self->sibling = NULL;
if (parent)
LogicalNode_addSMVControlBlock(parent, self);
}
return self;
@ -558,7 +604,8 @@ PhyComAddress_create(uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint
{
PhyComAddress* self = (PhyComAddress*) GLOBAL_MALLOC(sizeof(PhyComAddress));
if (self) {
if (self)
{
self->vlanPriority = vlanPriority;
self->vlanId = vlanId;
self->appId = appId;
@ -601,7 +648,8 @@ DataObject_create(const char* name, ModelNode* parent, int arrayElements)
{
DataObject* self = (DataObject*) GLOBAL_MALLOC(sizeof(DataObject));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->modelType = DataObjectModelType;
self->firstChild = NULL;
@ -624,7 +672,7 @@ DataObject_create(const char* name, ModelNode* parent, int arrayElements)
arrayElement->name = NULL;
arrayElement->modelType = DataObjectModelType;
arrayElement->firstChild = NULL;
arrayElement->parent = parent;
arrayElement->parent = (ModelNode*) self;
arrayElement->sibling = NULL;
arrayElement->elementCount = 0;
@ -677,7 +725,8 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type
{
DataAttribute* self = (DataAttribute*) GLOBAL_MALLOC(sizeof(DataAttribute));
if (self) {
if (self)
{
self->name = StringUtils_copyString(name);
self->elementCount = arrayElements;
self->arrayIndex = -1;
@ -691,13 +740,16 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type
self->triggerOptions = triggerOptions;
self->sAddr = sAddr;
if (arrayElements > 0) {
if (arrayElements > 0)
{
int i;
for (i = 0; i < arrayElements; i++) {
for (i = 0; i < arrayElements; i++)
{
DataAttribute* arrayElement = (DataAttribute*) GLOBAL_MALLOC(sizeof(DataAttribute));
if (arrayElement) {
if (arrayElement)
{
arrayElement->name = NULL;
arrayElement->elementCount = 0;
arrayElement->arrayIndex = i;
@ -759,7 +811,8 @@ DataSet_create(const char* name, LogicalNode* parent)
{
DataSet* self = (DataSet*) GLOBAL_MALLOC(sizeof(DataSet));
if (self) {
if (self)
{
LogicalDevice* ld = (LogicalDevice*) parent->parent;
self->name = StringUtils_createString(3, parent->name, "$", name);
@ -805,11 +858,12 @@ DataSet_addEntry(DataSet* self, DataSetEntry* newEntry)
if (self->fcdas == NULL)
self->fcdas = newEntry;
else {
else
{
DataSetEntry* lastEntry = self->fcdas;
while (lastEntry != NULL) {
while (lastEntry != NULL)
{
if (lastEntry->sibling == NULL) {
lastEntry->sibling = newEntry;
break;
@ -825,21 +879,24 @@ DataSetEntry_create(DataSet* dataSet, const char* variable, int index, const cha
{
DataSetEntry* self = (DataSetEntry*) GLOBAL_MALLOC(sizeof(DataSetEntry));
if (self) {
if (self)
{
char variableName[130];
StringUtils_copyStringMax(variableName, 130, variable);
char* separator = strchr(variableName, '/');
if (separator != NULL) {
if (separator != NULL)
{
*separator = 0;
self->variableName = StringUtils_copyString(separator + 1);
self->logicalDeviceName = StringUtils_copyString(variableName);
self->isLDNameDynamicallyAllocated = true;
}
else {
else
{
self->variableName = StringUtils_copyString(variable);
self->logicalDeviceName = dataSet->logicalDeviceName;
self->isLDNameDynamicallyAllocated = false;
@ -865,14 +922,15 @@ DataSetEntry_create(DataSet* dataSet, const char* variable, int index, const cha
static void
ModelNode_destroy(ModelNode* modelNode)
{
if (modelNode) {
if (modelNode)
{
if (modelNode->name)
GLOBAL_FREEMEM(modelNode->name);
ModelNode* currentChild = modelNode->firstChild;
while (currentChild != NULL) {
while (currentChild != NULL)
{
ModelNode* nextChild = currentChild->sibling;
ModelNode_destroy(currentChild);
@ -880,7 +938,8 @@ ModelNode_destroy(ModelNode* modelNode)
currentChild = nextChild;
}
if (modelNode->modelType == DataAttributeModelType) {
if (modelNode->modelType == DataAttributeModelType)
{
DataAttribute* dataAttribute = (DataAttribute*) modelNode;
if (dataAttribute->mmsValue != NULL) {
@ -903,8 +962,8 @@ IedModel_destroy(IedModel* model)
LogicalDevice* ld = model->firstChild;
while (ld != NULL) {
while (ld != NULL)
{
if (ld->name)
GLOBAL_FREEMEM(ld->name);
@ -913,7 +972,8 @@ IedModel_destroy(IedModel* model)
LogicalNode* ln = (LogicalNode*) ld->firstChild;
while (ln != NULL) {
while (ln != NULL)
{
GLOBAL_FREEMEM(ln->name);
/* delete all data objects */
@ -934,7 +994,6 @@ IedModel_destroy(IedModel* model)
GLOBAL_FREEMEM(currentLn);
}
LogicalDevice* currentLd = ld;
ld = (LogicalDevice*) ld->sibling;
@ -1066,4 +1125,3 @@ IedModel_destroy(IedModel* model)
GLOBAL_FREEMEM(model);
}
}

@ -538,19 +538,34 @@ static int
createObjectReference(ModelNode* node, char* objectReference, int bufSize, bool withoutIedName)
{
int bufPos;
int arrayIndex = -1;
if (node->modelType != LogicalNodeModelType) {
if (node->modelType != LogicalNodeModelType)
{
bufPos = createObjectReference(node->parent, objectReference, bufSize, withoutIedName);
if (node->modelType == DataAttributeModelType)
{
arrayIndex = ((DataAttribute*)(node))->arrayIndex;
}
else if (node->modelType == DataObjectModelType)
{
arrayIndex = ((DataObject*)(node))->arrayIndex;
}
if (bufPos == -1)
return -1;
if (bufPos < bufSize)
objectReference[bufPos++] = '.';
else
return -1;
if (arrayIndex < 0)
{
if (bufPos < bufSize)
objectReference[bufPos++] = '.';
else
return -1;
}
}
else {
else
{
LogicalNode* lNode = (LogicalNode*) node;
LogicalDevice* lDevice = (LogicalDevice*) lNode->parent;
@ -559,12 +574,13 @@ createObjectReference(ModelNode* node, char* objectReference, int bufSize, bool
bufPos = 0;
if (withoutIedName) {
if (withoutIedName)
{
objectReference[0] = 0;
StringUtils_appendString(objectReference, bufSize, lDevice->name);
}
else {
else
{
if (lDevice->ldName) {
StringUtils_copyStringMax(objectReference, bufSize, lDevice->ldName);
}
@ -581,20 +597,49 @@ createObjectReference(ModelNode* node, char* objectReference, int bufSize, bool
return -1;
}
/* append own name */
int nameLength = strlen(node->name);
if (node->name)
{
/* append own name */
int nameLength = strlen(node->name);
if (bufPos + nameLength < bufSize) {
int i;
for (i = 0; i < nameLength; i++) {
objectReference[bufPos++] = node->name[i];
}
if (bufPos + nameLength < bufSize)
{
int i;
for (i = 0; i < nameLength; i++) {
objectReference[bufPos++] = node->name[i];
}
return bufPos;
return bufPos;
}
else {
return -1;
}
}
else {
return -1;
if (arrayIndex > -1)
{
char arrayIndexStr[11];
snprintf(arrayIndexStr, 11, "%d", arrayIndex);
int arrayIndexStrLength = strlen(arrayIndexStr);
if (bufPos + arrayIndexStrLength + 2 < bufSize)
{
int i;
objectReference[bufPos++] = '(';
for (i = 0; i < arrayIndexStrLength; i++) {
objectReference[bufPos++] = arrayIndexStr[i];
}
objectReference[bufPos++] = ')';
}
else
return -1;
}
return bufPos;
}
char*
@ -608,15 +653,18 @@ ModelNode_getObjectReferenceEx(ModelNode* node, char* objectReference, bool with
{
bool allocated = false;
if (objectReference == NULL) {
if (objectReference == NULL)
{
objectReference = (char*) GLOBAL_MALLOC(130);
allocated = true;
}
if (objectReference) {
if (objectReference)
{
int bufPos = createObjectReference(node, objectReference, 130, withoutIedName);
if (bufPos == -1) {
if (bufPos == -1)
{
if (allocated)
GLOBAL_FREEMEM(objectReference);
@ -659,26 +707,30 @@ ModelNode_getChild(ModelNode* self, const char* name)
/* check for array separator */
const char* arraySeparator = strchr(name, '(');
if (arraySeparator) {
if (arraySeparator)
{
const char* arraySeparator2 = strchr(arraySeparator, ')');
if (arraySeparator2) {
if (arraySeparator2)
{
int idx = (int) strtol(arraySeparator + 1, NULL, 10);
ModelNode* arrayNode = NULL;
if (name == arraySeparator) {
if (name == arraySeparator)
{
arrayNode = ModelNode_getChildWithIdx(self, idx);
}
else {
else
{
char nameCopy[65];
const char* pos = name;
int cpyIdx = 0;
while (pos < arraySeparator) {
while (pos < arraySeparator)
{
nameCopy[cpyIdx] = *pos;
cpyIdx++;
pos++;
@ -695,18 +747,19 @@ ModelNode_getChild(ModelNode* self, const char* name)
return NULL;
}
if (arrayNode) {
if (*(arraySeparator2 + 1) == 0) {
if (arrayNode)
{
if (*(arraySeparator2 + 1) == 0)
{
return arrayNode;
}
else {
else
{
if (*(arraySeparator2 + 1) == '.')
return ModelNode_getChild(arrayNode, arraySeparator2 + 2);
else
return ModelNode_getChild(arrayNode, arraySeparator2 + 1);
}
}
else
return NULL;
@ -729,17 +782,18 @@ ModelNode_getChild(ModelNode* self, const char* name)
ModelNode* matchingNode = NULL;
while (nextNode) {
while (nextNode)
{
if (nextNode->name == NULL) {
break; /* is an array element */
}
int nodeNameLen = strlen(nextNode->name);
if (nodeNameLen == nameElementLength) {
if (memcmp(nextNode->name, name, nodeNameLen) == 0) {
if (nodeNameLen == nameElementLength)
{
if (memcmp(nextNode->name, name, nodeNameLen) == 0)
{
matchingNode = nextNode;
break;
}
@ -768,7 +822,8 @@ ModelNode_getChildWithIdx(ModelNode* self, int idx)
while (nextNode)
{
if (currentIdx == idx) {
if (currentIdx == idx)
{
foundElement = nextNode;
break;
}
@ -799,14 +854,18 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint
ModelNode* matchingNode = NULL;
while (nextNode != NULL) {
while (nextNode != NULL)
{
int nodeNameLen = strlen(nextNode->name);
if (nodeNameLen == nameElementLength) {
if (memcmp(nextNode->name, name, nodeNameLen) == 0) {
if (separator == NULL) {
if (nextNode->modelType == DataAttributeModelType) {
if (nodeNameLen == nameElementLength)
{
if (memcmp(nextNode->name, name, nodeNameLen) == 0)
{
if (separator == NULL)
{
if (nextNode->modelType == DataAttributeModelType)
{
DataAttribute* da = (DataAttribute*) nextNode;
if (da->fc == fc) {
@ -815,12 +874,14 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint
}
}
}
else {
if (nextNode->modelType == DataAttributeModelType) {
else
{
if (nextNode->modelType == DataAttributeModelType)
{
DataAttribute* da = (DataAttribute*) nextNode;
if (da->fc == fc) {
if (da->fc == fc)
{
matchingNode = nextNode;
break;
}
@ -829,7 +890,6 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint
matchingNode = nextNode;
break;
}
}
}
}

@ -160,6 +160,16 @@ MmsConnection_setFilestoreBasepath(MmsConnection self, const char* basepath);
LIB61850_API void
MmsConnection_setRequestTimeout(MmsConnection self, uint32_t timeoutInMs);
/**
* \brief Set the maximum number outstanding calls allowed for this connection
*
* \param self MmsConnection instance to operate on
* \param calling the maximum outstanding calls allowed by the caller (client)
* \param called the maximum outstanding calls allowed by the called endpoint (server)
*/
LIB61850_API void
MmsConnnection_setMaxOutstandingCalls(MmsConnection self, int calling, int called);
/**
* \brief Get the request timeout in ms for this connection
*

@ -95,6 +95,8 @@ struct sMmsConnection {
Semaphore outstandingCallsLock;
MmsOutstandingCall outstandingCalls;
int maxOutstandingCalled;
int maxOutstandingCalling;
uint32_t requestTimeout;
uint32_t connectTimeout;

@ -1,7 +1,7 @@
/*
* mms_common_internal.h
*
* Copyright 2013-2019 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -30,10 +30,10 @@
#include "byte_buffer.h"
#include "mms_server.h"
#define DEFAULT_MAX_SERV_OUTSTANDING_CALLING 5
#define DEFAULT_MAX_SERV_OUTSTANDING_CALLED 5
#define DEFAULT_DATA_STRUCTURE_NESTING_LEVEL 10
typedef struct sMmsOutstandingCall* MmsOutstandingCall;
#if (MMS_FILE_SERVICE == 1)
#ifndef CONFIG_MMS_MAX_NUMBER_OF_OPEN_FILES_PER_CONNECTION
@ -42,8 +42,6 @@
#include "hal_filesystem.h"
typedef struct sMmsOutstandingCall* MmsOutstandingCall;
typedef struct {
int32_t frsmId;
uint32_t readPosition;

@ -120,6 +120,9 @@ struct sMmsServer {
MmsWriteVariableHandler writeHandler;
void* writeHandlerParameter;
MmsListAccessHandler listAccessHandler;
void* listAccessHandlerParameter;
MmsConnectionHandler connectionHandler;
void* connectionHandlerParameter;
@ -396,7 +399,7 @@ mmsServer_isAccessToArrayComponent(AlternateAccess_t* alternateAccess);
LIB61850_INTERNAL MmsValue*
mmsServer_getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable,
MmsValue* structuredValue);
MmsValue* structuredValue, char* componentId);
LIB61850_INTERNAL int
mmsServer_getLowIndex(AlternateAccess_t* alternateAccess);
@ -414,6 +417,10 @@ LIB61850_INTERNAL MmsDataAccessError
mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* value,
MmsServerConnection connection);
LIB61850_INTERNAL MmsDataAccessError
mmsServer_setValueEx(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* value,
MmsServerConnection connection, int arrayIdx, const char* componentId);
/**
* \brief Get the current value of a variable in the server data model
*
@ -422,6 +429,9 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va
LIB61850_INTERNAL MmsValue*
mmsServer_getValue(MmsServer self, MmsDomain* domain, char* itemId, MmsServerConnection connection, bool isDirectAccess);
LIB61850_INTERNAL bool
mmsServer_checkListAccess(MmsServer self, MmsGetNameListType listType, MmsDomain* domain, char* itemId, MmsServerConnection connection);
LIB61850_INTERNAL void
mmsServer_createMmsWriteResponse(MmsServerConnection connection,
uint32_t invokeId, ByteBuffer* response, int numberOfItems, MmsDataAccessError* accessResults);

@ -34,9 +34,12 @@ typedef MmsDataAccessError (*MmsReadAccessHandler) (void* parameter, MmsDomain*
char* variableId, MmsServerConnection connection, bool isDirectAccess);
typedef MmsDataAccessError (*MmsWriteVariableHandler)(void* parameter,
MmsDomain* domain, char* variableId, MmsValue* value,
MmsDomain* domain, const char* variableId, int arrayIdx, const char* componentId, MmsValue* value,
MmsServerConnection connection);
typedef bool (*MmsListAccessHandler) (void* parameter, MmsGetNameListType listType, MmsDomain* domain,
char* variableId, MmsServerConnection connection);
typedef void (*MmsConnectionHandler)(void* parameter,
MmsServerConnection connection, MmsServerEvent event);
@ -63,6 +66,9 @@ LIB61850_INTERNAL void
MmsServer_installWriteHandler(MmsServer self, MmsWriteVariableHandler,
void* parameter);
LIB61850_INTERNAL void
MmsServer_installListAccessHandler(MmsServer self, MmsListAccessHandler listAccessHandler, void* parameter);
/**
* A connection handler will be invoked whenever a new client connection is opened or closed
*/

@ -1,7 +1,7 @@
/*
* acse.c
*
* Copyright 2013 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -43,9 +43,10 @@ checkAuthMechanismName(uint8_t* authMechanism, int authMechLen)
{
AcseAuthenticationMechanism authenticationMechanism = ACSE_AUTH_NONE;
if (authMechanism != NULL) {
if (authMechLen == 3) {
if (authMechanism != NULL)
{
if (authMechLen == 3)
{
if (memcmp(auth_mech_password_oid, authMechanism, 3) == 0) {
authenticationMechanism = ACSE_AUTH_PASSWORD;
}
@ -64,11 +65,13 @@ authenticateClient(AcseConnection* self, AcseAuthenticationMechanism mechanism,
authParameter->mechanism = mechanism;
if (mechanism == ACSE_AUTH_PASSWORD) {
if (mechanism == ACSE_AUTH_PASSWORD)
{
authParameter->value.password.octetString = authValue;
authParameter->value.password.passwordLength = authValueLen;
}
else if (mechanism == ACSE_AUTH_TLS) {
else if (mechanism == ACSE_AUTH_TLS)
{
authParameter->value.certificate.buf = authValue;
authParameter->value.certificate.length = authValueLen;
}
@ -81,15 +84,15 @@ checkAuthentication(AcseConnection* self, uint8_t* authMechanism, int authMechLe
{
self->securityToken = NULL;
if (self->authenticator != NULL) {
if (self->authenticator != NULL)
{
AcseAuthenticationMechanism mechanism = checkAuthMechanismName(authMechanism, authMechLen);
if (mechanism == ACSE_AUTH_NONE) {
if (mechanism == ACSE_AUTH_NONE)
{
#if (CONFIG_MMS_SUPPORT_TLS == 1)
if (self->tlsSocket) {
if (self->tlsSocket)
{
int certLen;
uint8_t* certBuf = TLSSocket_getPeerCertificate(self->tlsSocket, &certLen);
@ -120,13 +123,18 @@ parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxB
bool hasindirectReference = false;
bool isDataValid = false;
while (bufPos < maxBufPos) {
while (bufPos < maxBufPos)
{
uint8_t tag = buffer[bufPos++];
int len;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0) {
if (len == 0)
continue;
if ((bufPos < 0) || (bufPos + len > maxBufPos))
{
*userInfoValid = false;
return -1;
}
@ -155,7 +163,8 @@ parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxB
}
}
if (DEBUG_ACSE) {
if (DEBUG_ACSE)
{
if (!hasindirectReference)
printf("ACSE: User data has no indirect reference!\n");
@ -181,13 +190,22 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
uint32_t result = 99;
while (bufPos < maxBufPos) {
while (bufPos < maxBufPos)
{
uint8_t tag = buffer[bufPos++];
int len;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0)
if (len == 0)
continue;
if ((bufPos < 0) || (bufPos + len > maxBufPos))
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ERROR;
}
switch (tag)
{
@ -212,12 +230,14 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
break;
case 0xbe: /* user information */
if (buffer[bufPos] != 0x28) {
if (buffer[bufPos] != 0x28)
{
if (DEBUG_ACSE)
printf("ACSE: invalid user info\n");
bufPos += len;
}
else {
else
{
bufPos++;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
@ -263,13 +283,18 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
int authMechLen = 0;
bool userInfoValid = false;
while (bufPos < maxBufPos) {
while (bufPos < maxBufPos)
{
uint8_t tag = buffer[bufPos++];
int len;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0) {
if (len == 0)
continue;
if ((bufPos < 0) || (bufPos + len > maxBufPos))
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
@ -290,7 +315,9 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
case 0xa6: /* calling AP title */
{
if (buffer[bufPos] == 0x06) { /* ap-title-form2 */
if (buffer[bufPos] == 0x06)
{
/* ap-title-form2 */
int innerLength = buffer[bufPos + 1];
@ -303,7 +330,9 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
case 0xa7: /* calling AE qualifier */
{
if (buffer[bufPos] == 0x02) { /* ae-qualifier-form2 */
if (buffer[bufPos] == 0x02)
{
/* ae-qualifier-form2 */
int innerLength = buffer[bufPos + 1];
@ -328,7 +357,8 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
bufPos++;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0) {
if (bufPos < 0)
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
@ -340,17 +370,20 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
break;
case 0xbe: /* user information */
if (buffer[bufPos] != 0x28) {
if (buffer[bufPos] != 0x28)
{
if (DEBUG_ACSE)
printf("ACSE: invalid user info\n");
bufPos += len;
}
else {
else
{
bufPos++;
bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos);
if (bufPos < 0) {
if (bufPos < 0)
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
@ -358,7 +391,8 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
bufPos = parseUserInformation(self, buffer, bufPos, bufPos + len, &userInfoValid);
if (bufPos < 0) {
if (bufPos < 0)
{
if (DEBUG_ACSE)
printf("ACSE: Invalid PDU!\n");
return ACSE_ASSOCIATE_FAILED;
@ -378,14 +412,16 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos)
}
}
if (checkAuthentication(self, authMechanism, authMechLen, authValue, authValueLen) == false) {
if (checkAuthentication(self, authMechanism, authMechLen, authValue, authValueLen) == false)
{
if (DEBUG_ACSE)
printf("ACSE: parseAarqPdu: check authentication failed!\n");
return ACSE_ASSOCIATE_FAILED;
}
if (userInfoValid == false) {
if (userInfoValid == false)
{
if (DEBUG_ACSE)
printf("ACSE: parseAarqPdu: user info invalid!\n");
@ -420,6 +456,14 @@ AcseConnection_parseMessage(AcseConnection* self, ByteBuffer* message)
{
AcseIndication indication = ACSE_ERROR;
if (message == NULL || message->size < 1)
{
if (DEBUG_ACSE)
printf("ACSE: invalid message - no payload\n");
return ACSE_ERROR;
}
uint8_t* buffer = message->buffer;
int messageSize = message->size;
@ -826,4 +870,3 @@ AcseConnection_createReleaseResponseMessage(AcseConnection* self, BufferChain wr
writeBuffer->length = 2;
writeBuffer->nextPart = NULL;
}

@ -3,7 +3,7 @@
*
* Client side representation of the ISO stack (COTP, session, presentation, ACSE)
*
* Copyright 2013-2022 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -146,7 +146,8 @@ IsoClientConnection_create(IsoConnectionParameters parameters, IsoIndicationCall
{
IsoClientConnection self = (IsoClientConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoClientConnection));
if (self) {
if (self)
{
self->parameters = parameters;
self->callback = callback;
self->callbackParameter = callbackParameter;
@ -196,7 +197,8 @@ sendConnectionRequestMessage(IsoClientConnection self)
int socketExtensionBufferSize = CONFIG_MMS_MAXIMUM_PDU_SIZE + 1000;
uint8_t* socketExtensionBuffer = NULL;
if (self->cotpConnection) {
if (self->cotpConnection)
{
/* Destroy existing handle set when connection is reused */
if (self->cotpConnection->handleSet)
Handleset_destroy(self->cotpConnection->handleSet);
@ -205,19 +207,20 @@ sendConnectionRequestMessage(IsoClientConnection self)
socketExtensionBuffer = self->cotpConnection->socketExtensionBuffer;
}
if (socketExtensionBuffer == NULL) {
if (socketExtensionBuffer == NULL)
{
socketExtensionBuffer = (uint8_t*)GLOBAL_MALLOC(socketExtensionBufferSize);
}
if (socketExtensionBuffer) {
if (socketExtensionBuffer)
{
/* COTP (ISO transport) handshake */
CotpConnection_init(self->cotpConnection, self->socket, self->receiveBuffer, self->cotpReadBuffer, self->cotpWriteBuffer,
socketExtensionBuffer, socketExtensionBufferSize);
#if (CONFIG_MMS_SUPPORT_TLS == 1)
if (self->parameters->tlsConfiguration) {
if (self->parameters->tlsConfiguration)
{
TLSConfiguration_setClientMode(self->parameters->tlsConfiguration);
/* create TLSSocket and start TLS authentication */
@ -225,8 +228,8 @@ sendConnectionRequestMessage(IsoClientConnection self)
if (tlsSocket)
self->cotpConnection->tlsSocket = tlsSocket;
else {
else
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: TLS handshake failed!\n");
@ -244,7 +247,8 @@ sendConnectionRequestMessage(IsoClientConnection self)
else
return true;
}
else {
else
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Failed to allocate socket extension buffer\n");
@ -298,14 +302,14 @@ sendAcseInitiateRequest(IsoClientConnection self)
Semaphore_post(self->transmitBufferMutex);
}
static void
releaseSocket(IsoClientConnection self)
{
if (self->socket) {
if (self->socket)
{
#if (CONFIG_MMS_SUPPORT_TLS == 1)
if (self->cotpConnection->tlsSocket) {
if (self->cotpConnection->tlsSocket)
{
TLSSocket_close(self->cotpConnection->tlsSocket);
self->cotpConnection->tlsSocket = NULL;
}
@ -345,29 +349,34 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
{
SocketState socketState = Socket_checkAsyncConnectState(self->socket);
if (socketState == SOCKET_STATE_CONNECTED) {
if (sendConnectionRequestMessage(self)) {
if (socketState == SOCKET_STATE_CONNECTED)
{
if (sendConnectionRequestMessage(self))
{
self->nextReadTimeout = Hal_getTimeInMs() + self->readTimeoutInMs;
nextState = INT_STATE_WAIT_FOR_COTP_CONNECT_RESP;
}
else {
else
{
IsoClientConnection_releaseTransmitBuffer(self);
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
nextState = INT_STATE_CLOSE_ON_ERROR;
}
}
else if (socketState == SOCKET_STATE_FAILED) {
else if (socketState == SOCKET_STATE_FAILED)
{
IsoClientConnection_releaseTransmitBuffer(self);
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
else
{
/* check connect timeout */
uint64_t currentTime = Hal_getTimeInMs();
if (currentTime > self->nextReadTimeout) {
if (currentTime > self->nextReadTimeout)
{
IsoClientConnection_releaseTransmitBuffer(self);
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
nextState = INT_STATE_CLOSE_ON_ERROR;
@ -385,8 +394,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
{
uint64_t currentTime = Hal_getTimeInMs();
if (currentTime > self->nextReadTimeout) {
if (currentTime > self->nextReadTimeout)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Timeout waiting for COTP CR\n");
@ -396,15 +405,16 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
else
{
TpktState packetState = CotpConnection_readToTpktBuffer(self->cotpConnection);
if (packetState == TPKT_PACKET_COMPLETE) {
if (packetState == TPKT_PACKET_COMPLETE)
{
CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection);
if (cotpIndication != COTP_CONNECT_INDICATION) {
if (cotpIndication != COTP_CONNECT_INDICATION)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Unexpected COTP state (%i)\n", cotpIndication);
@ -414,7 +424,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
else
{
sendAcseInitiateRequest(self);
self->nextReadTimeout = Hal_getTimeInMs() + self->readTimeoutInMs;
@ -422,7 +433,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
nextState = INT_STATE_WAIT_FOR_ACSE_RESP;
}
}
else if (packetState == TPKT_ERROR) {
else if (packetState == TPKT_ERROR)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Error receiving COTP message\n");
@ -435,7 +447,6 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
else {
waits = true;
}
}
}
break;
@ -444,8 +455,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
{
uint64_t currentTime = Hal_getTimeInMs();
if (currentTime > self->nextReadTimeout) {
if (currentTime > self->nextReadTimeout)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Timeout waiting for ACSE initiate response\n");
@ -453,15 +464,16 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
else
{
TpktState packetState = CotpConnection_readToTpktBuffer(self->cotpConnection);
if (packetState == TPKT_PACKET_COMPLETE) {
if (packetState == TPKT_PACKET_COMPLETE)
{
CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection);
if (cotpIndication != COTP_DATA_INDICATION) {
if (cotpIndication != COTP_DATA_INDICATION)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Unexpected COTP state (%i)\n", cotpIndication);
@ -469,67 +481,71 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
else
{
/* parse ACSE response */
IsoSessionIndication sessionIndication;
sessionIndication =
IsoSession_parseMessage(self->session, CotpConnection_getPayload(self->cotpConnection));
IsoSessionIndication sessionIndication;
if (sessionIndication != SESSION_CONNECT) {
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_associate: no session connect indication\n");
sessionIndication =
IsoSession_parseMessage(self->session, CotpConnection_getPayload(self->cotpConnection));
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
if (IsoPresentation_parseAcceptMessage(self->presentation, IsoSession_getUserData(self->session)) == false) {
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_associate: no presentation ok indication\n");
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
AcseIndication acseIndication = AcseConnection_parseMessage(&(self->acseConnection), &self->presentation->nextPayload);
if (acseIndication != ACSE_ASSOCIATE) {
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_associate: no ACSE_ASSOCIATE indication\n");
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
ByteBuffer_wrap(self->receivePayloadBuffer, self->acseConnection.userDataBuffer,
self->acseConnection.userDataBufferSize, self->acseConnection.userDataBufferSize);
if (sessionIndication != SESSION_CONNECT)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_associate: no session connect indication\n");
setState(self, STATE_CONNECTED);
nextState = INT_STATE_WAIT_FOR_DATA_MSG;
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
if (self->callback(ISO_IND_ASSOCIATION_SUCCESS, self->callbackParameter, self->receivePayloadBuffer) == false) {
nextState = INT_STATE_CLOSE_ON_ERROR;
}
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else
{
if (IsoPresentation_parseAcceptMessage(self->presentation, IsoSession_getUserData(self->session)) == false)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: no presentation accept indication\n");
}
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
}
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else
{
AcseIndication acseIndication = AcseConnection_parseMessage(&(self->acseConnection), &self->presentation->nextPayload);
if (acseIndication != ACSE_ASSOCIATE)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: no ACSE_ASSOCIATE indication\n");
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: ACSE AARE - association accepted\n");
ByteBuffer_wrap(self->receivePayloadBuffer, self->acseConnection.userDataBuffer,
self->acseConnection.userDataBufferSize, self->acseConnection.userDataBufferSize);
setState(self, STATE_CONNECTED);
nextState = INT_STATE_WAIT_FOR_DATA_MSG;
if (self->callback(ISO_IND_ASSOCIATION_SUCCESS, self->callbackParameter, self->receivePayloadBuffer) == false) {
nextState = INT_STATE_CLOSE_ON_ERROR;
}
}
}
CotpConnection_resetPayload(self->cotpConnection);
}
CotpConnection_resetPayload(self->cotpConnection);
}
}
}
else if (packetState == TPKT_ERROR) {
else if (packetState == TPKT_ERROR)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Error receiving COTP message\n");
@ -540,7 +556,6 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
else {
waits = true;
}
}
}
break;
@ -552,8 +567,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
if (packetState == TPKT_ERROR) {
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else if (packetState == TPKT_PACKET_COMPLETE) {
else if (packetState == TPKT_PACKET_COMPLETE)
{
CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection);
switch (cotpIndication) {
@ -576,23 +591,24 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
IsoSession_parseMessage(self->session,
CotpConnection_getPayload(self->cotpConnection));
if (sessionIndication != SESSION_DATA) {
if (sessionIndication != SESSION_DATA)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT_CONNECTION: Invalid session message\n");
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
if (!IsoPresentation_parseUserData(self->presentation, IsoSession_getUserData(self->session))) {
else
{
if (!IsoPresentation_parseUserData(self->presentation, IsoSession_getUserData(self->session)))
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT_CONNECTION: Invalid presentation message\n");
nextState = INT_STATE_CLOSE_ON_ERROR;
}
else {
else
{
self->callback(ISO_IND_DATA, self->callbackParameter,
&(self->presentation->nextPayload));
@ -659,7 +675,6 @@ IsoClientConnection_handleConnection(IsoClientConnection self)
return waits;
}
bool
IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTimeoutInMs, uint32_t readTimeoutInMs)
{
@ -669,7 +684,8 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim
self->socket = TcpSocket_create();
if (self->socket == NULL) {
if (self->socket == NULL)
{
Semaphore_post(self->tickMutex);
return false;
}
@ -697,8 +713,8 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim
Socket_bind(self->socket, self->parameters->localIpAddress, self->parameters->localTcpPort);
}
if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) {
if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false)
{
Socket_destroy(self->socket);
self->socket = NULL;
@ -718,7 +734,8 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim
void
IsoClientConnection_sendMessage(IsoClientConnection self, ByteBuffer* payloadBuffer)
{
if (getState(self) == STATE_CONNECTED) {
if (getState(self) == STATE_CONNECTED)
{
struct sBufferChain payloadBCMemory;
BufferChain payload = &payloadBCMemory;
@ -743,7 +760,8 @@ IsoClientConnection_sendMessage(IsoClientConnection self, ByteBuffer* payloadBuf
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_sendMessage: send message failed!\n");
}
else {
else
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: Not connected --> cannot send message\n");
}
@ -762,7 +780,8 @@ IsoClientConnection_close(IsoClientConnection self)
eIsoClientInternalState intState = getIntState(self);
if ((intState != INT_STATE_IDLE) && (intState != INT_STATE_ERROR) && (intState != INT_STATE_CLOSE_ON_ERROR)) {
if ((intState != INT_STATE_IDLE) && (intState != INT_STATE_ERROR) && (intState != INT_STATE_CLOSE_ON_ERROR))
{
setIntState(self, INT_STATE_CLOSING_CONNECTION);
Semaphore_post(self->tickMutex);
@ -783,8 +802,8 @@ IsoClientConnection_destroy(IsoClientConnection self)
int state = getState(self);
if (state == STATE_CONNECTED) {
if (state == STATE_CONNECTED)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: call IsoClientConnection_close\n");
@ -798,7 +817,8 @@ IsoClientConnection_destroy(IsoClientConnection self)
if (self->receiveBuffer != NULL)
GLOBAL_FREEMEM(self->receiveBuffer);
if (self->cotpConnection != NULL) {
if (self->cotpConnection != NULL)
{
if (self->cotpConnection->handleSet != NULL)
Handleset_destroy(self->cotpConnection->handleSet);
@ -910,7 +930,6 @@ IsoClientConnection_release(IsoClientConnection self)
Semaphore_post(self->transmitBufferMutex);
}
ByteBuffer*
IsoClientConnection_allocateTransmitBuffer(IsoClientConnection self)
{

File diff suppressed because it is too large Load Diff

@ -180,7 +180,6 @@ exit_reject_invalid_pdu:
mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
}
void
mmsClient_handleFileReadRequest(
MmsConnection connection,
@ -191,7 +190,7 @@ mmsClient_handleFileReadRequest(
int32_t frsmId = BerDecoder_decodeInt32(buffer, maxBufPos - bufPos, bufPos);
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: mmsClient_handleFileReadRequest read request for frsmId: %i\n", frsmId);
printf("MMS_CLIENT: mmsClient_handleFileReadRequest read request for frsmId: %i\n", (int)frsmId);
MmsFileReadStateMachine* frsm = getFrsm(connection, frsmId);
@ -401,7 +400,6 @@ mmsClient_createFileDirectoryRequest(uint32_t invokeId, ByteBuffer* request, con
request->size = bufPos;
}
void
mmsClient_createFileRenameRequest(uint32_t invokeId, ByteBuffer* request, const char* currentFileName, const char* newFileName)
{
@ -466,7 +464,6 @@ mmsClient_createObtainFileRequest(uint32_t invokeId, ByteBuffer* request, const
request->size = bufPos;
}
static bool
parseFileAttributes(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t* fileSize, uint64_t* lastModified)
{
@ -610,7 +607,6 @@ parseListOfDirectoryEntries(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t
return true;
}
bool
mmsClient_parseFileDirectoryResponse(ByteBuffer* response, int bufPos, uint32_t invokeId, MmsConnection_FileDirectoryHandler handler, void* parameter)
{
@ -731,16 +727,14 @@ mmsMsg_parseFileOpenResponse(uint8_t* buffer, int bufPos, int maxBufPos, int32_t
}
bool
mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t invokeId, int frsmId, bool* moreFollows, MmsConnection_FileReadHandler handler, void* handlerParameter)
mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t invokeId, int32_t frsmId, bool* moreFollows, MmsConnection_FileReadHandler handler, void* handlerParameter)
{
int length;
uint8_t* data = NULL;
int dataLen = 0;
uint8_t tag = buffer[bufPos++];
if (tag != 0xbf) {
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT/SERVER: mmsClient_parseFileReadResponse: unknown tag %02x\n", tag);

@ -39,18 +39,16 @@ static uint8_t servicesSupported[] = { 0xee, 0x1c, 0x00, 0x00, 0x04, 0x08, 0x00,
void
mmsClient_createInitiateRequest(MmsConnection self, ByteBuffer* message)
{
int maxServerOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
int maxServerOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
int dataStructureNestingLevel = DEFAULT_DATA_STRUCTURE_NESTING_LEVEL;
uint32_t localDetailSize =
BerEncoder_UInt32determineEncodedSize(self->parameters.maxPduSize);
uint32_t proposedMaxServerOutstandingCallingSize =
BerEncoder_UInt32determineEncodedSize(maxServerOutstandingCalling);
BerEncoder_UInt32determineEncodedSize(self->maxOutstandingCalling);
uint32_t proposedMaxServerOutstandingCalledSize =
BerEncoder_UInt32determineEncodedSize(maxServerOutstandingCalled);
BerEncoder_UInt32determineEncodedSize(self->maxOutstandingCalled);
uint32_t dataStructureNestingLevelSize =
BerEncoder_UInt32determineEncodedSize(dataStructureNestingLevel);
@ -76,11 +74,11 @@ mmsClient_createInitiateRequest(MmsConnection self, ByteBuffer* message)
/* proposedMaxServerOutstandingCalling */
bufPos = BerEncoder_encodeTL(0x81, proposedMaxServerOutstandingCallingSize, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32(maxServerOutstandingCalling, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32(self->maxOutstandingCalling, buffer, bufPos);
/* proposedMaxServerOutstandingCalled */
bufPos = BerEncoder_encodeTL(0x82, proposedMaxServerOutstandingCalledSize, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32(maxServerOutstandingCalled, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32(self->maxOutstandingCalled, buffer, bufPos);
/* proposedDataStructureNestingLevel */
bufPos = BerEncoder_encodeTL(0x83, dataStructureNestingLevelSize, buffer, bufPos);
@ -147,7 +145,7 @@ parseInitResponseDetail(MmsConnection self, uint8_t* buffer, int bufPos, int max
int i;
for (i = 0; i < 11; i++)
self->parameters.servicesSupported[i] = buffer[bufPos + i];
self->parameters.servicesSupported[i] = buffer[bufPos + i + 1]; /* add 1 to skip padding */
}
break;
@ -169,8 +167,8 @@ mmsClient_parseInitiateResponse(MmsConnection self, ByteBuffer* response)
{
self->parameters.maxPduSize = CONFIG_MMS_MAXIMUM_PDU_SIZE;
self->parameters.dataStructureNestingLevel = DEFAULT_DATA_STRUCTURE_NESTING_LEVEL;
self->parameters.maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
self->parameters.maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
self->parameters.maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
self->parameters.maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
int bufPos = 1; /* ignore tag - already checked */
@ -203,16 +201,16 @@ mmsClient_parseInitiateResponse(MmsConnection self, ByteBuffer* response)
case 0x81: /* proposed-max-serv-outstanding-calling */
self->parameters.maxServOutstandingCalling = BerDecoder_decodeUint32(buffer, length, bufPos);
if (self->parameters.maxServOutstandingCalling > DEFAULT_MAX_SERV_OUTSTANDING_CALLING)
self->parameters.maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
if (self->parameters.maxServOutstandingCalling > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING)
self->parameters.maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
break;
case 0x82: /* proposed-max-serv-outstanding-called */
self->parameters.maxServOutstandingCalled = BerDecoder_decodeUint32(buffer, length, bufPos);
if (self->parameters.maxServOutstandingCalled > DEFAULT_MAX_SERV_OUTSTANDING_CALLED)
self->parameters.maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
if (self->parameters.maxServOutstandingCalled > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED)
self->parameters.maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
break;
case 0x83: /* proposed-data-structure-nesting-level */

@ -1,7 +1,7 @@
/*
* mms_type_spec.c
*
* Copyright 2013 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -108,36 +108,40 @@ MmsVariableSpecification_getType(MmsVariableSpecification* self)
bool
MmsVariableSpecification_isValueOfType(MmsVariableSpecification* self, const MmsValue* value)
{
if ((self->type) == (value->type)) {
if ((self->type == MMS_STRUCTURE) || (self->type == MMS_ARRAY)) {
if ((self->type) == (value->type))
{
if ((self->type == MMS_STRUCTURE) || (self->type == MMS_ARRAY))
{
int componentCount = self->typeSpec.structure.elementCount;
if (componentCount != (int) MmsValue_getArraySize(value))
return false;
if (self->type == MMS_STRUCTURE) {
if (self->type == MMS_STRUCTURE)
{
int i;
for (i = 0; i < componentCount; i++) {
for (i = 0; i < componentCount; i++)
{
if (MmsVariableSpecification_isValueOfType(self->typeSpec.structure.elements[i], MmsValue_getElement(value, i)) == false)
return false;
}
return true;
}
else {
else
{
int i;
for (i = 0; i < componentCount; i++) {
for (i = 0; i < componentCount; i++)
{
if (MmsVariableSpecification_isValueOfType(self->typeSpec.array.elementTypeSpec, MmsValue_getElement(value, i)) == false)
return false;
}
return true;
}
}
else if (self->type == MMS_BIT_STRING) {
else if (self->type == MMS_BIT_STRING)
{
if (self->typeSpec.bitString == value->value.bitString.size)
return true;

@ -1463,13 +1463,15 @@ MmsValue_newOctetString(int size, int maxSize)
{
MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
if (self) {
if (self)
{
self->type = MMS_OCTET_STRING;
self->value.octetString.size = size;
self->value.octetString.maxSize = maxSize;
self->value.octetString.buf = (uint8_t*) GLOBAL_CALLOC(1, abs(maxSize));
if (self->value.octetString.buf == NULL) {
if ((maxSize != 0) && (self->value.octetString.buf == NULL))
{
GLOBAL_FREEMEM(self);
self = NULL;
}
@ -1677,14 +1679,16 @@ exit_function:
}
static void
setVisibleStringValue(MmsValue* self, const char* string)
setVisibleStringValue(MmsValue* self, const char* value)
{
if (self->value.visibleString.buf != NULL) {
if (string != NULL) {
int newStringSize = strlen(string);
if (self->value.visibleString.buf != NULL)
{
if (value != NULL)
{
int newStringSize = strlen(value);
if (newStringSize > self->value.visibleString.size) {
if (newStringSize > self->value.visibleString.size)
{
GLOBAL_FREEMEM(self->value.visibleString.buf);
self->value.visibleString.buf = (char*) GLOBAL_MALLOC(newStringSize + 1);
@ -1694,7 +1698,7 @@ setVisibleStringValue(MmsValue* self, const char* string)
self->value.visibleString.size = newStringSize;
}
StringUtils_copyStringMax(self->value.visibleString.buf, self->value.visibleString.size + 1, string);
StringUtils_copyStringMax(self->value.visibleString.buf, self->value.visibleString.size + 1, value);
}
else
self->value.visibleString.buf[0] = 0;
@ -1906,7 +1910,8 @@ MmsValue_newStringFromByteArray(const uint8_t* byteArray, int size, MmsType type
self->value.visibleString.buf = StringUtils_createStringFromBuffer(byteArray, size);
if (self->value.visibleString.buf == NULL) {
if (self->value.visibleString.buf == NULL)
{
GLOBAL_FREEMEM(self);
self = NULL;
}
@ -2008,17 +2013,20 @@ MmsValue_createArray(const MmsVariableSpecification* elementType, int size)
self->value.structure.size = size;
self->value.structure.components = (MmsValue**) GLOBAL_CALLOC(size, sizeof(MmsValue*));
if (self->value.structure.components == NULL) {
if (self->value.structure.components == NULL)
{
GLOBAL_FREEMEM(self);
self = NULL;
goto exit_function;
}
int i;
for (i = 0; i < size; i++) {
for (i = 0; i < size; i++)
{
self->value.structure.components[i] = MmsValue_newDefaultValue(elementType);
if (self->value.structure.components[i] == NULL) {
if (self->value.structure.components[i] == NULL)
{
MmsValue_delete(self);
self = NULL;
goto exit_function;
@ -2102,9 +2110,10 @@ MmsValue_setDeletable(MmsValue* self)
void
MmsValue_setDeletableRecursive(MmsValue* self)
{
if (self != NULL) {
if ((MmsValue_getType(self) == MMS_ARRAY) || (MmsValue_getType(self) == MMS_STRUCTURE)) {
if (self)
{
if ((MmsValue_getType(self) == MMS_ARRAY) || (MmsValue_getType(self) == MMS_STRUCTURE))
{
int i;
int elementCount = MmsValue_getArraySize(self);
@ -2179,7 +2188,8 @@ MmsValue_getTypeString(MmsValue* self)
const char*
MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
{
if (self == NULL) {
if (self == NULL)
{
StringUtils_copyStringMax(buffer, bufferSize, "(null)");
return buffer;
@ -2240,7 +2250,8 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
int size = MmsValue_getBitStringSize(self);
/* fill buffer with zeros */
if (size + 1 > bufferSize) {
if (size + 1 > bufferSize)
{
memset(buffer, 0, bufferSize);
size = bufferSize - 1;
@ -2250,7 +2261,8 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
}
int i;
for (i = 0; i < size; i++) {
for (i = 0; i < size; i++)
{
if (MmsValue_getBitStringBit(self, i))
buffer[bufPos++] = '1';
else
@ -2290,7 +2302,8 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
int size = MmsValue_getOctetStringSize(self);
int bufPos = 0;
int i;
for (i = 0; i < size; i++) {
for (i = 0; i < size; i++)
{
snprintf(buffer + bufPos, bufferSize - bufPos, "%02x", self->value.octetString.buf[i]);
bufPos += 2;

@ -171,9 +171,12 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu
if (bufPos < 0)
goto exit_with_error;
/* if not indefinite length end tag, data length must be > 0 */
if ((tag != 0) && (dataLength == 0))
goto exit_with_error;
/* if not indefinite length end tag, visible-string, mms-string, or octet-string, data length must be > 0 */
if (tag != 0)
{
if (tag != 0x8a && tag != 0x90 && tag != 0x89 && dataLength == 0)
goto exit_with_error;
}
switch (tag) {
@ -192,8 +195,8 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu
int i;
for (i = 0; i < elementCount; i++) {
for (i = 0; i < elementCount; i++)
{
int elementLength;
int newBufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos + 1, dataEndBufPos);
@ -304,7 +307,8 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu
break;
case 0x91: /* MMS_UTC_TIME */
if (dataLength == 8) {
if (dataLength == 8)
{
value = MmsValue_newUtcTime(0);
MmsValue_setUtcTimeByBuffer(value, buffer + bufPos);
bufPos += dataLength;

@ -330,9 +330,9 @@ parseInitiateRequestPdu(MmsServerConnection self, uint8_t* buffer, int bufPos, i
self->dataStructureNestingLevel =
DEFAULT_DATA_STRUCTURE_NESTING_LEVEL;
self->maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
self->maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
self->maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
self->maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
self->negotiatedParameterCBC[0] = 0;
self->negotiatedParameterCBC[1] = 0;
@ -367,16 +367,16 @@ parseInitiateRequestPdu(MmsServerConnection self, uint8_t* buffer, int bufPos, i
case 0x81: /* proposed-max-serv-outstanding-calling */
self->maxServOutstandingCalling = BerDecoder_decodeUint32(buffer, length, bufPos);
if (self->maxServOutstandingCalling > DEFAULT_MAX_SERV_OUTSTANDING_CALLING)
self->maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
if (self->maxServOutstandingCalling > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING)
self->maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING;
break;
case 0x82: /* proposed-max-serv-outstanding-called */
self->maxServOutstandingCalled = BerDecoder_decodeUint32(buffer, length, bufPos);
if (self->maxServOutstandingCalled > DEFAULT_MAX_SERV_OUTSTANDING_CALLED)
self->maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
if (self->maxServOutstandingCalled > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED)
self->maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED;
break;
case 0x83: /* proposed-data-structure-nesting-level */

@ -129,7 +129,7 @@ appendMmsSubVariable(char* name, char* child)
}
static LinkedList
addSubNamedVaribleNamesToList(LinkedList nameList, char* prefix, MmsVariableSpecification* variable)
addSubNamedVaribleNamesToList(MmsServerConnection connection, LinkedList nameList, MmsDomain* domain, char* prefix, MmsVariableSpecification* variable)
{
LinkedList listElement = nameList;
@ -158,14 +158,23 @@ addSubNamedVaribleNamesToList(LinkedList nameList, char* prefix, MmsVariableSpec
#endif /* (CONFIG_MMS_SORT_NAME_LIST == 1) */
if (variableName)
{
listElement = LinkedList_insertAfter(listElement, variableName);
{
bool accessAllowed = mmsServer_checkListAccess(connection->server, MMS_GETNAMELIST_DATA, domain, variableName, connection);
if (accessAllowed) {
listElement = LinkedList_insertAfter(listElement, variableName);
#if (CONFIG_MMS_SORT_NAME_LIST == 1)
listElement = addSubNamedVaribleNamesToList(listElement, variableName, variables[index[i]]);
listElement = addSubNamedVaribleNamesToList(connection, listElement, domain, variableName, variables[index[i]]);
#else
listElement = addSubNamedVaribleNamesToList(listElement, variableName, variables[i]);
listElement = addSubNamedVaribleNamesToList(connection, listElement, domain, variableName, variables[i]);
#endif /* (CONFIG_MMS_SORT_NAME_LIST == 1) */
}
else {
GLOBAL_FREEMEM(variableName);
}
}
}
@ -209,7 +218,11 @@ getJournalListDomainSpecific(MmsServerConnection connection, char* domainName)
MmsJournal journal = (MmsJournal) LinkedList_getData(journalList);
LinkedList_add(nameList, (void*) journal->name);
allowAccess = mmsServer_checkListAccess(connection->server, MMS_GETNAMELIST_JOURNALS, domain, journal->name, connection);
if (allowAccess) {
LinkedList_add(nameList, (void*) journal->name);
}
}
}
@ -233,7 +246,7 @@ getNameListDomainSpecific(MmsServerConnection connection, char* domainName)
bool allowAccess = true;
if (connection->server->getNameListHandler) {
allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_DATASETS, domain, connection);
allowAccess = connection->server->getNameListHandler(connection->server->getNameListHandlerParameter, MMS_GETNAMELIST_DATA, domain, connection);
}
if (allowAccess) {
@ -255,22 +268,28 @@ getNameListDomainSpecific(MmsServerConnection connection, char* domainName)
for (i = 0; i < domain->namedVariablesCount; i++) {
bool accessAllowed = mmsServer_checkListAccess(connection->server, MMS_GETNAMELIST_DATA, domain, variables[index[i]]->name, connection);
if (accessAllowed) {
#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(connection, element, domain, prefix, variables[index[i]]);
#else
char* prefix = variables[i]->name;
element = addSubNamedVaribleNamesToList(element, prefix, variables[i]);
char* prefix = variables[i]->name;
element = addSubNamedVaribleNamesToList(connection, element, domain, 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)
@ -286,7 +305,7 @@ getNameListDomainSpecific(MmsServerConnection connection, char* domainName)
#if (MMS_DATA_SET_SERVICE == 1)
static LinkedList
createStringsFromNamedVariableList(LinkedList variableLists)
createStringsFromNamedVariableList(LinkedList variableLists, MmsServerConnection connection, MmsDomain* domain)
{
LinkedList nameList = LinkedList_create();
LinkedList variableListsElement = LinkedList_getNext(variableLists);
@ -295,8 +314,12 @@ createStringsFromNamedVariableList(LinkedList variableLists)
MmsNamedVariableList variableList =
(MmsNamedVariableList) variableListsElement->data;
LinkedList_add(nameList,
StringUtils_copyString(MmsNamedVariableList_getName(variableList)));
bool accessAllowed = mmsServer_checkListAccess(connection->server, MMS_GETNAMELIST_DATASETS, domain, variableList->name, connection);
if (accessAllowed) {
LinkedList_add(nameList,
StringUtils_copyString(MmsNamedVariableList_getName(variableList)));
}
variableListsElement = LinkedList_getNext(variableListsElement);
}
@ -323,7 +346,7 @@ getNamedVariableListsDomainSpecific(MmsServerConnection connection, char* domain
if (allowAccess) {
LinkedList variableLists = MmsDomain_getNamedVariableLists(domain);
nameList = createStringsFromNamedVariableList(variableLists);
nameList = createStringsFromNamedVariableList(variableLists, connection, domain);
}
}
@ -339,7 +362,7 @@ getNamedVariableListsVMDSpecific(MmsServerConnection connection)
LinkedList variableLists = MmsDevice_getNamedVariableLists(device);
nameList = createStringsFromNamedVariableList(variableLists);
nameList = createStringsFromNamedVariableList(variableLists, connection, NULL);
return nameList;
}
@ -352,7 +375,7 @@ getNamedVariableListAssociationSpecific(MmsServerConnection connection)
LinkedList variableLists = MmsServerConnection_getNamedVariableLists(connection);
nameList = createStringsFromNamedVariableList(variableLists);
nameList = createStringsFromNamedVariableList(variableLists, connection, NULL);
return nameList;
}

@ -1,7 +1,7 @@
/*
* mms_get_var_access_service.c
*
* Copyright 2013 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -32,236 +32,250 @@
static int
createTypeSpecification (
MmsVariableSpecification* namedVariable,
TypeSpecification_t* typeSpec)
MmsVariableSpecification* namedVariable,
TypeSpecification_t* typeSpec)
{
if (namedVariable->type == MMS_ARRAY) {
typeSpec->present = TypeSpecification_PR_array;
asn_long2INTEGER(&(typeSpec->choice.array.numberOfElements),
(long) namedVariable->typeSpec.array.elementCount);
typeSpec->choice.array.packed = NULL;
typeSpec->choice.array.elementType = (TypeSpecification_t*) GLOBAL_CALLOC(1, sizeof(TypeSpecification_t));
createTypeSpecification(namedVariable->typeSpec.array.elementTypeSpec,
typeSpec->choice.array.elementType);
}
else if (namedVariable->type == MMS_STRUCTURE) {
typeSpec->present = TypeSpecification_PR_structure;
int componentCount = namedVariable->typeSpec.structure.elementCount;
typeSpec->choice.structure.components.list.count = componentCount;
typeSpec->choice.structure.components.list.size = componentCount;
typeSpec->choice.structure.components.list.array
= (StructComponent_t**) GLOBAL_CALLOC(componentCount, sizeof(StructComponent_t*));
int i;
for (i = 0; i < componentCount; i++) {
typeSpec->choice.structure.components.list.array[i] =
(StructComponent_t*) GLOBAL_CALLOC(1, sizeof(StructComponent_t));
typeSpec->choice.structure.components.list.array[i]->componentName =
(Identifier_t*) GLOBAL_CALLOC(1, sizeof(Identifier_t));
typeSpec->choice.structure.components.list.array[i]->componentName->buf =
(uint8_t*) StringUtils_copyString(namedVariable->typeSpec.structure.elements[i]->name);
typeSpec->choice.structure.components.list.array[i]->componentName->size =
strlen(namedVariable->typeSpec.structure.elements[i]->name);
typeSpec->choice.structure.components.list.array[i]->componentType =
(TypeSpecification_t*) GLOBAL_CALLOC(1, sizeof(TypeSpecification_t));
createTypeSpecification(namedVariable->typeSpec.structure.elements[i],
typeSpec->choice.structure.components.list.array[i]->componentType);
}
}
else {
switch (namedVariable->type) {
case MMS_BOOLEAN:
typeSpec->present = TypeSpecification_PR_boolean;
break;
case MMS_BIT_STRING:
typeSpec->present = TypeSpecification_PR_bitstring;
typeSpec->choice.bitstring = namedVariable->typeSpec.bitString;
break;
case MMS_INTEGER:
typeSpec->present = TypeSpecification_PR_integer;
typeSpec->choice.integer = namedVariable->typeSpec.integer;
break;
case MMS_UNSIGNED:
typeSpec->present = TypeSpecification_PR_unsigned;
typeSpec->choice.Unsigned = namedVariable->typeSpec.unsignedInteger;
break;
case MMS_FLOAT:
typeSpec->present = TypeSpecification_PR_floatingpoint;
typeSpec->choice.floatingpoint.exponentwidth =
namedVariable->typeSpec.floatingpoint.exponentWidth;
typeSpec->choice.floatingpoint.formatwidth =
namedVariable->typeSpec.floatingpoint.formatWidth;
break;
case MMS_OCTET_STRING:
typeSpec->present = TypeSpecification_PR_octetstring;
typeSpec->choice.octetstring = namedVariable->typeSpec.octetString;
break;
case MMS_VISIBLE_STRING:
typeSpec->present = TypeSpecification_PR_visiblestring;
typeSpec->choice.visiblestring = namedVariable->typeSpec.visibleString;
break;
case MMS_STRING:
typeSpec->present = TypeSpecification_PR_mMSString;
typeSpec->choice.mMSString = namedVariable->typeSpec.mmsString;
break;
case MMS_UTC_TIME:
typeSpec->present = TypeSpecification_PR_utctime;
break;
case MMS_BINARY_TIME:
typeSpec->present = TypeSpecification_PR_binarytime;
if (namedVariable->typeSpec.binaryTime == 6)
typeSpec->choice.binarytime = 1;
else
typeSpec->choice.binarytime = 0;
break;
default:
if (DEBUG_MMS_SERVER)
printf("MMS-SERVER: Unsupported type %i!\n", namedVariable->type);
return -1;
break;
}
}
return 1;
if (namedVariable->type == MMS_ARRAY) {
typeSpec->present = TypeSpecification_PR_array;
asn_long2INTEGER(&(typeSpec->choice.array.numberOfElements),
(long) namedVariable->typeSpec.array.elementCount);
typeSpec->choice.array.packed = NULL;
typeSpec->choice.array.elementType = (TypeSpecification_t*) GLOBAL_CALLOC(1, sizeof(TypeSpecification_t));
createTypeSpecification(namedVariable->typeSpec.array.elementTypeSpec,
typeSpec->choice.array.elementType);
}
else if (namedVariable->type == MMS_STRUCTURE) {
typeSpec->present = TypeSpecification_PR_structure;
int componentCount = namedVariable->typeSpec.structure.elementCount;
typeSpec->choice.structure.components.list.count = componentCount;
typeSpec->choice.structure.components.list.size = componentCount;
typeSpec->choice.structure.components.list.array
= (StructComponent_t**) GLOBAL_CALLOC(componentCount, sizeof(StructComponent_t*));
int i;
for (i = 0; i < componentCount; i++) {
typeSpec->choice.structure.components.list.array[i] =
(StructComponent_t*) GLOBAL_CALLOC(1, sizeof(StructComponent_t));
typeSpec->choice.structure.components.list.array[i]->componentName =
(Identifier_t*) GLOBAL_CALLOC(1, sizeof(Identifier_t));
typeSpec->choice.structure.components.list.array[i]->componentName->buf =
(uint8_t*) StringUtils_copyString(namedVariable->typeSpec.structure.elements[i]->name);
typeSpec->choice.structure.components.list.array[i]->componentName->size =
strlen(namedVariable->typeSpec.structure.elements[i]->name);
typeSpec->choice.structure.components.list.array[i]->componentType =
(TypeSpecification_t*) GLOBAL_CALLOC(1, sizeof(TypeSpecification_t));
createTypeSpecification(namedVariable->typeSpec.structure.elements[i],
typeSpec->choice.structure.components.list.array[i]->componentType);
}
}
else {
switch (namedVariable->type) {
case MMS_BOOLEAN:
typeSpec->present = TypeSpecification_PR_boolean;
break;
case MMS_BIT_STRING:
typeSpec->present = TypeSpecification_PR_bitstring;
typeSpec->choice.bitstring = namedVariable->typeSpec.bitString;
break;
case MMS_INTEGER:
typeSpec->present = TypeSpecification_PR_integer;
typeSpec->choice.integer = namedVariable->typeSpec.integer;
break;
case MMS_UNSIGNED:
typeSpec->present = TypeSpecification_PR_unsigned;
typeSpec->choice.Unsigned = namedVariable->typeSpec.unsignedInteger;
break;
case MMS_FLOAT:
typeSpec->present = TypeSpecification_PR_floatingpoint;
typeSpec->choice.floatingpoint.exponentwidth =
namedVariable->typeSpec.floatingpoint.exponentWidth;
typeSpec->choice.floatingpoint.formatwidth =
namedVariable->typeSpec.floatingpoint.formatWidth;
break;
case MMS_OCTET_STRING:
typeSpec->present = TypeSpecification_PR_octetstring;
typeSpec->choice.octetstring = namedVariable->typeSpec.octetString;
break;
case MMS_VISIBLE_STRING:
typeSpec->present = TypeSpecification_PR_visiblestring;
typeSpec->choice.visiblestring = namedVariable->typeSpec.visibleString;
break;
case MMS_STRING:
typeSpec->present = TypeSpecification_PR_mMSString;
typeSpec->choice.mMSString = namedVariable->typeSpec.mmsString;
break;
case MMS_UTC_TIME:
typeSpec->present = TypeSpecification_PR_utctime;
break;
case MMS_BINARY_TIME:
typeSpec->present = TypeSpecification_PR_binarytime;
if (namedVariable->typeSpec.binaryTime == 6)
typeSpec->choice.binarytime = 1;
else
typeSpec->choice.binarytime = 0;
break;
default:
if (DEBUG_MMS_SERVER)
printf("MMS-SERVER: Unsupported type %i!\n", namedVariable->type);
return -1;
break;
}
}
return 1;
}
static void
freeTypeSpecRecursive(TypeSpecification_t* typeSpec) {
if (typeSpec->present == TypeSpecification_PR_structure) {
int elementCount =
typeSpec->choice.structure.components.list.count;
int i;
for (i = 0; i < elementCount; i++) {
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentName->buf);
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentName);
freeTypeSpecRecursive(typeSpec->choice.structure.components.list.array[i]->componentType);
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentType);
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]);
}
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array);
}
else if (typeSpec->present == TypeSpecification_PR_array) {
GLOBAL_FREEMEM(typeSpec->choice.array.numberOfElements.buf);
freeTypeSpecRecursive(typeSpec->choice.array.elementType);
GLOBAL_FREEMEM(typeSpec->choice.array.elementType);
}
if (typeSpec->present == TypeSpecification_PR_structure) {
int elementCount =
typeSpec->choice.structure.components.list.count;
int i;
for (i = 0; i < elementCount; i++) {
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentName->buf);
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentName);
freeTypeSpecRecursive(typeSpec->choice.structure.components.list.array[i]->componentType);
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentType);
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]);
}
GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array);
}
else if (typeSpec->present == TypeSpecification_PR_array) {
GLOBAL_FREEMEM(typeSpec->choice.array.numberOfElements.buf);
freeTypeSpecRecursive(typeSpec->choice.array.elementType);
GLOBAL_FREEMEM(typeSpec->choice.array.elementType);
}
}
static void
deleteVariableAccessAttributesResponse(
GetVariableAccessAttributesResponse_t* getVarAccessAttr)
GetVariableAccessAttributesResponse_t* getVarAccessAttr)
{
if (getVarAccessAttr->typeSpecification.present == TypeSpecification_PR_structure) {
int count = getVarAccessAttr->typeSpecification.choice.structure.components.list.count;
int i;
for (i = 0; i < count; i++) {
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName->buf);
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName);
TypeSpecification_t* typeSpec =
getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentType;
freeTypeSpecRecursive(typeSpec);
GLOBAL_FREEMEM(typeSpec);
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]);
}
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array);
getVarAccessAttr->typeSpecification.choice.structure.components.list.array = NULL;
getVarAccessAttr->typeSpecification.choice.structure.components.list.count = 0;
getVarAccessAttr->typeSpecification.choice.structure.components.list.size = 0;
} else if (getVarAccessAttr->typeSpecification.present == TypeSpecification_PR_array) {
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.array.numberOfElements.buf);
getVarAccessAttr->typeSpecification.choice.array.numberOfElements.buf = NULL;
getVarAccessAttr->typeSpecification.choice.array.numberOfElements.size = 0;
freeTypeSpecRecursive(getVarAccessAttr->typeSpecification.choice.array.elementType);
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.array.elementType);
getVarAccessAttr->typeSpecification.choice.array.elementType = NULL;
}
if (getVarAccessAttr->typeSpecification.present == TypeSpecification_PR_structure) {
int count = getVarAccessAttr->typeSpecification.choice.structure.components.list.count;
int i;
for (i = 0; i < count; i++) {
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName->buf);
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName);
TypeSpecification_t* typeSpec =
getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentType;
freeTypeSpecRecursive(typeSpec);
GLOBAL_FREEMEM(typeSpec);
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]);
}
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array);
getVarAccessAttr->typeSpecification.choice.structure.components.list.array = NULL;
getVarAccessAttr->typeSpecification.choice.structure.components.list.count = 0;
getVarAccessAttr->typeSpecification.choice.structure.components.list.size = 0;
}
else if (getVarAccessAttr->typeSpecification.present == TypeSpecification_PR_array) {
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.array.numberOfElements.buf);
getVarAccessAttr->typeSpecification.choice.array.numberOfElements.buf = NULL;
getVarAccessAttr->typeSpecification.choice.array.numberOfElements.size = 0;
freeTypeSpecRecursive(getVarAccessAttr->typeSpecification.choice.array.elementType);
GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.array.elementType);
getVarAccessAttr->typeSpecification.choice.array.elementType = NULL;
}
}
static void
createVariableAccessAttributesResponse(
MmsServerConnection connection,
char* domainId,
char* nameId,
int invokeId,
ByteBuffer* response)
MmsServerConnection connection,
char* domainId,
char* nameId,
int invokeId,
ByteBuffer* response)
{
MmsDevice* device = MmsServer_getDevice(connection->server);
MmsDevice* device = MmsServer_getDevice(connection->server);
MmsVariableSpecification* namedVariable = NULL;
MmsVariableSpecification* namedVariable = NULL;
if (domainId != NULL) {
MmsDomain* domain = MmsDevice_getDomain(device, domainId);
MmsDomain* domain = NULL;
if (domain == NULL) {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: domain %s not known\n", domainId);
if (domainId != NULL) {
domain = MmsDevice_getDomain(device, domainId);
mmsMsg_createServiceErrorPdu(invokeId, response,
MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_function;
}
if (domain == NULL) {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: domain %s not known\n", domainId);
namedVariable = MmsDomain_getNamedVariable(domain, nameId);
}
mmsMsg_createServiceErrorPdu(invokeId, response,
MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_function;
}
namedVariable = MmsDomain_getNamedVariable(domain, nameId);
}
#if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1)
else
namedVariable = MmsDevice_getNamedVariable(device, nameId);
else
namedVariable = MmsDevice_getNamedVariable(device, nameId);
#endif /* (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) */
if (namedVariable == NULL) {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: named variable %s.%s not known\n", domainId, nameId);
mmsMsg_createServiceErrorPdu(invokeId, response,
MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_function;
}
bool accessAllowed = mmsServer_checkListAccess(connection->server, MMS_GETNAMELIST_DATA, domain, nameId, connection);
if (namedVariable == NULL) {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: named variable %s not known\n", nameId);
if (!accessAllowed) {
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: named variable %s/%s not visible due to access restrictions\n", domainId, nameId);
mmsMsg_createServiceErrorPdu(invokeId, response,
MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsMsg_createServiceErrorPdu(invokeId, response,
MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_function;
}
goto exit_function;
}
MmsPdu_t* mmsPdu = mmsServer_createConfirmedResponse(invokeId);
MmsPdu_t* mmsPdu = mmsServer_createConfirmedResponse(invokeId);
mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.present =
ConfirmedServiceResponse_PR_getVariableAccessAttributes;
mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.present =
ConfirmedServiceResponse_PR_getVariableAccessAttributes;
GetVariableAccessAttributesResponse_t* getVarAccessAttr;
GetVariableAccessAttributesResponse_t* getVarAccessAttr;
getVarAccessAttr = &(mmsPdu->choice.confirmedResponsePdu.
confirmedServiceResponse.choice.getVariableAccessAttributes);
getVarAccessAttr = &(mmsPdu->choice.confirmedResponsePdu.
confirmedServiceResponse.choice.getVariableAccessAttributes);
getVarAccessAttr->mmsDeletable = 0;
getVarAccessAttr->mmsDeletable = 0;
createTypeSpecification(namedVariable, &getVarAccessAttr->typeSpecification);
createTypeSpecification(namedVariable, &getVarAccessAttr->typeSpecification);
asn_enc_rval_t rval =
der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response);
asn_enc_rval_t rval =
der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response);
if (rval.encoded == -1) {
response->size = 0;
if (rval.encoded == -1) {
response->size = 0;
if (DEBUG_MMS_SERVER)
printf("MMS getVariableAccessAttributes: message to large! send error PDU!\n");
@ -270,81 +284,90 @@ createVariableAccessAttributesResponse(
MMS_ERROR_SERVICE_OTHER);
goto exit_function;
}
}
deleteVariableAccessAttributesResponse(getVarAccessAttr);
deleteVariableAccessAttributesResponse(getVarAccessAttr);
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
exit_function:
return;
return;
}
int
mmsServer_handleGetVariableAccessAttributesRequest(
MmsServerConnection connection,
uint8_t* buffer, int bufPos, int maxBufPos,
uint32_t invokeId,
ByteBuffer* response)
MmsServerConnection connection,
uint8_t* buffer, int bufPos, int maxBufPos,
uint32_t invokeId,
ByteBuffer* response)
{
int retVal = 0;
int retVal = 0;
GetVariableAccessAttributesRequest_t* request = 0;
GetVariableAccessAttributesRequest_t* request = 0;
asn_dec_rval_t rval; /* Decoder return value */
asn_dec_rval_t rval; /* Decoder return value */
rval = ber_decode(NULL, &asn_DEF_GetVariableAccessAttributesRequest,
(void**) &request, buffer + bufPos, maxBufPos - bufPos);
rval = ber_decode(NULL, &asn_DEF_GetVariableAccessAttributesRequest,
(void**) &request, buffer + bufPos, maxBufPos - bufPos);
if (rval.code == RC_OK) {
if (request->present == GetVariableAccessAttributesRequest_PR_name) {
if (request->choice.name.present == ObjectName_PR_domainspecific) {
Identifier_t domainId = request->choice.name.choice.domainspecific.domainId;
Identifier_t nameId = request->choice.name.choice.domainspecific.itemId;
if (rval.code == RC_OK)
{
if (request->present == GetVariableAccessAttributesRequest_PR_name)
{
if (request->choice.name.present == ObjectName_PR_domainspecific)
{
Identifier_t domainId = request->choice.name.choice.domainspecific.domainId;
Identifier_t nameId = request->choice.name.choice.domainspecific.itemId;
char* domainIdStr = StringUtils_createStringFromBuffer(domainId.buf, domainId.size);
char* nameIdStr = StringUtils_createStringFromBuffer(nameId.buf, nameId.size);
char* domainIdStr = StringUtils_createStringFromBuffer(domainId.buf, domainId.size);
char* nameIdStr = StringUtils_createStringFromBuffer(nameId.buf, nameId.size);
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: getVariableAccessAttributes domainId: %s nameId: %s\n", domainIdStr, nameIdStr);
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: getVariableAccessAttributes domainId: %s nameId: %s\n", domainIdStr, nameIdStr);
createVariableAccessAttributesResponse(connection, domainIdStr, nameIdStr, invokeId, response);
createVariableAccessAttributesResponse(connection, domainIdStr, nameIdStr, invokeId, response);
GLOBAL_FREEMEM(domainIdStr);
GLOBAL_FREEMEM(nameIdStr);
}
GLOBAL_FREEMEM(domainIdStr);
GLOBAL_FREEMEM(nameIdStr);
}
#if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1)
else if (request->choice.name.present == ObjectName_PR_vmdspecific) {
Identifier_t nameId = request->choice.name.choice.vmdspecific;
else if (request->choice.name.present == ObjectName_PR_vmdspecific)
{
Identifier_t nameId = request->choice.name.choice.vmdspecific;
char* nameIdStr = StringUtils_createStringFromBuffer(nameId.buf, nameId.size);
char* nameIdStr = StringUtils_createStringFromBuffer(nameId.buf, nameId.size);
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getVariableAccessAttributes (VMD specific) nameId: %s\n", nameIdStr);
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getVariableAccessAttributes (VMD specific) nameId: %s\n", nameIdStr);
createVariableAccessAttributesResponse(connection, NULL, nameIdStr, invokeId, response);
createVariableAccessAttributesResponse(connection, NULL, nameIdStr, invokeId, response);
GLOBAL_FREEMEM(nameIdStr);
}
GLOBAL_FREEMEM(nameIdStr);
}
#endif /* (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) */
else {
if (DEBUG_MMS_SERVER) printf("GetVariableAccessAttributesRequest with name other than domainspecific is not supported!\n");
retVal = -1;
}
}
else {
if (DEBUG_MMS_SERVER) printf("GetVariableAccessAttributesRequest with address not supported!\n");
retVal = -1;
}
}
else {
if (DEBUG_MMS_SERVER) printf("GetVariableAccessAttributesRequest parsing request failed!\n");
retVal = -1;
}
asn_DEF_GetVariableAccessAttributesRequest.free_struct(&asn_DEF_GetVariableAccessAttributesRequest, request, 0);
return retVal;
else {
if (DEBUG_MMS_SERVER) printf("GetVariableAccessAttributesRequest with name other than domainspecific is not supported!\n");
retVal = -1;
}
}
else {
if (DEBUG_MMS_SERVER) printf("GetVariableAccessAttributesRequest with address not supported!\n");
retVal = -1;
}
}
else {
if (DEBUG_MMS_SERVER) printf("GetVariableAccessAttributesRequest parsing request failed!\n");
retVal = -1;
}
asn_DEF_GetVariableAccessAttributesRequest.free_struct(&asn_DEF_GetVariableAccessAttributesRequest, request, 0);
if (ByteBuffer_getSize(response) > connection->maxPduSize)
{
ByteBuffer_setSize(response, 0);
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
}
return retVal;
}
#endif /* (MMS_GET_VARIABLE_ACCESS_ATTRIBUTES == 1) */

@ -130,16 +130,22 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection,
goto exit_function;
}
if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) &&
(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present
== ConfirmedServiceRequest_PR_deleteNamedVariableList))
{
request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList);
}
else {
mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
goto exit_function;
}
if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) &&
(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present
== ConfirmedServiceRequest_PR_deleteNamedVariableList))
{
request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList);
}
else {
mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
goto exit_function;
}
if (request->listOfVariableListName == NULL)
{
mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
goto exit_function;
}
long scopeOfDelete = DeleteNamedVariableListRequest__scopeOfDelete_specific;
@ -675,7 +681,8 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response,
LinkedList variable = LinkedList_getNext(variables);
int i;
for (i = 0; i < variableCount; i++) {
for (i = 0; i < variableCount; i++)
{
MmsNamedVariableListEntry variableEntry = (MmsNamedVariableListEntry) variable->data;
varListResponse->listOfVariable.list.array[i] = (struct GetNamedVariableListAttributesResponse__listOfVariable__Member*)
@ -740,8 +747,8 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
goto exit_function;
}
if (request->present == ObjectName_PR_domainspecific) {
if (request->present == ObjectName_PR_domainspecific)
{
char domainName[65];
char itemName[65];
@ -761,11 +768,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
MmsDomain* domain = MmsDevice_getDomain(mmsDevice, domainName);
if (domain != NULL) {
if (domain != NULL)
{
MmsNamedVariableList varList = MmsDomain_getNamedVariableList(domain, itemName);
if (varList) {
if (varList)
{
MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_DOMAIN_SPECIFIC, domain, varList->name, connection);
if (accessError == MMS_ERROR_NONE) {
@ -792,8 +800,8 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
}
#if (MMS_DYNAMIC_DATA_SETS == 1)
else if (request->present == ObjectName_PR_aaspecific) {
else if (request->present == ObjectName_PR_aaspecific)
{
char listName[65];
if (request->choice.aaspecific.size > 64) {
@ -806,11 +814,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
MmsNamedVariableList varList = MmsServerConnection_getNamedVariableList(connection, listName);
if (varList) {
if (varList)
{
MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_ASSOCIATION_SPECIFIC, NULL, varList->name, connection);
if (accessError == MMS_ERROR_NONE) {
if (accessError == MMS_ERROR_NONE)
{
if (createGetNamedVariableListAttributesResponse(invokeId, response, varList) == false) {
/* encoding failed - probably because buffer size is too small for message */
@ -829,7 +838,8 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
#endif /* (MMS_DYNAMIC_DATA_SETS == 1) */
else if (request->present == ObjectName_PR_vmdspecific) {
else if (request->present == ObjectName_PR_vmdspecific)
{
char listName[65];
if (request->choice.vmdspecific.size > 64) {
@ -844,11 +854,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
MmsNamedVariableList varList = mmsServer_getNamedVariableListWithName(mmsDevice->namedVariableLists, listName);
if (varList) {
if (varList)
{
MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_VMD_SPECIFIC, NULL, varList->name, connection);
if (accessError == MMS_ERROR_NONE) {
if (accessError == MMS_ERROR_NONE)
{
if (createGetNamedVariableListAttributesResponse(invokeId, response, varList) == false) {
/* encoding failed - probably because buffer size is too small for message */
@ -870,6 +881,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
if (ByteBuffer_getSize(response) > connection->maxPduSize)
{
ByteBuffer_setSize(response, 0);
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
}
exit_function:
asn_DEF_GetVariableAccessAttributesRequest.free_struct(&asn_DEF_GetNamedVariableListAttributesRequest,

@ -1,7 +1,7 @@
/*
* mms_read_service.c
*
* Copyright 2013-2023 Michael Zillgith
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -202,30 +202,35 @@ alternateArrayAccess(MmsServerConnection connection,
MmsValue* arrayValue = mmsServer_getValue(connection->server, domain, itemId, connection, false);
if (arrayValue != NULL) {
if (arrayValue != NULL)
{
MmsValue* value = NULL;
if (numberOfElements == 0)
if (mmsServer_isAccessToArrayComponent(alternateAccess)) {
{
if (mmsServer_isAccessToArrayComponent(alternateAccess))
{
if (namedVariable->typeSpec.array.elementTypeSpec->type == MMS_STRUCTURE) {
MmsValue* structValue = MmsValue_getElement(arrayValue, index);
if (structValue != NULL)
value = mmsServer_getComponentOfArrayElement(alternateAccess,
namedVariable, structValue);
namedVariable, structValue, NULL);
}
}
else {
value = MmsValue_getElement(arrayValue, index);
}
else {
}
else
{
value = MmsValue_createEmptyArray(numberOfElements);
MmsValue_setDeletable(value);
int resultIndex = 0;
while (index < lowIndex + numberOfElements) {
while (index < lowIndex + numberOfElements)
{
MmsValue* elementValue = NULL;
elementValue = MmsValue_getElement(arrayValue, index);
@ -352,7 +357,8 @@ encodeVariableAccessSpecification(VarAccessSpec* accessSpec, uint8_t* buffer, in
varAccessSpecSize += itemIdLen + BerEncoder_determineLengthSize(itemIdLen) + 1;
if (accessSpec->domainId != NULL) {
if (accessSpec->domainId != NULL)
{
uint32_t domainIdLen = strlen(accessSpec->domainId);
varAccessSpecSize += domainIdLen + BerEncoder_determineLengthSize(domainIdLen) + 1;
@ -370,7 +376,8 @@ encodeVariableAccessSpecification(VarAccessSpec* accessSpec, uint8_t* buffer, in
varAccessSpecSize += 1 + BerEncoder_determineLengthSize(varAccessSpecLength);
if (encode == false) {
if (encode == false)
{
bufPos = varAccessSpecSize;
goto exit_function;
}
@ -378,8 +385,8 @@ encodeVariableAccessSpecification(VarAccessSpec* accessSpec, uint8_t* buffer, in
/* encode to buffer */
bufPos = BerEncoder_encodeTL(0xa0, varAccessSpecLength, buffer, bufPos);
if (accessSpec->isNamedVariableList == true) {
if (accessSpec->isNamedVariableList == true)
{
bufPos = BerEncoder_encodeTL(0xa1, variableListNameLength, buffer, bufPos);
if (accessSpec->specific == 0) { /* vmd-specific */
@ -425,8 +432,8 @@ encodeReadResponse(MmsServerConnection connection,
/* iterate values list to determine encoded size */
LinkedList value = LinkedList_getNext(values);
for (i = 0; i < variableCount; i++) {
for (i = 0; i < variableCount; i++)
{
MmsValue* data = (MmsValue*) value->data;
accessResultSize += MmsValue_encodeMmsData(data, NULL, 0, false);
@ -452,12 +459,13 @@ encodeReadResponse(MmsServerConnection connection,
confirmedResponseContentSize;
/* Check if message would fit in the MMS PDU */
if (mmsPduSize > connection->maxPduSize) {
if (mmsPduSize > connection->maxPduSize)
{
if (DEBUG_MMS_SERVER)
printf("MMS read: message to large! send error PDU!\n");
mmsMsg_createServiceErrorPdu(invokeId, response,
MMS_ERROR_SERVICE_OTHER);
MMS_ERROR_RESOURCE_OTHER);
goto exit_function;
}
@ -487,7 +495,8 @@ encodeReadResponse(MmsServerConnection connection,
/* encode access results */
value = LinkedList_getNext(values);
for (i = 0; i < variableCount; i++) {
for (i = 0; i < variableCount; i++)
{
MmsValue* data = (MmsValue*) value->data;
bufPos = MmsValue_encodeMmsData(data, buffer, bufPos, true);
@ -529,16 +538,18 @@ handleReadListOfVariablesRequest(
int i;
for (i = 0; i < variableCount; i++) {
for (i = 0; i < variableCount; i++)
{
VariableSpecification_t varSpec =
read->variableAccessSpecification.choice.listOfVariable.list.array[i]->variableSpecification;
AlternateAccess_t* alternateAccess =
read->variableAccessSpecification.choice.listOfVariable.list.array[i]->alternateAccess;
if (varSpec.present == VariableSpecification_PR_name) {
if (varSpec.choice.name.present == ObjectName_PR_domainspecific) {
if (varSpec.present == VariableSpecification_PR_name)
{
if (varSpec.choice.name.present == ObjectName_PR_domainspecific)
{
char domainIdStr[65];
char nameIdStr[65];
@ -571,7 +582,8 @@ handleReadListOfVariablesRequest(
}
#if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1)
else if (varSpec.choice.name.present == ObjectName_PR_vmdspecific) {
else if (varSpec.choice.name.present == ObjectName_PR_vmdspecific)
{
char nameIdStr[65];
mmsMsg_copyAsn1IdentifierToStringBuffer(varSpec.choice.name.choice.vmdspecific, nameIdStr, 65);
@ -607,11 +619,12 @@ handleReadListOfVariablesRequest(
LinkedList valueElement = LinkedList_getNext(values);
while (valueElement) {
while (valueElement)
{
MmsValue* value = (MmsValue*) LinkedList_getData(valueElement);
if (value) {
if (value)
{
if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) {
if (MmsValue_getDataAccessError(value) == DATA_ACCESS_ERROR_NO_RESPONSE) {
sendResponse = false;
@ -637,21 +650,24 @@ static void
addNamedVariableToNamedVariableListResultList(MmsVariableSpecification* namedVariable, MmsDomain* domain, char* nameIdStr,
LinkedList /*<MmsValue>*/ values, MmsServerConnection connection, MmsNamedVariableListEntry listEntry)
{
if (namedVariable != NULL) {
if (namedVariable != NULL)
{
if (DEBUG_MMS_SERVER)
printf("MMS read: found named variable %s with search string %s\n",
namedVariable->name, nameIdStr);
MmsValue* value = mmsServer_getValue(connection->server, domain, nameIdStr, connection, false);
if (value) {
if (listEntry->arrayIndex != -1) {
if (MmsValue_getType(value) == MMS_ARRAY) {
if (value)
{
if (listEntry->arrayIndex != -1)
{
if (MmsValue_getType(value) == MMS_ARRAY)
{
MmsValue* elementValue = MmsValue_getElement(value, listEntry->arrayIndex);
if (listEntry->componentName) {
if (listEntry->componentName)
{
MmsVariableSpecification* elementType = namedVariable->typeSpec.array.elementTypeSpec;
MmsValue* subElementValue = MmsVariableSpecification_getChildValue(elementType, elementValue, listEntry->componentName);
@ -669,7 +685,8 @@ addNamedVariableToNamedVariableListResultList(MmsVariableSpecification* namedVar
}
}
else {
else
{
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: data set entry of unexpected type!\n");
@ -697,8 +714,8 @@ createNamedVariableListResponse(MmsServerConnection connection, MmsNamedVariable
LinkedList variable = LinkedList_getNext(variables);
while (variable) {
while (variable)
{
MmsNamedVariableListEntry variableListEntry = (MmsNamedVariableListEntry) variable->data;
MmsDomain* variableDomain = MmsNamedVariableListEntry_getDomain(variableListEntry);
@ -757,11 +774,13 @@ handleReadNamedVariableListRequest(
MmsDomain* domain = MmsDevice_getDomain(MmsServer_getDevice(connection->server), domainIdStr);
if (domain == NULL) {
if (domain == NULL)
{
if (DEBUG_MMS_SERVER) printf("MMS read: domain %s not found!\n", domainIdStr);
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
else {
else
{
MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr);
if (namedList)
@ -796,12 +815,15 @@ handleReadNamedVariableListRequest(
MmsNamedVariableList namedList = mmsServer_getNamedVariableListWithName(connection->server->device->namedVariableLists, listName);
if (namedList == NULL)
{
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
else {
}
else
{
MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_READ, MMS_VMD_SPECIFIC, NULL, namedList->name, connection);
if (accessError == MMS_ERROR_NONE) {
if (accessError == MMS_ERROR_NONE)
{
VarAccessSpec accessSpec;
accessSpec.isNamedVariableList = true;
@ -816,7 +838,6 @@ handleReadNamedVariableListRequest(
mmsMsg_createServiceErrorPdu(invokeId, response, accessError);
}
}
}
#if (MMS_DYNAMIC_DATA_SETS == 1)
@ -831,13 +852,15 @@ handleReadNamedVariableListRequest(
MmsNamedVariableList namedList = MmsServerConnection_getNamedVariableList(connection, listName);
if (namedList == NULL)
{
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
else {
}
else
{
MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_READ, MMS_ASSOCIATION_SPECIFIC, NULL, namedList->name, connection);
if (accessError == MMS_ERROR_NONE) {
if (accessError == MMS_ERROR_NONE)
{
VarAccessSpec accessSpec;
accessSpec.isNamedVariableList = true;
@ -891,7 +914,8 @@ mmsServer_handleReadRequest(
goto exit_function;
}
if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) {
if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable)
{
MmsServer_lockModel(connection->server);
handleReadListOfVariablesRequest(connection, request, invokeId, response);
@ -899,7 +923,8 @@ mmsServer_handleReadRequest(
MmsServer_unlockModel(connection->server);
}
#if (MMS_DATA_SET_SERVICE == 1)
else if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) {
else if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName)
{
MmsServer_lockModel(connection->server);
handleReadNamedVariableListRequest(connection, request, invokeId, response);
@ -911,6 +936,12 @@ mmsServer_handleReadRequest(
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
if (ByteBuffer_getSize(response) > connection->maxPduSize)
{
ByteBuffer_setSize(response, 0);
mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
}
exit_function:
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
}

@ -351,6 +351,13 @@ MmsServer_installWriteHandler(MmsServer self, MmsWriteVariableHandler writeHandl
self->writeHandlerParameter = parameter;
}
void
MmsServer_installListAccessHandler(MmsServer self, MmsListAccessHandler listAccessHandler, void* parameter)
{
self->listAccessHandler = listAccessHandler;
self->listAccessHandlerParameter = parameter;
}
void
MmsServer_installConnectionHandler(MmsServer self, MmsConnectionHandler connectionHandler, void* parameter)
{
@ -529,9 +536,10 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va
{
MmsDataAccessError indication;
if (self->writeHandler != NULL) {
if (self->writeHandler)
{
indication = self->writeHandler(self->writeHandlerParameter, domain,
itemId, value, connection);
itemId, -1, NULL, value, connection);
}
else {
MmsValue* cachedValue;
@ -541,7 +549,36 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va
cachedValue = MmsServer_getValueFromCache(self, domain, itemId);
if (cachedValue != NULL) {
if (cachedValue) {
MmsValue_update(cachedValue, value);
indication = DATA_ACCESS_ERROR_SUCCESS;
} else
indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
}
return indication;
}
MmsDataAccessError
mmsServer_setValueEx(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* value,
MmsServerConnection connection, int arrayIdx, const char* componentId)
{
MmsDataAccessError indication;
if (self->writeHandler)
{
indication = self->writeHandler(self->writeHandlerParameter, domain,
itemId, arrayIdx, componentId, value, connection);
}
else {
MmsValue* cachedValue = NULL;
if (domain == NULL)
domain = (MmsDomain*) self->device;
cachedValue = MmsServer_getValueFromCacheEx2(self, domain, itemId, arrayIdx, componentId);
if (cachedValue) {
MmsValue_update(cachedValue, value);
indication = DATA_ACCESS_ERROR_SUCCESS;
} else
@ -579,6 +616,32 @@ exit_function:
return value;
}
MmsDataAccessError
mmsServer_checkReadAccess(MmsServer self, MmsDomain* domain, char* itemId, MmsServerConnection connection, bool isDirectAccess)
{
MmsDataAccessError accessError = DATA_ACCESS_ERROR_SUCCESS;
if (self->readAccessHandler) {
accessError =
self->readAccessHandler(self->readAccessHandlerParameter, (domain == (MmsDomain*) self->device) ? NULL : domain,
itemId, connection, isDirectAccess);
}
return accessError;
}
bool
mmsServer_checkListAccess(MmsServer self, MmsGetNameListType listType, MmsDomain* domain, char* itemId, MmsServerConnection connection)
{
bool allowAccess = true;
if (self->listAccessHandler) {
allowAccess = self->listAccessHandler(self->listAccessHandlerParameter, listType, domain, itemId, connection);
}
return allowAccess;
}
MmsDevice*
MmsServer_getDevice(MmsServer self)
{

@ -287,7 +287,7 @@ mmsServer_isAccessToArrayComponent(AlternateAccess_t* alternateAccess)
MmsValue*
mmsServer_getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable,
MmsValue* structuredValue)
MmsValue* structuredValue, char* componentId)
{
MmsValue* retValue = NULL;
@ -309,24 +309,43 @@ mmsServer_getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVari
goto exit_function;
int i;
for (i = 0; i < structSpec->typeSpec.structure.elementCount; i++) {
for (i = 0; i < structSpec->typeSpec.structure.elementCount; i++)
{
if ((int) strlen(structSpec->typeSpec.structure.elements[i]->name)
== component.size) {
== component.size)
{
if (strncmp(structSpec->typeSpec.structure.elements[i]->name,
(char*) component.buf, component.size) == 0) {
(char*) component.buf, component.size) == 0)
{
MmsValue* value = MmsValue_getElement(structuredValue, i);
if (mmsServer_isAccessToArrayComponent(
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess)) {
retValue =
mmsServer_getComponentOfArrayElement(
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess,
structSpec->typeSpec.structure.elements[i],
value);
if (value)
{
if (mmsServer_isAccessToArrayComponent(
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess))
{
if (componentId)
{
strcat(componentId, structSpec->typeSpec.structure.elements[i]->name);
strcat(componentId, "$");
}
retValue =
mmsServer_getComponentOfArrayElement(
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess,
structSpec->typeSpec.structure.elements[i],
value, componentId);
}
else
{
if (componentId)
{
strcat(componentId, structSpec->typeSpec.structure.elements[i]->name);
}
retValue = value;
}
}
else
retValue = value;
goto exit_function;
}

@ -517,33 +517,36 @@ getComponent(MmsServerConnection connection, MmsDomain* domain, AlternateAccess_
{
MmsVariableSpecification* retValue = NULL;
if (mmsServer_isComponentAccess(alternateAccess)) {
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) {
if (namedVariable->type == MMS_STRUCTURE)
{
int i;
for (i = 0; i < namedVariable->typeSpec.structure.elementCount; i++) {
for (i = 0; i < namedVariable->typeSpec.structure.elementCount; i++)
{
if ((int) strlen(namedVariable->typeSpec.structure.elements[i]->name)
== component.size) {
== component.size)
{
if (!strncmp(namedVariable->typeSpec.structure.elements[i]->name,
(char*) component.buf, component.size))
{
if (strlen(variableName) + component.size < 199) {
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) {
!= NULL)
{
retValue =
getComponent(connection, domain,
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess,
@ -598,12 +601,13 @@ mmsServer_handleWriteRequest(
MmsServer_lockModel(connection->server);
if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) {
if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName)
{
handleWriteNamedVariableListRequest(connection, writeRequest, invokeId, response);
goto exit_function;
}
else if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) {
else if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable)
{
int numberOfWriteItems = writeRequest->variableAccessSpecification.choice.listOfVariable.list.count;
if (numberOfWriteItems < 1) {
@ -627,7 +631,8 @@ mmsServer_handleWriteRequest(
int i;
for (i = 0; i < numberOfWriteItems; i++) {
for (i = 0; i < numberOfWriteItems; i++)
{
ListOfVariableSeq_t* varSpec =
writeRequest->variableAccessSpecification.choice.listOfVariable.list.array[i];
@ -644,7 +649,8 @@ mmsServer_handleWriteRequest(
char nameIdStr[65];
if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) {
if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_domainspecific)
{
Identifier_t domainId = varSpec->variableSpecification.choice.name.choice.domainspecific.domainId;
char domainIdStr[65];
@ -687,8 +693,8 @@ mmsServer_handleWriteRequest(
AlternateAccess_t* alternateAccess = varSpec->alternateAccess;
if (alternateAccess != NULL) {
if (alternateAccess)
{
if ((variable->type == MMS_STRUCTURE) && (mmsServer_isComponentAccess(alternateAccess) == false)) {
accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT;
continue;
@ -714,12 +720,13 @@ mmsServer_handleWriteRequest(
continue;
}
if (alternateAccess != NULL) {
if (alternateAccess)
{
if (domain == NULL)
domain = (MmsDomain*) device;
if (mmsServer_isIndexAccess(alternateAccess)) {
if (mmsServer_isIndexAccess(alternateAccess))
{
MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr);
if (cachedArray == NULL) {
@ -730,8 +737,8 @@ mmsServer_handleWriteRequest(
int index = mmsServer_getLowIndex(alternateAccess);
int numberOfElements = mmsServer_getNumberOfElements(alternateAccess);
if (numberOfElements == 0) { /* select single array element with index */
if (numberOfElements == 0) /* select single array element with index */
{
MmsValue* elementValue = MmsValue_getElement(cachedArray, index);
if (elementValue == NULL) {
@ -739,34 +746,45 @@ mmsServer_handleWriteRequest(
goto end_of_main_loop;
}
if (mmsServer_isAccessToArrayComponent(alternateAccess)) {
if (mmsServer_isAccessToArrayComponent(alternateAccess))
{
MmsVariableSpecification* namedVariable = MmsDomain_getNamedVariable(domain, nameIdStr);
char componentId[65];
componentId[0] = 0;
if (namedVariable) {
elementValue = mmsServer_getComponentOfArrayElement(alternateAccess, namedVariable, elementValue);
elementValue = mmsServer_getComponentOfArrayElement(alternateAccess, namedVariable, elementValue, componentId);
}
if ((namedVariable == NULL) || (elementValue == NULL)) {
accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
goto end_of_main_loop;
}
}
else
{
accessResults[i] = mmsServer_setValueEx(connection->server, domain, nameIdStr, value, connection, index, componentId);
}
if (MmsValue_update(elementValue, value) == false) {
accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT;
goto end_of_main_loop;
}
else
{
accessResults[i] = mmsServer_setValueEx(connection->server, domain, nameIdStr, value, connection, index, NULL);
goto end_of_main_loop;
}
}
else { /* select sub-array with start-index and number-of-elements */
if (MmsValue_getType(value) != MMS_ARRAY) {
else /* select sub-array with start-index and number-of-elements */
{
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++) {
for (elementNo = 0; elementNo < numberOfElements; elementNo++)
{
MmsValue* newElement = MmsValue_getElement(value, elementNo);
MmsValue* elementValue = MmsValue_getElement(cachedArray, index++);
@ -785,36 +803,37 @@ mmsServer_handleWriteRequest(
accessResults[i] = DATA_ACCESS_ERROR_SUCCESS;
goto end_of_main_loop;
}
else if (mmsServer_isComponentAccess(alternateAccess)) {
else if (mmsServer_isComponentAccess(alternateAccess))
{
variable = getComponent(connection, domain, alternateAccess, variable, nameIdStr);
if (variable == NULL) {
if (variable == NULL)
{
accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
goto end_of_main_loop;
}
}
else {
else
{
accessResults[i] = DATA_ACCESS_ERROR_SUCCESS;
goto end_of_main_loop;
}
}
/* Check for correct type */
if (MmsVariableSpecification_isValueOfType(variable, value) == false) {
if (MmsVariableSpecification_isValueOfType(variable, value) == false)
{
accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT;
goto end_of_main_loop;
}
MmsDataAccessError valueIndication =
mmsServer_setValue(connection->server, domain, nameIdStr, value, connection);
if (valueIndication == DATA_ACCESS_ERROR_NO_RESPONSE)
sendResponse = false;
accessResults[i] = valueIndication;
accessResults[i] = mmsServer_setValue(connection->server, domain, nameIdStr, value, connection);
end_of_main_loop:
if (accessResults[i] == DATA_ACCESS_ERROR_NO_RESPONSE)
sendResponse = false;
MmsValue_delete(value);
}

@ -57,6 +57,7 @@ struct sSVReceiver {
#if (CONFIG_MMS_THREADLESS_STACK == 0)
Semaphore subscriberListLock;
Thread thread;
#endif
};
@ -99,6 +100,7 @@ SVReceiver_create(void)
#if (CONFIG_MMS_THREADLESS_STACK == 0)
self->subscriberListLock = Semaphore_create(1);
self->thread = NULL;
#endif
}
@ -193,15 +195,19 @@ SVReceiver_start(SVReceiver self)
if (DEBUG_SV_SUBSCRIBER)
printf("SV_SUBSCRIBER: SV receiver started for interface %s\n", self->interfaceId);
Thread thread = Thread_create((ThreadExecutionFunction) svReceiverLoop, (void*) self, true);
#if (CONFIG_MMS_THREADLESS_STACK == 0)
self->thread = Thread_create((ThreadExecutionFunction) svReceiverLoop, (void*) self, false);
if (thread) {
Thread_start(thread);
if (self->thread) {
Thread_start(self->thread);
}
else {
if (DEBUG_SV_SUBSCRIBER)
printf("SV_SUBSCRIBER: Failed to start thread\n");
}
#endif /* (CONFIG_MMS_THREADLESS_STACK == 0) */
}
else {
if (DEBUG_SV_SUBSCRIBER)
@ -220,16 +226,24 @@ void
SVReceiver_stop(SVReceiver self)
{
if (self->running) {
SVReceiver_stopThreadless(self);
self->running = false;
while (self->stopped == false)
Thread_sleep(1);
#if (CONFIG_MMS_THREADLESS_STACK == 0)
if (self->thread) {
Thread_destroy(self->thread);
self->thread = NULL;
}
#endif /* (CONFIG_MMS_THREADLESS_STACK == 0) */
SVReceiver_stopThreadless(self);
}
}
void
SVReceiver_destroy(SVReceiver self)
{
SVReceiver_stop(self);
LinkedList_destroyDeep(self->subscriberList,
(LinkedListValueDeleteFunction) SVSubscriber_destroy);
@ -237,7 +251,12 @@ SVReceiver_destroy(SVReceiver self)
GLOBAL_FREEMEM(self->interfaceId);
#if (CONFIG_MMS_THREADLESS_STACK == 0)
Semaphore_destroy(self->subscriberListLock);
if (self->thread) {
Thread_destroy(self->thread);
self->thread = NULL;
}
Semaphore_destroy(self->subscriberListLock);
#endif
GLOBAL_FREEMEM(self->buffer);

@ -4,7 +4,7 @@ mkdir build
find src/ -name "*.java" > listFile.tmp
javac -target 1.6 -source 1.6 -d build @listFile.tmp
javac -target 1.8 -source 1.8 -d build @listFile.tmp
jar cfm genconfig.jar manifest-dynamic.mf -C build/ com/

@ -291,6 +291,25 @@ public class SclParser
SclParser sclParser = new SclParser(stream);
}
public List<ConnectedAP> getConnectedAPs()
{
List<ConnectedAP> aps = new LinkedList<ConnectedAP>();
if (communication != null) {
List<SubNetwork> subNetworks = communication.getSubNetworks();
for (SubNetwork subNetwork : subNetworks) {
List<ConnectedAP> connectedAPs = subNetwork.getConnectedAPs();
for (ConnectedAP connectedAP : connectedAPs) {
aps.add(connectedAP);
}
}
}
return aps;
}
public ConnectedAP getConnectedAP(IED ied, String accessPointName) {
communication = this.getCommunication();

@ -85,7 +85,7 @@ public class ConnectedAP {
public List<SMV> getSmvs() {
return smvs;
}
public GSE lookupGSE(String logicalDeviceName, String name) {
for (GSE gse : this.getGses()) {
@ -97,6 +97,18 @@ public class ConnectedAP {
return null;
}
public SMV lookupSMV(String logicalDeviceName, String name) {
for (SMV smv : this.getSmvs()) {
if (smv.getLdInst().equals(logicalDeviceName)) {
if (smv.getCbName().equals(name))
return smv;
}
}
return null;
}
public PhyComAddress lookupSMVAddress(String logicalDeviceName, String name) {

@ -188,19 +188,35 @@ public class DataModelValue {
case TIMESTAMP:
case ENTRY_TIME:
try {
{
String modValueString = value.replace(',', '.');
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss.SSS");
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss.SSS");
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = parser.parse(modValueString);
Date date = parser.parse(modValueString);
this.value = new Long(date.toInstant().toEpochMilli());
break;
}
catch (java.text.ParseException e) {};
try {
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss");
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = parser.parse(modValueString);
this.value = new Long(date.toInstant().toEpochMilli());
}
catch (java.text.ParseException e) {
this.value = new Long(date.toInstant().toEpochMilli());
break;
}
catch (java.text.ParseException e) {};
this.value = null;
System.out.println("Warning: Val element does not contain a valid time stamp: " + e.getMessage());
System.out.println("Warning: Val element does not contain a valid time stamp: " + value);
}
break;

@ -16,9 +16,8 @@ public class SampledValueControl {
private int nofASDU;
private boolean multicast = false;
private SmvOpts smvOpts;
private SmpMod smpMod = SmpMod.SMP_PER_PERIOD;
public SampledValueControl(Node smvControlNode) throws SclParserException {
this.name = ParserUtils.parseAttribute(smvControlNode, "name");
this.desc = ParserUtils.parseAttribute(smvControlNode, "desc");
@ -49,10 +48,25 @@ public class SampledValueControl {
Node smvOptsNode = ParserUtils.getChildNodeWithTag(smvControlNode, "SmvOpts");
this.smvOpts = new SmvOpts(smvOptsNode);
String smpModString = ParserUtils.parseAttribute(smvControlNode, "smpMod");
if (smpModString != null) {
if (smpModString.equals("SmpPerPeriod")) {
smpMod = SmpMod.SMP_PER_PERIOD;
}
else if (smpModString.equals("SmpPerSec")) {
smpMod = SmpMod.SMP_PER_SECOND;
}
else if (smpModString.equals("SecPerSmp")) {
smpMod = SmpMod.SEC_PER_SMP;
}
else {
throw new SclParserException(smvControlNode, "Invalid smpMod value " + smpModString);
}
}
}
public String getName() {
return name;
}
@ -88,5 +102,8 @@ public class SampledValueControl {
public SmvOpts getSmvOpts() {
return smvOpts;
}
public SmpMod getSmpMod() {
return smpMod;
}
}

@ -0,0 +1,56 @@
package com.libiec61850.scl.model;
/*
* Copyright 2023 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
public enum SmpMod
{
SMP_PER_PERIOD(0),
SMP_PER_SECOND(1),
SEC_PER_SMP(2);
private int value;
private SmpMod(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public boolean compare(int i)
{
return (value == i);
}
public static SmpMod fromValue(int val)
{
SmpMod[] errors = SmpMod.values();
for (int i = 0; i < errors.length; i++) {
if (errors[i].compare(val))
return errors[i];
}
return SmpMod.SMP_PER_PERIOD;
}
}

@ -3,7 +3,7 @@ package com.libiec61850.tools;
/*
* DynamicModelGenerator.java
*
* Copyright 2014-2020 Michael Zillgith
* Copyright 2014-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -37,6 +37,7 @@ import com.libiec61850.scl.SclParserException;
import com.libiec61850.scl.communication.ConnectedAP;
import com.libiec61850.scl.communication.GSE;
import com.libiec61850.scl.communication.PhyComAddress;
import com.libiec61850.scl.communication.SMV;
import com.libiec61850.scl.model.AccessPoint;
import com.libiec61850.scl.model.DataAttribute;
import com.libiec61850.scl.model.DataModelValue;
@ -51,6 +52,7 @@ import com.libiec61850.scl.model.LogicalDevice;
import com.libiec61850.scl.model.LogicalNode;
import com.libiec61850.scl.model.ReportControlBlock;
import com.libiec61850.scl.model.ReportSettings;
import com.libiec61850.scl.model.SampledValueControl;
import com.libiec61850.scl.model.Services;
import com.libiec61850.scl.model.SettingControl;
@ -59,6 +61,7 @@ public class DynamicModelGenerator {
private ConnectedAP connectedAP;
private IED ied = null;
private boolean hasOwner = false;
private List<ConnectedAP> connectedAPs;
public DynamicModelGenerator(InputStream stream, String icdFile, PrintStream output, String iedName, String accessPointName)
throws SclParserException {
@ -94,6 +97,8 @@ public class DynamicModelGenerator {
throw new SclParserException("No valid access point found!");
this.connectedAP = sclParser.getConnectedAP(ied, accessPoint.getName());
this.connectedAPs = sclParser.getConnectedAPs();
List<LogicalDevice> logicalDevices = accessPoint.getServer().getLogicalDevices();
@ -166,6 +171,64 @@ public class DynamicModelGenerator {
for (Log log : logicalNode.getLogs())
output.println("LOG(" + log.getName() + ");");
for (SampledValueControl svcb : logicalNode.getSampledValueControlBlocks()) {
LogicalDevice ld = logicalNode.getParentLogicalDevice();
SMV smv = null;
PhyComAddress smvAddress = null;
if (connectedAP != null) {
smv = connectedAP.lookupSMV(ld.getInst(), svcb.getName());
if (smv == null) {
for (ConnectedAP ap : connectedAPs) {
smv = ap.lookupSMV(ld.getInst(), svcb.getName());
if (smv != null)
break;
}
}
if (smv == null)
System.out.println("ConnectedAP not found for SMV");
if (smv != null)
smvAddress = smv.getAddress();
}
else
System.out.println("WARNING: IED \"" + ied.getName() + "\" has no connected access point!");
output.print("SMVC(");
output.print(svcb.getName() + " ");
output.print(svcb.getSmvID() + " ");
output.print(svcb.getDatSet() + " ");
output.print(svcb.getConfRev() + " ");
output.print(svcb.getSmpMod().getValue() + " ");
output.print(svcb.getSmpRate() + " ");
output.print(svcb.getSmvOpts().getIntValue() + " ");
output.print(svcb.isMulticast() ? "0" : "1");
output.print(")");
if (smvAddress != null) {
output.println("{");
output.print("PA(");
output.print(smvAddress.getVlanPriority() + " ");
output.print(smvAddress.getVlanId() + " ");
output.print(smvAddress.getAppId() + " ");
for (int i = 0; i < 6; i++)
output.printf("%02x", smvAddress.getMacAddress()[i]);
output.println(");");
output.println("}");
}
else {
output.println(";");
}
}
for (GSEControl gcb : logicalNode.getGSEControlBlocks()) {
LogicalDevice ld = logicalNode.getParentLogicalDevice();
@ -175,6 +238,18 @@ public class DynamicModelGenerator {
if (connectedAP != null) {
gse = connectedAP.lookupGSE(ld.getInst(), gcb.getName());
if (gse == null) {
for (ConnectedAP ap : connectedAPs) {
gse = ap.lookupGSE(ld.getInst(), gcb.getName());
if (gse != null)
break;
}
}
if (gse == null)
System.out.println("ConnectedAP not found for GSE");
if (gse != null)
gseAddress = gse.getAddress();
@ -372,11 +447,7 @@ public class DynamicModelGenerator {
output.println("}");
}
private void exportDataObject(PrintStream output, DataObject dataObject, boolean isTransient) {
if (dataObject.isTransient())
isTransient = true;
private void exportDataObjectChild(PrintStream output, DataObject dataObject, boolean isTransient) {
for (DataObject subDataObject : dataObject.getSubDataObjects()) {
output.print("DO(" + subDataObject.getName() + " " + subDataObject.getCount() + "){\n");
@ -390,6 +461,105 @@ public class DynamicModelGenerator {
}
}
private void exportDataObject(PrintStream output, DataObject dataObject, boolean isTransient) {
if (dataObject.isTransient())
isTransient = true;
if (dataObject.getCount() > 0) {
/* data object is an array */
for (int i = 0; i < dataObject.getCount(); i++) {
output.print("[" + i + "]{\n");
exportDataObjectChild(output, dataObject, isTransient);
output.print("}\n");
}
}
else {
exportDataObjectChild(output, dataObject, isTransient);
}
}
private void printDataAttributeValue(PrintStream output, DataAttribute dataAttribute, boolean isTransient)
{
if (dataAttribute.isBasicAttribute()) {
DataModelValue value = dataAttribute.getValue();
/* if no value is given use default value for type if present */
if (value == null) {
value = dataAttribute.getDefinition().getValue();
if (value != null)
if (value.getValue() == null)
value.updateEnumOrdValue(ied.getTypeDeclarations());
}
if (value != null) {
switch (dataAttribute.getType()) {
case ENUMERATED:
case INT8:
case INT16:
case INT32:
case INT64:
output.print("=" + value.getIntValue());
break;
case INT8U:
case INT16U:
case INT24U:
case INT32U:
output.print("=" + value.getLongValue());
break;
case BOOLEAN:
{
Boolean boolVal = (Boolean) value.getValue();
if (boolVal.booleanValue())
output.print("=1");
}
break;
case UNICODE_STRING_255:
output.print("=\"" + value.getValue()+ "\"");
break;
case CURRENCY:
case VISIBLE_STRING_32:
case VISIBLE_STRING_64:
case VISIBLE_STRING_129:
case VISIBLE_STRING_255:
case VISIBLE_STRING_65:
output.print("=\"" + value.getValue()+ "\"");
break;
case FLOAT32:
case FLOAT64:
output.print("=" + value.getValue());
break;
case TIMESTAMP:
case ENTRY_TIME:
output.print("=" + value.getLongValue());
break;
default:
System.out.println("Unknown default value for " + dataAttribute.getName() + " type: " + dataAttribute.getType());
break;
}
}
output.println(";");
}
else {
output.println("{");
for (DataAttribute subDataAttribute : dataAttribute.getSubDataAttributes()) {
exportDataAttribute(output, subDataAttribute, isTransient);
}
output.println("}");
}
}
private void exportDataAttribute(PrintStream output, DataAttribute dataAttribute, boolean isTransient) {
output.print("DA(" + dataAttribute.getName() + " ");
@ -418,78 +588,22 @@ public class DynamicModelGenerator {
else
output.print("0");
output.print(")");
if (dataAttribute.isBasicAttribute()) {
DataModelValue value = dataAttribute.getValue();
/* if no value is given use default value for type if present */
if (value == null) {
value = dataAttribute.getDefinition().getValue();
if (value != null)
if (value.getValue() == null)
value.updateEnumOrdValue(ied.getTypeDeclarations());
}
if (value != null) {
switch (dataAttribute.getType()) {
case ENUMERATED:
case INT8:
case INT16:
case INT32:
case INT64:
output.print("=" + value.getIntValue());
break;
case INT8U:
case INT16U:
case INT24U:
case INT32U:
output.print("=" + value.getLongValue());
break;
case BOOLEAN:
{
Boolean boolVal = (Boolean) value.getValue();
if (boolVal.booleanValue())
output.print("=1");
}
break;
case UNICODE_STRING_255:
output.print("=\"" + value.getValue()+ "\"");
break;
case CURRENCY:
case VISIBLE_STRING_32:
case VISIBLE_STRING_64:
case VISIBLE_STRING_129:
case VISIBLE_STRING_255:
case VISIBLE_STRING_65:
output.print("=\"" + value.getValue()+ "\"");
break;
case FLOAT32:
case FLOAT64:
output.print("=" + value.getValue());
break;
default:
System.out.println("Unknown default value for " + dataAttribute.getName() + " type: " + dataAttribute.getType());
break;
}
}
output.println(";");
}
else {
output.println("{");
output.print(")");
for (DataAttribute subDataAttribute : dataAttribute.getSubDataAttributes()) {
exportDataAttribute(output, subDataAttribute, isTransient);
if (dataAttribute.getCount() > 0) {
output.print("{\n");
for (int i = 0; i < dataAttribute.getCount(); i++) {
output.print("[" + i + "]");
printDataAttributeValue(output, dataAttribute, isTransient);
}
output.println("}");
output.print("}\n");
}
else {
printDataAttributeValue(output, dataAttribute, isTransient);
}
}
public static void main(String[] args) throws FileNotFoundException {

@ -1247,8 +1247,10 @@ public class StaticModelGenerator {
svString += "NULL, ";
svString += svCB.getSmvOpts().getIntValue() + ", ";
svString += svCB.getSmpMod().getValue() + ", ";
svString += "0, " + svCB.getSmpRate() + ", ";
svString += svCB.getSmpRate() + ", ";
svString += svCB.getConfRev() + ", ";

Loading…
Cancel
Save