Merge branch 'mz-automation:v1.5' into v1.5

pull/444/head
Nikunj Patel 2 years ago committed by GitHub
commit 516bc7890b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -1,7 +1,7 @@
/* /*
* IEC61850ClientAPI.cs * IEC61850ClientAPI.cs
* *
* Copyright 2014-2021 Michael Zillgith * Copyright 2014-2023 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -437,6 +437,9 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedConnection_connect(IntPtr self, out int error, string hostname, int tcpPort); static extern void IedConnection_connect(IntPtr self, out int error, string hostname, int tcpPort);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedConnection_setLocalAddress(IntPtr self, string localIpAddress, int localPort);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedConnection_abort(IntPtr self, out int error); static extern void IedConnection_abort(IntPtr self, out int error);
@ -894,6 +897,25 @@ namespace IEC61850
Connect(hostname, -1); Connect(hostname, -1);
} }
/// <summary>
/// Set the local IP address and port to be used by the client
/// </summary>
/// <param name="localIpAddress">the local IP address or hostname</param>
/// <param name="localPort">the local TCP port to use. When 0 the OS will chose the TCP port to use.</param>
public void SetLocalAddress(string localIpAddress, int localPort)
{
IedConnection_setLocalAddress(connection, localIpAddress, localPort);
}
/// <summary>
/// Set the local IP address to be used by the client
/// </summary>
/// <param name="localIpAddress">the local IP address or hostname</param>
public void SetLocalAddress(string localIpAddress)
{
IedConnection_setLocalAddress(connection, localIpAddress, 0);
}
/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception> /// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
public ControlObject CreateControlObject(string objectReference) public ControlObject CreateControlObject(string objectReference)
{ {

@ -1836,6 +1836,14 @@ namespace IEC61850
[return: MarshalAs(UnmanagedType.I1)] [return: MarshalAs(UnmanagedType.I1)]
static extern bool ControlAction_isSelect(IntPtr self); static extern bool ControlAction_isSelect(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
static extern bool ControlAction_getSynchroCheck(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
static extern bool ControlAction_getInterlockCheck(IntPtr self);
private IntPtr self; private IntPtr self;
private IedServer.ControlHandlerInfo info; private IedServer.ControlHandlerInfo info;
private IedServer iedServer; private IedServer iedServer;
@ -1955,6 +1963,16 @@ namespace IEC61850
{ {
return ControlAction_isSelect(self); return ControlAction_isSelect(self);
} }
public bool GetSynchroCheck()
{
return ControlAction_getSynchroCheck(self);
}
public bool GetInterlockCheck()
{
return ControlAction_getInterlockCheck(self);
}
} }
public delegate void GoCBEventHandler(MmsGooseControlBlock goCB, int cbEvent, object parameter); public delegate void GoCBEventHandler(MmsGooseControlBlock goCB, int cbEvent, object parameter);
@ -2408,6 +2426,9 @@ namespace IEC61850
/* store IedModel instance to prevent garbage collector */ /* store IedModel instance to prevent garbage collector */
private IedModel iedModel = null; private IedModel iedModel = null;
/* store TLSConfiguration instance to prevent garbage collector */
private TLSConfiguration tlsConfiguration = null;
public IedServer(IedModel iedModel, IedServerConfig config = null) public IedServer(IedModel iedModel, IedServerConfig config = null)
{ {
this.iedModel = iedModel; this.iedModel = iedModel;
@ -2423,6 +2444,7 @@ namespace IEC61850
public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null) public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null)
{ {
this.iedModel = iedModel; this.iedModel = iedModel;
this.tlsConfiguration = tlsConfig;
IntPtr nativeConfig = IntPtr.Zero; IntPtr nativeConfig = IntPtr.Zero;
IntPtr nativeTLSConfig = IntPtr.Zero; IntPtr nativeTLSConfig = IntPtr.Zero;
@ -2512,6 +2534,7 @@ namespace IEC61850
self = IntPtr.Zero; self = IntPtr.Zero;
internalConnectionHandler = null; internalConnectionHandler = null;
this.iedModel = null; this.iedModel = null;
this.tlsConfiguration = null;
} }
} }
} }

