diff --git a/dotnet/IEC61850forCSharp/Control.cs b/dotnet/IEC61850forCSharp/Control.cs index b3c2bcc9..97a3d767 100644 --- a/dotnet/IEC61850forCSharp/Control.cs +++ b/dotnet/IEC61850forCSharp/Control.cs @@ -775,7 +775,10 @@ namespace IEC61850 Dispose (true); } - + ~ControlObject() + { + Dispose (false); + } } } diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 4b45fe54..16d79a56 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -1,7 +1,7 @@ /* * IEC61850ClientAPI.cs * - * Copyright 2014-2021 Michael Zillgith + * Copyright 2014-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -437,6 +437,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_connect(IntPtr self, out int error, string hostname, int tcpPort); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_setLocalAddress(IntPtr self, string localIpAddress, int localPort); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_abort(IntPtr self, out int error); @@ -894,6 +897,25 @@ namespace IEC61850 Connect(hostname, -1); } + /// + /// Set the local IP address and port to be used by the client + /// + /// the local IP address or hostname + /// the local TCP port to use. When 0 the OS will chose the TCP port to use. + public void SetLocalAddress(string localIpAddress, int localPort) + { + IedConnection_setLocalAddress(connection, localIpAddress, localPort); + } + + /// + /// Set the local IP address to be used by the client + /// + /// the local IP address or hostname + public void SetLocalAddress(string localIpAddress) + { + IedConnection_setLocalAddress(connection, localIpAddress, 0); + } + /// This exception is thrown if there is a connection or service error public ControlObject CreateControlObject(string objectReference) { diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index c5c821cc..6e52a259 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -1836,6 +1836,14 @@ namespace IEC61850 [return: MarshalAs(UnmanagedType.I1)] static extern bool ControlAction_isSelect(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ControlAction_getSynchroCheck(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + static extern bool ControlAction_getInterlockCheck(IntPtr self); + private IntPtr self; private IedServer.ControlHandlerInfo info; private IedServer iedServer; @@ -1955,6 +1963,16 @@ namespace IEC61850 { return ControlAction_isSelect(self); } + + public bool GetSynchroCheck() + { + return ControlAction_getSynchroCheck(self); + } + + public bool GetInterlockCheck() + { + return ControlAction_getInterlockCheck(self); + } } public delegate void GoCBEventHandler(MmsGooseControlBlock goCB, int cbEvent, object parameter); @@ -2408,6 +2426,9 @@ namespace IEC61850 /* store IedModel instance to prevent garbage collector */ private IedModel iedModel = null; + /* store TLSConfiguration instance to prevent garbage collector */ + private TLSConfiguration tlsConfiguration = null; + public IedServer(IedModel iedModel, IedServerConfig config = null) { this.iedModel = iedModel; @@ -2423,6 +2444,7 @@ namespace IEC61850 public IedServer(IedModel iedModel, TLSConfiguration tlsConfig, IedServerConfig config = null) { this.iedModel = iedModel; + this.tlsConfiguration = tlsConfig; IntPtr nativeConfig = IntPtr.Zero; IntPtr nativeTLSConfig = IntPtr.Zero; @@ -2512,6 +2534,7 @@ namespace IEC61850 self = IntPtr.Zero; internalConnectionHandler = null; this.iedModel = null; + this.tlsConfiguration = null; } } } diff --git a/examples/iec61850_client_example1/client_example1.c b/examples/iec61850_client_example1/client_example1.c index f84d37a0..6c9241f0 100644 --- a/examples/iec61850_client_example1/client_example1.c +++ b/examples/iec61850_client_example1/client_example1.c @@ -33,6 +33,9 @@ int main(int argc, char** argv) { char* hostname; int tcpPort = 102; + const char* localIp = NULL; + int localTcpPort = -1; + if (argc > 1) hostname = argv[1]; @@ -42,19 +45,34 @@ int main(int argc, char** argv) { if (argc > 2) tcpPort = atoi(argv[2]); + if (argc > 3) + localIp = argv[3]; + + if (argc > 4) + localTcpPort = atoi(argv[4]); + IedClientError error; IedConnection con = IedConnection_create(); + /* Optional bind to local IP address/interface */ + if (localIp) { + IedConnection_setLocalAddress(con, localIp, localTcpPort); + printf("Bound to Local Address: %s:%i\n", localIp, localTcpPort); + } + IedConnection_connect(con, &error, hostname, tcpPort); + printf("Connecting to %s:%i\n", hostname, tcpPort); - if (error == IED_ERROR_OK) { + if (error == IED_ERROR_OK) + { + printf("Connected\n"); /* read an analog measurement value from server */ MmsValue* value = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", IEC61850_FC_MX); - if (value != NULL) { - + if (value != NULL) + { if (MmsValue_getType(value) == MMS_FLOAT) { float fval = MmsValue_toFloat(value); printf("read float value: %f\n", fval); @@ -137,7 +155,6 @@ close_connection: } IedConnection_destroy(con); + return 0; } - - diff --git a/examples/iec61850_client_example_control/client_example_control.c b/examples/iec61850_client_example_control/client_example_control.c index 33da635a..37d6b6bc 100644 --- a/examples/iec61850_client_example_control/client_example_control.c +++ b/examples/iec61850_client_example_control/client_example_control.c @@ -45,8 +45,10 @@ int main(int argc, char** argv) { IedConnection_connect(con, &error, hostname, tcpPort); - if (error == IED_ERROR_OK) { - + if (error == IED_ERROR_OK) + { + MmsValue* ctlVal = NULL; + MmsValue* stVal = NULL; /************************ * Direct control @@ -55,99 +57,116 @@ int main(int argc, char** argv) { ControlObjectClient control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO1", con); - MmsValue* ctlVal = MmsValue_newBoolean(true); + if (control) + { + ctlVal = MmsValue_newBoolean(true); - ControlObjectClient_setOrigin(control, NULL, 3); + ControlObjectClient_setOrigin(control, NULL, 3); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO1 operated successfully\n"); - } - else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO1\n"); - } + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO1 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO1\n"); + } + + MmsValue_delete(ctlVal); - MmsValue_delete(ctlVal); + ControlObjectClient_destroy(control); - ControlObjectClient_destroy(control); + /* Check if status value has changed */ - /* Check if status value has changed */ + stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO1.stVal", IEC61850_FC_ST); - MmsValue* stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO1.stVal", IEC61850_FC_ST); + if (error == IED_ERROR_OK) { + bool state = MmsValue_getBoolean(stVal); + MmsValue_delete(stVal); - if (error == IED_ERROR_OK) { - bool state = MmsValue_getBoolean(stVal); - MmsValue_delete(stVal); + printf("New status of simpleIOGenericIO/GGIO1.SPCSO1.stVal: %i\n", state); + } + else { + printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO1 failed!\n"); + } - printf("New status of simpleIOGenericIO/GGIO1.SPCSO1.stVal: %i\n", state); } else { - printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO1 failed!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO1 not found in server\n"); } - /************************ * Select before operate ***********************/ control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO2", con); - if (ControlObjectClient_select(control)) { + if (control) + { + if (ControlObjectClient_select(control)) { - ctlVal = MmsValue_newBoolean(true); + ctlVal = MmsValue_newBoolean(true); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO2 operated successfully\n"); + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO2 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO2!\n"); + } + + MmsValue_delete(ctlVal); } else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO2!\n"); + printf("failed to select simpleIOGenericIO/GGIO1.SPCSO2!\n"); } - MmsValue_delete(ctlVal); + ControlObjectClient_destroy(control); } else { - printf("failed to select simpleIOGenericIO/GGIO1.SPCSO2!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO2 not found in server\n"); } - ControlObjectClient_destroy(control); - - /**************************************** * Direct control with enhanced security ****************************************/ control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO3", con); - ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); + if (control) + { + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - ctlVal = MmsValue_newBoolean(true); + ctlVal = MmsValue_newBoolean(true); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO3 operated successfully\n"); - } - else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO3\n"); - } + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO3 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO3\n"); + } - MmsValue_delete(ctlVal); + MmsValue_delete(ctlVal); - /* Wait for command termination message */ - Thread_sleep(1000); + /* Wait for command termination message */ + Thread_sleep(1000); - ControlObjectClient_destroy(control); + ControlObjectClient_destroy(control); - /* Check if status value has changed */ + /* Check if status value has changed */ - stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO3.stVal", IEC61850_FC_ST); + stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO3.stVal", IEC61850_FC_ST); - if (error == IED_ERROR_OK) { - bool state = MmsValue_getBoolean(stVal); + if (error == IED_ERROR_OK) { + bool state = MmsValue_getBoolean(stVal); - printf("New status of simpleIOGenericIO/GGIO1.SPCSO3.stVal: %i\n", state); + printf("New status of simpleIOGenericIO/GGIO1.SPCSO3.stVal: %i\n", state); - MmsValue_delete(stVal); + MmsValue_delete(stVal); + } + else { + printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO3 failed!\n"); + } } else { - printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO3 failed!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO3 not found in server\n"); } /*********************************************** @@ -156,56 +175,66 @@ int main(int argc, char** argv) { control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO4", con); - ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); + if (control) + { + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - ctlVal = MmsValue_newBoolean(true); + ctlVal = MmsValue_newBoolean(true); - if (ControlObjectClient_selectWithValue(control, ctlVal)) { + if (ControlObjectClient_selectWithValue(control, ctlVal)) { + + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO4 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO4!\n"); + } - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO4 operated successfully\n"); } else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO4!\n"); + printf("failed to select simpleIOGenericIO/GGIO1.SPCSO4!\n"); } + MmsValue_delete(ctlVal); + + /* Wait for command termination message */ + Thread_sleep(1000); + + ControlObjectClient_destroy(control); } else { - printf("failed to select simpleIOGenericIO/GGIO1.SPCSO4!\n"); + printf("Control object simpleIOGenericIO/GGIO1.SPCSO4 not found in server\n"); } - MmsValue_delete(ctlVal); - - /* Wait for command termination message */ - Thread_sleep(1000); - - ControlObjectClient_destroy(control); - - /********************************************************************* * Direct control with enhanced security (expect CommandTermination-) *********************************************************************/ control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO9", con); - ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - - ctlVal = MmsValue_newBoolean(true); + if (control) + { + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); - if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { - printf("simpleIOGenericIO/GGIO1.SPCSO9 operated successfully\n"); - } - else { - printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO9\n"); - } + ctlVal = MmsValue_newBoolean(true); - MmsValue_delete(ctlVal); + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO9 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO9\n"); + } - /* Wait for command termination message */ - Thread_sleep(1000); + MmsValue_delete(ctlVal); - ControlObjectClient_destroy(control); + /* Wait for command termination message */ + Thread_sleep(1000); + ControlObjectClient_destroy(control); + } + else { + printf("Control object simpleIOGenericIO/GGIO1.SPCSO9 not found in server\n"); + } IedConnection_close(con); } diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index 8b43d9d7..24c909c9 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -101,6 +101,7 @@ printRawMmsMessage(void* parameter, uint8_t* message, int messageLength, bool re int main(int argc, char** argv) { + int returnCode = 0; char* hostname = StringUtils_copyString("localhost"); int tcpPort = 102; @@ -213,6 +214,10 @@ int main(int argc, char** argv) if (!MmsConnection_connect(con, &error, hostname, tcpPort)) { printf("MMS connect failed!\n"); + + if (error != MMS_ERROR_NONE) + returnCode = error; + goto exit; } else @@ -222,6 +227,9 @@ int main(int argc, char** argv) MmsServerIdentity* identity = MmsConnection_identify(con, &error); + if (error != MMS_ERROR_NONE) + returnCode = error; + if (identity != NULL) { printf("\nServer identity:\n----------------\n"); printf(" vendor:\t%s\n", identity->vendorName); @@ -235,14 +243,23 @@ int main(int argc, char** argv) if (readDeviceList) { printf("\nDomains present on server:\n--------------------------\n"); LinkedList nameList = MmsConnection_getDomainNames(con, &error); - LinkedList_printStringList(nameList); - LinkedList_destroy(nameList); + + if (error != MMS_ERROR_NONE) + returnCode = error; + + if (nameList) { + LinkedList_printStringList(nameList); + LinkedList_destroy(nameList); + } } if (getDeviceDirectory) { LinkedList variableList = MmsConnection_getDomainVariableNames(con, &error, domainName); + if (error != MMS_ERROR_NONE) + returnCode = error; + if (variableList) { LinkedList element = LinkedList_getNext(variableList); @@ -264,6 +281,9 @@ int main(int argc, char** argv) variableList = MmsConnection_getDomainJournals(con, &error, domainName); + if (error != MMS_ERROR_NONE) + returnCode = error; + if (variableList) { LinkedList element = variableList; @@ -309,6 +329,9 @@ int main(int argc, char** argv) LinkedList journalEntries = MmsConnection_readJournalTimeRange(con, &error, logDomain, logName, startTime, endTime, &moreFollows); + if (error != MMS_ERROR_NONE) + returnCode = error; + MmsValue_delete(startTime); MmsValue_delete(endTime); @@ -375,6 +398,8 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Reading variable failed: (ERROR %i)\n", error); + + returnCode = error; } else { printf("Read SUCCESS\n"); @@ -403,6 +428,8 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Reading variable failed: (ERROR %i)\n", error); + + returnCode = error; } else { printf("Read SUCCESS\n"); @@ -421,6 +448,8 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Reading variable list directory failed: (ERROR %i)\n", error); + + returnCode = error; } else { LinkedList varListElem = LinkedList_getNext(varListDir); @@ -454,12 +483,19 @@ int main(int argc, char** argv) char* continueAfter = NULL; while (MmsConnection_getFileDirectory(con, &error, "", continueAfter, mmsFileDirectoryHandler, lastName)) { + + if (error != MMS_ERROR_NONE) + returnCode = error; + continueAfter = lastName; } } if (getFileAttributes) { MmsConnection_getFileDirectory(con, &error, filename, NULL, mmsGetFileAttributeHandler, NULL); + + if (error != MMS_ERROR_NONE) + returnCode = error; } if (deleteFile) { @@ -467,13 +503,14 @@ int main(int argc, char** argv) if (error != MMS_ERROR_NONE) { printf("Delete file failed: (ERROR %i)\n", error); + returnCode = error; } else { printf("File deleted\n"); } } - exit: +exit: free(hostname); free(domainName); free(variableName); @@ -482,6 +519,6 @@ int main(int argc, char** argv) MmsConnection_destroy(con); - return 0; + return returnCode; } diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index 2d97a826..eaf1897a 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -263,6 +263,8 @@ void Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress) { struct packet_mreq mreq; + memset(&mreq, 0, sizeof(struct packet_mreq)); + mreq.mr_ifindex = ethSocket->socketAddress.sll_ifindex; mreq.mr_alen = ETH_ALEN; mreq.mr_type = PACKET_MR_MULTICAST; diff --git a/src/common/string_utilities.c b/src/common/string_utilities.c index dfb9d620..37e62ad7 100644 --- a/src/common/string_utilities.c +++ b/src/common/string_utilities.c @@ -194,6 +194,9 @@ StringUtils_copyStringMax(char* dest, int maxBufferSize, const char* str1) { char* res = dest; + if (maxBufferSize < 1) + return NULL; + if (dest == NULL) res = (char*)GLOBAL_MALLOC(maxBufferSize); diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index 06160008..63fe9710 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -210,13 +210,22 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) case 0x84: /* BIT STRING */ if (MmsValue_getType(value) == MMS_BIT_STRING) { int padding = buffer[bufPos]; - int bitStringLength = (8 * (elementLength - 1)) - padding; - if (bitStringLength == value->value.bitString.size) { - memcpy(value->value.bitString.buf, buffer + bufPos + 1, - elementLength - 1); + + if (padding > 7) { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: invalid bit-string (padding not plausible)\n"); + + pe = GOOSE_PARSE_ERROR_INVALID_PADDING; } else { - pe = GOOSE_PARSE_ERROR_LENGTH_MISMATCH; + int bitStringLength = (8 * (elementLength - 1)) - padding; + if (bitStringLength == value->value.bitString.size) { + memcpy(value->value.bitString.buf, buffer + bufPos + 1, + elementLength - 1); + } + else { + pe = GOOSE_PARSE_ERROR_LENGTH_MISMATCH; + } } } else { @@ -352,7 +361,7 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; } - if ( pe != GOOSE_PARSE_ERROR_NO_ERROR ) { + if (pe != GOOSE_PARSE_ERROR_NO_ERROR) { break; /* from while */ } @@ -362,14 +371,16 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) } if (elementIndex <= maxIndex) { - pe = GOOSE_PARSE_ERROR_UNDERFLOW; + if (pe == GOOSE_PARSE_ERROR_NO_ERROR) { + pe = GOOSE_PARSE_ERROR_UNDERFLOW; + } } if (DEBUG_GOOSE_SUBSCRIBER) { - switch ( pe ) { + switch (pe) { case GOOSE_PARSE_ERROR_UNKNOWN_TAG: printf("GOOSE_SUBSCRIBER: Found unkown tag %02x!\n", tag); - break; + break; case GOOSE_PARSE_ERROR_TAGDECODE: printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); break; @@ -388,6 +399,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) case GOOSE_PARSE_ERROR_LENGTH_MISMATCH: printf("GOOSE_SUBSCRIBER: Message contains value of wrong length!\n"); break; + case GOOSE_PARSE_ERROR_INVALID_PADDING: + printf("GOOSE_SUBSCRIBER: Malformed message: invalid padding!\n"); default: break; } @@ -500,7 +513,16 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt case 0x83: /* boolean */ if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found boolean\n"); - value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); + + if (elementLength > 0) { + value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); + } + else { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: invalid length for boolean\n"); + + goto exit_with_error; + } break; diff --git a/src/goose/goose_subscriber.h b/src/goose/goose_subscriber.h index e58f4f96..7408d6b5 100644 --- a/src/goose/goose_subscriber.h +++ b/src/goose/goose_subscriber.h @@ -47,6 +47,7 @@ typedef enum GOOSE_PARSE_ERROR_UNDERFLOW, GOOSE_PARSE_ERROR_TYPE_MISMATCH, GOOSE_PARSE_ERROR_LENGTH_MISMATCH, + GOOSE_PARSE_ERROR_INVALID_PADDING } GooseParseError; typedef struct sGooseSubscriber* GooseSubscriber; diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index b49a1fe7..bc581dea 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -651,6 +651,15 @@ IedConnection_tick(IedConnection self) return MmsConnection_tick(self->connection); } +void +IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, int localPort) +{ + MmsConnection connection = self->connection; + IsoConnectionParameters isoP = MmsConnection_getIsoConnectionParameters(connection); + + IsoConnectionParameters_setLocalTcpParameters(isoP, localIpAddress, localPort); +} + void IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs) { diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 190c6bce..986c951c 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -231,6 +231,17 @@ IedConnection_createWithTlsSupport(TLSConfiguration tlsConfig); LIB61850_API void IedConnection_destroy(IedConnection self); +/** +* \brief Set the local IP address and port to be used by the client +* +* NOTE: This function is optional. When not used the OS decides what IP address and TCP port to use. +* +* \param self IedConnection instance +* \param localIpAddress the local IP address or hostname as C string +* \param localPort the local TCP port to use. When < 1 the OS will chose the TCP port to use. +*/ +LIB61850_API void +IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, int localPort); /** * \brief set the connect timeout in ms diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index d206b904..2a7d6f2f 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -3,7 +3,7 @@ * * IEC 61850 server API for libiec61850. * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -42,6 +42,13 @@ extern "C" { #include "iso_connection_parameters.h" #include "iec61850_config_file_parser.h" +#define IEC61850_REPORTSETTINGS_RPT_ID 1 +#define IEC61850_REPORTSETTINGS_BUF_TIME 2 +#define IEC61850_REPORTSETTINGS_DATSET 4 +#define IEC61850_REPORTSETTINGS_TRG_OPS 8 +#define IEC61850_REPORTSETTINGS_OPT_FIELDS 16 +#define IEC61850_REPORTSETTINGS_INTG_PD 32 + /** * \brief Configuration object to configure IEC 61850 stack features */ @@ -99,6 +106,9 @@ struct sIedServerConfig /** integrity report start times will by synchronized with straight numbers (default: false) */ bool syncIntegrityReportTimes; + + /** for each configurable ReportSetting there is a separate flag (default: Dyn = enable write for all) */ + uint8_t reportSettingsWritable; }; /** @@ -388,6 +398,27 @@ IedServerConfig_useIntegratedGoosePublisher(IedServerConfig self, bool enable); LIB61850_API bool IedServerConfig_isLogServiceEnabled(IedServerConfig self); +/** + * \brief Make a configurable report setting writeable or read-only + * + * \note Can be used to implement some of Services\ReportSettings options + * + * \param[in] setting one of IEC61850_REPORTSETTINGS_RPT_ID, _BUF_TIME, _DATSET, _TRG_OPS, _OPT_FIELDS, _INTG_PD + * \param[in] isDyn true, when setting is writable ("Dyn") or false, when read-only + */ +LIB61850_API void +IedServerConfig_setReportSetting(IedServerConfig self, uint8_t setting, bool isDyn); + +/** + * \brief Check if a configurable report setting is writable or read-only + * + * \param[in] setting one of IEC61850_REPORTSETTINGS_RPT_ID, _BUF_TIME, _DATSET, _TRG_OPS, _OPT_FIELDS, _INTG_PD + * + * \return isDyn true, when setting is writable ("Dyn") or false, when read-only + */ +LIB61850_API bool +IedServerConfig_getReportSetting(IedServerConfig self, uint8_t setting); + /** * An opaque handle for an IED server instance */ @@ -1357,6 +1388,26 @@ ControlAction_getOrIdent(ControlAction self, int* orIdentSize); LIB61850_API int ControlAction_getCtlNum(ControlAction self); +/** + * \brief Gets the synchroCheck bit provided by the client + * + * \param self the control action instance + * + * \return the synchroCheck bit + */ +LIB61850_API bool +ControlAction_getSynchroCheck(ControlAction self); + +/** + * \brief Gets the interlockCheck bit provided by the client + * + * \param self the control action instance + * + * \return the interlockCheck bit + */ +LIB61850_API bool +ControlAction_getInterlockCheck(ControlAction self); + /** * \brief Check if the control callback is called by a select or operate command * diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index e6f6e412..d5555659 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -3,7 +3,7 @@ * * Library private function definitions for IedServer. * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -50,6 +50,7 @@ struct sIedServer bool enableBRCBResvTms; bool enableOwnerForRCB; bool syncIntegrityReportTimes; + uint8_t rcbSettingsWritable; #endif #if (CONFIG_MMS_THREADLESS_STACK != 1) diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index 3e896495..95727449 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -325,7 +325,10 @@ struct sMmsMapping { /* flag indicates if data model is locked --> prevents reports to be sent */ bool isModelLocked; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore isModelLockedMutex; +#endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ IedServer iedServer; diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index f09e42b2..2350984e 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -483,12 +483,19 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB; self->enableOwnerForRCB = serverConfiguration->enableOwnerForRCB; self->syncIntegrityReportTimes = serverConfiguration->syncIntegrityReportTimes; + self->rcbSettingsWritable = serverConfiguration->reportSettingsWritable; } else { self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->enableOwnerForRCB = false; self->syncIntegrityReportTimes = false; + self->rcbSettingsWritable = IEC61850_REPORTSETTINGS_RPT_ID + + IEC61850_REPORTSETTINGS_BUF_TIME + + IEC61850_REPORTSETTINGS_DATSET + + IEC61850_REPORTSETTINGS_TRG_OPS + + IEC61850_REPORTSETTINGS_OPT_FIELDS + + IEC61850_REPORTSETTINGS_INTG_PD; #if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) self->enableBRCBResvTms = true; #else @@ -814,11 +821,15 @@ IedServer_lockDataModel(IedServer self) { MmsServer_lockModel(self->mmsServer); +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->mmsMapping->isModelLockedMutex); +#endif self->mmsMapping->isModelLocked = true; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->mmsMapping->isModelLockedMutex); +#endif } void @@ -832,13 +843,17 @@ IedServer_unlockDataModel(IedServer self) /* check if reports have to be sent! */ Reporting_processReportEventsAfterUnlock(self->mmsMapping); +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->mmsMapping->isModelLockedMutex); +#endif MmsServer_unlockModel(self->mmsServer); self->mmsMapping->isModelLocked = false; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->mmsMapping->isModelLockedMutex); +#endif } #if (CONFIG_IEC61850_CONTROL_SERVICE == 1) diff --git a/src/iec61850/server/impl/ied_server_config.c b/src/iec61850/server/impl/ied_server_config.c index fdc38f8e..673f58b1 100644 --- a/src/iec61850/server/impl/ied_server_config.c +++ b/src/iec61850/server/impl/ied_server_config.c @@ -1,7 +1,7 @@ /* * ied_server_config.c * - * Copyright 2018-2022 Michael Zillgith + * Copyright 2018-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -59,6 +59,12 @@ IedServerConfig_create() self->enableResvTmsForBRCB = true; self->enableOwnerForRCB = false; self->syncIntegrityReportTimes = false; + self->reportSettingsWritable = IEC61850_REPORTSETTINGS_RPT_ID + + IEC61850_REPORTSETTINGS_BUF_TIME + + IEC61850_REPORTSETTINGS_DATSET + + IEC61850_REPORTSETTINGS_TRG_OPS + + IEC61850_REPORTSETTINGS_OPT_FIELDS + + IEC61850_REPORTSETTINGS_INTG_PD; } return self; @@ -264,3 +270,34 @@ IedServerConfig_getSyncIntegrityReportTimes(IedServerConfig self) { return self->syncIntegrityReportTimes; } + +static void +configureSetting(IedServerConfig self, uint8_t flags, uint8_t setting, bool value) +{ + if (flags & setting) + { + if (value) { + self->reportSettingsWritable |= setting; + } + else { + self->reportSettingsWritable &= ~setting; + } + } +} + +void +IedServerConfig_setReportSetting(IedServerConfig self, uint8_t setting, bool isDyn) +{ + configureSetting(self, setting, IEC61850_REPORTSETTINGS_RPT_ID, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_BUF_TIME, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_DATSET, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_TRG_OPS, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_OPT_FIELDS, isDyn); + configureSetting(self, setting, IEC61850_REPORTSETTINGS_INTG_PD, isDyn); +} + +bool +IedServerConfig_getReportSetting(IedServerConfig self, uint8_t setting) +{ + return (self->reportSettingsWritable & setting); +} diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 05d2d074..bcbb7d22 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -2498,6 +2498,22 @@ ControlAction_getCtlNum(ControlAction self) return -1; } +bool +ControlAction_getSynchroCheck(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + return (bool)(controlObject->synchroCheck); +} + +bool +ControlAction_getInterlockCheck(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + return (bool)(controlObject->interlockCheck); +} + bool ControlAction_isSelect(ControlAction self) { diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 096c90c7..707e8b57 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -3670,7 +3670,9 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) { LinkedList element = self->reportControls; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->isModelLockedMutex); +#endif bool modelLocked = self->isModelLocked; @@ -3686,8 +3688,7 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) continue; break; case REPORT_CONTROL_VALUE_CHANGED: - if (((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) && - ((rc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)) + if ((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) continue; break; case REPORT_CONTROL_QUALITY_CHANGED: @@ -3708,7 +3709,9 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) Reporting_processReportEventsAfterUnlock(self); } +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->isModelLockedMutex); +#endif } #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ @@ -3729,13 +3732,17 @@ MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value) if (DataSet_isMemberValue(dataSet, value, NULL)) { MmsGooseControlBlock_setStateChangePending(gcb); +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->isModelLockedMutex); +#endif if (self->isModelLocked == false) { MmsGooseControlBlock_publishNewState(gcb); } +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->isModelLockedMutex); +#endif } } } diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index ad60bef3..cba98b86 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1909,6 +1909,9 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (updateReportDataset(self, rc, NULL, connection)) { if (rc->reserved == false) { + + rc->resvTms = RESV_TMS_IMPLICIT_VALUE; + reserveRcb(rc, connection); if (self->rcbEventHandler) { @@ -2088,6 +2091,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "DatSet") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_DATSET)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2135,6 +2144,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "IntgPd") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_INTG_PD)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2182,6 +2197,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "TrgOps") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_TRG_OPS)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2255,6 +2276,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme else if (strcmp(elementName, "BufTm") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_BUF_TIME)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2288,6 +2315,12 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "RptID") == 0) { + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_RPT_ID)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); #endif @@ -2395,6 +2428,14 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme goto exit_function; } + else if (strcmp(elementName, "OptFlds") == 0) { + + if (!(self->iedServer->rcbSettingsWritable & IEC61850_REPORTSETTINGS_OPT_FIELDS)) + { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; + } + } #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(rc->rcbValuesLock); @@ -2445,7 +2486,6 @@ exit_function: } } - } else if (rc->resvTms == -1) { if (rc->reserved == false) { @@ -3877,7 +3917,9 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) void Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->isModelLockedMutex); +#endif if (self->isModelLocked == false) { @@ -3894,7 +3936,9 @@ Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs) } } +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->isModelLockedMutex); +#endif } /* diff --git a/src/mms/inc/iso_connection_parameters.h b/src/mms/inc/iso_connection_parameters.h index cdd04bcb..3514f437 100644 --- a/src/mms/inc/iso_connection_parameters.h +++ b/src/mms/inc/iso_connection_parameters.h @@ -145,6 +145,9 @@ struct sIsoConnectionParameters const char* hostname; int tcpPort; + const char* localIpAddress; + int localTcpPort; + uint8_t remoteApTitle[10]; int remoteApTitleLen; int remoteAEQualifier; @@ -215,6 +218,20 @@ IsoConnectionParameters_setAcseAuthenticationParameter(IsoConnectionParameters s LIB61850_API void IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const char* hostname, int tcpPort); +/** +* \brief Set Local TCP parameters (FOR LIBRARY INTERNAL USE) +* +* NOTE: This function used internally by the MMS Client library. When using the MMS or IEC 61850 API +* there should be no reason for the user to call this function +* +* \param self the IsoConnectionParameters instance +* \param localIpAddress the hostname of local IP address of the server +* \param localTcpPort the local TCP port number of the server +*/ +LIB61850_API void +IsoConnectionParameters_setLocalTcpParameters(IsoConnectionParameters self, const char* localIpAddress, int localTcpPort); + + /** * \brief set the remote AP-Title and AE-Qualifier * diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 70bedc28..86f12c9b 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -648,6 +648,23 @@ MmsConnection_writeVariableAsync(MmsConnection self, uint32_t* usedInvokeId, Mms MmsConnection_WriteVariableHandler handler, void* parameter); +/** + * \brief Write a single variable to the server (using component alternate access) + * + * \param self MmsConnection instance to operate on + * \param mmsError user provided variable to store error code + * \param domainId the domain name of the variable to be written + * \param itemId name of the variable to be written + * \param componentId the name of the variable component + * \param value value of the variable to be written + * + * \return when successful, the data access error value returned by the server + */ +LIB61850_API MmsDataAccessError +MmsConnection_writeVariableComponent(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, + const char* componentId, MmsValue* value); + /** * \brief Write a single array element with a component to an array type variable * @@ -672,6 +689,11 @@ MmsConnection_writeSingleArrayElementWithComponentAsync(MmsConnection self, uint uint32_t arrayIndex, const char* componentId, MmsValue* value, MmsConnection_WriteVariableHandler handler, void* parameter); +LIB61850_API void +MmsConnection_writeVariableComponentAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, + const char* domainId, const char* itemId, const char* componentId, MmsValue* value, + MmsConnection_WriteVariableHandler handler, void* parameter); + /** * \brief Write a single array element or a sub array to an array type variable * diff --git a/src/mms/inc_private/cotp.h b/src/mms/inc_private/cotp.h index b7877cc8..3cbe2681 100644 --- a/src/mms/inc_private/cotp.h +++ b/src/mms/inc_private/cotp.h @@ -114,4 +114,7 @@ CotpConnection_getRemoteRef(CotpConnection* self); LIB61850_INTERNAL int CotpConnection_getLocalRef(CotpConnection* self); +LIB61850_INTERNAL void +CotpConnection_flushBuffer(CotpConnection* self); + #endif /* COTP_H_ */ diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 57243230..aa0e60f3 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -272,6 +272,11 @@ mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const int startIndex, int elementCount, MmsValue* value, ByteBuffer* writeBuffer); +LIB61850_INTERNAL int +mmsClient_createWriteRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component, + MmsValue* value, + ByteBuffer* writeBuffer); + LIB61850_INTERNAL int mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, uint32_t arrayIndex, const char* component, diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c index cb58e98f..4f37176b 100644 --- a/src/mms/iso_client/iso_client_connection.c +++ b/src/mms/iso_client/iso_client_connection.c @@ -692,8 +692,13 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim /* set timeout for connect */ self->nextReadTimeout = Hal_getTimeInMs() + connectTimeoutInMs; + /* Connect to Local Ip Address*/ + if (self->parameters->localIpAddress) { + Socket_bind(self->socket, self->parameters->localIpAddress, self->parameters->localTcpPort); + } + if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) { - + Socket_destroy(self->socket); self->socket = NULL; @@ -704,9 +709,9 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim success = false; } - + Semaphore_post(self->tickMutex); - + return success; } diff --git a/src/mms/iso_common/iso_connection_parameters.c b/src/mms/iso_common/iso_connection_parameters.c index 3fdf32c9..aa5334b3 100644 --- a/src/mms/iso_common/iso_connection_parameters.c +++ b/src/mms/iso_common/iso_connection_parameters.c @@ -104,6 +104,18 @@ IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const cha self->tcpPort = tcpPort; } +void +IsoConnectionParameters_setLocalTcpParameters(IsoConnectionParameters self, const char* localIpAddress, int localTcpPort) +{ + if (self) { + if (localIpAddress) { + self->localIpAddress = strdup(localIpAddress); + self->localTcpPort = localTcpPort; + } + } +} + + void IsoConnectionParameters_setRemoteApTitle(IsoConnectionParameters self, const char* apTitle, int aeQualifier) { diff --git a/src/mms/iso_cotp/cotp.c b/src/mms/iso_cotp/cotp.c index f57e05bc..2b4e43e6 100644 --- a/src/mms/iso_cotp/cotp.c +++ b/src/mms/iso_cotp/cotp.c @@ -5,7 +5,7 @@ * * Partial implementation of the ISO 8073 COTP (ISO TP0) protocol for MMS. * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -174,6 +174,38 @@ writeToSocket(CotpConnection* self, uint8_t* buf, int size) #endif } +static bool +flushBuffer(CotpConnection* self) +{ + if (self->socketExtensionBufferFill > 0) { + + int sentBytes = writeToSocket(self, self->socketExtensionBuffer, self->socketExtensionBufferFill); + + if (sentBytes > 0) { + + if (sentBytes != self->socketExtensionBufferFill) { + int target = 0; + int i; + uint8_t* buf = self->socketExtensionBuffer; + + for (i = sentBytes; i < self->socketExtensionBufferFill; i++) { + buf[target++] = buf[i]; + } + + self->socketExtensionBufferFill = self->socketExtensionBufferFill - sentBytes; + } + else { + self->socketExtensionBufferFill = 0; + } + } + else if (sentBytes == -1) { + return false; + } + } + + return true; +} + static bool sendBuffer(CotpConnection* self) { @@ -182,7 +214,15 @@ sendBuffer(CotpConnection* self) bool retVal = false; - int sentBytes = writeToSocket(self, buffer, remainingSize); + if (flushBuffer(self) == false) { + goto exit_function; + } + + int sentBytes = 0; + + if (self->socketExtensionBufferFill == 0) { + sentBytes = writeToSocket(self, buffer, remainingSize); + } if (sentBytes == -1) goto exit_function; @@ -215,33 +255,6 @@ exit_function: return retVal; } -static void -flushBuffer(CotpConnection* self) -{ - if (self->socketExtensionBufferFill > 0) { - - int sentBytes = writeToSocket(self, self->socketExtensionBuffer, self->socketExtensionBufferFill); - - if (sentBytes > 0) { - - if (sentBytes != self->socketExtensionBufferFill) { - int target = 0; - int i; - uint8_t* buf = self->socketExtensionBuffer; - - for (i = sentBytes; i < self->socketExtensionBufferFill; i++) { - buf[target++] = buf[i]; - } - - self->socketExtensionBufferFill = self->socketExtensionBufferFill - sentBytes; - } - else { - self->socketExtensionBufferFill = 0; - } - } - } -} - CotpIndication CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) { @@ -262,7 +275,9 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) int totalSize = (fragments * (COTP_DATA_HEADER_SIZE + 4)) + payload->length; /* try to flush extension buffer */ - flushBuffer(self); + if (flushBuffer(self) == false) { + return COTP_ERROR; + } /* check if totalSize will fit in extension buffer */ if (self->socketExtensionBuffer) { @@ -281,7 +296,7 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) int currentChainIndex = 0; if (DEBUG_COTP) - printf("\nCOTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); + printf("COTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); uint8_t* buffer = self->writeBuffer->buffer; @@ -307,7 +322,7 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) if (currentChainIndex >= currentChain->partLength) { currentChain = currentChain->nextPart; if (DEBUG_COTP) - printf("\nCOTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); + printf("COTP: nextBufferPart: len:%i partLen:%i\n", currentChain->length, currentChain->partLength); currentChainIndex = 0; } @@ -756,6 +771,13 @@ readFromSocket(CotpConnection* self, uint8_t* buf, int size) #endif } +void +CotpConnection_flushBuffer(CotpConnection* self) +{ + if (self->socketExtensionBufferFill > 0) + flushBuffer(self); +} + TpktState CotpConnection_readToTpktBuffer(CotpConnection* self) { @@ -765,6 +787,14 @@ CotpConnection_readToTpktBuffer(CotpConnection* self) assert (bufferSize > 4); + if (self->socketExtensionBufferFill > 0) { + if (flushBuffer(self) == false) + goto exit_error; + + if (self->socketExtensionBufferFill > 0) + goto exit_waiting; + } + int readBytes; if (bufPos < 4) { diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index e1bb0039..cc63582b 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -4384,6 +4384,69 @@ exit_function: return; } +MmsDataAccessError +MmsConnection_writeVariableComponent(MmsConnection self, MmsError* mmsError, + const char* domainId, const char* itemId, + const char* componentId, MmsValue* value) +{ + struct writeVariableParameters parameter; + + MmsError err = MMS_ERROR_NONE; + + parameter.waitForResponse = Semaphore_create(1); + parameter.err = MMS_ERROR_NONE; + parameter.accessError = DATA_ACCESS_ERROR_SUCCESS; + + Semaphore_wait(parameter.waitForResponse); + + MmsConnection_writeVariableComponentAsync(self, NULL, &err, domainId, itemId, componentId, value, writeVariableHandler, ¶meter); + + if (err == MMS_ERROR_NONE) { + Semaphore_wait(parameter.waitForResponse); + + err = parameter.err; + } + + Semaphore_destroy(parameter.waitForResponse); + + if (mmsError) + *mmsError = err; + + return parameter.accessError; +} + +void +MmsConnection_writeVariableComponentAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, + const char* domainId, const char* itemId, const char* componentId, MmsValue* value, + MmsConnection_WriteVariableHandler handler, void* parameter) +{ + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (mmsError) + *mmsError = MMS_ERROR_CONNECTION_LOST; + goto exit_function; + } + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + if (usedInvokeId) + *usedInvokeId = invokeId; + + mmsClient_createWriteRequestComponent(invokeId, domainId, itemId, componentId, value, payload); + + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, intParam); + + if (mmsError) + *mmsError = err; + +exit_function: + return; +} + struct writeMultipleVariablesParameter { Semaphore sem; diff --git a/src/mms/iso_mms/client/mms_client_write.c b/src/mms/iso_mms/client/mms_client_write.c index e99a6e1b..fc2b3d62 100644 --- a/src/mms/iso_mms/client/mms_client_write.c +++ b/src/mms/iso_mms/client/mms_client_write.c @@ -529,6 +529,63 @@ mmsClient_createWriteRequestArray(uint32_t invokeId, const char* domainId, const return rval.encoded; } +int +mmsClient_createWriteRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component, + MmsValue* value, + ByteBuffer* writeBuffer) +{ + MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); + + mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present = + ConfirmedServiceRequest_PR_write; + WriteRequest_t* request = + &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.write); + + /* Create list of variable specifications */ + request->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable; + request->variableAccessSpecification.choice.listOfVariable.list.count = 1; + request->variableAccessSpecification.choice.listOfVariable.list.array = + (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); + + ListOfVariableSeq_t* variableIdentifier = createNewDomainVariableSpecification(domainId, itemId); + + request->variableAccessSpecification.choice.listOfVariable.list.array[0] = variableIdentifier; + + variableIdentifier->alternateAccess = mmsClient_createAlternateAccessComponent(component); + + /* Create list of typed data values */ + request->listOfData.list.count = 1; + request->listOfData.list.size = 1; + request->listOfData.list.array = (Data_t**) GLOBAL_CALLOC(1, sizeof(struct Data*)); + request->listOfData.list.array[0] = mmsMsg_createBasicDataElement(value); + + /* Encode complete ASN1 structure */ + + asn_enc_rval_t rval; + + rval = der_encode(&asn_DEF_MmsPdu, mmsPdu, + (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer); + + /* Free ASN structure */ + mmsClient_deleteAlternateAccess(variableIdentifier->alternateAccess); + request->variableAccessSpecification.choice.listOfVariable.list.count = 0; + + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array[0]); + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array); + request->variableAccessSpecification.choice.listOfVariable.list.array = 0; + + request->listOfData.list.count = 0; + + deleteDataElement(request->listOfData.list.array[0]); + + GLOBAL_FREEMEM(request->listOfData.list.array); + request->listOfData.list.array = 0; + + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + + return rval.encoded; +} + int mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(uint32_t invokeId, const char* domainId, const char* itemId, uint32_t arrayIndex, const char* component, diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 29e96e4d..549619a8 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -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); - bufPos += strlen(currentStr); + bufPos += strnlen(currentStr, bufferSize - bufPos); if (bufPos >= bufferSize) break; @@ -2240,9 +2240,13 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) int size = MmsValue_getBitStringSize(self); /* fill buffer with zeros */ - if (size > bufferSize) { + if (size + 1 > bufferSize) { memset(buffer, 0, bufferSize); - break; + + size = bufferSize - 1; + + if (size < 1) + break; } int i; diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index 124de4fe..5f68ab48 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -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 mmsServer_handleWriteRequest( @@ -604,15 +655,21 @@ mmsServer_handleWriteRequest( AlternateAccess_t* alternateAccess = varSpec->alternateAccess; if (alternateAccess != NULL) { - if (variable->type != MMS_ARRAY) { + + if ((variable->type == MMS_STRUCTURE) && (mmsServer_isComponentAccess(alternateAccess) == false)) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; } - if (!mmsServer_isIndexAccess(alternateAccess)) { + if ((variable->type == MMS_ARRAY) && (mmsServer_isIndexAccess(alternateAccess) == false)) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; continue; } + + if (variable->type != MMS_ARRAY && variable->type != MMS_STRUCTURE) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + continue; + } } Data_t* dataElement = writeRequest->listOfData.list.array[i]; @@ -626,62 +683,75 @@ mmsServer_handleWriteRequest( if (alternateAccess != NULL) { - if (domain == NULL) + if (domain == NULL) domain = (MmsDomain*) device; - MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); + if (mmsServer_isIndexAccess(alternateAccess)) { + MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); - if (cachedArray == NULL) { - accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; - goto end_of_main_loop; - } - - int index = mmsServer_getLowIndex(alternateAccess); - int numberOfElements = mmsServer_getNumberOfElements(alternateAccess); - - if (numberOfElements == 0) { /* select single array element with index */ - - MmsValue* elementValue = MmsValue_getElement(cachedArray, index); - - if (elementValue == NULL) { + if (cachedArray == NULL) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; goto end_of_main_loop; } - if (MmsValue_update(elementValue, value) == false) { - accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; - goto end_of_main_loop; - } - } - else { /* select sub-array with start-index and number-of-elements */ + int index = mmsServer_getLowIndex(alternateAccess); + int numberOfElements = mmsServer_getNumberOfElements(alternateAccess); - if (MmsValue_getType(value) != MMS_ARRAY) { - accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; - goto end_of_main_loop; - } + if (numberOfElements == 0) { /* select single array element with index */ - int elementNo; + MmsValue* elementValue = MmsValue_getElement(cachedArray, index); - for (elementNo = 0; elementNo < numberOfElements; elementNo++) { - MmsValue* newElement = MmsValue_getElement(value, elementNo); - MmsValue* elementValue = MmsValue_getElement(cachedArray, index++); + if (elementValue == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + goto end_of_main_loop; + } - if ((elementValue == NULL) || (newElement == NULL) ) { + if (MmsValue_update(elementValue, value) == false) { accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; goto end_of_main_loop; } + } + else { /* select sub-array with start-index and number-of-elements */ - if (MmsValue_update(elementValue, newElement) == false) { + if (MmsValue_getType(value) != MMS_ARRAY) { accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; goto end_of_main_loop; } + int elementNo; + + for (elementNo = 0; elementNo < numberOfElements; elementNo++) { + MmsValue* newElement = MmsValue_getElement(value, elementNo); + MmsValue* elementValue = MmsValue_getElement(cachedArray, index++); + + if ((elementValue == NULL) || (newElement == NULL) ) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + else { /* select sub-array with start-index and number-of-elements */ + if (MmsValue_update(elementValue, newElement) == false) { + accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + } + } } - } - accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; - goto end_of_main_loop; + accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; + goto end_of_main_loop; + } + else if (mmsServer_isComponentAccess(alternateAccess)) { + variable = getComponent(connection, domain, alternateAccess, variable, nameIdStr); + if (variable == NULL) { + accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; + goto end_of_main_loop; + } + } + else { + accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; + goto end_of_main_loop; + } } /* Check for correct type */ diff --git a/src/mms/iso_server/iso_connection.c b/src/mms/iso_server/iso_connection.c index 7f997a9b..795b0636 100644 --- a/src/mms/iso_server/iso_connection.c +++ b/src/mms/iso_server/iso_connection.c @@ -1,7 +1,7 @@ /* * iso_connection.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -160,6 +160,8 @@ IsoConnection_removeFromHandleSet(const IsoConnection self, HandleSet handles) void IsoConnection_callTickHandler(IsoConnection self) { + CotpConnection_flushBuffer(self->cotpConnection); + if (self->tickHandler) { self->tickHandler(self->handlerParameter); } @@ -171,10 +173,7 @@ IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread) #if (CONFIG_MMS_SINGLE_THREADED != 1) if (isSingleThread == false) { - /* call tick handler */ - if (self->tickHandler) { - self->tickHandler(self->handlerParameter); - } + IsoConnection_callTickHandler(self); if (Handleset_waitReady(self->handleSet, 10) < 1) goto exit_function; diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index 62c43ebb..a2daa6ad 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -455,7 +455,6 @@ exit_function: return success; } - /** used by single and multi-threaded versions * * \param isSingleThread when true server is running in single thread or non-thread mode