diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 00000000..b1dbd55e --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,12 @@ +name: Greetings + +on: [pull_request, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: 'Push requests and other code contributions can only be accepted from authorized persons that have signed our contributor license agreement(CLA). When in doubt please write to info@libiec61850.com.' diff --git a/CHANGELOG b/CHANGELOG index eaaa9046..b4ee9f12 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,41 @@ Changes to version 1.5.0 ------------------------ - IEC 61850 server: control models - allow delaying select response with check handler (new handler return value CONTROL_WAITING_FOR_SELECT) +Changes to version 1.4.2.1 +-------------------------- + +- IEC 61850 server: RCB - fixed problem that other client can "steal" reservation +- MMS client: fixed bug in log entry parsing (#224) +- IEC 61850 server: fixed potential null pointer dereference in multi-thread mode when server is stopped +- IEC 61850 server: fixed bug in single threaded mode (windows) +- IEC 61850 server: fixed compilation error in single thread mode (was new in release 1.4.2) + +Changes to version 1.4.2 +------------------------- + +- IEC 61850 server: wait for background thread termination before data model is released +- MMS server: fixed potential crash when get-named-variable-list-attributes response doesn't fit in MMS PDU. Server returns service error in this case +- IEC 61850 server: unbuffered reporting - send first integrity report after integrity timeout (before it was sent when the RCB was enabled) +- IEC 61850 server: allow server to start without logical devices in data model (#218) +- IEC 61850 client: fixed bug in ClientReportControlBlock - some allowed structures for RCB were rejected +- IEC 61850 server: control - unselect when operate with wrong origin parameters, check if check parameter matches for SBO +- IEC 61850 server: fixed missing report timestamp update for unbuffered reporting +- IEC 618580 server: Added function IedServer_setServerIdentity to set values for MMS identity service +- CDC helpers: added functions to create ISC and BAC CDCs +- CDC helpers: added stSeld and attributes from ControlTestingCDC to control CDCs +- CDC helper functions: added stSeld and attributes from ControlTestingCDC to control CDCs +- updated cmake files +- .NET API: fixed memory management issue in MmsValue.SetElement (see #213) +- IEC 61850 server: unselect control when oper is not accepted +- IEC 61850 server: send select-failed for control when origin parameter is not valid +- IEC 61850 server: fixed control handling to comply with test case sCtl25 +- IEC 61850 server: fixed control handling to comply with test case sCtl11 +- IEC 61850 server: pass origin, ctlNum, ctlVal to select handler (perform check handler) for SBOw +- removed internal header files from install target (#214) +- .NET API: additional function mappings +- TLS: fixed memory leak when TLS authentication fails +- fixed bug in windows socket layer + Changes to version 1.4.1 ------------------------ - MMS server: refactored connection handling; more efficient use of HandleSet diff --git a/CMakeLists.txt b/CMakeLists.txt index b64b1280..ebbfc94d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12) # automagically detect if we should cross-compile if(DEFINED ENV{TOOLCHAIN}) @@ -76,19 +76,15 @@ include_directories( ) set(API_HEADERS - hal/inc/hal_base.h + hal/inc/hal_base.h hal/inc/hal_time.h hal/inc/hal_thread.h hal/inc/hal_filesystem.h hal/inc/hal_ethernet.h hal/inc/hal_socket.h hal/inc/tls_config.h - hal/inc/platform_endian.h - hal/inc/lib_memory.h src/common/inc/libiec61850_common_api.h - src/common/inc/libiec61850_platform_includes.h src/common/inc/linked_list.h - src/common/inc/string_utilities.h src/iec61850/inc/iec61850_client.h src/iec61850/inc/iec61850_common.h src/iec61850/inc/iec61850_server.h @@ -173,3 +169,5 @@ if(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") include(CPack) endif(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") + + diff --git a/config/stack_config.h b/config/stack_config.h index 54907198..1ecd9da9 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -164,6 +164,9 @@ /* allow user to control read access by callback */ #define CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL 1 +/* allow application to set server identity (for MMS identity service) at runtime */ +#define CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY 1 + /* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ #define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 45963309..164e2602 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -154,6 +154,9 @@ /* allow user to control read access by callback */ #cmakedefine01 CONFIG_IEC61850_SUPPORT_USER_READ_ACCESS_CONTROL +/* allow application to set server identity (for MMS identity service) at runtime */ +#define CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY 1 + /* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ #define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 19acd559..6ae0d53e 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -178,7 +178,7 @@ namespace IEC61850 /// public class Quality { - private UInt16 value; + private UInt16 value; private const UInt16 QUALITY_DETAIL_OVERFLOW = 4; private const UInt16 QUALITY_DETAIL_OUT_OF_RANGE = 8; @@ -193,6 +193,8 @@ namespace IEC61850 private const UInt16 QUALITY_OPERATOR_BLOCKED = 4096; private const UInt16 QUALITY_DERIVED = 8192; + public ushort Value => value; + public override string ToString () { return GetValidity ().ToString (); diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 8e58b129..90748455 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -837,6 +837,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ClientConnection_getPeerAddress(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientConnection_getLocalAddress(IntPtr self); + internal IntPtr self; internal ClientConnection (IntPtr self) { @@ -852,6 +855,16 @@ namespace IEC61850 else return null; } + + public string GetLocalAddress() + { + IntPtr localAddrPtr = ClientConnection_getLocalAddress(self); + + if (localAddrPtr != IntPtr.Zero) + return Marshal.PtrToStringAnsi(localAddrPtr); + else + return null; + } } /// @@ -1065,6 +1078,9 @@ namespace IEC61850 [return: MarshalAs(UnmanagedType.Bool)] static extern bool IedServer_isRunning(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServer_getNumberOfOpenConnections(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServer_lockDataModel(IntPtr self); @@ -1098,7 +1114,10 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServer_updateQuality(IntPtr self, IntPtr dataAttribute, ushort value); - [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_setServerIdentity(IntPtr self, string vendor, string model, string revision); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedServer_getAttributeValue(IntPtr self, IntPtr dataAttribute); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -1372,12 +1391,36 @@ namespace IEC61850 internalConnectionHandler = null; } + /// + /// Set the identify for the MMS identify service + /// + /// the IED vendor name + /// the IED model name + /// the IED revision/version number + public void SetServerIdentity(string vendor, string model, string revision) + { + IedServer_setServerIdentity(self, vendor, model, revision); + } + + /// + /// Check if server is running (accepting client connections) + /// + /// true, if running, false otherwise. public bool IsRunning() + { + return IedServer_isRunning(self); + } + + /// + /// Get number of open MMS connections + /// + /// the number of open and accepted MMS connections + public int GetNumberOfOpenConnections() { - return IedServer_isRunning(self); + return IedServer_getNumberOfOpenConnections(self); } - private ControlHandlerInfo GetControlHandlerInfo(DataObject controlObject) + private ControlHandlerInfo GetControlHandlerInfo(DataObject controlObject) { ControlHandlerInfo info; diff --git a/dotnet/IEC61850forCSharp/IedServerConfig.cs b/dotnet/IEC61850forCSharp/IedServerConfig.cs index aa8dc6c9..d2b4f71b 100644 --- a/dotnet/IEC61850forCSharp/IedServerConfig.cs +++ b/dotnet/IEC61850forCSharp/IedServerConfig.cs @@ -44,6 +44,12 @@ namespace IEC61850.Server [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int IedServerConfig_getReportBufferSize(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServerConfig_setReportBufferSizeForURCBs(IntPtr self, int reportBufferSize); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int IedServerConfig_getReportBufferSizeForURCBs(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServerConfig_setFileServiceBasePath(IntPtr self, string basepath); @@ -122,6 +128,22 @@ namespace IEC61850.Server } } + /// + /// Gets or sets the size of the report buffer for unbuffered report control blocks + /// + /// The size of the report buffer. + public int ReportBufferSizeForURCBs + { + get + { + return IedServerConfig_getReportBufferSizeForURCBs(self); + } + set + { + IedServerConfig_setReportBufferSizeForURCBs(self, value); + } + } + /// /// Gets or sets the file service base path. /// diff --git a/dotnet/IEC61850forCSharp/MmsValue.cs b/dotnet/IEC61850forCSharp/MmsValue.cs index 4e883fa5..5d6a90c7 100644 --- a/dotnet/IEC61850forCSharp/MmsValue.cs +++ b/dotnet/IEC61850forCSharp/MmsValue.cs @@ -533,43 +533,60 @@ namespace IEC61850 throw new MmsValueException ("Value is of wrong type"); } - /// - /// Sets the element of an array of structure - /// - /// index of the element starting with 0 - /// MmsValue instance that will be used as element value - /// This exception is thrown if the value has the wrong type. - /// This exception is thrown if the index is out of range. - public void SetElement(int index, MmsValue elementValue) - { - MmsType elementType = GetType (); + /// + /// Sets the element of an array or structure + /// + /// + /// After calling this function the native memory of the element will be managed by the array or structure. + /// Therefore an element can only be used in a single array or structure. + /// When the value is required in multiple arrays or structures + /// a clone has to be created before using this function! + /// To be save, always use a clone for setting the element. + /// + /// index of the element starting with 0 + /// MmsValue instance that will be used as element value + /// This exception is thrown if the value has the wrong type. + /// This exception is thrown if the index is out of range. + public void SetElement(int index, MmsValue elementValue) + { + MmsType elementType = GetType(); + + if ((elementType == MmsType.MMS_ARRAY) || (elementType == MmsType.MMS_STRUCTURE)) + { + + if ((index >= 0) && (index < Size())) + { - if ((elementType == MmsType.MMS_ARRAY) || (elementType == MmsType.MMS_STRUCTURE)) { - - if ((index >= 0) && (index < Size ())) { if (elementValue != null) - MmsValue_setElement (valueReference, index, elementValue.valueReference); + { + MmsValue_setElement(valueReference, index, elementValue.valueReference); + + /* will be deleted by structure */ + elementValue.responsableForDeletion = false; + } else - MmsValue_setElement (valueReference, index, IntPtr.Zero); + MmsValue_setElement(valueReference, index, IntPtr.Zero); - } else - throw new MmsValueException ("Index out of bounds"); - - } else - throw new MmsValueException ("Value is of wrong type"); + } + else + throw new MmsValueException("Index out of bounds"); - } + } + else + throw new MmsValueException("Value is of wrong type"); + } - public MmsDataAccessError GetDataAccessError () - { - if (GetType () == MmsType.MMS_DATA_ACCESS_ERROR) { - int errorCode = MmsValue_getDataAccessError (valueReference); + public MmsDataAccessError GetDataAccessError() + { + if (GetType() == MmsType.MMS_DATA_ACCESS_ERROR) + { + int errorCode = MmsValue_getDataAccessError(valueReference); - return (MmsDataAccessError)errorCode; - } - else - throw new MmsValueException ("Value is of wrong type"); - } + return (MmsDataAccessError)errorCode; + } + else + throw new MmsValueException("Value is of wrong type"); + } /// /// Gets the timestamp value as UTC time in s (UNIX time stamp). diff --git a/dotnet/tests/Test.cs b/dotnet/tests/Test.cs index a835d92b..8b945f7d 100644 --- a/dotnet/tests/Test.cs +++ b/dotnet/tests/Test.cs @@ -294,9 +294,6 @@ namespace tests { IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("../../model.cfg"); - - - ModelNode ind1 = iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.Ind1.stVal"); Assert.IsTrue (ind1.GetType ().Equals (typeof(IEC61850.Server.DataAttribute))); @@ -556,6 +553,45 @@ namespace tests Assert.AreEqual (Validity.QUESTIONABLE, q.Validity); } - } + + [Test()] + public void MmsValaueCreateStructureAndAddElement() + { + MmsValue structure1 = MmsValue.NewEmptyStructure(1); + MmsValue structure2 = MmsValue.NewEmptyStructure(1); + MmsValue element = MmsValue.NewEmptyStructure(1); + + structure1.SetElement(0, element); + + /* Clone is required when adding the value to another structure or element */ + MmsValue elementClone = element.Clone(); + structure2.SetElement(0, elementClone); + + element.Dispose(); + + structure1.Dispose(); + structure2.Dispose(); + + Assert.AreEqual(true, true); + } + + [Test()] + public void MmsValueClone() + { + MmsValue boolValue = new MmsValue(true); + + MmsValue boolClone = boolValue.Clone(); + + boolValue.Dispose(); + boolClone.Dispose(); + + MmsValue structure = MmsValue.NewEmptyStructure(1); + MmsValue structureClone = structure.Clone(); + + structure.Dispose(); + structureClone.Dispose(); + } + + } } diff --git a/examples/iec61850_client_example_reporting/client_example_reporting.c b/examples/iec61850_client_example_reporting/client_example_reporting.c index f070248a..29805ae3 100644 --- a/examples/iec61850_client_example_reporting/client_example_reporting.c +++ b/examples/iec61850_client_example_reporting/client_example_reporting.c @@ -43,19 +43,33 @@ reportCallbackFunction(void* parameter, ClientReport report) printf(" report contains timestamp (%u): %s", (unsigned int) unixTime, timeBuf); } - int i; - for (i = 0; i < LinkedList_size(dataSetDirectory); i++) { - ReasonForInclusion reason = ClientReport_getReasonForInclusion(report, i); + if (dataSetDirectory) { + int i; + for (i = 0; i < LinkedList_size(dataSetDirectory); i++) { + ReasonForInclusion reason = ClientReport_getReasonForInclusion(report, i); - if (reason != IEC61850_REASON_NOT_INCLUDED) { + if (reason != IEC61850_REASON_NOT_INCLUDED) { - LinkedList entry = LinkedList_get(dataSetDirectory, i); + char valBuffer[500]; + sprintf(valBuffer, "no value"); - char* entryName = (char*) entry->data; + if (dataSetValues) { + MmsValue* value = MmsValue_getElement(dataSetValues, i); - printf(" %s (included for reason %i)\n", entryName, reason); + if (value) { + MmsValue_printToBuffer(value, valBuffer, 500); + } + } + + LinkedList entry = LinkedList_get(dataSetDirectory, i); + + char* entryName = (char*) entry->data; + + printf(" %s (included for reason %i): %s\n", entryName, reason, valBuffer); + } } } + } int @@ -89,23 +103,23 @@ main(int argc, char** argv) LinkedList dataSetDirectory = NULL; /* read data set directory */ - dataSetDirectory = IedConnection_getDataSetDirectory(con, &error, "simpleIOGenericIO/LLN0.Events", NULL); + dataSetDirectory = IedConnection_getDataSetDirectory(con, &error, "testmodelSENSORS/LLN0.DataSetST_Attr", NULL); if (error != IED_ERROR_OK) { printf("Reading data set directory failed!\n"); - goto exit_error; + // goto exit_error; } /* read data set */ - clientDataSet = IedConnection_readDataSetValues(con, &error, "simpleIOGenericIO/LLN0.Events", NULL); + clientDataSet = IedConnection_readDataSetValues(con, &error, "testmodelSENSORS/LLN0.DataSetST_Attr", NULL); if (clientDataSet == NULL) { printf("failed to read dataset\n"); - goto exit_error; + // goto exit_error; } /* Read RCB values */ - rcb = IedConnection_getRCBValues(con, &error, "simpleIOGenericIO/LLN0.RP.EventsRCB01", NULL); + rcb = IedConnection_getRCBValues(con, &error, "testmodelSENSORS/LLN0.RP.events01", NULL); if (error != IED_ERROR_OK) { printf("getRCBValues service error!\n"); @@ -115,12 +129,12 @@ main(int argc, char** argv) /* prepare the parameters of the RCP */ ClientReportControlBlock_setResv(rcb, true); ClientReportControlBlock_setTrgOps(rcb, TRG_OPT_DATA_CHANGED | TRG_OPT_QUALITY_CHANGED | TRG_OPT_GI); - ClientReportControlBlock_setDataSetReference(rcb, "simpleIOGenericIO/LLN0$Events"); /* NOTE the "$" instead of "." ! */ + ClientReportControlBlock_setDataSetReference(rcb, "testmodelSENSORS/LLN0$DataSetST_Attr"); /* NOTE the "$" instead of "." ! */ ClientReportControlBlock_setRptEna(rcb, true); ClientReportControlBlock_setGI(rcb, true); /* Configure the report receiver */ - IedConnection_installReportHandler(con, "simpleIOGenericIO/LLN0.RP.EventsRCB", ClientReportControlBlock_getRptId(rcb), reportCallbackFunction, + IedConnection_installReportHandler(con, "testmodelSENSORS/LLN0.events01", ClientReportControlBlock_getRptId(rcb), reportCallbackFunction, (void*) dataSetDirectory); /* Write RCB parameters and enable report */ diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index 98bcf49a..3bd87ee2 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -114,6 +114,9 @@ main(int argc, char** argv) /* configuration object is no longer required */ IedServerConfig_destroy(config); + /* set the identity values for MMS identify service */ + IedServer_setServerIdentity(iedServer, "MZ", "basic io", "1.4.2"); + /* Install handler for operate command */ IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1, (ControlHandler) controlHandlerForBinaryOutput, diff --git a/examples/server_example_logging/server_example_logging.c b/examples/server_example_logging/server_example_logging.c index 938b6aa3..e535c75b 100644 --- a/examples/server_example_logging/server_example_logging.c +++ b/examples/server_example_logging/server_example_logging.c @@ -33,10 +33,12 @@ sigint_handler(int signalId) } static ControlHandlerResult -controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test) +controlHandlerForBinaryOutput(ControlAction action, void* parameter, MmsValue* value, bool test) { - if (test) + if (test) { + printf("Received test command\n"); return CONTROL_RESULT_FAILED; + } if (MmsValue_getType(value) == MMS_BOOLEAN) { printf("received binary control command: "); @@ -148,7 +150,7 @@ main(int argc, char** argv) IedServer_setLogStorage(iedServer, "GenericIO/LLN0$EventLog", statusLog); -#if 0 +#if 1 uint64_t entryID = LogStorage_addEntry(statusLog, Hal_getTimeInMs()); MmsValue* value = MmsValue_newIntegerFromInt32(123); diff --git a/hal/CMakeLists.txt b/hal/CMakeLists.txt index bdd1a9c6..65e8e404 100644 --- a/hal/CMakeLists.txt +++ b/hal/CMakeLists.txt @@ -164,3 +164,18 @@ target_link_libraries(hal ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/packet.lib ) ENDIF(WITH_WPCAP) + +set(BINDIR "bin") +set(LIBDIR "lib") +if(UNIX) + # GNUInstallDirs is required for Debian multiarch + include(GNUInstallDirs) + set(LIBDIR ${CMAKE_INSTALL_LIBDIR}) + set(BINDIR ${CMAKE_INSTALL_BINDIR}) +endif() + +install (TARGETS hal hal-shared + RUNTIME DESTINATION ${BINDIR} COMPONENT Applications + ARCHIVE DESTINATION ${LIBDIR} COMPONENT Libraries + LIBRARY DESTINATION ${LIBDIR} COMPONENT Libraries +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 52cd5503..ff2ee08b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -345,6 +345,15 @@ target_link_libraries(iec61850-shared ) ENDIF(WITH_WPCAP) +find_package(Doxygen) +if(DOXYGEN_FOUND) + configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) + add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Generating API documentation with Doxygen" VERBATIM) + + configure_file(doxygen/Doxyfile.NET.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.NET @ONLY) + add_custom_target(doc-net ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.NET WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Generating API documentation with Doxygen" VERBATIM) + +endif(DOXYGEN_FOUND) set(BINDIR "bin") set(LIBDIR "lib") @@ -361,18 +370,9 @@ if(UNIX) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libiec61850.pc" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/pkgconfig") endif() -find_package(Doxygen) -if(DOXYGEN_FOUND) - configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) - add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Generating API documentation with Doxygen" VERBATIM) - - configure_file(doxygen/Doxyfile.NET.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.NET @ONLY) - add_custom_target(doc-net ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.NET WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Generating API documentation with Doxygen" VERBATIM) - -endif(DOXYGEN_FOUND) - install (TARGETS iec61850 iec61850-shared RUNTIME DESTINATION ${BINDIR} COMPONENT Applications ARCHIVE DESTINATION ${LIBDIR} COMPONENT Libraries LIBRARY DESTINATION ${LIBDIR} COMPONENT Libraries ) + diff --git a/src/common/byte_buffer.c b/src/common/byte_buffer.c index 9c1a4f9d..b3747d11 100644 --- a/src/common/byte_buffer.c +++ b/src/common/byte_buffer.c @@ -61,8 +61,9 @@ ByteBuffer_append(ByteBuffer* self, uint8_t* data, int dataSize) self->size += dataSize; return dataSize; } - else + else { return -1; + } } int diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index afae2565..cbd10383 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -650,7 +650,15 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) matchingSubscriber->confRev = confRev; matchingSubscriber->ndsCom = ndsCom; matchingSubscriber->simulation = simulation; - MmsValue_setUtcTimeByBuffer(matchingSubscriber->timestamp, timestampBufPos); + + if (timestampBufPos) + MmsValue_setUtcTimeByBuffer(matchingSubscriber->timestamp, timestampBufPos); + else { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: GOOSE message has no time stamp\n"); + + MmsValue_setUtcTime(matchingSubscriber->timestamp, 0); + } if (matchingSubscriber->dataSetValues == NULL) matchingSubscriber->dataSetValues = parseAllDataUnknownValue(matchingSubscriber, dataSetBufferAddress, dataSetBufferLength, false); diff --git a/src/iec61850/client/client_report_control.c b/src/iec61850/client/client_report_control.c index 07f6df0c..5373c417 100644 --- a/src/iec61850/client/client_report_control.c +++ b/src/iec61850/client/client_report_control.c @@ -438,7 +438,8 @@ clientReportControlBlock_updateValues(ClientReportControlBlock self, MmsValue* v if (!checkElementType(values, 12, MMS_BINARY_TIME)) return false; if (rcbElementCount == 14) { - if (!checkElementType(values, 13, MMS_OCTET_STRING)) return false; + if (!checkElementType(values, 13, MMS_OCTET_STRING) && !checkElementType(values, 13, MMS_INTEGER)) + return false; } else if (rcbElementCount == 15) { if (!checkElementType(values, 13, MMS_INTEGER)) return false; diff --git a/src/iec61850/inc/iec61850_cdc.h b/src/iec61850/inc/iec61850_cdc.h index 8615af13..04a077bf 100644 --- a/src/iec61850/inc/iec61850_cdc.h +++ b/src/iec61850/inc/iec61850_cdc.h @@ -496,11 +496,33 @@ CDC_ENC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, LIB61850_API DataObject* CDC_BSC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool hasTransientIndicator); +/** + * \brief Integer controlled step position information (ISC) + * + * CDC_OPTION_IS_TIME_ACTICATED + * + * substitution options + * CDC_OPTION_BLK_ENA + * standard description and namespace options + * + * \param dataObjectName the name of the new object + * \param parent the parent of the new data object (either a LogicalNode or another DataObject) + * \param options bit mask to encode required optional elements + * \param controlOptions specify which control model to set as default and other control specific options + * \param hasTransientIndicator specifies if the step position information contains the transient indicator + * + */ +LIB61850_API DataObject* +CDC_ISC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool hasTransientIndicator); + /** * \brief Controllable analogue process value (APC) * * CDC_OPTION_IS_TIME_ACTICATED * + * CDC_OPTION_MIN + * CDC_OPTION_MAX + * * substitution options * CDC_OPTION_BLK_ENA * standard description and namespace options @@ -514,6 +536,28 @@ CDC_BSC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, LIB61850_API DataObject* CDC_APC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat); +/** + * \brief Binary controlled ananlogue process value (BAC) + * + * CDC_OPTION_IS_TIME_ACTICATED + * + * CDC_OPTION_MIN + * CDC_OPTION_MAX + * CDC_OPTION_STEP_SIZE + * + * substitution options + * CDC_OPTION_BLK_ENA + * standard description and namespace options + * + * \param dataObjectName the name of the new object + * \param parent the parent of the new data object (either a LogicalNode or another DataObject) + * \param options bit mask to encode required optional elements + * \param controlOptions specify which control model to set as default and other control specific options + * \param isIntegerNotFloat + */ +LIB61850_API DataObject* +CDC_BAC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat); + /** Minimum measured value */ #define CDC_OPTION_61400_MIN_MX_VAL (1 << 10) diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index f1d653c2..83a13cf6 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -347,6 +347,19 @@ IedServer_destroy(IedServer self); LIB61850_API void IedServer_setLocalIpAddress(IedServer self, const char* localIpAddress); +/** + * \brief Set the identify for the MMS identify service + * + * CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY required + * + * \param self the IedServer instance + * \param vendor the IED vendor name + * \param model the IED model name + * \param revision the IED revision/version number + */ +LIB61850_API void +IedServer_setServerIdentity(IedServer self, const char* vendor, const char* model, const char* revision); + /** * \brief Set the virtual filestore basepath for the MMS file services * diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index 55271c85..e866a503 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -61,6 +61,12 @@ struct sIedServer Thread serverThread; #endif +#if (CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY == 1) + char* vendorName; + char* modelName; + char* revision; +#endif + uint8_t edition; bool running; diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index 24cc22c1..2606cf5c 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -65,7 +65,6 @@ struct sMmsMapping { #if (CONFIG_MMS_THREADLESS_STACK != 1) bool reportThreadRunning; - bool reportThreadFinished; Thread reportWorkerThread; #endif diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index be5a1293..96dc362b 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -69,9 +69,9 @@ typedef struct { bool gi; /* flag to indicate that a GI report is triggered */ uint16_t sqNum; - uint32_t intgPd; + uint32_t intgPd; /* integrity period in ms */ uint32_t bufTm; - uint64_t nextIntgReportTime; + uint64_t nextIntgReportTime; /* time when to send next integrity report */ uint64_t reservationTimeout; MmsServerConnection clientConnection; diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 91e9d3ef..f31bdf9f 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -517,28 +517,18 @@ IedServer_destroy(IedServer self) #endif } - MmsServer_destroy(self->mmsServer); - - if (self->localIpAddress != NULL) - GLOBAL_FREEMEM(self->localIpAddress); - #if ((CONFIG_MMS_SINGLE_THREADED == 1) && (CONFIG_MMS_THREADLESS_STACK == 0)) - /* trigger stopping background task thread */ - if (self->mmsMapping->reportThreadRunning) { - self->mmsMapping->reportThreadRunning = false; - - /* waiting for thread to finish */ - while (self->mmsMapping->reportThreadFinished == false) { - Thread_sleep(10); - } - } - if (self->serverThread) Thread_destroy(self->serverThread); #endif + MmsServer_destroy(self->mmsServer); + + if (self->localIpAddress != NULL) + GLOBAL_FREEMEM(self->localIpAddress); + MmsMapping_destroy(self->mmsMapping); LinkedList_destroyDeep(self->clientConnections, (LinkedListValueDeleteFunction) private_ClientConnection_destroy); @@ -548,6 +538,18 @@ IedServer_destroy(IedServer self) Semaphore_destroy(self->clientConnectionsLock); #endif +#if (CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY == 1) + + if (self->vendorName) + GLOBAL_FREEMEM(self->vendorName); + + if (self->modelName) + GLOBAL_FREEMEM(self->modelName); + + if (self->revision) + GLOBAL_FREEMEM(self->revision); +#endif /* (CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY == 1) */ + GLOBAL_FREEMEM(self); } @@ -587,8 +589,6 @@ singleThreadedServerThread(void* parameter) if (DEBUG_IED_SERVER) printf("IED_SERVER: server thread finished!\n"); - - mmsMapping->reportThreadFinished = true; } #endif /* (CONFIG_MMS_SINGLE_THREADED == 1) */ #endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ @@ -602,7 +602,6 @@ IedServer_start(IedServer self, int tcpPort) #if (CONFIG_MMS_SINGLE_THREADED == 1) MmsServer_startListeningThreadless(self->mmsServer, tcpPort); - self->mmsMapping->reportThreadFinished = false; self->mmsMapping->reportThreadRunning = true; self->serverThread = Thread_create((ThreadExecutionFunction) singleThreadedServerThread, (void*) self, false); @@ -640,9 +639,10 @@ IedServer_stop(IedServer self) MmsMapping_stopEventWorkerThread(self->mmsMapping); #if (CONFIG_MMS_SINGLE_THREADED == 1) - MmsServer_stopListeningThreadless(self->mmsServer); Thread_destroy(self->serverThread); self->serverThread = NULL; + + MmsServer_stopListeningThreadless(self->mmsServer); #else MmsServer_stopListening(self->mmsServer); #endif @@ -1528,6 +1528,28 @@ IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorag #endif } +void +IedServer_setServerIdentity(IedServer self, const char* vendor, const char* model, const char* revision) +{ +#if (CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY == 1) + + if (self->vendorName) + GLOBAL_FREEMEM(self->vendorName); + + if (self->modelName) + GLOBAL_FREEMEM(self->modelName); + + if (self->revision) + GLOBAL_FREEMEM(self->revision); + + self->vendorName = StringUtils_copyString(vendor); + self->modelName = StringUtils_copyString(model); + self->revision = StringUtils_copyString(revision); + + MmsServer_setServerIdentity(self->mmsServer, self->vendorName, self->modelName, self->revision); +#endif +} + ClientConnection private_IedServer_getClientConnectionByHandle(IedServer self, void* serverConnectionHandle) { diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index e78a1894..af289121 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -1211,11 +1211,13 @@ ControlObject_sendLastApplError(ControlObject* self, MmsServerConnection connect } static void -updateControlParameters(ControlObject* controlObject, MmsValue* ctlVal, MmsValue* ctlNum, MmsValue* origin) +updateControlParameters(ControlObject* controlObject, MmsValue* ctlVal, MmsValue* ctlNum, MmsValue* origin, bool synchroCheck, bool interlockCheck) { MmsValue_update(controlObject->ctlVal, ctlVal); MmsValue_update(controlObject->ctlNum, ctlNum); MmsValue_update(controlObject->origin, origin); + controlObject->synchroCheck = synchroCheck; + controlObject->interlockCheck = interlockCheck; if (controlObject->ctlNumSt) MmsValue_update(controlObject->ctlNumSt, ctlNum); @@ -1507,6 +1509,13 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if (checkValidityOfOriginParameter(origin) == false) { indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + + ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, + ADD_CAUSE_SELECT_FAILED, ctlNum, origin, true); + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: SBOw - invalid origin value\n"); + goto free_and_return; } @@ -1519,12 +1528,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if (state != STATE_UNSELECTED) { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; - if (connection != controlObject->mmsConnection) - ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, - ADD_CAUSE_LOCKED_BY_OTHER_CLIENT, ctlNum, origin, true); - else - ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, - ADD_CAUSE_OBJECT_ALREADY_SELECTED, ctlNum, origin, true); + ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, + ADD_CAUSE_OBJECT_ALREADY_SELECTED, ctlNum, origin, true); if (DEBUG_IED_SERVER) printf("IED_SERVER: SBOw - select failed!\n"); @@ -1536,11 +1541,13 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari /* opRcvd must not be set here! */ bool interlockCheck = MmsValue_getBitStringBit(check, 1); - + bool synchroCheck = MmsValue_getBitStringBit(check, 0); bool testCondition = MmsValue_getBoolean(test); controlObject->addCauseValue = ADD_CAUSE_SELECT_FAILED; + updateControlParameters(controlObject, ctlVal, ctlNum, origin, interlockCheck, synchroCheck); + if (controlObject->checkHandler != NULL) { /* perform operative tests */ controlObject->isSelect = 1; @@ -1554,8 +1561,6 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if (checkResult == CONTROL_ACCEPTED) { selectObject(controlObject, currentTime, connection); - updateControlParameters(controlObject, ctlVal, ctlNum, origin); - indication = DATA_ACCESS_ERROR_SUCCESS; if (DEBUG_IED_SERVER) @@ -1609,6 +1614,15 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari if (checkValidityOfOriginParameter(origin) == false) { indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + + if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) { + ControlObject_sendLastApplError(controlObject, connection, "Oper", + CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS, + ctlNum, origin, true); + + unselectObject(controlObject); + } + goto free_and_return; } @@ -1641,13 +1655,20 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; if (DEBUG_IED_SERVER) printf("IED_SERVER: Oper - operate from wrong client connection!\n"); + + ControlObject_sendLastApplError(controlObject, connection, "Oper", CONTROL_ERROR_NO_ERROR, + ADD_CAUSE_LOCKED_BY_OTHER_CLIENT, ctlNum, origin, true); + goto free_and_return; } if (controlObject->ctlModel == 4) { /* select-before-operate with enhanced security */ if ((MmsValue_equals(ctlVal, controlObject->ctlVal) && MmsValue_equals(origin, controlObject->origin) && - MmsValue_equals(ctlNum, controlObject->ctlNum)) == false) + MmsValue_equals(ctlNum, controlObject->ctlNum) && + (controlObject->interlockCheck == interlockCheck) && + (controlObject->synchroCheck == synchroCheck) + ) == false) { indication = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; @@ -1655,12 +1676,14 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS, ctlNum, origin, true); + unselectObject(controlObject); + goto free_and_return; } } } - updateControlParameters(controlObject, ctlVal, ctlNum, origin); + updateControlParameters(controlObject, ctlVal, ctlNum, origin, synchroCheck, interlockCheck); MmsValue* operTm = getOperParameterOperTime(value); @@ -1738,6 +1761,9 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari setOpRcvd(controlObject, false); abortControlOperation(controlObject); + + if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) + unselectObject(controlObject); } } @@ -1784,6 +1810,9 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* vari ctlNum, origin, true); } } + else { + indication = DATA_ACCESS_ERROR_SUCCESS; + } } if (controlObject->timeActivatedOperate) { diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 5a097453..be3c7cd0 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1290,9 +1290,9 @@ createMmsModelFromIedModel(MmsMapping* self, IedModel* iedModel) { MmsDevice* mmsDevice = NULL; - if (iedModel->firstChild != NULL) { + mmsDevice = MmsDevice_create(iedModel->name); - mmsDevice = MmsDevice_create(iedModel->name); + if (iedModel->firstChild != NULL) { int iedDeviceCount = IedModel_getLogicalDeviceCount(iedModel); @@ -1352,7 +1352,7 @@ MmsMapping_destroy(MmsMapping* self) { #if (CONFIG_MMS_THREADLESS_STACK != 1) - if (self->reportWorkerThread != NULL) { + if (self->reportWorkerThread) { self->reportThreadRunning = false; Thread_destroy(self->reportWorkerThread); } @@ -2752,31 +2752,34 @@ DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index) #if (CONFIG_IEC61850_LOG_SERVICE == 1) static bool -DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef, const char* iedName) +DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef, const char* iedName, int* index) { int i = 0; - DataSetEntry* dataSetEntry = dataSet->fcdas; + DataSetEntry* dataSetEntry = dataSet->fcdas; - while (dataSetEntry != NULL) { + while (dataSetEntry != NULL) { - MmsValue* dataSetValue = dataSetEntry->value; + MmsValue *dataSetValue = dataSetEntry->value; - if (dataSetValue != NULL) { /* prevent invalid data set members */ - if (isMemberValueRecursive(dataSetValue, value)) { - if (dataRef != NULL) - sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName); + if (dataSetValue != NULL) { /* prevent invalid data set members */ + if (isMemberValueRecursive(dataSetValue, value)) { + if (dataRef != NULL) + sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName); - return true; - } - } + if (index) + *index = i; - i++; + return true; + } + } + + i++; - dataSetEntry = dataSetEntry->sibling; - } + dataSetEntry = dataSetEntry->sibling; + } - return false; + return false; } void @@ -2824,10 +2827,33 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl char dataRef[130]; - if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef, self->model->name)) { + int dsEntryIdx = 0; + + if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef, self->model->name, &dsEntryIdx)) { if (lc->logInstance != NULL) { - LogInstance_logSingleData(lc->logInstance, dataRef, value, reasonCode); + + if (lc->dataSet) { + + DataSetEntry* dsEntry = lc->dataSet->fcdas; + + while (dsEntry && (dsEntryIdx > 0)) { + dsEntry = dsEntry->sibling; + + if (dsEntry == NULL) + break; + + dsEntryIdx--; + } + + if (dsEntry) { + MmsValue* dsValue = dsEntry->value; + + LogInstance_logSingleData(lc->logInstance, dataRef, dsValue, reasonCode); + } + + } + } else { if (DEBUG_IED_SERVER) @@ -3044,7 +3070,6 @@ static void eventWorkerThread(MmsMapping* self) { bool running = true; - self->reportThreadFinished = false; while (running) { @@ -3057,8 +3082,6 @@ eventWorkerThread(MmsMapping* self) if (DEBUG_IED_SERVER) printf("IED_SERVER: event worker thread finished!\n"); - - self->reportThreadFinished = true; } void @@ -3078,8 +3101,10 @@ MmsMapping_stopEventWorkerThread(MmsMapping* self) self->reportThreadRunning = false; - while (self->reportThreadFinished == false) - Thread_sleep(1); + if (self->reportWorkerThread) { + Thread_destroy(self->reportWorkerThread); + self->reportWorkerThread = NULL; + } } } #endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index ea73a7d8..f679ab40 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -615,7 +615,7 @@ refreshIntegrityPeriod(ReportControl* rc) rc->intgPd = MmsValue_toUint32(intgPd); if (rc->buffered == false) - rc->nextIntgReportTime = 0; + rc->nextIntgReportTime = Hal_getTimeInMs() + rc->intgPd; } static void @@ -1365,6 +1365,11 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } } + if ((rc->reserved) && (rc->clientConnection != connection)) { + retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + goto exit_function; + } + if (strcmp(elementName, "RptEna") == 0) { if (value->value.boolean == true) { @@ -2104,6 +2109,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ ReportBufferEntry* entry = (ReportBufferEntry*) entryBufPos; + entry->timeOfEntry = timeOfEntry; + if (isBuffered) { /* ENTRY_ID is set to system time in ms! */ uint64_t entryId = timeOfEntry; @@ -2111,8 +2118,6 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ if (entryId <= reportControl->lastEntryId) entryId = reportControl->lastEntryId + 1; - entry->timeOfEntry = entryId; - #if (ORDER_LITTLE_ENDIAN == 1) memcpyReverseByteOrder(entry->entryId, (uint8_t*) &entryId, 8); #else diff --git a/src/iec61850/server/model/cdc.c b/src/iec61850/server/model/cdc.c index 9b5038b2..f3f27f11 100644 --- a/src/iec61850/server/model/cdc.c +++ b/src/iec61850/server/model/cdc.c @@ -554,6 +554,19 @@ addOriginatorAndCtlNumOptions(ModelNode* parent, uint32_t controlOptions) DataAttribute_create("ctlNum", parent, IEC61850_INT8U, IEC61850_FC_ST, 0, 0, 0); } +static void +addCommonControlAttributes(DataObject* dobj, uint32_t controlOptions) +{ + if (controlOptions & CDC_CTL_OPTION_OP_RCVD) + DataAttribute_create("opRcvd", (ModelNode*) dobj, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0); + + if (controlOptions & CDC_CTL_OPTION_OP_OK) + DataAttribute_create("opOk", (ModelNode*) dobj, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0); + + if (controlOptions & CDC_CTL_OPTION_T_OP_OK) + DataAttribute_create("tOpOk", (ModelNode*) dobj, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0); +} + /** * * CDC_OPTION_IS_TIME_ACTICATED @@ -574,6 +587,11 @@ CDC_SPC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, addControls(newSPC, IEC61850_BOOLEAN, controlOptions); + if (controlOptions & CDC_CTL_OPTION_ST_SELD) + DataAttribute_create("stSeld", (ModelNode*) newSPC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0); + + addCommonControlAttributes(newSPC, controlOptions); + if (options & CDC_OPTION_PICS_SUBST) CDC_addOptionPicsSubst(newSPC, IEC61850_BOOLEAN); @@ -605,6 +623,11 @@ CDC_DPC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, addControls(newDPC, IEC61850_BOOLEAN, controlOptions); + if (controlOptions & CDC_CTL_OPTION_ST_SELD) + DataAttribute_create("stSeld", (ModelNode*) newDPC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0); + + addCommonControlAttributes(newDPC, controlOptions); + if (options & CDC_OPTION_PICS_SUBST) CDC_addOptionPicsSubst(newDPC, IEC61850_CODEDENUM); @@ -661,16 +684,22 @@ addAnalogControls(DataObject* parent, uint32_t controlOptions, bool isIntegerNot } } +static void +addControlStatusAttributesForAnalogControl(DataObject* dobj, uint32_t controlOptions) +{ + if (controlOptions & CDC_CTL_OPTION_ORIGIN) + addOriginator("origin", (ModelNode*) dobj, IEC61850_FC_MX); + + if (controlOptions & CDC_CTL_OPTION_CTL_NUM) + DataAttribute_create("ctlNum", (ModelNode*) dobj, IEC61850_INT8U, IEC61850_FC_MX, 0, 0, 0); +} + DataObject* CDC_APC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat) { DataObject* newAPC = DataObject_create(dataObjectName, parent, 0); - if (controlOptions & CDC_CTL_OPTION_ORIGIN) - addOriginator("origin", (ModelNode*) newAPC, IEC61850_FC_MX); - - if (controlOptions & CDC_CTL_OPTION_CTL_NUM) - DataAttribute_create("ctlNum", (ModelNode*) newAPC, IEC61850_INT8U, IEC61850_FC_MX, 0, 0, 0); + addControlStatusAttributesForAnalogControl(newAPC, controlOptions); CAC_AnalogueValue_create("mxVal", (ModelNode*) newAPC, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, isIntegerNotFloat); @@ -679,14 +708,7 @@ CDC_APC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, if (controlOptions & CDC_CTL_OPTION_ST_SELD) DataAttribute_create("stSeld", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, 0, 0); - if (controlOptions & CDC_CTL_OPTION_OP_RCVD) - DataAttribute_create("opRcvd", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0); - - if (controlOptions & CDC_CTL_OPTION_OP_OK) - DataAttribute_create("opOk", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0); - - if (controlOptions & CDC_CTL_OPTION_T_OP_OK) - DataAttribute_create("tOpOk", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0); + addCommonControlAttributes(newAPC, controlOptions); if (options & CDC_OPTION_PICS_SUBST) { DataAttribute_create("subEna", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_SV, 0, 0, 0); @@ -705,7 +727,6 @@ CDC_APC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, return newAPC; } - DataObject* CDC_INC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions) { @@ -717,6 +738,11 @@ CDC_INC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, addControls(newINC, IEC61850_INT32, controlOptions); + if (controlOptions & CDC_CTL_OPTION_ST_SELD) + DataAttribute_create("stSeld", (ModelNode*) newINC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0); + + addCommonControlAttributes(newINC, controlOptions); + if (options & CDC_OPTION_PICS_SUBST) CDC_addOptionPicsSubst(newINC, IEC61850_INT32); @@ -748,6 +774,11 @@ CDC_ENC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, addControls(newENC, IEC61850_ENUMERATED, controlOptions); + if (controlOptions & CDC_CTL_OPTION_ST_SELD) + DataAttribute_create("stSeld", (ModelNode*) newENC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0); + + addCommonControlAttributes(newENC, controlOptions); + if (options & CDC_OPTION_PICS_SUBST) CDC_addOptionPicsSubst(newENC, IEC61850_ENUMERATED); @@ -773,6 +804,11 @@ CDC_BSC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, addControls(newBSC, IEC61850_CODEDENUM, controlOptions); + if (controlOptions & CDC_CTL_OPTION_ST_SELD) + DataAttribute_create("stSeld", (ModelNode*) newBSC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0); + + addCommonControlAttributes(newBSC, controlOptions); + if (options & CDC_OPTION_PICS_SUBST) CDC_addOptionPicsSubstValWithTrans(newBSC, hasTransientIndicator); @@ -784,6 +820,84 @@ CDC_BSC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, return newBSC; } +DataObject* +CDC_ISC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool hasTransientIndicator) +{ + DataObject* newISC = DataObject_create(dataObjectName, parent, 0); + + addOriginatorAndCtlNumOptions((ModelNode*) newISC, controlOptions); + + CAC_ValWithTrans_create("valWTr", (ModelNode*) newISC, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, hasTransientIndicator); + CDC_addTimeQuality(newISC, IEC61850_FC_ST); + + addControls(newISC, IEC61850_INT8, controlOptions); + + if (controlOptions & CDC_CTL_OPTION_ST_SELD) + DataAttribute_create("stSeld", (ModelNode*) newISC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0); + + addCommonControlAttributes(newISC, controlOptions); + + if (options & CDC_OPTION_PICS_SUBST) + CDC_addOptionPicsSubstValWithTrans(newISC, hasTransientIndicator); + + if (options & CDC_OPTION_BLK_ENA) + DataAttribute_create("blkEna", (ModelNode*) newISC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0); + + if (options & CDC_OPTION_MIN) + DataAttribute_create("minVal", (ModelNode*) newISC, IEC61850_INT32, IEC61850_FC_CF, 0, 0, 0); + + if (options & CDC_OPTION_MAX) + DataAttribute_create("maxVal", (ModelNode*) newISC, IEC61850_INT32, IEC61850_FC_CF, 0, 0, 0); + + CDC_addStandardOptions(newISC, options); + + return newISC; +} + +DataObject* +CDC_BAC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat) +{ + DataObject* newBAC = DataObject_create(dataObjectName, parent, 0); + + addControlStatusAttributesForAnalogControl(newBAC, controlOptions); + + CAC_AnalogueValue_create("mxVal", (ModelNode*) newBAC, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, isIntegerNotFloat); + + CDC_addTimeQuality(newBAC, IEC61850_FC_MX); + + if (controlOptions & CDC_CTL_OPTION_ST_SELD) + DataAttribute_create("stSeld", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, 0, 0); + + addControls(newBAC, IEC61850_INT8, controlOptions); + + if (options & CDC_OPTION_PICS_SUBST) { + DataAttribute_create("subEna", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_SV, 0, 0, 0); + CAC_AnalogueValue_create("subVal", (ModelNode*) newBAC, IEC61850_FC_SV, 0, isIntegerNotFloat); + DataAttribute_create("subQ", (ModelNode*) newBAC, IEC61850_QUALITY, IEC61850_FC_SV, 0, 0, 0); + DataAttribute_create("subID", (ModelNode*) newBAC, IEC61850_VISIBLE_STRING_64, IEC61850_FC_SV, 0, 0, 0); + } + + if (options & CDC_OPTION_BLK_ENA) + DataAttribute_create("blkEna", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0); + + DataAttribute_create("persistent", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0); + + addAnalogControls(newBAC, controlOptions, isIntegerNotFloat); + + if (options & CDC_OPTION_MIN) + CAC_AnalogueValue_create("minVal", (ModelNode*) newBAC, IEC61850_FC_CF, 0, isIntegerNotFloat); + + if (options & CDC_OPTION_MAX) + CAC_AnalogueValue_create("maxVal", (ModelNode*) newBAC, IEC61850_FC_CF, 0, isIntegerNotFloat); + + if (options & CDC_OPTION_STEP_SIZE) + CAC_AnalogueValue_create("stepSize", (ModelNode*) newBAC, IEC61850_FC_CF, 0, isIntegerNotFloat); + + CDC_addStandardOptions(newBAC, options); + + return newBAC; +} + DataObject* CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options) { diff --git a/src/mms/asn1/ber_encoder.c b/src/mms/asn1/ber_encoder.c index ba406dea..0a63df3d 100644 --- a/src/mms/asn1/ber_encoder.c +++ b/src/mms/asn1/ber_encoder.c @@ -436,27 +436,33 @@ BerEncoder_encodeOIDToBuffer(const char* oidString, uint8_t* buffer, int maxBufL int requiredBytes = 0; - int val2 = val; - while (val2 > 0) { - requiredBytes++; - val2 = val2 >> 7; + if (val == 0) { + buffer[encodedBytes++] = 0; } + else { + int val2 = val; + while (val2 > 0) { + requiredBytes++; + val2 = val2 >> 7; + } - while (requiredBytes > 0) { - val2 = val >> (7 * (requiredBytes - 1)); + while (requiredBytes > 0) { + val2 = val >> (7 * (requiredBytes - 1)); - val2 = val2 & 0x7f; + val2 = val2 & 0x7f; - if (requiredBytes > 1) - val2 += 128; + if (requiredBytes > 1) + val2 += 128; - if (encodedBytes == maxBufLen) - return 0; + if (encodedBytes == maxBufLen) + return 0; - buffer[encodedBytes++] = (uint8_t) val2; + buffer[encodedBytes++] = (uint8_t) val2; - requiredBytes--; + requiredBytes--; + } } + } return encodedBytes; diff --git a/src/mms/iso_mms/client/mms_client_common.c b/src/mms/iso_mms/client/mms_client_common.c index 468cd00c..1b5e1972 100644 --- a/src/mms/iso_mms/client/mms_client_common.c +++ b/src/mms/iso_mms/client/mms_client_common.c @@ -33,7 +33,15 @@ int mmsClient_write_out(void *buffer, size_t size, void *app_key) { ByteBuffer* writeBuffer = (ByteBuffer*) app_key; - return ByteBuffer_append(writeBuffer, (uint8_t*) buffer, size); + + int appendedBytes = ByteBuffer_append(writeBuffer, (uint8_t*) buffer, size); + + if (appendedBytes == -1) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: message exceeds maximum PDU size!\n"); + } + + return appendedBytes; } diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index fe41e3bf..8dff7dfc 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -4334,15 +4334,20 @@ MmsConnection_writeMultipleVariablesAsync(MmsConnection self, MmsError* mmsError invokeId = getNextInvokeId(self); - mmsClient_createWriteMultipleItemsRequest(invokeId, domainId, items, values, payload); + if (mmsClient_createWriteMultipleItemsRequest(invokeId, domainId, items, values, payload) != -1) { + MmsClientInternalParameter intParam; + intParam.ptr = NULL; - MmsClientInternalParameter intParam; - intParam.ptr = NULL; + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES, handler, parameter, intParam); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES, handler, parameter, intParam); + if (mmsError) + *mmsError = err; + } + else { + *mmsError = MMS_ERROR_RESOURCE_OTHER; + return 0; + } - if (mmsError) - *mmsError = err; exit_function: return invokeId; diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c index c979fc17..4d80c613 100644 --- a/src/mms/iso_mms/client/mms_client_journals.c +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -67,7 +67,7 @@ parseJournalVariable(uint8_t* buffer, int bufPos, int maxLength, MmsJournalVaria case 0xa1: /* valueSpec */ if (journalVariable->value == NULL) { - journalVariable->value = MmsValue_decodeMmsData(buffer, bufPos, length, NULL); + journalVariable->value = MmsValue_decodeMmsData(buffer, bufPos, bufPos + length, NULL); } break; diff --git a/src/mms/iso_mms/client/mms_client_write.c b/src/mms/iso_mms/client/mms_client_write.c index ec88c8a6..e1e62239 100644 --- a/src/mms/iso_mms/client/mms_client_write.c +++ b/src/mms/iso_mms/client/mms_client_write.c @@ -319,7 +319,6 @@ mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, const char* domainI asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); return rval.encoded; - } int diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 2b6e5f07..e63321a2 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -744,11 +744,13 @@ MmsValue_getUtcTimeQuality(const MmsValue* self) void MmsValue_setUtcTimeByBuffer(MmsValue* self, const uint8_t* buffer) { - uint8_t* valueArray = self->value.utcTime; + if (buffer) { + uint8_t* valueArray = self->value.utcTime; - int i; - for (i = 0; i < 8; i++) { - valueArray[i] = buffer[i]; + int i; + for (i = 0; i < 8; i++) { + valueArray[i] = buffer[i]; + } } } @@ -2007,6 +2009,9 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) return buffer; } + if (bufferSize) + buffer[0] = 0; + switch (MmsValue_getType(self)) { case MMS_STRUCTURE: @@ -2105,7 +2110,7 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) break; case MMS_INTEGER: - snprintf(buffer, bufferSize, "%i", MmsValue_toInt32(self)); + snprintf(buffer, bufferSize, "%lld", (long long) MmsValue_toInt64(self)); break; case MMS_OCTET_STRING: diff --git a/src/mms/iso_mms/server/mms_named_variable_list_service.c b/src/mms/iso_mms/server/mms_named_variable_list_service.c index 5e4f0704..72d30f90 100644 --- a/src/mms/iso_mms/server/mms_named_variable_list_service.c +++ b/src/mms/iso_mms/server/mms_named_variable_list_service.c @@ -601,7 +601,7 @@ exit_free_struct: #if (MMS_GET_DATA_SET_ATTRIBUTES == 1) -static void +static bool createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, MmsNamedVariableList variableList) { @@ -658,9 +658,14 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, variable = LinkedList_getNext(variable); } - der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response); + asn_enc_rval_t res = der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response); asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + + if (res.encoded == -1) + return false; + else + return true; } void @@ -705,8 +710,16 @@ mmsServer_handleGetNamedVariableListAttributesRequest( MmsNamedVariableList variableList = MmsDomain_getNamedVariableList(domain, itemName); - if (variableList != NULL) - createGetNamedVariableListAttributesResponse(invokeId, response, variableList); + if (variableList != NULL) { + + if (createGetNamedVariableListAttributesResponse(invokeId, response, variableList) == false) { + + /* encoding failed - probably because buffer size is too small for message */ + ByteBuffer_setSize(response, 0); + + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + } + } else mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } diff --git a/src/mms/iso_mms/server/mms_server_common.c b/src/mms/iso_mms/server/mms_server_common.c index 96709b6c..b28bcce9 100644 --- a/src/mms/iso_mms/server/mms_server_common.c +++ b/src/mms/iso_mms/server/mms_server_common.c @@ -1,7 +1,7 @@ /* * mms_server_common.c * - * Copyright 2013-2016 Michael Zillgith + * Copyright 2013-2020 Michael Zillgith * * This file is part of libIEC61850. * @@ -29,7 +29,15 @@ int mmsServer_write_out(const void *buffer, size_t size, void *app_key) { ByteBuffer* writeBuffer = (ByteBuffer*) app_key; - return ByteBuffer_append(writeBuffer, (uint8_t*) buffer, size); + + int appendedBytes = ByteBuffer_append(writeBuffer, (uint8_t*) buffer, size); + + if (appendedBytes == -1) { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: message exceeds maximum PDU size!\n"); + } + + return appendedBytes; } MmsPdu_t* diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index 07e89acd..9b3b3253 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -541,6 +541,8 @@ isoServerThread(void* isoServerParam) if (DEBUG_ISO_SERVER) printf("ISO_SERVER: starting server failed!\n"); + self->serverSocket = NULL; + goto cleanUp; } @@ -556,8 +558,7 @@ isoServerThread(void* isoServerParam) self->state = ISO_SVR_STATE_STOPPED; - cleanUp: - self->serverSocket = NULL; +cleanUp: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: isoServerThread %p stopped\n", &isoServerParam); @@ -758,11 +759,16 @@ IsoServer_stopListeningThreadless(IsoServer self) void IsoServer_stopListening(IsoServer self) { - stopListening(self); + setState(self, ISO_SVR_STATE_STOPPED); if (self->serverThread != NULL) Thread_destroy(self->serverThread); + if (self->serverSocket != NULL) { + ServerSocket_destroy((ServerSocket) self->serverSocket); + self->serverSocket = NULL; + } + closeAllOpenClientConnections(self); /* Wait for connection threads to finish */