@ -33,6 +33,9 @@ int main(int argc, char** argv) {
char* hostname; char* hostname;
int tcpPort = 102; int tcpPort = 102;
const char* localIp = NULL;
int localTcpPort = -1;
if (argc > 1) if (argc > 1)
hostname = argv[1]; hostname = argv[1];
@ -42,19 +45,34 @@ int main(int argc, char** argv) {
if (argc > 2) if (argc > 2)
tcpPort = atoi(argv[2]); tcpPort = atoi(argv[2]);
if (argc > 3)
localIp = argv[3];
if (argc > 4)
localTcpPort = atoi(argv[4]);
IedClientError error; IedClientError error;
IedConnection con = IedConnection_create(); IedConnection con = IedConnection_create();
/* Optional bind to local IP address/interface */
if (localIp) {
IedConnection_setLocalAddress(con, localIp, localTcpPort);
printf("Bound to Local Address: %s:%i\n", localIp, localTcpPort);
}
IedConnection_connect(con, &error, hostname, tcpPort); IedConnection_connect(con, &error, hostname, tcpPort);
printf("Connecting to %s:%i\n", hostname, tcpPort);
if (error == IED_ERROR_OK) { if (error == IED_ERROR_OK)
{
printf("Connected\n");
/* read an analog measurement value from server */ /* read an analog measurement value from server */
MmsValue* value = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX); MmsValue* value = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX);
if (value != NULL) { if (value != NULL)
{
if (MmsValue_getType(value) == MMS_FLOAT) { if (MmsValue_getType(value) == MMS_FLOAT) {
float fval = MmsValue_toFloat(value); float fval = MmsValue_toFloat(value);
printf("read float value: %f\n", fval); printf("read float value: %f\n", fval);
@ -137,7 +155,6 @@ close_connection:
} }
IedConnection_destroy(con); IedConnection_destroy(con);
return 0; return 0;
} }

@ -45,8 +45,10 @@ int main(int argc, char** argv) {
IedConnection_connect(con, &error, hostname, tcpPort); IedConnection_connect(con, &error, hostname, tcpPort);
if (error == IED_ERROR_OK) { if (error == IED_ERROR_OK)
{
MmsValue* ctlVal = NULL;
MmsValue* stVal = NULL;
/************************ /************************
* Direct control * Direct control
@ -55,7 +57,9 @@ int main(int argc, char** argv) {
ControlObjectClient control ControlObjectClient control
= ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO1", con); = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO1", con);
MmsValue* ctlVal = MmsValue_newBoolean(true); if (control)
{
ctlVal = MmsValue_newBoolean(true);
ControlObjectClient_setOrigin(control, NULL, 3); ControlObjectClient_setOrigin(control, NULL, 3);
@ -72,7 +76,7 @@ int main(int argc, char** argv) {
/* Check if status value has changed */ /* Check if status value has changed */
MmsValue* stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO1.stVal", IEC61850_FC_ST); stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO1.stVal", IEC61850_FC_ST);
if (error == IED_ERROR_OK) { if (error == IED_ERROR_OK) {
bool state = MmsValue_getBoolean(stVal); bool state = MmsValue_getBoolean(stVal);
@ -84,6 +88,10 @@ int main(int argc, char** argv) {
printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO1 failed!\n"); printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO1 failed!\n");
} }
}
else {
printf("Control object simpleIOGenericIO/GGIO1.SPCSO1 not found in server\n");
}
/************************ /************************
* Select before operate * Select before operate
@ -91,6 +99,8 @@ int main(int argc, char** argv) {
control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO2", con); control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO2", con);
if (control)
{
if (ControlObjectClient_select(control)) { if (ControlObjectClient_select(control)) {
ctlVal = MmsValue_newBoolean(true); ctlVal = MmsValue_newBoolean(true);
@ -109,7 +119,10 @@ int main(int argc, char** argv) {
} }
ControlObjectClient_destroy(control); ControlObjectClient_destroy(control);
}
else {
printf("Control object simpleIOGenericIO/GGIO1.SPCSO2 not found in server\n");
}
/**************************************** /****************************************
* Direct control with enhanced security * Direct control with enhanced security
@ -117,6 +130,8 @@ int main(int argc, char** argv) {
control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO3", con); control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO3", con);
if (control)
{
ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL);
ctlVal = MmsValue_newBoolean(true); ctlVal = MmsValue_newBoolean(true);
@ -149,6 +164,10 @@ int main(int argc, char** argv) {
else { else {
printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO3 failed!\n"); printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO3 failed!\n");
} }
}
else {
printf("Control object simpleIOGenericIO/GGIO1.SPCSO3 not found in server\n");
}
/*********************************************** /***********************************************
* Select before operate with enhanced security * Select before operate with enhanced security
@ -156,6 +175,8 @@ int main(int argc, char** argv) {
control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO4", con); control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO4", con);
if (control)
{
ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL);
ctlVal = MmsValue_newBoolean(true); ctlVal = MmsValue_newBoolean(true);
@ -180,7 +201,10 @@ int main(int argc, char** argv) {
Thread_sleep(1000); Thread_sleep(1000);
ControlObjectClient_destroy(control); ControlObjectClient_destroy(control);
}
else {
printf("Control object simpleIOGenericIO/GGIO1.SPCSO4 not found in server\n");
}
/********************************************************************* /*********************************************************************
* Direct control with enhanced security (expect CommandTermination-) * Direct control with enhanced security (expect CommandTermination-)
@ -188,6 +212,8 @@ int main(int argc, char** argv) {
control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO9", con); control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO9", con);
if (control)
{
ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL);
ctlVal = MmsValue_newBoolean(true); ctlVal = MmsValue_newBoolean(true);
@ -205,7 +231,10 @@ int main(int argc, char** argv) {
Thread_sleep(1000); Thread_sleep(1000);
ControlObjectClient_destroy(control); ControlObjectClient_destroy(control);
}
else {
printf("Control object simpleIOGenericIO/GGIO1.SPCSO9 not found in server\n");
}
IedConnection_close(con); IedConnection_close(con);
} }

@ -101,6 +101,7 @@ printRawMmsMessage(void* parameter, uint8_t* message, int messageLength, bool re
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
int returnCode = 0;
char* hostname = StringUtils_copyString("localhost"); char* hostname = StringUtils_copyString("localhost");
int tcpPort = 102; int tcpPort = 102;
@ -213,6 +214,10 @@ int main(int argc, char** argv)
if (!MmsConnection_connect(con, &error, hostname, tcpPort)) { if (!MmsConnection_connect(con, &error, hostname, tcpPort)) {
printf("MMS connect failed!\n"); printf("MMS connect failed!\n");
if (error != MMS_ERROR_NONE)
returnCode = error;
goto exit; goto exit;
} }
else else
@ -222,6 +227,9 @@ int main(int argc, char** argv)
MmsServerIdentity* identity = MmsServerIdentity* identity =
MmsConnection_identify(con, &error); MmsConnection_identify(con, &error);
if (error != MMS_ERROR_NONE)
returnCode = error;
if (identity != NULL) { if (identity != NULL) {
printf("\nServer identity:\n----------------\n"); printf("\nServer identity:\n----------------\n");
printf(" vendor:\t%s\n", identity->vendorName); printf(" vendor:\t%s\n", identity->vendorName);
@ -235,14 +243,23 @@ int main(int argc, char** argv)
if (readDeviceList) { if (readDeviceList) {
printf("\nDomains present on server:\n--------------------------\n"); printf("\nDomains present on server:\n--------------------------\n");
LinkedList nameList = MmsConnection_getDomainNames(con, &error); LinkedList nameList = MmsConnection_getDomainNames(con, &error);
if (error != MMS_ERROR_NONE)
returnCode = error;
if (nameList) {
LinkedList_printStringList(nameList); LinkedList_printStringList(nameList);
LinkedList_destroy(nameList); LinkedList_destroy(nameList);
} }
}
if (getDeviceDirectory) { if (getDeviceDirectory) {
LinkedList variableList = MmsConnection_getDomainVariableNames(con, &error, LinkedList variableList = MmsConnection_getDomainVariableNames(con, &error,
domainName); domainName);
if (error != MMS_ERROR_NONE)
returnCode = error;
if (variableList) { if (variableList) {
LinkedList element = LinkedList_getNext(variableList); LinkedList element = LinkedList_getNext(variableList);
@ -264,6 +281,9 @@ int main(int argc, char** argv)
variableList = MmsConnection_getDomainJournals(con, &error, domainName); variableList = MmsConnection_getDomainJournals(con, &error, domainName);
if (error != MMS_ERROR_NONE)
returnCode = error;
if (variableList) { if (variableList) {
LinkedList element = variableList; LinkedList element = variableList;
@ -309,6 +329,9 @@ int main(int argc, char** argv)
LinkedList journalEntries = MmsConnection_readJournalTimeRange(con, &error, logDomain, logName, startTime, endTime, LinkedList journalEntries = MmsConnection_readJournalTimeRange(con, &error, logDomain, logName, startTime, endTime,
&moreFollows); &moreFollows);
if (error != MMS_ERROR_NONE)
returnCode = error;
MmsValue_delete(startTime); MmsValue_delete(startTime);
MmsValue_delete(endTime); MmsValue_delete(endTime);
@ -375,6 +398,8 @@ int main(int argc, char** argv)
if (error != MMS_ERROR_NONE) { if (error != MMS_ERROR_NONE) {
printf("Reading variable failed: (ERROR %i)\n", error); printf("Reading variable failed: (ERROR %i)\n", error);
returnCode = error;
} }
else { else {
printf("Read SUCCESS\n"); printf("Read SUCCESS\n");
@ -403,6 +428,8 @@ int main(int argc, char** argv)
if (error != MMS_ERROR_NONE) { if (error != MMS_ERROR_NONE) {
printf("Reading variable failed: (ERROR %i)\n", error); printf("Reading variable failed: (ERROR %i)\n", error);
returnCode = error;
} }
else { else {
printf("Read SUCCESS\n"); printf("Read SUCCESS\n");
@ -421,6 +448,8 @@ int main(int argc, char** argv)
if (error != MMS_ERROR_NONE) { if (error != MMS_ERROR_NONE) {
printf("Reading variable list directory failed: (ERROR %i)\n", error); printf("Reading variable list directory failed: (ERROR %i)\n", error);
returnCode = error;
} }
else { else {
LinkedList varListElem = LinkedList_getNext(varListDir); LinkedList varListElem = LinkedList_getNext(varListDir);
@ -454,12 +483,19 @@ int main(int argc, char** argv)
char* continueAfter = NULL; char* continueAfter = NULL;
while (MmsConnection_getFileDirectory(con, &error, "", continueAfter, mmsFileDirectoryHandler, lastName)) { while (MmsConnection_getFileDirectory(con, &error, "", continueAfter, mmsFileDirectoryHandler, lastName)) {
if (error != MMS_ERROR_NONE)
returnCode = error;
continueAfter = lastName; continueAfter = lastName;
} }
} }
if (getFileAttributes) { if (getFileAttributes) {
MmsConnection_getFileDirectory(con, &error, filename, NULL, mmsGetFileAttributeHandler, NULL); MmsConnection_getFileDirectory(con, &error, filename, NULL, mmsGetFileAttributeHandler, NULL);
if (error != MMS_ERROR_NONE)
returnCode = error;
} }
if (deleteFile) { if (deleteFile) {
@ -467,13 +503,14 @@ int main(int argc, char** argv)
if (error != MMS_ERROR_NONE) { if (error != MMS_ERROR_NONE) {
printf("Delete file failed: (ERROR %i)\n", error); printf("Delete file failed: (ERROR %i)\n", error);
returnCode = error;
} }
else { else {
printf("File deleted\n"); printf("File deleted\n");
} }
} }
exit: exit:
free(hostname); free(hostname);
free(domainName); free(domainName);
free(variableName); free(variableName);
@ -482,6 +519,6 @@ int main(int argc, char** argv)
MmsConnection_destroy(con); MmsConnection_destroy(con);
return 0; return returnCode;
} }

@ -263,6 +263,8 @@ void
Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress) Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress)
{ {
struct packet_mreq mreq; struct packet_mreq mreq;
memset(&mreq, 0, sizeof(struct packet_mreq));
mreq.mr_ifindex = ethSocket->socketAddress.sll_ifindex; mreq.mr_ifindex = ethSocket->socketAddress.sll_ifindex;
mreq.mr_alen = ETH_ALEN; mreq.mr_alen = ETH_ALEN;
mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_type = PACKET_MR_MULTICAST;

@ -194,6 +194,9 @@ StringUtils_copyStringMax(char* dest, int maxBufferSize, const char* str1)
{ {
char* res = dest; char* res = dest;
if (maxBufferSize < 1)
return NULL;
if (dest == NULL) if (dest == NULL)
res = (char*)GLOBAL_MALLOC(maxBufferSize); res = (char*)GLOBAL_MALLOC(maxBufferSize);

@ -210,6 +210,14 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
case 0x84: /* BIT STRING */ case 0x84: /* BIT STRING */
if (MmsValue_getType(value) == MMS_BIT_STRING) { if (MmsValue_getType(value) == MMS_BIT_STRING) {
int padding = buffer[bufPos]; int padding = buffer[bufPos];
if (padding > 7) {
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: invalid bit-string (padding not plausible)\n");
pe = GOOSE_PARSE_ERROR_INVALID_PADDING;
}
else {
int bitStringLength = (8 * (elementLength - 1)) - padding; 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, memcpy(value->value.bitString.buf, buffer + bufPos + 1,
@ -219,6 +227,7 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
pe = GOOSE_PARSE_ERROR_LENGTH_MISMATCH; pe = GOOSE_PARSE_ERROR_LENGTH_MISMATCH;
} }
} }
}
else { else {
pe = GOOSE_PARSE_ERROR_TYPE_MISMATCH; pe = GOOSE_PARSE_ERROR_TYPE_MISMATCH;
} }
@ -352,7 +361,7 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
break; break;
} }
if ( pe != GOOSE_PARSE_ERROR_NO_ERROR ) { if (pe != GOOSE_PARSE_ERROR_NO_ERROR) {
break; /* from while */ break; /* from while */
} }
@ -362,11 +371,13 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
} }
if (elementIndex <= maxIndex) { if (elementIndex <= maxIndex) {
if (pe == GOOSE_PARSE_ERROR_NO_ERROR) {
pe = GOOSE_PARSE_ERROR_UNDERFLOW; pe = GOOSE_PARSE_ERROR_UNDERFLOW;
} }
}
if (DEBUG_GOOSE_SUBSCRIBER) { if (DEBUG_GOOSE_SUBSCRIBER) {
switch ( pe ) { switch (pe) {
case GOOSE_PARSE_ERROR_UNKNOWN_TAG: case GOOSE_PARSE_ERROR_UNKNOWN_TAG:
printf("GOOSE_SUBSCRIBER: Found unkown tag %02x!\n", tag); printf("GOOSE_SUBSCRIBER: Found unkown tag %02x!\n", tag);
break; break;
@ -388,6 +399,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues)
case GOOSE_PARSE_ERROR_LENGTH_MISMATCH: case GOOSE_PARSE_ERROR_LENGTH_MISMATCH:
printf("GOOSE_SUBSCRIBER: Message contains value of wrong length!\n"); printf("GOOSE_SUBSCRIBER: Message contains value of wrong length!\n");
break; break;
case GOOSE_PARSE_ERROR_INVALID_PADDING:
printf("GOOSE_SUBSCRIBER: Malformed message: invalid padding!\n");
default: default:
break; break;
} }
@ -500,7 +513,16 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt
case 0x83: /* boolean */ case 0x83: /* boolean */
if (DEBUG_GOOSE_SUBSCRIBER) if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: found boolean\n"); printf("GOOSE_SUBSCRIBER: found boolean\n");
if (elementLength > 0) {
value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos));
}
else {
if (DEBUG_GOOSE_SUBSCRIBER)
printf("GOOSE_SUBSCRIBER: invalid length for boolean\n");
goto exit_with_error;
}
break; break;

@ -47,6 +47,7 @@ typedef enum
GOOSE_PARSE_ERROR_UNDERFLOW, GOOSE_PARSE_ERROR_UNDERFLOW,
GOOSE_PARSE_ERROR_TYPE_MISMATCH, GOOSE_PARSE_ERROR_TYPE_MISMATCH,
GOOSE_PARSE_ERROR_LENGTH_MISMATCH, GOOSE_PARSE_ERROR_LENGTH_MISMATCH,
GOOSE_PARSE_ERROR_INVALID_PADDING
} GooseParseError; } GooseParseError;
typedef struct sGooseSubscriber* GooseSubscriber; typedef struct sGooseSubscriber* GooseSubscriber;

@ -651,6 +651,15 @@ IedConnection_tick(IedConnection self)
return MmsConnection_tick(self->connection); return MmsConnection_tick(self->connection);
} }
void
IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, int localPort)
{
MmsConnection connection = self->connection;
IsoConnectionParameters isoP = MmsConnection_getIsoConnectionParameters(connection);
IsoConnectionParameters_setLocalTcpParameters(isoP, localIpAddress, localPort);
}
void void
IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs) IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs)
{ {

@ -231,6 +231,17 @@ IedConnection_createWithTlsSupport(TLSConfiguration tlsConfig);
LIB61850_API void LIB61850_API void
IedConnection_destroy(IedConnection self); IedConnection_destroy(IedConnection self);
/**
* \brief Set the local IP address and port to be used by the client
*
* NOTE: This function is optional. When not used the OS decides what IP address and TCP port to use.
*
* \param self IedConnection instance
* \param localIpAddress the local IP address or hostname as C string
* \param localPort the local TCP port to use. When < 1 the OS will chose the TCP port to use.
*/
LIB61850_API void
IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, int localPort);
/** /**
* \brief set the connect timeout in ms * \brief set the connect timeout in ms

@ -3,7 +3,7 @@
* *
* IEC 61850 server API for libiec61850. * IEC 61850 server API for libiec61850.
* *
* Copyright 2013-2022 Michael Zillgith * Copyright 2013-2023 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -42,6 +42,13 @@ extern "C" {
#include "iso_connection_parameters.h" #include "iso_connection_parameters.h"
#include "iec61850_config_file_parser.h" #include "iec61850_config_file_parser.h"
#define IEC61850_REPORTSETTINGS_RPT_ID 1
#define IEC61850_REPORTSETTINGS_BUF_TIME 2
#define IEC61850_REPORTSETTINGS_DATSET 4
#define IEC61850_REPORTSETTINGS_TRG_OPS 8
#define IEC61850_REPORTSETTINGS_OPT_FIELDS 16
#define IEC61850_REPORTSETTINGS_INTG_PD 32
/** /**
* \brief Configuration object to configure IEC 61850 stack features * \brief Configuration object to configure IEC 61850 stack features
*/ */
@ -99,6 +106,9 @@ struct sIedServerConfig
/** integrity report start times will by synchronized with straight numbers (default: false) */ /** integrity report start times will by synchronized with straight numbers (default: false) */
bool syncIntegrityReportTimes; bool syncIntegrityReportTimes;
/** for each configurable ReportSetting there is a separate flag (default: Dyn = enable write for all) */
uint8_t reportSettingsWritable;
}; };
/** /**
@ -388,6 +398,27 @@ IedServerConfig_useIntegratedGoosePublisher(IedServerConfig self, bool enable);
LIB61850_API bool LIB61850_API bool
IedServerConfig_isLogServiceEnabled(IedServerConfig self); IedServerConfig_isLogServiceEnabled(IedServerConfig self);
/**
* \brief Make a configurable report setting writeable or read-only
*
* \note Can be used to implement some of Services\ReportSettings options
*
* \param[in] setting one of IEC61850_REPORTSETTINGS_RPT_ID, _BUF_TIME, _DATSET, _TRG_OPS, _OPT_FIELDS, _INTG_PD
* \param[in] isDyn true, when setting is writable ("Dyn") or false, when read-only
*/
LIB61850_API void
IedServerConfig_setReportSetting(IedServerConfig self, uint8_t setting, bool isDyn);
/**
* \brief Check if a configurable report setting is writable or read-only
*
* \param[in] setting one of IEC61850_REPORTSETTINGS_RPT_ID, _BUF_TIME, _DATSET, _TRG_OPS, _OPT_FIELDS, _INTG_PD
*
* \return isDyn true, when setting is writable ("Dyn") or false, when read-only
*/
LIB61850_API bool
IedServerConfig_getReportSetting(IedServerConfig self, uint8_t setting);
/** /**
* An opaque handle for an IED server instance * An opaque handle for an IED server instance
*/ */
@ -1357,6 +1388,26 @@ ControlAction_getOrIdent(ControlAction self, int* orIdentSize);
LIB61850_API int LIB61850_API int
ControlAction_getCtlNum(ControlAction self); ControlAction_getCtlNum(ControlAction self);
/**
* \brief Gets the synchroCheck bit provided by the client
*
* \param self the control action instance
*
* \return the synchroCheck bit
*/
LIB61850_API bool
ControlAction_getSynchroCheck(ControlAction self);
/**
* \brief Gets the interlockCheck bit provided by the client
*
* \param self the control action instance
*
* \return the interlockCheck bit
*/
LIB61850_API bool
ControlAction_getInterlockCheck(ControlAction self);
/** /**
* \brief Check if the control callback is called by a select or operate command * \brief Check if the control callback is called by a select or operate command
* *

@ -3,7 +3,7 @@
* *
* Library private function definitions for IedServer. * Library private function definitions for IedServer.
* *
* Copyright 2013-2018 Michael Zillgith * Copyright 2013-2023 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -50,6 +50,7 @@ struct sIedServer
bool enableBRCBResvTms; bool enableBRCBResvTms;
bool enableOwnerForRCB; bool enableOwnerForRCB;
bool syncIntegrityReportTimes; bool syncIntegrityReportTimes;
uint8_t rcbSettingsWritable;
#endif #endif
#if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_THREADLESS_STACK != 1)

@ -325,7 +325,10 @@ struct sMmsMapping {
/* flag indicates if data model is locked --> prevents reports to be sent */ /* flag indicates if data model is locked --> prevents reports to be sent */
bool isModelLocked; bool isModelLocked;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore isModelLockedMutex; Semaphore isModelLockedMutex;
#endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */
IedServer iedServer; IedServer iedServer;

@ -483,12 +483,19 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio
self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB; self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB;
self->enableOwnerForRCB = serverConfiguration->enableOwnerForRCB; self->enableOwnerForRCB = serverConfiguration->enableOwnerForRCB;
self->syncIntegrityReportTimes = serverConfiguration->syncIntegrityReportTimes; self->syncIntegrityReportTimes = serverConfiguration->syncIntegrityReportTimes;
self->rcbSettingsWritable = serverConfiguration->reportSettingsWritable;
} }
else { else {
self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE;
self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE;
self->enableOwnerForRCB = false; self->enableOwnerForRCB = false;
self->syncIntegrityReportTimes = false; self->syncIntegrityReportTimes = false;
self->rcbSettingsWritable = IEC61850_REPORTSETTINGS_RPT_ID +
IEC61850_REPORTSETTINGS_BUF_TIME +
IEC61850_REPORTSETTINGS_DATSET +
IEC61850_REPORTSETTINGS_TRG_OPS +
IEC61850_REPORTSETTINGS_OPT_FIELDS +
IEC61850_REPORTSETTINGS_INTG_PD;
#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1)
self->enableBRCBResvTms = true; self->enableBRCBResvTms = true;
#else #else
@ -814,11 +821,15 @@ IedServer_lockDataModel(IedServer self)
{ {
MmsServer_lockModel(self->mmsServer); MmsServer_lockModel(self->mmsServer);
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(self->mmsMapping->isModelLockedMutex); Semaphore_wait(self->mmsMapping->isModelLockedMutex);
#endif
self->mmsMapping->isModelLocked = true; self->mmsMapping->isModelLocked = true;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_post(self->mmsMapping->isModelLockedMutex); Semaphore_post(self->mmsMapping->isModelLockedMutex);
#endif
} }
void void
@ -832,13 +843,17 @@ IedServer_unlockDataModel(IedServer self)
/* check if reports have to be sent! */ /* check if reports have to be sent! */
Reporting_processReportEventsAfterUnlock(self->mmsMapping); Reporting_processReportEventsAfterUnlock(self->mmsMapping);
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(self->mmsMapping->isModelLockedMutex); Semaphore_wait(self->mmsMapping->isModelLockedMutex);
#endif
MmsServer_unlockModel(self->mmsServer); MmsServer_unlockModel(self->mmsServer);
self->mmsMapping->isModelLocked = false; self->mmsMapping->isModelLocked = false;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_post(self->mmsMapping->isModelLockedMutex); Semaphore_post(self->mmsMapping->isModelLockedMutex);
#endif
} }
#if (CONFIG_IEC61850_CONTROL_SERVICE == 1) #if (CONFIG_IEC61850_CONTROL_SERVICE == 1)

@ -1,7 +1,7 @@
/* /*
* ied_server_config.c * ied_server_config.c
* *
* Copyright 2018-2022 Michael Zillgith * Copyright 2018-2023 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -59,6 +59,12 @@ IedServerConfig_create()
self->enableResvTmsForBRCB = true; self->enableResvTmsForBRCB = true;
self->enableOwnerForRCB = false; self->enableOwnerForRCB = false;
self->syncIntegrityReportTimes = false; self->syncIntegrityReportTimes = false;
self->reportSettingsWritable = IEC61850_REPORTSETTINGS_RPT_ID +
IEC61850_REPORTSETTINGS_BUF_TIME +
IEC61850_REPORTSETTINGS_DATSET +
IEC61850_REPORTSETTINGS_TRG_OPS +
IEC61850_REPORTSETTINGS_OPT_FIELDS +
IEC61850_REPORTSETTINGS_INTG_PD;
} }
return self; return self;
@ -264,3 +270,34 @@ IedServerConfig_getSyncIntegrityReportTimes(IedServerConfig self)
{ {
return self->syncIntegrityReportTimes; return self->syncIntegrityReportTimes;
} }
static void
configureSetting(IedServerConfig self, uint8_t flags, uint8_t setting, bool value)
{
if (flags & setting)
{
if (value) {
self->reportSettingsWritable |= setting;
}
else {
self->reportSettingsWritable &= ~setting;
}
}
}
void
IedServerConfig_setReportSetting(IedServerConfig self, uint8_t setting, bool isDyn)
{
configureSetting(self, setting, IEC61850_REPORTSETTINGS_RPT_ID, isDyn);
configureSetting(self, setting, IEC61850_REPORTSETTINGS_BUF_TIME, isDyn);
configureSetting(self, setting, IEC61850_REPORTSETTINGS_DATSET, isDyn);
configureSetting(self, setting, IEC61850_REPORTSETTINGS_TRG_OPS, isDyn);
configureSetting(self, setting, IEC61850_REPORTSETTINGS_OPT_FIELDS, isDyn);
configureSetting(self, setting, IEC61850_REPORTSETTINGS_INTG_PD, isDyn);
}
bool
IedServerConfig_getReportSetting(IedServerConfig self, uint8_t setting)
{
return (self->reportSettingsWritable & setting);
}

@ -2498,6 +2498,22 @@ ControlAction_getCtlNum(ControlAction self)
return -1; return -1;
} }
bool
ControlAction_getSynchroCheck(ControlAction self)
{
ControlObject* controlObject = (ControlObject*) self;
return (bool)(controlObject->synchroCheck);
}
bool
ControlAction_getInterlockCheck(ControlAction self)
{
ControlObject* controlObject = (ControlObject*) self;
return (bool)(controlObject->interlockCheck);
}
bool bool
ControlAction_isSelect(ControlAction self) ControlAction_isSelect(ControlAction self)
{ {

@ -3670,7 +3670,9 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag)
{ {
LinkedList element = self->reportControls; LinkedList element = self->reportControls;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(self->isModelLockedMutex); Semaphore_wait(self->isModelLockedMutex);
#endif
bool modelLocked = self->isModelLocked; bool modelLocked = self->isModelLocked;
@ -3686,8 +3688,7 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag)
continue; continue;
break; break;
case REPORT_CONTROL_VALUE_CHANGED: case REPORT_CONTROL_VALUE_CHANGED:
if (((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) && if ((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0)
((rc->triggerOps & TRG_OPT_DATA_UPDATE) == 0))
continue; continue;
break; break;
case REPORT_CONTROL_QUALITY_CHANGED: case REPORT_CONTROL_QUALITY_CHANGED:
@ -3708,7 +3709,9 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag)
Reporting_processReportEventsAfterUnlock(self); Reporting_processReportEventsAfterUnlock(self);
} }
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_post(self->isModelLockedMutex); Semaphore_post(self->isModelLockedMutex);
#endif
} }
#endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */
@ -3729,13 +3732,17 @@ MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value)
if (DataSet_isMemberValue(dataSet, value, NULL)) { if (DataSet_isMemberValue(dataSet, value, NULL)) {
MmsGooseControlBlock_setStateChangePending(gcb); MmsGooseControlBlock_setStateChangePending(gcb);
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(self->isModelLockedMutex); Semaphore_wait(self->isModelLockedMutex);
#endif
if (self->isModelLocked == false) { if (self->isModelLocked == false) {
MmsGooseControlBlock_publishNewState(gcb); MmsGooseControlBlock_publishNewState(gcb);
} }
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_post(self->isModelLockedMutex); Semaphore_post(self->isModelLockedMutex);
#endif
} }
} }
} }

@ -1909,6 +1909,9 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme
if (updateReportDataset(self, rc, NULL, connection)) { if (updateReportDataset(self, rc, NULL, connection)) {
if (rc->reserved == false) { if (rc->reserved == false) {
rc->resvTms = RESV_TMS_IMPLICIT_VALUE;
reserveRcb(rc, connection); reserveRcb(rc, connection);
if (self->rcbEventHandler) { if (self->rcbEventHandler) {
@ -2088,6 +2091,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme
} }
else if (strcmp(elementName, "DatSet") == 0) { else if (strcmp(elementName, "DatSet") == 0) {
if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_DATSET))
{
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
goto exit_function;
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(rc->rcbValuesLock); Semaphore_wait(rc->rcbValuesLock);
#endif #endif
@ -2135,6 +2144,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme
} }
else if (strcmp(elementName, "IntgPd") == 0) { else if (strcmp(elementName, "IntgPd") == 0) {
if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_INTG_PD))
{
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
goto exit_function;
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(rc->rcbValuesLock); Semaphore_wait(rc->rcbValuesLock);
#endif #endif
@ -2182,6 +2197,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme
} }
else if (strcmp(elementName, "TrgOps") == 0) { else if (strcmp(elementName, "TrgOps") == 0) {
if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_TRG_OPS))
{
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
goto exit_function;
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(rc->rcbValuesLock); Semaphore_wait(rc->rcbValuesLock);
#endif #endif
@ -2255,6 +2276,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme
else if (strcmp(elementName, "BufTm") == 0) { else if (strcmp(elementName, "BufTm") == 0) {
if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_BUF_TIME))
{
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
goto exit_function;
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(rc->rcbValuesLock); Semaphore_wait(rc->rcbValuesLock);
#endif #endif
@ -2288,6 +2315,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme
} }
else if (strcmp(elementName, "RptID") == 0) { else if (strcmp(elementName, "RptID") == 0) {
if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_RPT_ID))
{
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
goto exit_function;
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(rc->rcbValuesLock); Semaphore_wait(rc->rcbValuesLock);
#endif #endif
@ -2395,6 +2428,14 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme
goto exit_function; goto exit_function;
} }
else if (strcmp(elementName, "OptFlds") == 0) {
if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_OPT_FIELDS))
{
retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
goto exit_function;
}
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(rc->rcbValuesLock); Semaphore_wait(rc->rcbValuesLock);
@ -2445,7 +2486,6 @@ exit_function:
} }
} }
} }
else if (rc->resvTms == -1) { else if (rc->resvTms == -1) {
if (rc->reserved == false) { if (rc->reserved == false) {
@ -3877,7 +3917,9 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
void void
Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs) Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs)
{ {
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(self->isModelLockedMutex); Semaphore_wait(self->isModelLockedMutex);
#endif
if (self->isModelLocked == false) { if (self->isModelLocked == false) {
@ -3894,7 +3936,9 @@ Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs)
} }
} }
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_post(self->isModelLockedMutex); Semaphore_post(self->isModelLockedMutex);
#endif
} }
/* /*

@ -145,6 +145,9 @@ struct sIsoConnectionParameters
const char* hostname; const char* hostname;
int tcpPort; int tcpPort;
const char* localIpAddress;
int localTcpPort;
uint8_t remoteApTitle[10]; uint8_t remoteApTitle[10];
int remoteApTitleLen; int remoteApTitleLen;
int remoteAEQualifier; int remoteAEQualifier;
@ -215,6 +218,20 @@ IsoConnectionParameters_setAcseAuthenticationParameter(IsoConnectionParameters s
LIB61850_API void LIB61850_API void
IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const char* hostname, int tcpPort); IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const char* hostname, int tcpPort);
/**
* \brief Set Local TCP parameters (FOR LIBRARY INTERNAL USE)
*
* NOTE: This function used internally by the MMS Client library. When using the MMS or IEC 61850 API
* there should be no reason for the user to call this function
*
* \param self the IsoConnectionParameters instance
* \param localIpAddress the hostname of local IP address of the server
* \param localTcpPort the local TCP port number of the server
*/
LIB61850_API void
IsoConnectionParameters_setLocalTcpParameters(IsoConnectionParameters self, const char* localIpAddress, int localTcpPort);
/** /**
* \brief set the remote AP-Title and AE-Qualifier * \brief set the remote AP-Title and AE-Qualifier
* *

@ -648,6 +648,23 @@ MmsConnection_writeVariableAsync(MmsConnection self, uint32_t* usedInvokeId, Mms
MmsConnection_WriteVariableHandler handler, void* parameter); MmsConnection_WriteVariableHandler handler, void* parameter);
/**
* \brief Write a single variable to the server (using component alternate access)
*
* \param self MmsConnection instance to operate on
* \param mmsError user provided variable to store error code
* \param domainId the domain name of the variable to be written
* \param itemId name of the variable to be written
* \param componentId the name of the variable component
* \param value value of the variable to be written
*
* \return when successful, the data access error value returned by the server
*/
LIB61850_API MmsDataAccessError
MmsConnection_writeVariableComponent(MmsConnection self, MmsError* mmsError,
const char* domainId, const char* itemId,
const char* componentId, MmsValue* value);
/** /**
* \brief Write a single array element with a component to an array type variable * \brief Write a single array element with a component to an array type variable
* *
@ -672,6 +689,11 @@ MmsConnection_writeSingleArrayElementWithComponentAsync(MmsConnection self, uint
uint32_t arrayIndex, const char* componentId, MmsValue* value, uint32_t arrayIndex, const char* componentId, MmsValue* value,
MmsConnection_WriteVariableHandler handler, void* parameter); MmsConnection_WriteVariableHandler handler, void* parameter);
LIB61850_API void
MmsConnection_writeVariableComponentAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError,
const char* domainId, const char* itemId, const char* componentId, MmsValue* value,
MmsConnection_WriteVariableHandler handler, void* parameter);
/** /**
* \brief Write a single array element or a sub array to an array type variable * \brief Write a single array element or a sub array to an array type variable
* *

@ -114,4 +114,7 @@ CotpConnection_getRemoteRef(CotpConnection* self);
LIB61850_INTERNAL int LIB61850_INTERNAL int
CotpConnection_getLocalRef(CotpConnection* self); CotpConnection_getLocalRef(CotpConnection* self);
LIB61850_INTERNAL void
CotpConnection_flushBuffer(CotpConnection* self);
#endif /* COTP_H_ */ #endif /* COTP_H_ */

@ -272,6 +272,11 @@ mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const
int startIndex, int elementCount, int startIndex, int elementCount,
MmsValue* value, ByteBuffer* writeBuffer); MmsValue* value, ByteBuffer* writeBuffer);
LIB61850_INTERNAL int
mmsClient_createWriteRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component,
MmsValue* value,
ByteBuffer* writeBuffer);
LIB61850_INTERNAL int LIB61850_INTERNAL int
mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId,
uint32_t arrayIndex, const char* component, uint32_t arrayIndex, const char* component,

@ -692,6 +692,11 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim
/* set timeout for connect */ /* set timeout for connect */
self->nextReadTimeout = Hal_getTimeInMs() + connectTimeoutInMs; self->nextReadTimeout = Hal_getTimeInMs() + connectTimeoutInMs;
/* Connect to Local Ip Address*/
if (self->parameters->localIpAddress) {
Socket_bind(self->socket, self->parameters->localIpAddress, self->parameters->localTcpPort);
}
if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) { if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) {
Socket_destroy(self->socket); Socket_destroy(self->socket);

@ -104,6 +104,18 @@ IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const cha
self->tcpPort = tcpPort; self->tcpPort = tcpPort;
} }
void
IsoConnectionParameters_setLocalTcpParameters(IsoConnectionParameters self, const char* localIpAddress, int localTcpPort)
{
if (self) {
if (localIpAddress) {
self->localIpAddress = strdup(localIpAddress);
self->localTcpPort = localTcpPort;
}
}
}
void void
IsoConnectionParameters_setRemoteApTitle(IsoConnectionParameters self, const char* apTitle, int aeQualifier) IsoConnectionParameters_setRemoteApTitle(IsoConnectionParameters self, const char* apTitle, int aeQualifier)
{ {

@ -5,7 +5,7 @@
* *
* Partial implementation of the ISO 8073 COTP (ISO TP0) protocol for MMS. * Partial implementation of the ISO 8073 COTP (ISO TP0) protocol for MMS.
* *
* Copyright 2013-2018 Michael Zillgith * Copyright 2013-2023 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -174,6 +174,38 @@ writeToSocket(CotpConnection* self, uint8_t* buf, int size)
#endif #endif
} }
static bool
flushBuffer(CotpConnection* self)
{
if (self->socketExtensionBufferFill > 0) {
int sentBytes = writeToSocket(self, self->socketExtensionBuffer, self->socketExtensionBufferFill);
if (sentBytes > 0) {
if (sentBytes != self->socketExtensionBufferFill) {
int target = 0;
int i;
uint8_t* buf = self->socketExtensionBuffer;
for (i = sentBytes; i < self->socketExtensionBufferFill; i++) {
buf[target++] = buf[i];
}
self->socketExtensionBufferFill = self->socketExtensionBufferFill - sentBytes;
}
else {
self->socketExtensionBufferFill = 0;
}
}
else if (sentBytes == -1) {
return false;
}
}
return true;
}
static bool static bool
sendBuffer(CotpConnection* self) sendBuffer(CotpConnection* self)
{ {
@ -182,7 +214,15 @@ sendBuffer(CotpConnection* self)
bool retVal = false; bool retVal = false;
int sentBytes = writeToSocket(self, buffer, remainingSize); if (flushBuffer(self) == false) {
goto exit_function;
}
int sentBytes = 0;
if (self->socketExtensionBufferFill == 0) {
sentBytes = writeToSocket(self, buffer, remainingSize);
}
if (sentBytes == -1) if (sentBytes == -1)
goto exit_function; goto exit_function;
@ -215,33 +255,6 @@ exit_function:
return retVal; return retVal;
} }
static void
flushBuffer(CotpConnection* self)
{
if (self->socketExtensionBufferFill > 0) {
int sentBytes = writeToSocket(self, self->socketExtensionBuffer, self->socketExtensionBufferFill);
if (sentBytes > 0) {
if (sentBytes != self->socketExtensionBufferFill) {
int target = 0;
int i;
uint8_t* buf = self->socketExtensionBuffer;
for (i = sentBytes; i < self->socketExtensionBufferFill; i++) {
buf[target++] = buf[i];
}
self->socketExtensionBufferFill = self->socketExtensionBufferFill - sentBytes;
}
else {
self->socketExtensionBufferFill = 0;
}
}
}
}
CotpIndication CotpIndication
CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload)
{ {
@ -262,7 +275,9 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload)
int totalSize = (fragments * (COTP_DATA_HEADER_SIZE + 4)) + payload->length; int totalSize = (fragments * (COTP_DATA_HEADER_SIZE + 4)) + payload->length;
/* try to flush extension buffer */ /* try to flush extension buffer */
flushBuffer(self); if (flushBuffer(self) == false) {
return COTP_ERROR;
}
/* check if totalSize will fit in extension buffer */ /* check if totalSize will fit in extension buffer */
if (self->socketExtensionBuffer) { if (self->socketExtensionBuffer) {
@ -281,7 +296,7 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload)
int currentChainIndex = 0; int currentChainIndex = 0;
if (DEBUG_COTP) if (DEBUG_COTP)
printf("\nCOTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); printf("COTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength);
uint8_t* buffer = self->writeBuffer->buffer; uint8_t* buffer = self->writeBuffer->buffer;
@ -307,7 +322,7 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload)
if (currentChainIndex >= currentChain->partLength) { if (currentChainIndex >= currentChain->partLength) {
currentChain = currentChain->nextPart; currentChain = currentChain->nextPart;
if (DEBUG_COTP) if (DEBUG_COTP)
printf("\nCOTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); printf("COTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength);
currentChainIndex = 0; currentChainIndex = 0;
} }
@ -756,6 +771,13 @@ readFromSocket(CotpConnection* self, uint8_t* buf, int size)
#endif #endif
} }
void
CotpConnection_flushBuffer(CotpConnection* self)
{
if (self->socketExtensionBufferFill > 0)
flushBuffer(self);
}
TpktState TpktState
CotpConnection_readToTpktBuffer(CotpConnection* self) CotpConnection_readToTpktBuffer(CotpConnection* self)
{ {
@ -765,6 +787,14 @@ CotpConnection_readToTpktBuffer(CotpConnection* self)
assert (bufferSize > 4); assert (bufferSize > 4);
if (self->socketExtensionBufferFill > 0) {
if (flushBuffer(self) == false)
goto exit_error;
if (self->socketExtensionBufferFill > 0)
goto exit_waiting;
}
int readBytes; int readBytes;
if (bufPos < 4) { if (bufPos < 4) {

@ -4384,6 +4384,69 @@ exit_function:
return; return;
} }
MmsDataAccessError
MmsConnection_writeVariableComponent(MmsConnection self, MmsError* mmsError,
const char* domainId, const char* itemId,
const char* componentId, MmsValue* value)
{
struct writeVariableParameters parameter;
MmsError err = MMS_ERROR_NONE;
parameter.waitForResponse = Semaphore_create(1);
parameter.err = MMS_ERROR_NONE;
parameter.accessError = DATA_ACCESS_ERROR_SUCCESS;
Semaphore_wait(parameter.waitForResponse);
MmsConnection_writeVariableComponentAsync(self, NULL, &err, domainId, itemId, componentId, value, writeVariableHandler, &parameter);
if (err == MMS_ERROR_NONE) {
Semaphore_wait(parameter.waitForResponse);
err = parameter.err;
}
Semaphore_destroy(parameter.waitForResponse);
if (mmsError)
*mmsError = err;
return parameter.accessError;
}
void
MmsConnection_writeVariableComponentAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError,
const char* domainId, const char* itemId, const char* componentId, MmsValue* value,
MmsConnection_WriteVariableHandler handler, void* parameter)
{
if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) {
if (mmsError)
*mmsError = MMS_ERROR_CONNECTION_LOST;
goto exit_function;
}
ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient);
uint32_t invokeId = getNextInvokeId(self);
if (usedInvokeId)
*usedInvokeId = invokeId;
mmsClient_createWriteRequestComponent(invokeId, domainId, itemId, componentId, value, payload);
MmsClientInternalParameter intParam;
intParam.ptr = NULL;
MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, intParam);
if (mmsError)
*mmsError = err;
exit_function:
return;
}
struct writeMultipleVariablesParameter struct writeMultipleVariablesParameter
{ {
Semaphore sem; Semaphore sem;

@ -529,6 +529,63 @@ mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const
return rval.encoded; return rval.encoded;
} }
int
mmsClient_createWriteRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component,
MmsValue* value,
ByteBuffer* writeBuffer)
{
MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId);
mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present =
ConfirmedServiceRequest_PR_write;
WriteRequest_t* request =
&(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write);
/* Create list of variable specifications */
request->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable;
request->variableAccessSpecification.choice.listOfVariable.list.count = 1;
request->variableAccessSpecification.choice.listOfVariable.list.array =
(ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*));
ListOfVariableSeq_t* variableIdentifier = createNewDomainVariableSpecification(domainId, itemId);
request->variableAccessSpecification.choice.listOfVariable.list.array[0] = variableIdentifier;
variableIdentifier->alternateAccess = mmsClient_createAlternateAccessComponent(component);
/* Create list of typed data values */
request->listOfData.list.count = 1;
request->listOfData.list.size = 1;
request->listOfData.list.array = (Data_t**) GLOBAL_CALLOC(1, sizeof(struct Data*));
request->listOfData.list.array[0] = mmsMsg_createBasicDataElement(value);
/* Encode complete ASN1 structure */
asn_enc_rval_t rval;
rval = der_encode(&asn_DEF_MmsPdu, mmsPdu,
(asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer);
/* Free ASN structure */
mmsClient_deleteAlternateAccess(variableIdentifier->alternateAccess);
request->variableAccessSpecification.choice.listOfVariable.list.count = 0;
GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array[0]);
GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array);
request->variableAccessSpecification.choice.listOfVariable.list.array = 0;
request->listOfData.list.count = 0;
deleteDataElement(request->listOfData.list.array[0]);
GLOBAL_FREEMEM(request->listOfData.list.array);
request->listOfData.list.array = 0;
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
return rval.encoded;
}
int int
mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId,
uint32_t arrayIndex, const char* component, uint32_t arrayIndex, const char* component,

@ -2205,7 +2205,7 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
const char* currentStr = MmsValue_printToBuffer((const MmsValue*) MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos); const char* currentStr = MmsValue_printToBuffer((const MmsValue*) MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos);
bufPos += strlen(currentStr); bufPos += strnlen(currentStr, bufferSize - bufPos);
if (bufPos >= bufferSize) if (bufPos >= bufferSize)
break; break;
@ -2240,8 +2240,12 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
int size = MmsValue_getBitStringSize(self); int size = MmsValue_getBitStringSize(self);
/* fill buffer with zeros */ /* fill buffer with zeros */
if (size > bufferSize) { if (size + 1 > bufferSize) {
memset(buffer, 0, bufferSize); memset(buffer, 0, bufferSize);
size = bufferSize - 1;
if (size < 1)
break; break;
} }

@ -479,6 +479,57 @@ handleWriteNamedVariableListRequest(
} }
static MmsVariableSpecification*
getComponent(MmsServerConnection connection, MmsDomain* domain, AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable, char* variableName)
{
MmsVariableSpecification* retValue = NULL;
if (mmsServer_isComponentAccess(alternateAccess)) {
Identifier_t component =
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component;
if (component.size > 129)
goto exit_function;
if (namedVariable->type == MMS_STRUCTURE) {
int i;
for (i = 0; i < namedVariable->typeSpec.structure.elementCount; i++) {
if ((int) strlen(namedVariable->typeSpec.structure.elements[i]->name)
== component.size) {
if (!strncmp(namedVariable->typeSpec.structure.elements[i]->name,
(char*) component.buf, component.size))
{
if (strlen(variableName) + component.size < 199) {
StringUtils_appendString(variableName, 200, "$");
/* here we need strncat because component.buf is not null terminated! */
strncat(variableName, (const char*)component.buf, (size_t)component.size);
if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess
!= NULL) {
retValue =
getComponent(connection, domain,
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess,
namedVariable->typeSpec.structure.elements[i],
variableName);
}
else {
retValue = namedVariable->typeSpec.structure.elements[i];
}
}
}
}
}
}
}
exit_function:
return retValue;
}
void void
mmsServer_handleWriteRequest( mmsServer_handleWriteRequest(
@ -604,15 +655,21 @@ mmsServer_handleWriteRequest(
AlternateAccess_t* alternateAccess = varSpec->alternateAccess; AlternateAccess_t* alternateAccess = varSpec->alternateAccess;
if (alternateAccess != NULL) { if (alternateAccess != NULL) {
if (variable->type != MMS_ARRAY) {
if ((variable->type == MMS_STRUCTURE) && (mmsServer_isComponentAccess(alternateAccess) == false)) {
accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT;
continue; continue;
} }
if (!mmsServer_isIndexAccess(alternateAccess)) { if ((variable->type == MMS_ARRAY) && (mmsServer_isIndexAccess(alternateAccess) == false)) {
accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED;
continue; continue;
} }
if (variable->type != MMS_ARRAY && variable->type != MMS_STRUCTURE) {
accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT;
continue;
}
} }
Data_t* dataElement = writeRequest->listOfData.list.array[i]; Data_t* dataElement = writeRequest->listOfData.list.array[i];
@ -629,6 +686,7 @@ mmsServer_handleWriteRequest(
if (domain == NULL) if (domain == NULL)
domain = (MmsDomain*) device; domain = (MmsDomain*) device;
if (mmsServer_isIndexAccess(alternateAccess)) {
MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr);
if (cachedArray == NULL) { if (cachedArray == NULL) {
@ -670,18 +728,30 @@ mmsServer_handleWriteRequest(
accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT;
goto end_of_main_loop; goto end_of_main_loop;
} }
else { /* select sub-array with start-index and number-of-elements */
if (MmsValue_update(elementValue, newElement) == false) { if (MmsValue_update(elementValue, newElement) == false) {
accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT;
goto end_of_main_loop; goto end_of_main_loop;
} }
}
} }
} }
accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; accessResults[i] = DATA_ACCESS_ERROR_SUCCESS;
goto end_of_main_loop; goto end_of_main_loop;
}
else if (mmsServer_isComponentAccess(alternateAccess)) {
variable = getComponent(connection, domain, alternateAccess, variable, nameIdStr);
if (variable == NULL) {
accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
goto end_of_main_loop;
}
}
else {
accessResults[i] = DATA_ACCESS_ERROR_SUCCESS;
goto end_of_main_loop;
}
} }
/* Check for correct type */ /* Check for correct type */

@ -1,7 +1,7 @@
/* /*
* iso_connection.c * iso_connection.c
* *
* Copyright 2013-2022 Michael Zillgith * Copyright 2013-2023 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -160,6 +160,8 @@ IsoConnection_removeFromHandleSet(const IsoConnection self, HandleSet handles)
void void
IsoConnection_callTickHandler(IsoConnection self) IsoConnection_callTickHandler(IsoConnection self)
{ {
CotpConnection_flushBuffer(self->cotpConnection);
if (self->tickHandler) { if (self->tickHandler) {
self->tickHandler(self->handlerParameter); self->tickHandler(self->handlerParameter);
} }
@ -171,10 +173,7 @@ IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread)
#if (CONFIG_MMS_SINGLE_THREADED != 1) #if (CONFIG_MMS_SINGLE_THREADED != 1)
if (isSingleThread == false) { if (isSingleThread == false) {
/* call tick handler */ IsoConnection_callTickHandler(self);
if (self->tickHandler) {
self->tickHandler(self->handlerParameter);
}
if (Handleset_waitReady(self->handleSet, 10) < 1) if (Handleset_waitReady(self->handleSet, 10) < 1)
goto exit_function; goto exit_function;

@ -455,7 +455,6 @@ exit_function:
return success; return success;
} }
/** used by single and multi-threaded versions /** used by single and multi-threaded versions
* *
* \param isSingleThread when true server is running in single thread or non-thread mode * \param isSingleThread when true server is running in single thread or non-thread mode

Loading…
Cancel
Save