From c16314c426caf9da48d3f834d2c1bbbb2407177a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 29 Jun 2023 16:54:54 +0100 Subject: [PATCH 01/53] - config file parser: added support for arrays of basic and complex data attributes including initialization (LIB61850-415) --- .../server/model/config_file_parser.c | 295 +++++++++++------- 1 file changed, 189 insertions(+), 106 deletions(-) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index 8f2abeca..55a9f937 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -1,7 +1,7 @@ /* * config_file_parser.c * - * Copyright 2014-2022 Michael Zillgith + * Copyright 2014-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -28,6 +28,8 @@ #include "libiec61850_platform_includes.h" #include "stack_config.h" +#include + #define READ_BUFFER_MAX_SIZE 1024 static uint8_t lineBuffer[READ_BUFFER_MAX_SIZE]; @@ -114,6 +116,112 @@ ConfigFileParser_createModelFromConfigFileEx(const char* filename) return model; } +static bool +setValue(char* lineBuffer, DataAttribute* dataAttribute) +{ + char* valueIndicator = strchr((char*) lineBuffer, '='); + + if (valueIndicator != NULL) { + switch (dataAttribute->type) { + case IEC61850_UNICODE_STRING_255: + { + char* stringStart = valueIndicator + 2; + terminateString(stringStart, '"'); + dataAttribute->mmsValue = MmsValue_newMmsString(stringStart); + } + break; + + case IEC61850_VISIBLE_STRING_255: + case IEC61850_VISIBLE_STRING_129: + case IEC61850_VISIBLE_STRING_65: + case IEC61850_VISIBLE_STRING_64: + case IEC61850_VISIBLE_STRING_32: + case IEC61850_CURRENCY: + { + char* stringStart = valueIndicator + 2; + terminateString(stringStart, '"'); + dataAttribute->mmsValue = MmsValue_newVisibleString(stringStart); + } + break; + + case IEC61850_INT8: + case IEC61850_INT16: + case IEC61850_INT32: + case IEC61850_INT64: + case IEC61850_INT128: + case IEC61850_ENUMERATED: + { + int32_t intValue; + if (sscanf(valueIndicator + 1, "%i", &intValue) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newIntegerFromInt32(intValue); + } + break; + + case IEC61850_INT8U: + case IEC61850_INT16U: + case IEC61850_INT24U: + case IEC61850_INT32U: + { + uint32_t uintValue; + if (sscanf(valueIndicator + 1, "%u", &uintValue) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newUnsignedFromUint32(uintValue); + } + break; + + case IEC61850_FLOAT32: + { + float floatValue; + if (sscanf(valueIndicator + 1, "%f", &floatValue) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newFloat(floatValue); + } + break; + + case IEC61850_FLOAT64: + { + double doubleValue; + if (sscanf(valueIndicator + 1, "%lf", &doubleValue) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newDouble(doubleValue); + } + break; + + case IEC61850_BOOLEAN: + { + int boolean; + if (sscanf(valueIndicator + 1, "%i", &boolean) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newBoolean((bool) boolean); + } + break; + + case IEC61850_OPTFLDS: + { + int value; + if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newBitString(-10); + MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value); + } + break; + + case IEC61850_TRGOPS: + { + int value; + if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error; + dataAttribute->mmsValue = MmsValue_newBitString(-6); + MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value); + } + break; + + default: + break; + + } + } + + return true; + +exit_error: + return false; +} + IedModel* ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) { @@ -121,11 +229,14 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) bool stateInModel = false; int indendation = 0; + bool inArray = false; + bool inArrayElement = false; IedModel* model = NULL; LogicalDevice* currentLD = NULL; LogicalNode* currentLN = NULL; ModelNode* currentModelNode = NULL; + ModelNode* currentArrayNode = NULL; DataSet* currentDataSet = NULL; GSEControlBlock* currentGoCB = NULL; SVControlBlock* currentSMVCB = NULL; @@ -144,6 +255,18 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) if (bytesRead > 0) { lineBuffer[bytesRead] = 0; + /* trim trailing spaces */ + while (bytesRead > 1) { + bytesRead--; + + if (isspace(lineBuffer[bytesRead])) { + lineBuffer[bytesRead] = 0; + } + else { + break; + } + } + if (stateInModel) { if (StringUtils_startsWith((char*) lineBuffer, "}")) { @@ -161,11 +284,21 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) indendation = 3; } else if (indendation > 4) { + + if (inArrayElement && currentModelNode->parent == currentArrayNode) { + inArrayElement = false; + } + else { + indendation--; + } + + if (inArray && currentModelNode == currentArrayNode) { + inArray = false; + } + currentModelNode = currentModelNode->parent; - indendation--; } } - else if (indendation == 1) { if (StringUtils_startsWith((char*) lineBuffer, "LD")) { indendation = 2; @@ -210,7 +343,9 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) int arrayElements = 0; - sscanf((char*) lineBuffer, "DO(%129s %i)", nameString, &arrayElements); + if (sscanf((char*)lineBuffer, "DO(%129s %i)", nameString, &arrayElements) != 2) { + goto exit_error; + } currentModelNode = (ModelNode*) DataObject_create(nameString, (ModelNode*) currentLN, arrayElements); @@ -218,7 +353,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) else if (StringUtils_startsWith((char*) lineBuffer, "DS")) { indendation = 4; - sscanf((char*) lineBuffer, "DS(%129s)", nameString); + if (sscanf((char*)lineBuffer, "DS(%129s)", nameString) != 1) { + goto exit_error; + } + terminateString(nameString, ')'); currentDataSet = DataSet_create(nameString, currentLN); @@ -296,7 +434,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) nameString3, confRef, fixedOffs, minTime, maxTime); indendation = 4; - } else if (StringUtils_startsWith((char*) lineBuffer, "SMVC")) { uint32_t confRev; @@ -313,7 +450,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentSMVCB = SVControlBlock_create(nameString, currentLN, nameString2, nameString3, confRev, smpMod, smpRate, optFlds, (bool) isUnicast); indendation = 4; - } #if (CONFIG_IEC61850_SETTING_GROUPS == 1) else if (StringUtils_startsWith((char*) lineBuffer, "SG")) { @@ -357,6 +493,41 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentModelNode = (ModelNode*) DataObject_create(nameString, currentModelNode, arrayElements); } + else if (StringUtils_startsWith((char*) lineBuffer, "[")) { + if (inArray == false) { + goto exit_error; + } + + int arrayIndex; + + if (sscanf((char*)lineBuffer, "[%i]", &arrayIndex) != 1) { + goto exit_error; + } + + if (StringUtils_endsWith((char*)lineBuffer, ";")) { + /* array of basic data attribute */ + ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + + if (arrayElementNode) { + setValue((char*)lineBuffer, (DataAttribute*)arrayElementNode); + } + else { + goto exit_error; + } + + } + else if (StringUtils_endsWith((char*)lineBuffer, "{")) { + /* array of constructed data attribtute */ + currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + + if (currentModelNode) { + inArrayElement = true; + } + else { + goto exit_error; + } + } + } else if (StringUtils_startsWith((char*) lineBuffer, "DA")) { int arrayElements = 0; @@ -366,108 +537,20 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) int triggerOptions = 0; uint32_t sAddr = 0; - sscanf((char*) lineBuffer, "DA(%129s %i %i %i %i %u)", nameString, &arrayElements, &attributeType, &functionalConstraint, &triggerOptions, &sAddr); + if (sscanf((char*)lineBuffer, "DA(%129s %i %i %i %i %u)", nameString, &arrayElements, &attributeType, &functionalConstraint, &triggerOptions, &sAddr) != 6) { + goto exit_error; + } DataAttribute* dataAttribute = DataAttribute_create(nameString, currentModelNode, (DataAttributeType) attributeType, (FunctionalConstraint) functionalConstraint, triggerOptions, arrayElements, sAddr); - char* valueIndicator = strchr((char*) lineBuffer, '='); - - if (valueIndicator != NULL) { - switch (dataAttribute->type) { - case IEC61850_UNICODE_STRING_255: - { - char* stringStart = valueIndicator + 2; - terminateString(stringStart, '"'); - dataAttribute->mmsValue = MmsValue_newMmsString(stringStart); - } - break; - - case IEC61850_VISIBLE_STRING_255: - case IEC61850_VISIBLE_STRING_129: - case IEC61850_VISIBLE_STRING_65: - case IEC61850_VISIBLE_STRING_64: - case IEC61850_VISIBLE_STRING_32: - case IEC61850_CURRENCY: - { - char* stringStart = valueIndicator + 2; - terminateString(stringStart, '"'); - dataAttribute->mmsValue = MmsValue_newVisibleString(stringStart); - } - break; - - case IEC61850_INT8: - case IEC61850_INT16: - case IEC61850_INT32: - case IEC61850_INT64: - case IEC61850_INT128: - case IEC61850_ENUMERATED: - { - int32_t intValue; - if (sscanf(valueIndicator + 1, "%i", &intValue) != 1) goto exit_error; - dataAttribute->mmsValue = MmsValue_newIntegerFromInt32(intValue); - } - break; - - case IEC61850_INT8U: - case IEC61850_INT16U: - case IEC61850_INT24U: - case IEC61850_INT32U: - { - uint32_t uintValue; - if (sscanf(valueIndicator + 1, "%u", &uintValue) != 1) goto exit_error; - dataAttribute->mmsValue = MmsValue_newUnsignedFromUint32(uintValue); - } - break; - - case IEC61850_FLOAT32: - { - float floatValue; - if (sscanf(valueIndicator + 1, "%f", &floatValue) != 1) goto exit_error; - dataAttribute->mmsValue = MmsValue_newFloat(floatValue); - } - break; - - case IEC61850_FLOAT64: - { - double doubleValue; - if (sscanf(valueIndicator + 1, "%lf", &doubleValue) != 1) goto exit_error; - dataAttribute->mmsValue = MmsValue_newDouble(doubleValue); - } - break; - - case IEC61850_BOOLEAN: - { - int boolean; - if (sscanf(valueIndicator + 1, "%i", &boolean) != 1) goto exit_error; - dataAttribute->mmsValue = MmsValue_newBoolean((bool) boolean); - } - break; - - case IEC61850_OPTFLDS: - { - int value; - if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error; - dataAttribute->mmsValue = MmsValue_newBitString(-10); - MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value); - } - break; - - case IEC61850_TRGOPS: - { - int value; - if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error; - dataAttribute->mmsValue = MmsValue_newBitString(-6); - MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value); - } - break; - - default: - break; - - } + if (arrayElements > 0) { + inArray = true; + currentArrayNode = (ModelNode*)dataAttribute; } + setValue((char*)lineBuffer, dataAttribute); + int lineLength = (int) strlen((char*) lineBuffer); if (lineBuffer[lineLength - 1] == '{') { @@ -541,8 +624,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) else goto exit_error; } - - } else { if (StringUtils_startsWith((char*) lineBuffer, "MODEL{")) { @@ -552,7 +633,9 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) indendation = 1; } else if (StringUtils_startsWith((char*) lineBuffer, "MODEL(")) { - sscanf((char*) lineBuffer, "MODEL(%129s)", nameString); + if (sscanf((char*)lineBuffer, "MODEL(%129s)", nameString) != 1) + goto exit_error; + terminateString(nameString, ')'); model = IedModel_create(nameString); stateInModel = true; From 178312aa139fc4349ac32720ce23b8fde6cb7910 Mon Sep 17 00:00:00 2001 From: Mikael Bourhis Date: Wed, 11 Oct 2023 11:00:28 +0200 Subject: [PATCH 02/53] Python wrapper: update CMakeLists, and rename the module to 'pyiec61850' * update the CMakeLists - remove the deprecated commands ('PythonInterp', ...) (https://cmake.org/cmake/help/latest/module/FindPythonInterp.html) - change the CMake minimum version from 3.8 to 3.12 (released in July 2018) * rename the libiec61850 Python module into 'pyiec61850' - to avoid name conflict with the name for the C static lib - the build artifacts are now 'pyiec61850.py' and '_pyiec61850.so' Signed-off-by: Mikael Bourhis --- pyiec61850/CMakeLists.txt | 33 ++++++++----------- pyiec61850/examples/dispServerStruct.py | 2 +- pyiec61850/examples/rcbSubscriptionExample.py | 2 +- pyiec61850/iec61850.i | 2 +- pyiec61850/test_pyiec61850.py | 2 +- pyiec61850/tutorial.md | 19 +++++++++-- 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/pyiec61850/CMakeLists.txt b/pyiec61850/CMakeLists.txt index 159ef4bc..ce03da9c 100644 --- a/pyiec61850/CMakeLists.txt +++ b/pyiec61850/CMakeLists.txt @@ -1,14 +1,13 @@ -# The SWIG functions/macros used in this module, swig_add_module and swig_add_library -# are not available in CMake versions earlier than 3.8 -# cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.12) + +set(CMAKE_POLICY_DEFAULT_CMP0078 NEW) find_package(SWIG REQUIRED) include(${SWIG_USE_FILE}) -find_package(PythonInterp ${BUILD_PYTHON_VERSION} REQUIRED) -find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT REQUIRED) +find_package(Python COMPONENTS Interpreter Development REQUIRED) -include_directories(${PYTHON_INCLUDE_PATH}) +include_directories(${Python_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_SWIG_FLAGS "") @@ -20,25 +19,21 @@ else() set(LIBS iec61850-shared) endif() -if(${CMAKE_VERSION} VERSION_LESS 3.8) - swig_add_module(iec61850 python iec61850.i) -else() - swig_add_library(iec61850 - LANGUAGE python - SOURCES iec61850.i - ) -endif() +swig_add_library(pyiec61850 + LANGUAGE python + SOURCES iec61850.i +) -swig_link_libraries(iec61850 ${PYTHON_LIBRARIES} ${LIBS}) +swig_link_libraries(pyiec61850 ${LIBS}) # Finding python modules install path execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c + COMMAND ${Python_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib())" OUTPUT_VARIABLE PYTHON_SITE_DIR ) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/iec61850.py DESTINATION ${PYTHON_SITE_DIR}) -install(TARGETS _iec61850 LIBRARY DESTINATION ${PYTHON_SITE_DIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyiec61850.py DESTINATION ${PYTHON_SITE_DIR}) +install(TARGETS pyiec61850 LIBRARY DESTINATION ${PYTHON_SITE_DIR}) -add_test(test_pyiec61850 ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/test_pyiec61850.py) +add_test(test_pyiec61850 ${Python_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/test_pyiec61850.py) diff --git a/pyiec61850/examples/dispServerStruct.py b/pyiec61850/examples/dispServerStruct.py index aea02b4f..2b49479b 100755 --- a/pyiec61850/examples/dispServerStruct.py +++ b/pyiec61850/examples/dispServerStruct.py @@ -1,6 +1,6 @@ #!/usr/bin/python import os,sys -import iec61850 +import pyiec61850 as iec61850 if __name__=="__main__": hostname = "localhost"; tcpPort = 102 diff --git a/pyiec61850/examples/rcbSubscriptionExample.py b/pyiec61850/examples/rcbSubscriptionExample.py index d4d05aa5..7d1243be 100644 --- a/pyiec61850/examples/rcbSubscriptionExample.py +++ b/pyiec61850/examples/rcbSubscriptionExample.py @@ -27,7 +27,7 @@ The user needs to: import time import sys -import iec61850 +import pyiec61850 as iec61850 def open_connection(ip_address, mms_port): diff --git a/pyiec61850/iec61850.i b/pyiec61850/iec61850.i index 254fec64..4cdbe0ea 100644 --- a/pyiec61850/iec61850.i +++ b/pyiec61850/iec61850.i @@ -1,5 +1,5 @@ /* File : iec61850.i */ -%module(directors="1") iec61850 +%module(directors="1") pyiec61850 %ignore ControlObjectClient_setTestMode(ControlObjectClient self); %ignore CDA_OperBoolean(ModelNode* parent, bool isTImeActivated); %ignore LogicalNode_hasBufferedReports(LogicalNode* node); diff --git a/pyiec61850/test_pyiec61850.py b/pyiec61850/test_pyiec61850.py index aceb3fdb..6f6d8cdd 100755 --- a/pyiec61850/test_pyiec61850.py +++ b/pyiec61850/test_pyiec61850.py @@ -6,7 +6,7 @@ import traceback import signal import sys sys.path.append('.') -import iec61850 +import pyiec61850 as iec61850 def signal_handler(signal, frame): global running running =0 diff --git a/pyiec61850/tutorial.md b/pyiec61850/tutorial.md index ce766cab..1e6b4f4f 100644 --- a/pyiec61850/tutorial.md +++ b/pyiec61850/tutorial.md @@ -2,14 +2,27 @@ Before building you should install swig and python. To build python bindings you have to turn on the BUILD\_PYTHON\_BINDINGS flag in CMake from cmake-gui or in command line: ```sh -$ cmake -DBUILD_PYTHON_BINDINGS=ON . +$ mkdir build && cd build +$ cmake -DBUILD_PYTHON_BINDINGS=ON .. +``` + +Then compile the library and install it: +```sh +$ make +$ sudo make install +``` +CMake and swig will automatically detect your python version and install the python library in python library directories. + +For running the integrated tests: +```sh +$ make test ``` -Then compile the library and install it. CMake and swig will automatically detect your python version and install the python library in python library directories. pyiec61850 library is to be imported calling ```python -import iec61850 +import pyiec61850 as iec61850 ``` + # Client tutorial The python bindings works similarly to the basic C library. However there are some differences: From 0f0d22ab61a5d252830e67e20906070b52cbe95a Mon Sep 17 00:00:00 2001 From: Mikael Bourhis Date: Wed, 25 Oct 2023 16:22:49 +0200 Subject: [PATCH 03/53] Python wrapper: update and improve CMakeLists Tested and compatible with: Ubuntu 20.04.6 LTS : Python 3.8.10; CMake 3.16.3; Swig 4.0.1 Debian 11 : Python 3.9.2; CMake 3.13.4; Swig 4.0.2 Ubuntu 22.04.3 LTS : Python 3.10.12; CMake 3.22.1; Swig 4.0.2 Ubuntu 23.10 : Python 3.11.6; CMake 3.27.4; Swig 4.1.0 Alpine 3.18.4 : Python 3.11.6; CMake 3.26.5; Swig 4.1.1 Signed-off-by: Mikael Bourhis --- pyiec61850/CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyiec61850/CMakeLists.txt b/pyiec61850/CMakeLists.txt index ce03da9c..a42960f9 100644 --- a/pyiec61850/CMakeLists.txt +++ b/pyiec61850/CMakeLists.txt @@ -1,6 +1,10 @@ cmake_minimum_required(VERSION 3.12) -set(CMAKE_POLICY_DEFAULT_CMP0078 NEW) +cmake_policy(SET CMP0078 NEW) + +if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14.0") + cmake_policy(SET CMP0086 NEW) +endif() find_package(SWIG REQUIRED) include(${SWIG_USE_FILE}) @@ -12,6 +16,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_SWIG_FLAGS "") set_property(SOURCE iec61850.i PROPERTY CPLUSPLUS ON) +set_property(SOURCE iec61850.i PROPERTY SWIG_MODULE_NAME pyiec61850) if(WIN32) set(LIBS iec61850 ws2_32) @@ -29,7 +34,7 @@ swig_link_libraries(pyiec61850 ${LIBS}) # Finding python modules install path execute_process( COMMAND ${Python_EXECUTABLE} -c - "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib())" + "from sysconfig import get_path; import sys; sys.stdout.write(get_path('platlib'))" OUTPUT_VARIABLE PYTHON_SITE_DIR ) From 65847e4ffec6e9d68dbcbebf31238a2bbefe8e8d Mon Sep 17 00:00:00 2001 From: Mikael Bourhis Date: Thu, 26 Oct 2023 12:31:00 +0200 Subject: [PATCH 04/53] Python wrapper: update the README file Signed-off-by: Mikael Bourhis --- pyiec61850/{tutorial.md => README.md} | 46 +++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) rename pyiec61850/{tutorial.md => README.md} (54%) diff --git a/pyiec61850/tutorial.md b/pyiec61850/README.md similarity index 54% rename from pyiec61850/tutorial.md rename to pyiec61850/README.md index 1e6b4f4f..b2398919 100644 --- a/pyiec61850/tutorial.md +++ b/pyiec61850/README.md @@ -1,6 +1,10 @@ -# Building -Before building you should install swig and python. -To build python bindings you have to turn on the BUILD\_PYTHON\_BINDINGS flag in CMake from cmake-gui or in command line: +# Python wrapper for libIEC61850 + +## Building +Before building you should install SWIG and Python +(see the '[Setup development environment on Linux](#setup-development-environment-on-linux-ubuntu)' section for help). + +To build the Python bindings you have to turn on the BUILD\_PYTHON\_BINDINGS flag in CMake from cmake-gui or in command line: ```sh $ mkdir build && cd build $ cmake -DBUILD_PYTHON_BINDINGS=ON .. @@ -11,21 +15,23 @@ Then compile the library and install it: $ make $ sudo make install ``` -CMake and swig will automatically detect your python version and install the python library in python library directories. +(Eventually, update your ld cache with: `sudo ldconfig`) + +CMake and SWIG will automatically detect your Python version and install the Python library in Python library directories. For running the integrated tests: ```sh $ make test ``` -pyiec61850 library is to be imported calling +pyiec61850 library is to be imported calling: ```python import pyiec61850 as iec61850 ``` -# Client tutorial +## Client tutorial -The python bindings works similarly to the basic C library. However there are some differences: +The Python bindings works similarly to the basic C library. However there are some differences: * a specific function is to be called to cast variables from one type to another * arguments passed by pointer are to be removed from arguments and append to the return list @@ -61,3 +67,29 @@ Reading and writing operations can be performed using this syntax: err = iec61850.IedConnection_writeFloatValue(con, "simpleIOGenericIO/GGIO1.AnIn1.mag.f", iec61850.IEC61850_FC_MX, 10.0) ``` + +## Appendix + +## Setup development environment on Linux Ubuntu + +_[Tested on Ubuntu 20.04 LTS, Ubuntu 22.04 LTS, Ubuntu 23.10]_ + +Here the minimum required packages for compiling libiec61850 and the Python +wrapper (without TLS, SQlite, ...): + +```sh +$ sudo apt-get update +$ sudo apt-get install g++ cmake swig git python3 python3-all-dev +``` + +## Setup development environment on Linux Alpine + +_[Tested on Alpine 3.18]_ + +Here the minimum required packages for compiling libiec61850 and the Python +wrapper (without TLS, SQlite, ...): + +```sh +$ apk update +$ apk add git g++ swig make cmake python3 python3-dev linux-headers +``` From c20b3f8a70ad37ccf424d66a84d84745fa0ecbfa Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 25 Nov 2023 16:39:24 +0000 Subject: [PATCH 05/53] - fixed conflicting parameter declaration --- src/mms/iso_mms/client/mms_client_files.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index 4fca418e..5ea8fb01 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -180,7 +180,6 @@ exit_reject_invalid_pdu: mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); } - void mmsClient_handleFileReadRequest( MmsConnection connection, @@ -191,7 +190,7 @@ mmsClient_handleFileReadRequest( int32_t frsmId = BerDecoder_decodeInt32(buffer, maxBufPos - bufPos, bufPos); if (DEBUG_MMS_CLIENT) - printf("MMS_CLIENT: mmsClient_handleFileReadRequest read request for frsmId: %i\n", frsmId); + printf("MMS_CLIENT: mmsClient_handleFileReadRequest read request for frsmId: %i\n", (int)frsmId); MmsFileReadStateMachine* frsm = getFrsm(connection, frsmId); @@ -401,7 +400,6 @@ mmsClient_createFileDirectoryRequest(uint32_t invokeId, ByteBuffer* request, con request->size = bufPos; } - void mmsClient_createFileRenameRequest(uint32_t invokeId, ByteBuffer* request, const char* currentFileName, const char* newFileName) { @@ -466,7 +464,6 @@ mmsClient_createObtainFileRequest(uint32_t invokeId, ByteBuffer* request, const request->size = bufPos; } - static bool parseFileAttributes(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t* fileSize, uint64_t* lastModified) { @@ -610,7 +607,6 @@ parseListOfDirectoryEntries(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t return true; } - bool mmsClient_parseFileDirectoryResponse(ByteBuffer* response, int bufPos, uint32_t invokeId, MmsConnection_FileDirectoryHandler handler, void* parameter) { @@ -731,16 +727,14 @@ mmsMsg_parseFileOpenResponse(uint8_t* buffer, int bufPos, int maxBufPos, int32_t } bool -mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t invokeId, int frsmId, bool* moreFollows, MmsConnection_FileReadHandler handler, void* handlerParameter) +mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t invokeId, int32_t frsmId, bool* moreFollows, MmsConnection_FileReadHandler handler, void* handlerParameter) { int length; uint8_t* data = NULL; int dataLen = 0; - uint8_t tag = buffer[bufPos++]; - if (tag != 0xbf) { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT/SERVER: mmsClient_parseFileReadResponse: unknown tag %02x\n", tag); From b658a1ed8fafcbb3d038012e0e372e9231af64db Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 15 Dec 2023 22:24:52 +0000 Subject: [PATCH 06/53] - config file parser dynamically allocates linebuffer to allow multithreaded applications (#484) --- src/iec61850/server/model/config_file_parser.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index 8f2abeca..3f98b217 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -30,8 +30,6 @@ #define READ_BUFFER_MAX_SIZE 1024 -static uint8_t lineBuffer[READ_BUFFER_MAX_SIZE]; - static int readLine(FileHandle fileHandle, uint8_t* buffer, int maxSize) { @@ -117,6 +115,11 @@ ConfigFileParser_createModelFromConfigFileEx(const char* filename) IedModel* ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) { + uint8_t* lineBuffer = (uint8_t*)GLOBAL_MALLOC(READ_BUFFER_MAX_SIZE); + + if (lineBuffer == NULL) + goto exit_error; + int bytesRead = 1; bool stateInModel = false; @@ -541,8 +544,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) else goto exit_error; } - - } else { if (StringUtils_startsWith((char*) lineBuffer, "MODEL{")) { @@ -564,14 +565,18 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) } } + GLOBAL_FREEMEM(lineBuffer); + return model; exit_error: + + GLOBAL_FREEMEM(lineBuffer); + if (DEBUG_IED_SERVER) printf("IED_SERVER: error parsing line %i (indentation level = %i)\n", currentLine, indendation); IedModel_destroy(model); + return NULL; } - - From 167c24278c52f07d86c8f4724be999bc527f1474 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 22 Jan 2024 15:10:58 +0000 Subject: [PATCH 07/53] - Java tools: Support for time stamp Val elements --- tools/model_generator/build2.sh | 2 +- .../libiec61850/scl/model/DataModelValue.java | 32 ++++++++++++++----- .../tools/DynamicModelGenerator.java | 5 +++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/tools/model_generator/build2.sh b/tools/model_generator/build2.sh index 8bf31dfb..52bd6f80 100755 --- a/tools/model_generator/build2.sh +++ b/tools/model_generator/build2.sh @@ -4,7 +4,7 @@ mkdir build find src/ -name "*.java" > listFile.tmp -javac -target 1.6 -source 1.6 -d build @listFile.tmp +javac -target 1.8 -source 1.8 -d build @listFile.tmp jar cfm genconfig.jar manifest-dynamic.mf -C build/ com/ diff --git a/tools/model_generator/src/com/libiec61850/scl/model/DataModelValue.java b/tools/model_generator/src/com/libiec61850/scl/model/DataModelValue.java index 2bd18a3d..ccf4d8f0 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/DataModelValue.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/DataModelValue.java @@ -188,19 +188,35 @@ public class DataModelValue { case TIMESTAMP: case ENTRY_TIME: - try { + { String modValueString = value.replace(',', '.'); - SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss.SSS"); - parser.setTimeZone(TimeZone.getTimeZone("UTC")); + try { + SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss.SSS"); + parser.setTimeZone(TimeZone.getTimeZone("UTC")); + + Date date = parser.parse(modValueString); - Date date = parser.parse(modValueString); + this.value = new Long(date.toInstant().toEpochMilli()); + + break; + } + catch (java.text.ParseException e) {}; + + try { + SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss"); + parser.setTimeZone(TimeZone.getTimeZone("UTC")); + + Date date = parser.parse(modValueString); - this.value = new Long(date.toInstant().toEpochMilli()); - } - catch (java.text.ParseException e) { + this.value = new Long(date.toInstant().toEpochMilli()); + + break; + } + catch (java.text.ParseException e) {}; + this.value = null; - System.out.println("Warning: Val element does not contain a valid time stamp: " + e.getMessage()); + System.out.println("Warning: Val element does not contain a valid time stamp: " + value); } break; diff --git a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java index 2d5bf36e..ef3c7cfe 100644 --- a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java @@ -546,6 +546,11 @@ public class DynamicModelGenerator { case FLOAT64: output.print("=" + value.getValue()); break; + case TIMESTAMP: + case ENTRY_TIME: + output.print("=" + value.getLongValue()); + break; + default: System.out.println("Unknown default value for " + dataAttribute.getName() + " type: " + dataAttribute.getType()); break; From be9d4b56f413d3301dfe3d070514afb66fc1495c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 22 Jan 2024 15:30:29 +0000 Subject: [PATCH 08/53] - Java tools: Support for time stamp Val elements (updated binary) --- tools/model_generator/genconfig.jar | Bin 100900 -> 101613 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 0ef0c1d305dd4dc2a37c2827db0d7ab86ea46ad4..2bb8ddbeb70a96e73b38d3b1b054fd8150bd7e81 100644 GIT binary patch literal 101613 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 literal 100900 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 Z^(f210xmMJl?D970GqNt&VWY*@qcRI5fcCa From b84e06eb1714faf5da5759499d85feb255d4ee85 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 26 Jan 2024 07:31:39 +0000 Subject: [PATCH 09/53] - added function IedClientError_toString --- src/iec61850/client/ied_connection.c | 94 ++++++++++++++++++++++++++++ src/iec61850/inc/iec61850_client.h | 8 +++ 2 files changed, 102 insertions(+) diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index d64fc595..e065cc10 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -53,6 +53,100 @@ struct sFileDirectoryEntry { uint64_t lastModified; }; +const char* +IedClientError_toString(IedClientError err) +{ + switch (err) + { + case IED_ERROR_OK: + return "ok"; + + case IED_ERROR_NOT_CONNECTED: + return "not-connected"; + + case IED_ERROR_ALREADY_CONNECTED: + return "already-connected"; + + case IED_ERROR_CONNECTION_LOST: + return "connection-lost"; + + case IED_ERROR_SERVICE_NOT_SUPPORTED: + return "service-not-supported"; + + case IED_ERROR_CONNECTION_REJECTED: + return "connection-rejected"; + + case IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED: + return "outstanding-call-limit-reached"; + + case IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT: + return "invalid-argument"; + + case IED_ERROR_ENABLE_REPORT_FAILED_DATASET_MISMATCH: + return "enable-report-failed-due-to-dataset-mismatch"; + + case IED_ERROR_OBJECT_REFERENCE_INVALID: + return "object-reference-invalid"; + + case IED_ERROR_UNEXPECTED_VALUE_RECEIVED: + return "unexpected-value-received"; + + case IED_ERROR_TIMEOUT: + return "timeout"; + + case IED_ERROR_ACCESS_DENIED: + return "access-denied"; + + case IED_ERROR_OBJECT_DOES_NOT_EXIST: + return "object-does-not-exist"; + + case IED_ERROR_OBJECT_EXISTS: + return "object-exists"; + + case IED_ERROR_OBJECT_ACCESS_UNSUPPORTED: + return "object-access-unsupported"; + + case IED_ERROR_TYPE_INCONSISTENT: + return "type-inconsistent"; + + case IED_ERROR_TEMPORARILY_UNAVAILABLE: + return "temporary-unavailable"; + + case IED_ERROR_OBJECT_UNDEFINED: + return "object-undefined"; + + case IED_ERROR_INVALID_ADDRESS: + return "invalid-address"; + + case IED_ERROR_HARDWARE_FAULT: + return "hardware-fault"; + + case IED_ERROR_TYPE_UNSUPPORTED: + return "type-unsupported"; + + case IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT: + return "object-attribute-inconsistent"; + + case IED_ERROR_OBJECT_VALUE_INVALID: + return "object-value-invalid"; + + case IED_ERROR_OBJECT_INVALIDATED: + return "object-invalidated"; + + case IED_ERROR_MALFORMED_MESSAGE: + return "malformed-message"; + + case IED_ERROR_OBJECT_CONSTRAINT_CONFLICT: + return "object-constraint-conflict"; + + case IED_ERROR_SERVICE_NOT_IMPLEMENTED: + return "service-not-implemented"; + + default: + return "unknown-error"; + } +} + IedClientError iedConnection_mapMmsErrorToIedError(MmsError mmsError) { diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 5e53efe2..21adf9cf 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -172,6 +172,14 @@ typedef enum { IED_ERROR_UNKNOWN = 99 } IedClientError; +/** + * \brief Convert error value to string + * + * \return string constant representing the error + */ +LIB61850_API const char* +IedClientError_toString(IedClientError err); + /************************************************** * Connection creation and destruction **************************************************/ From 0a177836e66eb04ba2b02711d6129b7398cb829f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 26 Jan 2024 12:41:17 +0000 Subject: [PATCH 10/53] - IedConnection: fixed potential null pointer dereferences --- src/iec61850/client/ied_connection.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index e065cc10..c65ada16 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -1884,11 +1884,15 @@ IedConnection_getLogicalDeviceList(IedConnection self, IedClientError* error) logicalDevice = LinkedList_getNext(logicalDevice); } - *error = IED_ERROR_OK; + if (error) + *error = IED_ERROR_OK; + return logicalDeviceList; } else { - *error = IED_ERROR_UNKNOWN; + if (error) + *error = IED_ERROR_UNKNOWN; + return NULL; } } @@ -3378,8 +3382,8 @@ createDataSetAsyncHandler(uint32_t invokeId, void* parameter, MmsError mmsError, IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler)call->callback; IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError); @@ -3409,7 +3413,7 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons if (call == NULL) { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; - goto exit_function; + goto exit_function; } call->callback = handler; @@ -3494,8 +3498,10 @@ cleanup_list: exit_function: - if (*error != IED_ERROR_OK) { - iedConnection_releaseOutstandingCall(self, call); + if (*error != IED_ERROR_OK) + { + if (call) + iedConnection_releaseOutstandingCall(self, call); return 0; } From 6dd273764854a70f0dd6333e3d38fdfdf0c06122 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 26 Jan 2024 12:42:48 +0000 Subject: [PATCH 11/53] - control.c: small code cleanup --- src/iec61850/server/mms_mapping/control.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index de2b6f89..6fd42d01 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -1766,9 +1766,6 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia char* lnName = variableId; - if (lnName == NULL) - return NULL; - char* objectName = MmsMapping_getNextNameElement(separator + 1); if (objectName == NULL) @@ -1795,10 +1792,10 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia nextVarName = MmsMapping_getNextNameElement(varName); - if (nextVarName != NULL) + if (nextVarName) varName = nextVarName; - } while (nextVarName != NULL); + } while (nextVarName); if (foundVar == false) varName = NULL; From 8e64ae4fd5a77099da3064a64544c989452f4e2b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 29 Jan 2024 11:34:04 +0000 Subject: [PATCH 12/53] - fixed compilation problem when MMS_FILE_SERVICE is not set --- src/mms/inc_private/mms_common_internal.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mms/inc_private/mms_common_internal.h b/src/mms/inc_private/mms_common_internal.h index f096e7bc..0c508fbf 100644 --- a/src/mms/inc_private/mms_common_internal.h +++ b/src/mms/inc_private/mms_common_internal.h @@ -1,7 +1,7 @@ /* * mms_common_internal.h * - * Copyright 2013-2019 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -34,6 +34,8 @@ #define DEFAULT_MAX_SERV_OUTSTANDING_CALLED 5 #define DEFAULT_DATA_STRUCTURE_NESTING_LEVEL 10 +typedef struct sMmsOutstandingCall* MmsOutstandingCall; + #if (MMS_FILE_SERVICE == 1) #ifndef CONFIG_MMS_MAX_NUMBER_OF_OPEN_FILES_PER_CONNECTION @@ -42,8 +44,6 @@ #include "hal_filesystem.h" -typedef struct sMmsOutstandingCall* MmsOutstandingCall; - typedef struct { int32_t frsmId; uint32_t readPosition; From cf94d64206cf53298edf4799a75b31657bb7cbb3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 2 Feb 2024 06:44:47 +0000 Subject: [PATCH 13/53] - fixed - null pointer dereference in mmsServer_handleDeleteNamedVariableListRequest when receiving malformed message (LIB61850-430) --- .../server/mms_named_variable_list_service.c | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) 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 85f1b960..8d27376f 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 @@ -130,16 +130,22 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, goto exit_function; } - if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) && - (mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present - == ConfirmedServiceRequest_PR_deleteNamedVariableList)) - { - request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList); - } - else { - mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); - goto exit_function; - } + if ((mmsPdu->present == MmsPdu_PR_confirmedRequestPdu) && + (mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.present + == ConfirmedServiceRequest_PR_deleteNamedVariableList)) + { + request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList); + } + else { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + goto exit_function; + } + + if (request->listOfVariableListName == NULL) + { + mmsMsg_createMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + goto exit_function; + } long scopeOfDelete = DeleteNamedVariableListRequest__scopeOfDelete_specific; From da3c69eb539b95e7e888f00f0d8dc118b71d3929 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 15 Feb 2024 16:56:44 +0000 Subject: [PATCH 14/53] - IedConnection: fixed potential memory leak in getDataSetHandlerInternal --- src/iec61850/client/ied_connection.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index c65ada16..3336b5b1 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -3806,25 +3806,28 @@ getDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_ReadDataSetHandler handler = (IedConnection_ReadDataSetHandler) call->callback; ClientDataSet dataSet = (ClientDataSet) call->specificParameter; char* dataSetReference = (char*) call->specificParameter2.pointer; - if (value != NULL) { - + if (value) + { if (dataSet == NULL) { dataSet = ClientDataSet_create(dataSetReference); ClientDataSet_setDataSetValues(dataSet, value); - GLOBAL_FREEMEM(dataSetReference); } else { MmsValue* dataSetValues = ClientDataSet_getValues(dataSet); MmsValue_update(dataSetValues, value); - MmsValue_delete(value); } + + if (dataSetReference) + GLOBAL_FREEMEM(dataSetReference); + + MmsValue_delete(value); } handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), dataSet); From a420d36727c8f7e57493912487412ccc8a790e29 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 15 Feb 2024 17:35:21 +0000 Subject: [PATCH 15/53] - IedServer: fixed crash when client tries to write complete SGCB structure --- src/iec61850/server/mms_mapping/mms_mapping.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 27471a58..4cc38d39 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1,7 +1,7 @@ /* * mms_mapping.c * - * Copyright 2013-2023 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -1335,8 +1335,8 @@ checkForServiceTrackingVariables(MmsMapping* self, LogicalNode* logicalNode) { ModelNode* modelNode = logicalNode->firstChild; - while (modelNode) { - + while (modelNode) + { if (!strcmp(modelNode->name, "SpcTrk") || !strcmp(modelNode->name, "DpcTrk") || !strcmp(modelNode->name, "IncTrk") || !strcmp(modelNode->name, "EncTrk1") || !strcmp(modelNode->name, "ApcFTrk") || !strcmp(modelNode->name, "ApcIntTrk") || @@ -2713,6 +2713,10 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, if (nextSep != NULL) { nextSep = strchr(nextSep + 1, '$'); + if (nextSep == NULL) { + return DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; + } + char* nameId = nextSep + 1; /* check access permissions */ From d658b6ce275c042aa958da713b89c66b46840c19 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 15 Feb 2024 20:12:07 +0000 Subject: [PATCH 16/53] - fixed - potential race condition when using IedConnection_installReportHandler and IedConnection_uninstallReportHandler --- src/iec61850/client/client_report.c | 33 ++++++++++++++++++----------- src/iec61850/inc/iec61850_client.h | 8 +++++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/iec61850/client/client_report.c b/src/iec61850/client/client_report.c index af12d26a..321532e5 100644 --- a/src/iec61850/client/client_report.c +++ b/src/iec61850/client/client_report.c @@ -3,7 +3,7 @@ * * Client implementation for IEC 61850 reporting. * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -283,14 +283,27 @@ lookupReportHandler(IedConnection self, const char* rcbReference) return NULL; } +static void +uninstallReportHandler(IedConnection self, const char* rcbReference) +{ + ClientReport report = lookupReportHandler(self, rcbReference); + + if (report != NULL) { + LinkedList_remove(self->enabledReports, report); + ClientReport_destroy(report); + } +} + void IedConnection_installReportHandler(IedConnection self, const char* rcbReference, const char* rptId, ReportCallbackFunction handler, void* handlerParameter) { + Semaphore_wait(self->reportHandlerMutex); + ClientReport report = lookupReportHandler(self, rcbReference); if (report != NULL) { - IedConnection_uninstallReportHandler(self, rcbReference); + uninstallReportHandler(self, rcbReference); if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: Removed existing report callback handler for %s\n", rcbReference); @@ -306,8 +319,8 @@ IedConnection_installReportHandler(IedConnection self, const char* rcbReference, else report->rptId = NULL; - Semaphore_wait(self->reportHandlerMutex); LinkedList_add(self->enabledReports, report); + Semaphore_post(self->reportHandlerMutex); if (DEBUG_IED_CLIENT) @@ -319,12 +332,7 @@ IedConnection_uninstallReportHandler(IedConnection self, const char* rcbReferenc { Semaphore_wait(self->reportHandlerMutex); - ClientReport report = lookupReportHandler(self, rcbReference); - - if (report != NULL) { - LinkedList_remove(self->enabledReports, report); - ClientReport_destroy(report); - } + uninstallReportHandler(self, rcbReference); Semaphore_post(self->reportHandlerMutex); } @@ -367,6 +375,8 @@ IedConnection_triggerGIReport(IedConnection self, IedClientError* error, const c void iedConnection_handleReport(IedConnection self, MmsValue* value) { + Semaphore_wait(self->reportHandlerMutex); + MmsValue* rptIdValue = MmsValue_getElement(value, 0); if ((rptIdValue == NULL) || (MmsValue_getType(rptIdValue) != MMS_VISIBLE_STRING)) { @@ -769,15 +779,14 @@ iedConnection_handleReport(IedConnection self, MmsValue* value) matchingReport->reasonForInclusion[i] = IEC61850_REASON_NOT_INCLUDED; } } - - Semaphore_wait(self->reportHandlerMutex); if (matchingReport->callback != NULL) matchingReport->callback(matchingReport->callbackParameter, matchingReport); +exit_function: + Semaphore_post(self->reportHandlerMutex); -exit_function: return; } diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 21adf9cf..911b0202 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1,7 +1,7 @@ /* * iec61850_client.h * - * Copyright 2013-2021 Michael Zillgith + * Copyright 2013-2023 Michael Zillgith * * This file is part of libIEC61850. * @@ -1267,9 +1267,11 @@ typedef void (*ReportCallbackFunction) (void* parameter, ClientReport report); * Otherwise the internal data structures storing the received data set values will not be updated * correctly. * - * When replacing a report handler you only have to call this function. There is no separate call to + * \note Replacing a report handler you only have to call this function. There is no separate call to * IedConnection_uninstallReportHandler() required. * + * \note Do not call this function inside of the ReportCallbackFunction. Doing so will cause a deadlock. + * * \param self the connection object * \param rcbReference object reference of the report control block * \param rptId a string that identifies the report. If the rptId is not available then the @@ -1284,6 +1286,8 @@ IedConnection_installReportHandler(IedConnection self, const char* rcbReference, /** * \brief uninstall a report handler function for the specified report control block (RCB) * + * \note Do not call this function inside of the ReportCallbackFunction. Doing so will cause a deadlock. + * * \param self the connection object * \param rcbReference object reference of the report control block */ From 1182cd177155d639368038cf0cb3cb4541cdb7ff Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 15 Feb 2024 20:32:54 +0000 Subject: [PATCH 17/53] - MMS client: avoid invoke ID 0 as it is used as return value of client function in case of an error --- src/mms/iso_mms/client/mms_client_connection.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 6f743b0b..0ba12801 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1,7 +1,7 @@ /* * mms_client_connection.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -242,6 +242,10 @@ getNextInvokeId(MmsConnection self) Semaphore_wait(self->nextInvokeIdLock); self->nextInvokeId++; + + if (self->nextInvokeId == 0) + self->nextInvokeId = 1; + nextInvokeId = self->nextInvokeId; Semaphore_post(self->nextInvokeIdLock); From 0fee01e1b6c2a953d8938f772488401d8033618f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 15 Feb 2024 20:42:07 +0000 Subject: [PATCH 18/53] - IedConnection: calling ControlObjectClient_destroy in IedConnection_destroy to prevent memory leak when user forgets to call --- src/iec61850/client/ied_connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 3336b5b1..014f204f 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -949,7 +949,7 @@ IedConnection_destroy(IedConnection self) GLOBAL_FREEMEM(self->outstandingCalls); - LinkedList_destroyStatic(self->clientControls); + LinkedList_destroyDeep(self->clientControls, (LinkedListValueDeleteFunction)ControlObjectClient_destroy); Semaphore_destroy(self->clientControlsLock); Semaphore_destroy(self->outstandingCallsLock); From 267e9037b0748b42cded5680ee3560c75e539409 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 15 Feb 2024 20:59:27 +0000 Subject: [PATCH 19/53] - removed legacy defines for report reasons (#449) --- src/iec61850/inc/iec61850_client.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 911b0202..06efd917 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1162,15 +1162,6 @@ typedef int ReasonForInclusion; /** the reason for inclusion is unknown (e.g. report is not configured to include reason-for-inclusion) */ #define IEC61850_REASON_UNKNOWN 32 -#define REASON_NOT_INCLUDED IEC61850_REASON_NOT_INCLUDED -#define REASON_DATA_CHANGE IEC61850_REASON_DATA_CHANGE -#define REASON_QUALITY_CHANGE IEC61850_REASON_QUALITY_CHANGE -#define REASON_DATA_UPDATE IEC61850_REASON_DATA_UPDATE -#define REASON_INTEGRITY IEC61850_REASON_INTEGRITY -#define REASON_GI IEC61850_REASON_GI -#define REASON_UNKNOWN IEC61850_REASON_UNKNOWN - - /* Element encoding mask values for ClientReportControlBlock */ /** include the report ID into the setRCB request */ From 7d6e851af19c016f1c20a43243091dc7b4067760 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 4 Mar 2024 11:59:12 +0000 Subject: [PATCH 20/53] - fixed potential memory leak when GooseReceiver is immediately stopped after start (I6PLLCV-71) --- src/goose/goose_receiver.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index 63fe9710..164ddca8 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -1014,9 +1014,12 @@ gooseReceiverLoop(void *threadParameter) EthernetHandleSet handleSet = EthernetHandleSet_new(); EthernetHandleSet_addSocket(handleSet, self->ethSocket); - if (self->running) { + bool running = true; - while (self->running) { + if (running) + { + while (running) + { switch (EthernetHandleSet_waitReady(handleSet, 100)) { case -1: @@ -1030,6 +1033,8 @@ gooseReceiverLoop(void *threadParameter) } if (self->stop) break; + + running = self->running; } GooseReceiver_stopThreadless(self); @@ -1046,7 +1051,8 @@ void GooseReceiver_start(GooseReceiver self) { #if (CONFIG_MMS_THREADLESS_STACK == 0) - if (GooseReceiver_startThreadless(self)) { + if (GooseReceiver_startThreadless(self)) + { self->thread = Thread_create((ThreadExecutionFunction) gooseReceiverLoop, (void*) self, false); if (self->thread != NULL) { From 933388128c9757972d0c6a411bcfa5c8cca096fb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 19 Mar 2024 15:43:49 +0000 Subject: [PATCH 21/53] - .NET API: fixed - crash when GetDataSetDirectoryAsync returns error (LIB61850-434) --- dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 16d79a56..4bb653bb 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -1175,6 +1175,9 @@ namespace IEC61850 private static List WrapNativeLogQueryResult(IntPtr linkedList) { + if (linkedList == IntPtr.Zero) + return null; + List journalEntries = new List(); IntPtr element = LinkedList_getNext(linkedList); @@ -2238,22 +2241,27 @@ namespace IEC61850 GetDataSetDirectoryHandler handler = callbackInfo.Item1; object handlerParameter = callbackInfo.Item2; - IntPtr element = LinkedList_getNext(dataSetDirectory); - handle.Free(); - List newList = new List(); + List newList = null; - while (element != IntPtr.Zero) + if (dataSetDirectory != IntPtr.Zero) { - string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); + newList = new List(); - newList.Add(dataObject); + IntPtr element = LinkedList_getNext(dataSetDirectory); - element = LinkedList_getNext(element); - } + while (element != IntPtr.Zero) + { + string dataObject = Marshal.PtrToStringAnsi(LinkedList_getData(element)); - LinkedList_destroy(dataSetDirectory); + newList.Add(dataObject); + + element = LinkedList_getNext(element); + } + + LinkedList_destroy(dataSetDirectory); + } handler.Invoke(invokeId, handlerParameter, (IedClientError)err, newList, isDeletable); } @@ -2428,11 +2436,9 @@ namespace IEC61850 dataSet = new DataSet(nativeDataSet); } - handler(invokeId, handlerParameter, clientError, dataSet); } - public delegate void ReadDataSetHandler(UInt32 invokeId,object parameter,IedClientError err,DataSet dataSet); ///

@@ -2566,7 +2572,6 @@ namespace IEC61850 { handler(invokeId, handlerParameter, clientError, null, moreFollows); } - } /// @@ -2632,7 +2637,6 @@ namespace IEC61850 return GetLogicalDeviceDataSetsAsync(null, ldName, continueAfter, handler, parameter); } - public UInt32 GetLogicalDeviceDataSetsAsync(List result, string ldName, string continueAfter, GetNameListHandler handler, object parameter) { int error; From ea327837cc0e5746799031d8aa81503551297db8 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 19 Mar 2024 18:03:54 +0000 Subject: [PATCH 22/53] - MMS server: fixed - server is sending data set response larger than negotiated MMS PDU size (LIB61850-435) --- .../server/mms_get_var_access_service.c | 20 +++- .../server/mms_named_variable_list_service.c | 41 ++++--- src/mms/iso_mms/server/mms_read_service.c | 106 +++++++++++------- 3 files changed, 107 insertions(+), 60 deletions(-) diff --git a/src/mms/iso_mms/server/mms_get_var_access_service.c b/src/mms/iso_mms/server/mms_get_var_access_service.c index c27ff76b..4adb6f4c 100644 --- a/src/mms/iso_mms/server/mms_get_var_access_service.c +++ b/src/mms/iso_mms/server/mms_get_var_access_service.c @@ -1,7 +1,7 @@ /* * mms_get_var_access_service.c * - * Copyright 2013-2023 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -310,9 +310,12 @@ mmsServer_handleGetVariableAccessAttributesRequest( rval = ber_decode(NULL, &asn_DEF_GetVariableAccessAttributesRequest, (void**) &request, buffer + bufPos, maxBufPos - bufPos); - if (rval.code == RC_OK) { - if (request->present == GetVariableAccessAttributesRequest_PR_name) { - if (request->choice.name.present == ObjectName_PR_domainspecific) { + if (rval.code == RC_OK) + { + if (request->present == GetVariableAccessAttributesRequest_PR_name) + { + if (request->choice.name.present == ObjectName_PR_domainspecific) + { Identifier_t domainId = request->choice.name.choice.domainspecific.domainId; Identifier_t nameId = request->choice.name.choice.domainspecific.itemId; @@ -328,7 +331,8 @@ mmsServer_handleGetVariableAccessAttributesRequest( GLOBAL_FREEMEM(nameIdStr); } #if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) - else if (request->choice.name.present == ObjectName_PR_vmdspecific) { + else if (request->choice.name.present == ObjectName_PR_vmdspecific) + { Identifier_t nameId = request->choice.name.choice.vmdspecific; char* nameIdStr = StringUtils_createStringFromBuffer(nameId.buf, nameId.size); @@ -357,6 +361,12 @@ mmsServer_handleGetVariableAccessAttributesRequest( asn_DEF_GetVariableAccessAttributesRequest.free_struct(&asn_DEF_GetVariableAccessAttributesRequest, request, 0); + if (ByteBuffer_getSize(response) > connection->maxPduSize) + { + ByteBuffer_setSize(response, 0); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + } + return retVal; } 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 8d27376f..a38cfd7f 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 @@ -681,7 +681,8 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, LinkedList variable = LinkedList_getNext(variables); int i; - for (i = 0; i < variableCount; i++) { + for (i = 0; i < variableCount; i++) + { MmsNamedVariableListEntry variableEntry = (MmsNamedVariableListEntry) variable->data; varListResponse->listOfVariable.list.array[i] = (struct GetNamedVariableListAttributesResponse__listOfVariable__Member*) @@ -746,8 +747,8 @@ mmsServer_handleGetNamedVariableListAttributesRequest( goto exit_function; } - if (request->present == ObjectName_PR_domainspecific) { - + if (request->present == ObjectName_PR_domainspecific) + { char domainName[65]; char itemName[65]; @@ -767,11 +768,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest( MmsDomain* domain = MmsDevice_getDomain(mmsDevice, domainName); - if (domain != NULL) { + if (domain != NULL) + { MmsNamedVariableList varList = MmsDomain_getNamedVariableList(domain, itemName); - if (varList) { - + if (varList) + { MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_DOMAIN_SPECIFIC, domain, varList->name, connection); if (accessError == MMS_ERROR_NONE) { @@ -798,8 +800,8 @@ mmsServer_handleGetNamedVariableListAttributesRequest( } #if (MMS_DYNAMIC_DATA_SETS == 1) - else if (request->present == ObjectName_PR_aaspecific) { - + else if (request->present == ObjectName_PR_aaspecific) + { char listName[65]; if (request->choice.aaspecific.size > 64) { @@ -812,11 +814,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest( MmsNamedVariableList varList = MmsServerConnection_getNamedVariableList(connection, listName); - if (varList) { - + if (varList) + { MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_ASSOCIATION_SPECIFIC, NULL, varList->name, connection); - if (accessError == MMS_ERROR_NONE) { + if (accessError == MMS_ERROR_NONE) + { if (createGetNamedVariableListAttributesResponse(invokeId, response, varList) == false) { /* encoding failed - probably because buffer size is too small for message */ @@ -835,7 +838,8 @@ mmsServer_handleGetNamedVariableListAttributesRequest( mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ - else if (request->present == ObjectName_PR_vmdspecific) { + else if (request->present == ObjectName_PR_vmdspecific) + { char listName[65]; if (request->choice.vmdspecific.size > 64) { @@ -850,11 +854,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest( MmsNamedVariableList varList = mmsServer_getNamedVariableListWithName(mmsDevice->namedVariableLists, listName); - if (varList) { - + if (varList) + { MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_GET_DIRECTORY, MMS_VMD_SPECIFIC, NULL, varList->name, connection); - if (accessError == MMS_ERROR_NONE) { + if (accessError == MMS_ERROR_NONE) + { if (createGetNamedVariableListAttributesResponse(invokeId, response, varList) == false) { /* encoding failed - probably because buffer size is too small for message */ @@ -876,6 +881,12 @@ mmsServer_handleGetNamedVariableListAttributesRequest( mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } + if (ByteBuffer_getSize(response) > connection->maxPduSize) + { + ByteBuffer_setSize(response, 0); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + } + exit_function: asn_DEF_GetVariableAccessAttributesRequest.free_struct(&asn_DEF_GetNamedVariableListAttributesRequest, diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index 2b33dc41..21b40c1c 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -1,7 +1,7 @@ /* * mms_read_service.c * - * Copyright 2013-2023 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -352,7 +352,8 @@ encodeVariableAccessSpecification(VarAccessSpec* accessSpec, uint8_t* buffer, in varAccessSpecSize += itemIdLen + BerEncoder_determineLengthSize(itemIdLen) + 1; - if (accessSpec->domainId != NULL) { + if (accessSpec->domainId != NULL) + { uint32_t domainIdLen = strlen(accessSpec->domainId); varAccessSpecSize += domainIdLen + BerEncoder_determineLengthSize(domainIdLen) + 1; @@ -370,7 +371,8 @@ encodeVariableAccessSpecification(VarAccessSpec* accessSpec, uint8_t* buffer, in varAccessSpecSize += 1 + BerEncoder_determineLengthSize(varAccessSpecLength); - if (encode == false) { + if (encode == false) + { bufPos = varAccessSpecSize; goto exit_function; } @@ -378,8 +380,8 @@ encodeVariableAccessSpecification(VarAccessSpec* accessSpec, uint8_t* buffer, in /* encode to buffer */ bufPos = BerEncoder_encodeTL(0xa0, varAccessSpecLength, buffer, bufPos); - if (accessSpec->isNamedVariableList == true) { - + if (accessSpec->isNamedVariableList == true) + { bufPos = BerEncoder_encodeTL(0xa1, variableListNameLength, buffer, bufPos); if (accessSpec->specific == 0) { /* vmd-specific */ @@ -425,8 +427,8 @@ encodeReadResponse(MmsServerConnection connection, /* iterate values list to determine encoded size */ LinkedList value = LinkedList_getNext(values); - for (i = 0; i < variableCount; i++) { - + for (i = 0; i < variableCount; i++) + { MmsValue* data = (MmsValue*) value->data; accessResultSize += MmsValue_encodeMmsData(data, NULL, 0, false); @@ -452,12 +454,13 @@ encodeReadResponse(MmsServerConnection connection, confirmedResponseContentSize; /* Check if message would fit in the MMS PDU */ - if (mmsPduSize > connection->maxPduSize) { + if (mmsPduSize > connection->maxPduSize) + { if (DEBUG_MMS_SERVER) printf("MMS read: message to large! send error PDU!\n"); mmsMsg_createServiceErrorPdu(invokeId, response, - MMS_ERROR_SERVICE_OTHER); + MMS_ERROR_RESOURCE_OTHER); goto exit_function; } @@ -487,7 +490,8 @@ encodeReadResponse(MmsServerConnection connection, /* encode access results */ value = LinkedList_getNext(values); - for (i = 0; i < variableCount; i++) { + for (i = 0; i < variableCount; i++) + { MmsValue* data = (MmsValue*) value->data; bufPos = MmsValue_encodeMmsData(data, buffer, bufPos, true); @@ -529,16 +533,18 @@ handleReadListOfVariablesRequest( int i; - for (i = 0; i < variableCount; i++) { + for (i = 0; i < variableCount; i++) + { VariableSpecification_t varSpec = read->variableAccessSpecification.choice.listOfVariable.list.array[i]->variableSpecification; AlternateAccess_t* alternateAccess = read->variableAccessSpecification.choice.listOfVariable.list.array[i]->alternateAccess; - if (varSpec.present == VariableSpecification_PR_name) { - - if (varSpec.choice.name.present == ObjectName_PR_domainspecific) { + if (varSpec.present == VariableSpecification_PR_name) + { + if (varSpec.choice.name.present == ObjectName_PR_domainspecific) + { char domainIdStr[65]; char nameIdStr[65]; @@ -571,7 +577,8 @@ handleReadListOfVariablesRequest( } #if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) - else if (varSpec.choice.name.present == ObjectName_PR_vmdspecific) { + else if (varSpec.choice.name.present == ObjectName_PR_vmdspecific) + { char nameIdStr[65]; mmsMsg_copyAsn1IdentifierToStringBuffer(varSpec.choice.name.choice.vmdspecific, nameIdStr, 65); @@ -607,11 +614,12 @@ handleReadListOfVariablesRequest( LinkedList valueElement = LinkedList_getNext(values); - while (valueElement) { - + while (valueElement) + { MmsValue* value = (MmsValue*) LinkedList_getData(valueElement); - if (value) { + if (value) + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) { if (MmsValue_getDataAccessError(value) == DATA_ACCESS_ERROR_NO_RESPONSE) { sendResponse = false; @@ -637,21 +645,24 @@ static void addNamedVariableToNamedVariableListResultList(MmsVariableSpecification* namedVariable, MmsDomain* domain, char* nameIdStr, LinkedList /**/ values, MmsServerConnection connection, MmsNamedVariableListEntry listEntry) { - if (namedVariable != NULL) { - + if (namedVariable != NULL) + { if (DEBUG_MMS_SERVER) printf("MMS read: found named variable %s with search string %s\n", namedVariable->name, nameIdStr); MmsValue* value = mmsServer_getValue(connection->server, domain, nameIdStr, connection, false); - if (value) { - if (listEntry->arrayIndex != -1) { - if (MmsValue_getType(value) == MMS_ARRAY) { - + if (value) + { + if (listEntry->arrayIndex != -1) + { + if (MmsValue_getType(value) == MMS_ARRAY) + { MmsValue* elementValue = MmsValue_getElement(value, listEntry->arrayIndex); - if (listEntry->componentName) { + if (listEntry->componentName) + { MmsVariableSpecification* elementType = namedVariable->typeSpec.array.elementTypeSpec; MmsValue* subElementValue = MmsVariableSpecification_getChildValue(elementType, elementValue, listEntry->componentName); @@ -669,7 +680,8 @@ addNamedVariableToNamedVariableListResultList(MmsVariableSpecification* namedVar } } - else { + else + { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: data set entry of unexpected type!\n"); @@ -697,8 +709,8 @@ createNamedVariableListResponse(MmsServerConnection connection, MmsNamedVariable LinkedList variable = LinkedList_getNext(variables); - while (variable) { - + while (variable) + { MmsNamedVariableListEntry variableListEntry = (MmsNamedVariableListEntry) variable->data; MmsDomain* variableDomain = MmsNamedVariableListEntry_getDomain(variableListEntry); @@ -757,11 +769,13 @@ handleReadNamedVariableListRequest( MmsDomain* domain = MmsDevice_getDomain(MmsServer_getDevice(connection->server), domainIdStr); - if (domain == NULL) { + if (domain == NULL) + { if (DEBUG_MMS_SERVER) printf("MMS read: domain %s not found!\n", domainIdStr); mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } - else { + else + { MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr); if (namedList) @@ -796,12 +810,15 @@ handleReadNamedVariableListRequest( MmsNamedVariableList namedList = mmsServer_getNamedVariableListWithName(connection->server->device->namedVariableLists, listName); if (namedList == NULL) + { mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); - else { - + } + else + { MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_READ, MMS_VMD_SPECIFIC, NULL, namedList->name, connection); - if (accessError == MMS_ERROR_NONE) { + if (accessError == MMS_ERROR_NONE) + { VarAccessSpec accessSpec; accessSpec.isNamedVariableList = true; @@ -816,7 +833,6 @@ handleReadNamedVariableListRequest( mmsMsg_createServiceErrorPdu(invokeId, response, accessError); } - } } #if (MMS_DYNAMIC_DATA_SETS == 1) @@ -831,13 +847,15 @@ handleReadNamedVariableListRequest( MmsNamedVariableList namedList = MmsServerConnection_getNamedVariableList(connection, listName); if (namedList == NULL) + { mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); - else { - + } + else + { MmsError accessError = mmsServer_callVariableListChangedHandler(MMS_VARLIST_READ, MMS_ASSOCIATION_SPECIFIC, NULL, namedList->name, connection); - if (accessError == MMS_ERROR_NONE) { - + if (accessError == MMS_ERROR_NONE) + { VarAccessSpec accessSpec; accessSpec.isNamedVariableList = true; @@ -891,7 +909,8 @@ mmsServer_handleReadRequest( goto exit_function; } - if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) { + if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) + { MmsServer_lockModel(connection->server); handleReadListOfVariablesRequest(connection, request, invokeId, response); @@ -899,7 +918,8 @@ mmsServer_handleReadRequest( MmsServer_unlockModel(connection->server); } #if (MMS_DATA_SET_SERVICE == 1) - else if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) { + else if (request->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) + { MmsServer_lockModel(connection->server); handleReadNamedVariableListRequest(connection, request, invokeId, response); @@ -911,6 +931,12 @@ mmsServer_handleReadRequest( mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } + if (ByteBuffer_getSize(response) > connection->maxPduSize) + { + ByteBuffer_setSize(response, 0); + mmsMsg_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + } + exit_function: asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); } From 49c64ebfeb47e49efa79a503f3aa4a57c8421abf Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 21 Mar 2024 19:55:03 +0000 Subject: [PATCH 23/53] - IED server: fixed - write access to whole array doesn't work (LIB61850-436)(#499) --- src/mms/iso_mms/common/mms_type_spec.c | 30 ++++++------ src/mms/iso_mms/server/mms_write_service.c | 53 +++++++++++++--------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/mms/iso_mms/common/mms_type_spec.c b/src/mms/iso_mms/common/mms_type_spec.c index f83b804e..c1db5d89 100644 --- a/src/mms/iso_mms/common/mms_type_spec.c +++ b/src/mms/iso_mms/common/mms_type_spec.c @@ -1,7 +1,7 @@ /* * mms_type_spec.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -108,36 +108,40 @@ MmsVariableSpecification_getType(MmsVariableSpecification* self) bool MmsVariableSpecification_isValueOfType(MmsVariableSpecification* self, const MmsValue* value) { - if ((self->type) == (value->type)) { - - if ((self->type == MMS_STRUCTURE) || (self->type == MMS_ARRAY)) { - + if ((self->type) == (value->type)) + { + if ((self->type == MMS_STRUCTURE) || (self->type == MMS_ARRAY)) + { int componentCount = self->typeSpec.structure.elementCount; if (componentCount != (int) MmsValue_getArraySize(value)) return false; - if (self->type == MMS_STRUCTURE) { - + if (self->type == MMS_STRUCTURE) + { int i; - for (i = 0; i < componentCount; i++) { - + for (i = 0; i < componentCount; i++) + { if (MmsVariableSpecification_isValueOfType(self->typeSpec.structure.elements[i], MmsValue_getElement(value, i)) == false) return false; } return true; } - else { + else + { int i; - for (i = 0; i < componentCount; i++) { - + for (i = 0; i < componentCount; i++) + { if (MmsVariableSpecification_isValueOfType(self->typeSpec.array.elementTypeSpec, MmsValue_getElement(value, i)) == false) return false; } + + return true; } } - else if (self->type == MMS_BIT_STRING) { + else if (self->type == MMS_BIT_STRING) + { if (self->typeSpec.bitString == value->value.bitString.size) return true; diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index 096bc30f..fdde1f16 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -598,12 +598,13 @@ mmsServer_handleWriteRequest( MmsServer_lockModel(connection->server); - if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) { + if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_variableListName) + { handleWriteNamedVariableListRequest(connection, writeRequest, invokeId, response); goto exit_function; } - else if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) { - + else if (writeRequest->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) + { int numberOfWriteItems = writeRequest->variableAccessSpecification.choice.listOfVariable.list.count; if (numberOfWriteItems < 1) { @@ -627,7 +628,8 @@ mmsServer_handleWriteRequest( int i; - for (i = 0; i < numberOfWriteItems; i++) { + for (i = 0; i < numberOfWriteItems; i++) + { ListOfVariableSeq_t* varSpec = writeRequest->variableAccessSpecification.choice.listOfVariable.list.array[i]; @@ -644,7 +646,8 @@ mmsServer_handleWriteRequest( char nameIdStr[65]; - if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) { + if (varSpec->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) + { Identifier_t domainId = varSpec->variableSpecification.choice.name.choice.domainspecific.domainId; char domainIdStr[65]; @@ -687,8 +690,8 @@ mmsServer_handleWriteRequest( AlternateAccess_t* alternateAccess = varSpec->alternateAccess; - if (alternateAccess != NULL) { - + if (alternateAccess != NULL) + { if ((variable->type == MMS_STRUCTURE) && (mmsServer_isComponentAccess(alternateAccess) == false)) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; @@ -714,12 +717,13 @@ mmsServer_handleWriteRequest( continue; } - if (alternateAccess != NULL) { - + if (alternateAccess != NULL) + { if (domain == NULL) domain = (MmsDomain*) device; - if (mmsServer_isIndexAccess(alternateAccess)) { + if (mmsServer_isIndexAccess(alternateAccess)) + { MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); if (cachedArray == NULL) { @@ -730,8 +734,8 @@ mmsServer_handleWriteRequest( int index = mmsServer_getLowIndex(alternateAccess); int numberOfElements = mmsServer_getNumberOfElements(alternateAccess); - if (numberOfElements == 0) { /* select single array element with index */ - + if (numberOfElements == 0) /* select single array element with index */ + { MmsValue* elementValue = MmsValue_getElement(cachedArray, index); if (elementValue == NULL) { @@ -739,7 +743,8 @@ mmsServer_handleWriteRequest( goto end_of_main_loop; } - if (mmsServer_isAccessToArrayComponent(alternateAccess)) { + if (mmsServer_isAccessToArrayComponent(alternateAccess)) + { MmsVariableSpecification* namedVariable = MmsDomain_getNamedVariable(domain, nameIdStr); if (namedVariable) { @@ -757,16 +762,18 @@ mmsServer_handleWriteRequest( goto end_of_main_loop; } } - else { /* select sub-array with start-index and number-of-elements */ - - if (MmsValue_getType(value) != MMS_ARRAY) { + else /* select sub-array with start-index and number-of-elements */ + { + if (MmsValue_getType(value) != MMS_ARRAY) + { accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; goto end_of_main_loop; } int elementNo; - for (elementNo = 0; elementNo < numberOfElements; elementNo++) { + for (elementNo = 0; elementNo < numberOfElements; elementNo++) + { MmsValue* newElement = MmsValue_getElement(value, elementNo); MmsValue* elementValue = MmsValue_getElement(cachedArray, index++); @@ -785,22 +792,26 @@ mmsServer_handleWriteRequest( accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; goto end_of_main_loop; } - else if (mmsServer_isComponentAccess(alternateAccess)) { + else if (mmsServer_isComponentAccess(alternateAccess)) + { variable = getComponent(connection, domain, alternateAccess, variable, nameIdStr); - if (variable == NULL) { + if (variable == NULL) + { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; goto end_of_main_loop; } } - else { + else + { accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; goto end_of_main_loop; } } /* Check for correct type */ - if (MmsVariableSpecification_isValueOfType(variable, value) == false) { + if (MmsVariableSpecification_isValueOfType(variable, value) == false) + { accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; goto end_of_main_loop; } From a1e85e1452f350e85a38563c49aaa67a46168ba3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 22 Mar 2024 23:32:18 +0000 Subject: [PATCH 24/53] - extended ModelNode_getObjectReferenceEx to support arrays (LIB61850-437) --- src/iec61850/server/model/model.c | 100 +++++++++++++++------ src/mms/iso_mms/server/mms_server_common.c | 10 ++- 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 375d62f9..9593de48 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -538,19 +538,34 @@ static int createObjectReference(ModelNode* node, char* objectReference, int bufSize, bool withoutIedName) { int bufPos; + int arrayIndex = -1; - if (node->modelType != LogicalNodeModelType) { + if (node->modelType != LogicalNodeModelType) + { bufPos = createObjectReference(node->parent, objectReference, bufSize, withoutIedName); + if (node->modelType == DataAttributeModelType) + { + arrayIndex = ((DataAttribute*)(node))->arrayIndex; + } + else if (node->modelType == DataObjectModelType) + { + arrayIndex = ((DataObject*)(node))->arrayIndex; + } + if (bufPos == -1) return -1; - if (bufPos < bufSize) - objectReference[bufPos++] = '.'; - else - return -1; + if (arrayIndex < 0) + { + if (bufPos < bufSize) + objectReference[bufPos++] = '.'; + else + return -1; + } } - else { + else + { LogicalNode* lNode = (LogicalNode*) node; LogicalDevice* lDevice = (LogicalDevice*) lNode->parent; @@ -559,12 +574,13 @@ createObjectReference(ModelNode* node, char* objectReference, int bufSize, bool bufPos = 0; - if (withoutIedName) { + if (withoutIedName) + { objectReference[0] = 0; StringUtils_appendString(objectReference, bufSize, lDevice->name); } - else { - + else + { if (lDevice->ldName) { StringUtils_copyStringMax(objectReference, bufSize, lDevice->ldName); } @@ -581,20 +597,49 @@ createObjectReference(ModelNode* node, char* objectReference, int bufSize, bool return -1; } - /* append own name */ - int nameLength = strlen(node->name); + if (node->name) + { + /* append own name */ + int nameLength = strlen(node->name); + + if (bufPos + nameLength < bufSize) + { + int i; + for (i = 0; i < nameLength; i++) { + objectReference[bufPos++] = node->name[i]; + } - if (bufPos + nameLength < bufSize) { - int i; - for (i = 0; i < nameLength; i++) { - objectReference[bufPos++] = node->name[i]; + return bufPos; + } + else { + return -1; } - - return bufPos; } - else { - return -1; + + if (arrayIndex > -1) + { + char arrayIndexStr[11]; + + snprintf(arrayIndexStr, 11, "%d", arrayIndex); + + int arrayIndexStrLength = strlen(arrayIndexStr); + + if (bufPos + arrayIndexStrLength + 2 < bufSize) + { + int i; + + objectReference[bufPos++] = '('; + + for (i = 0; i < arrayIndexStrLength; i++) { + objectReference[bufPos++] = arrayIndexStr[i]; + } + objectReference[bufPos++] = ')'; + } + else + return -1; } + + return bufPos; } char* @@ -608,15 +653,18 @@ ModelNode_getObjectReferenceEx(ModelNode* node, char* objectReference, bool with { bool allocated = false; - if (objectReference == NULL) { + if (objectReference == NULL) + { objectReference = (char*) GLOBAL_MALLOC(130); allocated = true; } - if (objectReference) { + if (objectReference) + { int bufPos = createObjectReference(node, objectReference, 130, withoutIedName); - if (bufPos == -1) { + if (bufPos == -1) + { if (allocated) GLOBAL_FREEMEM(objectReference); @@ -729,16 +777,16 @@ ModelNode_getChild(ModelNode* self, const char* name) ModelNode* matchingNode = NULL; - while (nextNode) { - + while (nextNode) + { if (nextNode->name == NULL) { break; /* is an array element */ } int nodeNameLen = strlen(nextNode->name); - if (nodeNameLen == nameElementLength) { - + if (nodeNameLen == nameElementLength) + { if (memcmp(nextNode->name, name, nodeNameLen) == 0) { matchingNode = nextNode; break; diff --git a/src/mms/iso_mms/server/mms_server_common.c b/src/mms/iso_mms/server/mms_server_common.c index 455a26ec..f1524e83 100644 --- a/src/mms/iso_mms/server/mms_server_common.c +++ b/src/mms/iso_mms/server/mms_server_common.c @@ -309,12 +309,14 @@ mmsServer_getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVari goto exit_function; int i; - for (i = 0; i < structSpec->typeSpec.structure.elementCount; i++) { - + for (i = 0; i < structSpec->typeSpec.structure.elementCount; i++) + { if ((int) strlen(structSpec->typeSpec.structure.elements[i]->name) - == component.size) { + == component.size) + { if (strncmp(structSpec->typeSpec.structure.elements[i]->name, - (char*) component.buf, component.size) == 0) { + (char*) component.buf, component.size) == 0) + { MmsValue* value = MmsValue_getElement(structuredValue, i); if (mmsServer_isAccessToArrayComponent( From d1ab50298fcba3f87b1e58dabff92f8615b14ee7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 27 Mar 2024 12:26:58 +0000 Subject: [PATCH 25/53] - ACSE: added check for minimum message size (LIB61850-438) --- src/mms/iso_acse/acse.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mms/iso_acse/acse.c b/src/mms/iso_acse/acse.c index f46b024d..40ecafe0 100644 --- a/src/mms/iso_acse/acse.c +++ b/src/mms/iso_acse/acse.c @@ -420,6 +420,14 @@ AcseConnection_parseMessage(AcseConnection* self, ByteBuffer* message) { AcseIndication indication = ACSE_ERROR; + if (message == NULL || message->size < 1) + { + if (DEBUG_ACSE) + printf("ACSE: invalid message - no payload\n"); + + return ACSE_ERROR; + } + uint8_t* buffer = message->buffer; int messageSize = message->size; From 3280712e5ac6fe1145a35fa69bf82ca1f9e5a883 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 27 Mar 2024 18:55:07 +0000 Subject: [PATCH 26/53] - IED server: implemented write access handler for array elements and components of array elements (LIB61850-437) - IED server: new function IedServer_handleWriteAccessForDataObject (LIB61850-437) --- src/common/string_utilities.c | 14 ++--- src/iec61850/inc/iec61850_server.h | 15 ++++- src/iec61850/inc_private/logging.h | 2 +- src/iec61850/inc_private/mms_mapping.h | 2 +- src/iec61850/inc_private/mms_sv.h | 2 +- src/iec61850/inc_private/reporting.h | 4 +- src/iec61850/server/impl/ied_server.c | 32 +++++++++++ src/iec61850/server/mms_mapping/control.c | 2 +- src/iec61850/server/mms_mapping/logging.c | 2 +- src/iec61850/server/mms_mapping/mms_mapping.c | 55 +++++++++++++------ src/iec61850/server/mms_mapping/mms_sv.c | 2 +- src/iec61850/server/mms_mapping/reporting.c | 6 +- src/mms/inc_private/mms_server_internal.h | 6 +- src/mms/inc_private/mms_server_libinternal.h | 2 +- src/mms/iso_mms/server/mms_read_service.c | 17 ++++-- src/mms/iso_mms/server/mms_server.c | 38 +++++++++++-- src/mms/iso_mms/server/mms_server_common.c | 37 +++++++++---- src/mms/iso_mms/server/mms_write_service.c | 54 ++++++++++-------- 18 files changed, 210 insertions(+), 82 deletions(-) diff --git a/src/common/string_utilities.c b/src/common/string_utilities.c index 2f659e6f..1608f964 100644 --- a/src/common/string_utilities.c +++ b/src/common/string_utilities.c @@ -90,14 +90,14 @@ StringUtils_copyStringToBufferAndReplace(const char* str, char* buffer, char old char* StringUtils_createStringFromBuffer(const uint8_t* buf, int size) { - char* newStr = (char*) GLOBAL_MALLOC(size + 1); + char* newStr = (char*) GLOBAL_MALLOC(size + 1); - if (newStr) { - memcpy(newStr, buf, size); - newStr[size] = 0; - } + if (newStr) { + memcpy(newStr, buf, size); + newStr[size] = 0; + } - return newStr; + return newStr; } char* @@ -429,11 +429,11 @@ getCharWeight(int c) { static bool initialized = false; static char lookupTable[LT_MAX_CHARS + 1]; + static const char* charOrder = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz$_0123456789"; if (!initialized) { int ltIndex; int weight = 1; - const char* charOrder = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz$_0123456789"; for (ltIndex = 1; ltIndex < LT_MAX_CHARS; ltIndex++) { if (strchr(charOrder, ltIndex)) continue; diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index b14bb6cb..e059175f 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-2023 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -1845,6 +1845,19 @@ LIB61850_API void IedServer_handleWriteAccessForComplexAttribute(IedServer self, DataAttribute* dataAttribute, WriteAccessHandler handler, void* parameter); +/** + * \brief Install a WriteAccessHandler for all data attributes of a data object with a specific FC + * + * \param self the instance of IedServer to operate on. + * \param dataObject the data object to monitor + * \param fc the functional constraint to monitor + * \param handler the callback function that is invoked if a client tries to write to + * the monitored data attribute. + * \param parameter a user provided parameter that is passed to the WriteAccessHandler when called. +*/ +LIB61850_API void +IedServer_handleWriteAccessForDataObject(IedServer self, DataObject* dataObject, FunctionalConstraint fc, WriteAccessHandler handler, void* parameter); + typedef enum { ACCESS_POLICY_ALLOW, ACCESS_POLICY_DENY diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 82ef712b..79c3f246 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -124,7 +124,7 @@ LIB61850_INTERNAL MmsValue* LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, MmsServerConnection connection); LIB61850_INTERNAL MmsDataAccessError -LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, +LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig, MmsValue* value, MmsServerConnection connection); #endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ */ diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index 63ec5742..340a2d81 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -149,7 +149,7 @@ LIB61850_INTERNAL void MmsMapping_installReadAccessHandler(MmsMapping* self, ReadAccessHandler handler, void* paramter); LIB61850_INTERNAL MmsDataAccessError -Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, +Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig, MmsValue* value, MmsServerConnection connection); LIB61850_INTERNAL MmsValue* diff --git a/src/iec61850/inc_private/mms_sv.h b/src/iec61850/inc_private/mms_sv.h index 6f8663bf..a0bb0ded 100644 --- a/src/iec61850/inc_private/mms_sv.h +++ b/src/iec61850/inc_private/mms_sv.h @@ -41,7 +41,7 @@ LIB61850_INTERNAL MmsValue* LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig , MmsServerConnection connection); LIB61850_INTERNAL MmsDataAccessError -LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, +LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig, MmsValue* value, MmsServerConnection connection); LIB61850_INTERNAL void diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index 558343c2..eddeb2d1 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -126,7 +126,7 @@ LIB61850_INTERNAL void ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag, bool modelLocked); LIB61850_INTERNAL MmsValue* -ReportControl_getRCBValue(ReportControl* rc, char* elementName); +ReportControl_getRCBValue(ReportControl* rc, const char* elementName); LIB61850_INTERNAL MmsVariableSpecification* Reporting_createMmsBufferedRCBs(MmsMapping* self, MmsDomain* domain, @@ -137,7 +137,7 @@ Reporting_createMmsUnbufferedRCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, int reportsCount); LIB61850_INTERNAL MmsDataAccessError -Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value, +Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, const char* elementName, MmsValue* value, MmsServerConnection connection); LIB61850_INTERNAL bool diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 8a5c4baa..8c83ddc0 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -1679,6 +1679,38 @@ IedServer_handleWriteAccessForComplexAttribute(IedServer self, DataAttribute* da } } +void +IedServer_handleWriteAccessForDataObject(IedServer self, DataObject* dataObject, FunctionalConstraint fc, WriteAccessHandler handler, void* parameter) +{ + if (dataObject == NULL) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: IedServer_handlerWriteAccessForDataObject - dataObject == NULL!\n"); + } + else + { + ModelNode* childElement = dataObject->firstChild; + + while (childElement) + { + if (childElement->modelType == DataAttributeModelType) + { + DataAttribute* dataAttribute = (DataAttribute*) childElement; + + if (dataAttribute->fc == fc) + { + IedServer_handleWriteAccessForComplexAttribute(self, dataAttribute, handler, parameter); + } + } + else if (childElement->modelType == DataObjectModelType) + { + IedServer_handleWriteAccessForDataObject(self, (DataObject*) childElement, fc, handler, parameter); + } + + childElement = childElement->sibling; + } + } +} + void IedServer_setReadAccessHandler(IedServer self, ReadAccessHandler handler, void* parameter) { diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 6fd42d01..6643bb91 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -1923,7 +1923,7 @@ checkValidityOfOriginParameter(MmsValue* origin) } MmsDataAccessError -Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, +Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig, MmsValue* value, MmsServerConnection connection) { MmsDataAccessError indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 7fcb78f1..59b0ba40 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -482,7 +482,7 @@ copyLCBValuesToTrackingObject(MmsMapping* self, LogControl* logControl) #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ MmsDataAccessError -LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, +LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig, MmsValue* value, MmsServerConnection connection) { (void)connection; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 4cc38d39..3371e502 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2344,7 +2344,7 @@ lookupGCB(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName) #endif static MmsDataAccessError -writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, +writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig, MmsValue* value, MmsServerConnection connection) { char variableId[130]; @@ -2605,12 +2605,24 @@ getAccessPolicyForFC(MmsMapping* self, FunctionalConstraint fc) static MmsDataAccessError mmsWriteHandler(void* parameter, MmsDomain* domain, - char* variableId, MmsValue* value, MmsServerConnection connection) + const char* variableId, int arrayIdx, const char* componentId, MmsValue* value, MmsServerConnection connection) { MmsMapping* self = (MmsMapping*) parameter; if (DEBUG_IED_SERVER) - printf("IED_SERVER: Write requested %s\n", variableId); + { + if (arrayIdx != -1) { + if (componentId) { + printf("IED_SERVER: Write requested %s(%i).%s\n", variableId, arrayIdx, componentId); + } + else { + printf("IED_SERVER: Write requested %s(%i)\n", variableId, arrayIdx); + } + } + else { + printf("IED_SERVER: Write requested %s\n", variableId); + } + } /* Access control based on functional constraint */ @@ -2691,7 +2703,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, if (rcNameLen == variableIdLen) { if (strncmp(variableId, rc->name, variableIdLen) == 0) { - char* elementName = variableId + rcNameLen + 1; + const char* elementName = variableId + rcNameLen + 1; return Reporting_RCBWriteAccessHandler(self, rc, elementName, value, connection); } @@ -2946,13 +2958,19 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, #endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */ /* writable data model elements - SP, SV, CF, DC, BL */ - if (fc != IEC61850_FC_NONE) { + if (fc != IEC61850_FC_NONE) + { MmsValue* cachedValue; - cachedValue = MmsServer_getValueFromCache(self->mmsServer, domain, variableId); - - if (cachedValue) { + if (arrayIdx != -1) { + cachedValue = MmsServer_getValueFromCacheEx2(self->mmsServer, domain, variableId, arrayIdx, componentId); + } + else { + cachedValue = MmsServer_getValueFromCache(self->mmsServer, domain, variableId); + } + if (cachedValue) + { if (!MmsValue_equalTypes(cachedValue, value)) { return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; } @@ -2965,7 +2983,8 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, printf("IED_SERVER: write to %s policy:%i\n", variableId, nodeAccessPolicy); #if (CONFIG_IEC61850_SETTING_GROUPS == 1) - if (isFunctionalConstraint("SE", separator)) { + if (isFunctionalConstraint("SE", separator)) + { SettingGroup* sg = getSettingGroupByMmsDomain(self, domain); if (sg != NULL) { @@ -2982,12 +3001,13 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, /* Call write access handlers */ LinkedList writeHandlerListElement = LinkedList_getNext(self->attributeAccessHandlers); - while (writeHandlerListElement != NULL) { + while (writeHandlerListElement) + { AttributeAccessHandler* accessHandler = (AttributeAccessHandler*) writeHandlerListElement->data; DataAttribute* dataAttribute = accessHandler->attribute; - if (dataAttribute->mmsValue == cachedValue) { - + if (dataAttribute->mmsValue == cachedValue) + { ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); @@ -2995,7 +3015,8 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, accessHandler->handler(dataAttribute, value, clientConnection, accessHandler->parameter); - if ((handlerResult == DATA_ACCESS_ERROR_SUCCESS) || (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE)) { + if ((handlerResult == DATA_ACCESS_ERROR_SUCCESS) || (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE)) + { handlerFound = true; if (handlerResult == DATA_ACCESS_ERROR_SUCCESS_NO_UPDATE) @@ -3011,14 +3032,14 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, } /* DENY access if no handler is found and default policy is DENY */ - if (!handlerFound) { - + if (!handlerFound) + { if (nodeAccessPolicy == ACCESS_POLICY_DENY) return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; - } - if (updateValue) { + if (updateValue) + { DataAttribute* da = IedModel_lookupDataAttributeByMmsValue(self->model, cachedValue); if (da) diff --git a/src/iec61850/server/mms_mapping/mms_sv.c b/src/iec61850/server/mms_mapping/mms_sv.c index ef2ad775..3fbd0d2b 100644 --- a/src/iec61850/server/mms_mapping/mms_sv.c +++ b/src/iec61850/server/mms_mapping/mms_sv.c @@ -131,7 +131,7 @@ MmsSampledValueControlBlock_isEnabled(MmsSampledValueControlBlock self) } MmsDataAccessError -LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, +LIBIEC61850_SV_writeAccessSVControlBlock(MmsMapping* self, MmsDomain* domain, const char* variableIdOrig, MmsValue* value, MmsServerConnection connection) { char variableId[130]; diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 493d5e48..33143eb1 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -252,7 +252,7 @@ ReportControl_destroy(ReportControl* self) } MmsValue* -ReportControl_getRCBValue(ReportControl* rc, char* elementName) +ReportControl_getRCBValue(ReportControl* rc, const char* elementName) { if (rc->buffered) { if (strcmp(elementName, "RptID") == 0) @@ -460,7 +460,7 @@ copyRCBValuesToTrackingObject(MmsMapping* self, ReportControl* rc) } static void -updateSingleTrackingValue(MmsMapping* self, ReportControl* rc, char* name, MmsValue* newValue) +updateSingleTrackingValue(MmsMapping* self, ReportControl* rc, const char* name, MmsValue* newValue) { if (rc->buffered) { if (self->brcbTrk) { @@ -1906,7 +1906,7 @@ reserveRcb(ReportControl* rc, MmsServerConnection connection) } MmsDataAccessError -Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value, +Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, const char* elementName, MmsValue* value, MmsServerConnection connection) { MmsDataAccessError retVal = DATA_ACCESS_ERROR_SUCCESS; diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 08d0988a..3fae4f26 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -399,7 +399,7 @@ mmsServer_isAccessToArrayComponent(AlternateAccess_t* alternateAccess); LIB61850_INTERNAL MmsValue* mmsServer_getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable, - MmsValue* structuredValue); + MmsValue* structuredValue, char* componentId); LIB61850_INTERNAL int mmsServer_getLowIndex(AlternateAccess_t* alternateAccess); @@ -417,6 +417,10 @@ LIB61850_INTERNAL MmsDataAccessError mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* value, MmsServerConnection connection); +LIB61850_INTERNAL MmsDataAccessError +mmsServer_setValueEx(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* value, + MmsServerConnection connection, int arrayIdx, const char* componentId); + /** * \brief Get the current value of a variable in the server data model * diff --git a/src/mms/inc_private/mms_server_libinternal.h b/src/mms/inc_private/mms_server_libinternal.h index bdaf4b68..f7daa70c 100644 --- a/src/mms/inc_private/mms_server_libinternal.h +++ b/src/mms/inc_private/mms_server_libinternal.h @@ -34,7 +34,7 @@ typedef MmsDataAccessError (*MmsReadAccessHandler) (void* parameter, MmsDomain* char* variableId, MmsServerConnection connection, bool isDirectAccess); typedef MmsDataAccessError (*MmsWriteVariableHandler)(void* parameter, - MmsDomain* domain, char* variableId, MmsValue* value, + MmsDomain* domain, const char* variableId, int arrayIdx, const char* componentId, MmsValue* value, MmsServerConnection connection); typedef bool (*MmsListAccessHandler) (void* parameter, MmsGetNameListType listType, MmsDomain* domain, diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index 21b40c1c..043e03ce 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -202,30 +202,35 @@ alternateArrayAccess(MmsServerConnection connection, MmsValue* arrayValue = mmsServer_getValue(connection->server, domain, itemId, connection, false); - if (arrayValue != NULL) { - + if (arrayValue != NULL) + { MmsValue* value = NULL; if (numberOfElements == 0) - if (mmsServer_isAccessToArrayComponent(alternateAccess)) { + { + if (mmsServer_isAccessToArrayComponent(alternateAccess)) + { if (namedVariable->typeSpec.array.elementTypeSpec->type == MMS_STRUCTURE) { MmsValue* structValue = MmsValue_getElement(arrayValue, index); if (structValue != NULL) value = mmsServer_getComponentOfArrayElement(alternateAccess, - namedVariable, structValue); + namedVariable, structValue, NULL); } } else { value = MmsValue_getElement(arrayValue, index); } - else { + } + else + { value = MmsValue_createEmptyArray(numberOfElements); MmsValue_setDeletable(value); int resultIndex = 0; - while (index < lowIndex + numberOfElements) { + while (index < lowIndex + numberOfElements) + { MmsValue* elementValue = NULL; elementValue = MmsValue_getElement(arrayValue, index); diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 18b95464..2f6f2b27 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -536,9 +536,10 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va { MmsDataAccessError indication; - if (self->writeHandler != NULL) { + if (self->writeHandler) + { indication = self->writeHandler(self->writeHandlerParameter, domain, - itemId, value, connection); + itemId, -1, NULL, value, connection); } else { MmsValue* cachedValue; @@ -548,7 +549,36 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va cachedValue = MmsServer_getValueFromCache(self, domain, itemId); - if (cachedValue != NULL) { + if (cachedValue) { + MmsValue_update(cachedValue, value); + indication = DATA_ACCESS_ERROR_SUCCESS; + } else + indication = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + } + + return indication; +} + +MmsDataAccessError +mmsServer_setValueEx(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* value, + MmsServerConnection connection, int arrayIdx, const char* componentId) +{ + MmsDataAccessError indication; + + if (self->writeHandler) + { + indication = self->writeHandler(self->writeHandlerParameter, domain, + itemId, arrayIdx, componentId, value, connection); + } + else { + MmsValue* cachedValue = NULL; + + if (domain == NULL) + domain = (MmsDomain*) self->device; + + cachedValue = MmsServer_getValueFromCacheEx2(self, domain, itemId, arrayIdx, componentId); + + if (cachedValue) { MmsValue_update(cachedValue, value); indication = DATA_ACCESS_ERROR_SUCCESS; } else @@ -591,8 +621,6 @@ mmsServer_checkReadAccess(MmsServer self, MmsDomain* domain, char* itemId, MmsSe { MmsDataAccessError accessError = DATA_ACCESS_ERROR_SUCCESS; - printf("mmsServer_checkReadAccess(%s/%s)\n", domain->domainName, itemId); - if (self->readAccessHandler) { accessError = self->readAccessHandler(self->readAccessHandlerParameter, (domain == (MmsDomain*) self->device) ? NULL : domain, diff --git a/src/mms/iso_mms/server/mms_server_common.c b/src/mms/iso_mms/server/mms_server_common.c index f1524e83..545702be 100644 --- a/src/mms/iso_mms/server/mms_server_common.c +++ b/src/mms/iso_mms/server/mms_server_common.c @@ -287,7 +287,7 @@ mmsServer_isAccessToArrayComponent(AlternateAccess_t* alternateAccess) MmsValue* mmsServer_getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable, - MmsValue* structuredValue) + MmsValue* structuredValue, char* componentId) { MmsValue* retValue = NULL; @@ -319,16 +319,33 @@ mmsServer_getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVari { MmsValue* value = MmsValue_getElement(structuredValue, i); - if (mmsServer_isAccessToArrayComponent( - alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess)) { - retValue = - mmsServer_getComponentOfArrayElement( - alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess, - structSpec->typeSpec.structure.elements[i], - value); + if (value) + { + if (mmsServer_isAccessToArrayComponent( + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess)) + { + if (componentId) + { + strcat(componentId, structSpec->typeSpec.structure.elements[i]->name); + strcat(componentId, "$"); + } + + retValue = + mmsServer_getComponentOfArrayElement( + alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess, + structSpec->typeSpec.structure.elements[i], + value, componentId); + } + else + { + if (componentId) + { + strcat(componentId, structSpec->typeSpec.structure.elements[i]->name); + } + + retValue = value; + } } - else - retValue = value; goto exit_function; } diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index fdde1f16..f46ddef6 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -517,33 +517,36 @@ getComponent(MmsServerConnection connection, MmsDomain* domain, AlternateAccess_ { MmsVariableSpecification* retValue = NULL; - if (mmsServer_isComponentAccess(alternateAccess)) { + if (mmsServer_isComponentAccess(alternateAccess)) + { Identifier_t component = alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component; if (component.size > 129) goto exit_function; - if (namedVariable->type == MMS_STRUCTURE) { - + if (namedVariable->type == MMS_STRUCTURE) + { int i; - for (i = 0; i < namedVariable->typeSpec.structure.elementCount; i++) { - + for (i = 0; i < namedVariable->typeSpec.structure.elementCount; i++) + { if ((int) strlen(namedVariable->typeSpec.structure.elements[i]->name) - == component.size) { + == component.size) + { if (!strncmp(namedVariable->typeSpec.structure.elements[i]->name, (char*) component.buf, component.size)) { - if (strlen(variableName) + component.size < 199) { - + if (strlen(variableName) + component.size < 199) + { StringUtils_appendString(variableName, 200, "$"); /* here we need strncat because component.buf is not null terminated! */ strncat(variableName, (const char*)component.buf, (size_t)component.size); if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess - != NULL) { + != NULL) + { retValue = getComponent(connection, domain, alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess, @@ -690,7 +693,7 @@ mmsServer_handleWriteRequest( AlternateAccess_t* alternateAccess = varSpec->alternateAccess; - if (alternateAccess != NULL) + if (alternateAccess) { if ((variable->type == MMS_STRUCTURE) && (mmsServer_isComponentAccess(alternateAccess) == false)) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; @@ -717,7 +720,7 @@ mmsServer_handleWriteRequest( continue; } - if (alternateAccess != NULL) + if (alternateAccess) { if (domain == NULL) domain = (MmsDomain*) device; @@ -747,18 +750,26 @@ mmsServer_handleWriteRequest( { MmsVariableSpecification* namedVariable = MmsDomain_getNamedVariable(domain, nameIdStr); + char componentId[65]; + componentId[0] = 0; + if (namedVariable) { - elementValue = mmsServer_getComponentOfArrayElement(alternateAccess, namedVariable, elementValue); + elementValue = mmsServer_getComponentOfArrayElement(alternateAccess, namedVariable, elementValue, componentId); } if ((namedVariable == NULL) || (elementValue == NULL)) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; - goto end_of_main_loop; } - } + else + { + accessResults[i] = mmsServer_setValueEx(connection->server, domain, nameIdStr, value, connection, index, componentId); + } - if (MmsValue_update(elementValue, value) == false) { - accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; + goto end_of_main_loop; + } + else + { + accessResults[i] = mmsServer_setValueEx(connection->server, domain, nameIdStr, value, connection, index, NULL); goto end_of_main_loop; } } @@ -816,16 +827,13 @@ mmsServer_handleWriteRequest( goto end_of_main_loop; } - MmsDataAccessError valueIndication = - mmsServer_setValue(connection->server, domain, nameIdStr, value, connection); - - if (valueIndication == DATA_ACCESS_ERROR_NO_RESPONSE) - sendResponse = false; - - accessResults[i] = valueIndication; + accessResults[i] = mmsServer_setValue(connection->server, domain, nameIdStr, value, connection); end_of_main_loop: + if (accessResults[i] == DATA_ACCESS_ERROR_NO_RESPONSE) + sendResponse = false; + MmsValue_delete(value); } From 1a0bc8ab8b81e29e92f0721e537d319c978e0573 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Mar 2024 18:17:10 +0000 Subject: [PATCH 27/53] - .NET API: added function IedServer.HandlerWriteAccessForDataObject (LIB61850-437) --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index b0d2c054..97adae02 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2247,6 +2247,10 @@ namespace IEC61850 static extern void IedServer_handleWriteAccessForComplexAttribute(IntPtr self, IntPtr dataAttribute, InternalWriteAccessHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_handleWriteAccessForDataObject(IntPtr self, IntPtr dataObject, int fc, + InternalWriteAccessHandler handler, IntPtr parameter); + public delegate void ConnectionIndicationHandler(IedServer iedServer, ClientConnection clientConnection, bool connected, object parameter); private ConnectionIndicationHandler connectionHandler = null; @@ -2713,12 +2717,27 @@ namespace IEC61850 } } + private void AddHandlerInfoForDataObjectRecursive(DataObject dataObject, FunctionalConstraint fc, WriteAccessHandler handler, object parameter, InternalWriteAccessHandler internalHandler) + { + foreach (ModelNode child in dataObject.GetChildren()) + { + if (child is DataAttribute && (child as DataAttribute).FC == fc) + { + AddHandlerInfoForDataAttributeRecursive(child as DataAttribute, handler, parameter, internalHandler); + } + else if (child is DataObject) + { + AddHandlerInfoForDataObjectRecursive(child as DataObject, fc, handler, parameter, internalHandler); + } + } + } + /// /// Install a WriteAccessHandler for a data attribute and for all sub data attributes /// /// This instructs the server to monitor write attempts by MMS clients to specific - /// data attributes.If a client tries to write to the monitored data attribute the - /// handler is invoked.The handler can decide if the write access will be allowed + /// data attributes. If a client tries to write to the monitored data attribute the + /// handler is invoked. The handler can decide if the write access will be allowed /// or denied.If a WriteAccessHandler is set for a specific data attribute - the /// default write access policy will not be performed for that data attribute. /// @@ -2738,6 +2757,27 @@ namespace IEC61850 IedServer_handleWriteAccessForComplexAttribute(self, dataAttr.self, internalHandler, IntPtr.Zero); } + /// + /// Install a WriteAccessHandler for a data object and for all sub data objects and sub data attributes that have the same functional constraint + /// + /// This instructs the server to monitor write attempts by MMS clients to specific + /// data attributes. If a client tries to write to the monitored data attribute the + /// handler is invoked. The handler can decide if the write access will be allowed + /// or denied. If a WriteAccessHandler is set the + /// default write access policy will not be performed for the matching data attributes. + /// the data object to monitor + /// the functional constraint (FC) to monitor + /// the callback function that is invoked if a client tries to write to a monitored data attribute that is a child of the data object. + /// a user provided parameter that is passed to the WriteAccessHandler when called. + public void HandleWriteAccessForDataObject(DataObject dataObj, FunctionalConstraint fc, WriteAccessHandler handler, object parameter) + { + InternalWriteAccessHandler internalHandler = new InternalWriteAccessHandler(WriteAccessHandlerImpl); + + AddHandlerInfoForDataObjectRecursive(dataObj, fc, handler, parameter, internalHandler); + + IedServer_handleWriteAccessForDataObject(self, dataObj.self, (int)fc, internalHandler, IntPtr.Zero); + } + /// /// Set the defualt write access policy for a specific FC. The default policy is applied when no handler is installed for a data attribute /// From 4eebcb96a2d2a0e1441342d856a19b41159573a5 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 28 Mar 2024 18:24:52 +0000 Subject: [PATCH 28/53] - some format updates in config file parser (LIB61850-415) --- .../server/model/config_file_parser.c | 86 ++++++++++++------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index 25d3946d..1e6b5405 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -250,12 +250,14 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) int currentLine = 0; - while (bytesRead > 0) { + while (bytesRead > 0) + { bytesRead = readLine(fileHandle, lineBuffer, READ_BUFFER_MAX_SIZE); currentLine++; - if (bytesRead > 0) { + if (bytesRead > 0) + { lineBuffer[bytesRead] = 0; /* trim trailing spaces */ @@ -270,8 +272,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) } } - if (stateInModel) { - + if (stateInModel) + { if (StringUtils_startsWith((char*) lineBuffer, "}")) { if (indendation == 1) { stateInModel = false; @@ -302,8 +304,11 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentModelNode = currentModelNode->parent; } } - else if (indendation == 1) { - if (StringUtils_startsWith((char*) lineBuffer, "LD")) { + + else if (indendation == 1) + { + if (StringUtils_startsWith((char*) lineBuffer, "LD")) + { indendation = 2; char ldName[65]; @@ -314,7 +319,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) terminateString(nameString, ')'); - if (ldName[0] != 0) { + if (ldName[0] != 0) + { terminateString(ldName, ')'); currentLD = LogicalDevice_createEx(nameString, model, ldName); @@ -326,8 +332,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) else goto exit_error; } - else if (indendation == 2) { - if (StringUtils_startsWith((char*) lineBuffer, "LN")) { + else if (indendation == 2) + { + if (StringUtils_startsWith((char*) lineBuffer, "LN")) + { indendation = 3; if (sscanf((char*) lineBuffer, "LN(%129s)", nameString) < 1) @@ -340,8 +348,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) else goto exit_error; } - else if (indendation == 3) { - if (StringUtils_startsWith((char*) lineBuffer, "DO")) { + else if (indendation == 3) + { + if (StringUtils_startsWith((char*) lineBuffer, "DO")) + { indendation = 4; int arrayElements = 0; @@ -353,7 +363,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentModelNode = (ModelNode*) DataObject_create(nameString, (ModelNode*) currentLN, arrayElements); } - else if (StringUtils_startsWith((char*) lineBuffer, "DS")) { + else if (StringUtils_startsWith((char*) lineBuffer, "DS")) + { indendation = 4; if (sscanf((char*)lineBuffer, "DS(%129s)", nameString) != 1) { @@ -364,7 +375,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentDataSet = DataSet_create(nameString, currentLN); } - else if (StringUtils_startsWith((char*) lineBuffer, "RC")) { + else if (StringUtils_startsWith((char*) lineBuffer, "RC")) + { int isBuffered; uint32_t confRef; int trgOps; @@ -391,7 +403,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) ReportControlBlock_create(nameString, currentLN, rptId, (bool) isBuffered, dataSetName, confRef, trgOps, options, bufTm, intgPd); } - else if (StringUtils_startsWith((char*) lineBuffer, "LC")) { + else if (StringUtils_startsWith((char*) lineBuffer, "LC")) + { uint32_t trgOps; uint32_t intgPd; int logEna; @@ -412,7 +425,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) LogControlBlock_create(nameString, currentLN, dataSet, logRef, trgOps, intgPd, logEna, withReasonCode); } - else if (StringUtils_startsWith((char*) lineBuffer, "LOG")) { + else if (StringUtils_startsWith((char*) lineBuffer, "LOG")) + { int matchedItems = sscanf((char*) lineBuffer, "LOG(%129s)", nameString); if (matchedItems < 1) goto exit_error; @@ -422,7 +436,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) Log_create(nameString, currentLN); } - else if (StringUtils_startsWith((char*) lineBuffer, "GC")) { + else if (StringUtils_startsWith((char*) lineBuffer, "GC")) + { uint32_t confRef; int fixedOffs; int minTime = -1; @@ -438,7 +453,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) indendation = 4; } - else if (StringUtils_startsWith((char*) lineBuffer, "SMVC")) { + else if (StringUtils_startsWith((char*) lineBuffer, "SMVC")) + { uint32_t confRev; int smpMod; int smpRate; @@ -455,7 +471,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) indendation = 4; } #if (CONFIG_IEC61850_SETTING_GROUPS == 1) - else if (StringUtils_startsWith((char*) lineBuffer, "SG")) { + else if (StringUtils_startsWith((char*) lineBuffer, "SG")) + { if (strcmp(currentLN->name, "LLN0") != 0) { if (DEBUG_IED_SERVER) @@ -484,8 +501,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) } } - else if (indendation > 3) { - if (StringUtils_startsWith((char*) lineBuffer, "DO")) { + else if (indendation > 3) + { + if (StringUtils_startsWith((char*) lineBuffer, "DO")) + { indendation++; int arrayElements = 0; @@ -507,7 +526,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) goto exit_error; } - if (StringUtils_endsWith((char*)lineBuffer, ";")) { + if (StringUtils_endsWith((char*)lineBuffer, ";")) + { /* array of basic data attribute */ ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); @@ -517,9 +537,9 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) else { goto exit_error; } - } - else if (StringUtils_endsWith((char*)lineBuffer, "{")) { + else if (StringUtils_endsWith((char*)lineBuffer, "{")) + { /* array of constructed data attribtute */ currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); @@ -531,8 +551,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) } } } - else if (StringUtils_startsWith((char*) lineBuffer, "DA")) { - + else if (StringUtils_startsWith((char*) lineBuffer, "DA")) + { int arrayElements = 0; int attributeType = 0; @@ -561,7 +581,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentModelNode = (ModelNode*) dataAttribute; } } - else if (StringUtils_startsWith((char*) lineBuffer, "DE")) { + else if (StringUtils_startsWith((char*) lineBuffer, "DE")) + { char* start = strchr((char*) lineBuffer, '('); if (start) { @@ -595,7 +616,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) DataSetEntry_create(currentDataSet, nameString, indexVal, componentVal); } } - else if (StringUtils_startsWith((char*) lineBuffer, "PA")) { + else if (StringUtils_startsWith((char*) lineBuffer, "PA")) + { uint32_t vlanPrio; uint32_t vlanId; uint32_t appId; @@ -628,14 +650,16 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) goto exit_error; } } - else { - if (StringUtils_startsWith((char*) lineBuffer, "MODEL{")) { - + else + { + if (StringUtils_startsWith((char*) lineBuffer, "MODEL{")) + { model = IedModel_create(""); stateInModel = true; indendation = 1; } - else if (StringUtils_startsWith((char*) lineBuffer, "MODEL(")) { + else if (StringUtils_startsWith((char*) lineBuffer, "MODEL(")) + { if (sscanf((char*)lineBuffer, "MODEL(%129s)", nameString) != 1) goto exit_error; From f536d1c324a5415dc4bb4424de3c2633b73d3003 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 31 Mar 2024 20:16:42 +0100 Subject: [PATCH 29/53] - IED server: extended config file format to support arrays of data objects (LIB61850-415) --- .../server/model/config_file_parser.c | 79 +++++-- src/iec61850/server/model/dynamic_model.c | 131 +++++++----- src/iec61850/server/model/model.c | 26 ++- tools/model_generator/genconfig.jar | Bin 101613 -> 101863 bytes .../tools/DynamicModelGenerator.java | 192 +++++++++++------- 5 files changed, 268 insertions(+), 160 deletions(-) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index 1e6b5405..f9489375 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -1,7 +1,7 @@ /* * config_file_parser.c * - * Copyright 2014-2023 Michael Zillgith + * Copyright 2014-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -304,7 +304,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentModelNode = currentModelNode->parent; } } - else if (indendation == 1) { if (StringUtils_startsWith((char*) lineBuffer, "LD")) @@ -514,8 +513,15 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) if (matchedItems != 2) goto exit_error; currentModelNode = (ModelNode*) DataObject_create(nameString, currentModelNode, arrayElements); + + if (arrayElements > 0) + { + inArray = true; + currentArrayNode = currentModelNode; + } } - else if (StringUtils_startsWith((char*) lineBuffer, "[")) { + else if (StringUtils_startsWith((char*) lineBuffer, "[")) + { if (inArray == false) { goto exit_error; } @@ -526,27 +532,55 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) goto exit_error; } - if (StringUtils_endsWith((char*)lineBuffer, ";")) + if (arrayIndex < 0) { + goto exit_error; + } + + if (currentArrayNode->modelType == DataAttributeModelType) { - /* array of basic data attribute */ - ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + if (StringUtils_endsWith((char*)lineBuffer, ";")) + { + /* array of basic data attribute */ + ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); - if (arrayElementNode) { - setValue((char*)lineBuffer, (DataAttribute*)arrayElementNode); + if (arrayElementNode) { + setValue((char*)lineBuffer, (DataAttribute*)arrayElementNode); + } + else { + goto exit_error; + } } - else { - goto exit_error; + else if (StringUtils_endsWith((char*)lineBuffer, "{")) + { + /* array of constructed data attribtute */ + currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + + if (currentModelNode) { + inArrayElement = true; + } + else { + goto exit_error; + } } } - else if (StringUtils_endsWith((char*)lineBuffer, "{")) + else if (currentArrayNode->modelType == DataObjectModelType) { - /* array of constructed data attribtute */ - currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + if (StringUtils_endsWith((char*)lineBuffer, "{")) + { + /* array of constructed data attribtute */ + currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); - if (currentModelNode) { - inArrayElement = true; + if (currentModelNode) { + inArrayElement = true; + } + else { + goto exit_error; + } } - else { + else + { + if (DEBUG_IED_SERVER) + printf("Unexpected character at end of line: %s\n", lineBuffer); goto exit_error; } } @@ -567,7 +601,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) DataAttribute* dataAttribute = DataAttribute_create(nameString, currentModelNode, (DataAttributeType) attributeType, (FunctionalConstraint) functionalConstraint, triggerOptions, arrayElements, sAddr); - if (arrayElements > 0) { + if (arrayElements > 0) + { inArray = true; currentArrayNode = (ModelNode*)dataAttribute; } @@ -576,7 +611,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) int lineLength = (int) strlen((char*) lineBuffer); - if (lineBuffer[lineLength - 1] == '{') { + if (lineBuffer[lineLength - 1] == '{') + { indendation++; currentModelNode = (ModelNode*) dataAttribute; } @@ -585,7 +621,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) { char* start = strchr((char*) lineBuffer, '('); - if (start) { + if (start) + { start++; StringUtils_copyStringMax(nameString, 130, start); @@ -598,7 +635,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) /* check for index */ char* sep = strchr(nameString, ' '); - if (sep) { + if (sep) + { char* indexStr = sep + 1; *sep = 0; @@ -633,7 +671,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) if (StringUtils_createBufferFromHexString(nameString, (uint8_t*) nameString2) != 6) goto exit_error; - PhyComAddress* dstAddress = PhyComAddress_create((uint8_t) vlanPrio, (uint16_t) vlanId, (uint16_t) appId, (uint8_t*) nameString2); diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 68710320..bfe266ed 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -1,7 +1,7 @@ /* * dynamic_model.c * - * Copyright 2014-2022 Michael Zillgith + * Copyright 2014-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -75,10 +75,12 @@ IedModel_addDataSet(IedModel* self, DataSet* dataSet) { if (self->dataSets == NULL) self->dataSets = dataSet; - else { + else + { DataSet* lastDataSet = self->dataSets; - while (lastDataSet != NULL) { + while (lastDataSet != NULL) + { if (lastDataSet->sibling == NULL) { lastDataSet->sibling = dataSet; break; @@ -94,7 +96,8 @@ IedModel_addLogicalDevice(IedModel* self, LogicalDevice* lDevice) { if (self->firstChild == NULL) self->firstChild = lDevice; - else { + else + { LogicalDevice* sibling = self->firstChild; while (sibling->sibling != NULL) @@ -109,7 +112,8 @@ IedModel_addLog(IedModel* self, Log* log) { if (self->logs == NULL) self->logs = log; - else { + else + { Log* lastLog = self->logs; while (lastLog->sibling != NULL) @@ -124,7 +128,8 @@ IedModel_addLogControlBlock(IedModel* self, LogControlBlock* lcb) { if (self->lcbs == NULL) self->lcbs = lcb; - else { + else + { LogControlBlock* lastLcb = self->lcbs; while (lastLcb->sibling != NULL) @@ -139,7 +144,8 @@ IedModel_addReportControlBlock(IedModel* self, ReportControlBlock* rcb) { if (self->rcbs == NULL) self->rcbs = rcb; - else { + else + { ReportControlBlock* lastRcb = self->rcbs; while (lastRcb->sibling != NULL) @@ -155,7 +161,8 @@ IedModel_addSettingGroupControlBlock(IedModel* self, SettingGroupControlBlock* s { if (self->sgcbs == NULL) self->sgcbs = sgcb; - else { + else + { SettingGroupControlBlock* lastSgcb = self->sgcbs; while (lastSgcb->sibling != NULL) @@ -171,7 +178,8 @@ IedModel_addGSEControlBlock(IedModel* self, GSEControlBlock* gcb) { if (self->gseCBs == NULL) self->gseCBs = gcb; - else { + else + { GSEControlBlock* lastGcb = self->gseCBs; while (lastGcb->sibling) @@ -187,7 +195,8 @@ IedModel_addSMVControlBlock(IedModel* self, SVControlBlock* smvcb) if (self->svCBs == NULL) { self->svCBs = smvcb; } - else { + else + { SVControlBlock* lastSvCB = self->svCBs; while (lastSvCB->sibling) @@ -202,7 +211,8 @@ LogicalDevice_createEx(const char* inst, IedModel* parent, const char* ldName) { LogicalDevice* self = (LogicalDevice*) GLOBAL_CALLOC(1, sizeof(LogicalDevice)); - if (self) { + if (self) + { self->name = StringUtils_copyString(inst); self->modelType = LogicalDeviceModelType; self->parent = (ModelNode*) parent; @@ -257,7 +267,8 @@ LogicalNode_create(const char* name, LogicalDevice* parent) { LogicalNode* self = (LogicalNode*) GLOBAL_MALLOC(sizeof(LogicalNode)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = (ModelNode*) parent; self->modelType = LogicalNodeModelType; @@ -311,7 +322,8 @@ Log_create(const char* name, LogicalNode* parent) { Log* self = (Log*) GLOBAL_MALLOC(sizeof(Log)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; self->sibling = NULL; @@ -336,7 +348,8 @@ LogControlBlock_create(const char* name, LogicalNode* parent, const char* dataSe { LogControlBlock* self = (LogControlBlock*) GLOBAL_MALLOC(sizeof(LogControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; self->sibling = NULL; @@ -388,7 +401,8 @@ ReportControlBlock_create(const char* name, LogicalNode* parent, const char* rpt { ReportControlBlock* self = (ReportControlBlock*) GLOBAL_MALLOC(sizeof(ReportControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; @@ -469,7 +483,8 @@ SettingGroupControlBlock_create(LogicalNode* parent, uint8_t actSG, uint8_t numO SettingGroupControlBlock* self = (SettingGroupControlBlock*) GLOBAL_MALLOC(sizeof(SettingGroupControlBlock)); - if (self) { + if (self) + { self->parent = parent; self->actSG = actSG; self->numOfSGs = numOfSGs; @@ -497,7 +512,8 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, const char* appId, { GSEControlBlock* self = (GSEControlBlock*) GLOBAL_MALLOC(sizeof(GSEControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; @@ -541,7 +557,8 @@ SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, c { SVControlBlock* self = (SVControlBlock*) GLOBAL_MALLOC(sizeof(SVControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; @@ -587,7 +604,8 @@ PhyComAddress_create(uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint { PhyComAddress* self = (PhyComAddress*) GLOBAL_MALLOC(sizeof(PhyComAddress)); - if (self) { + if (self) + { self->vlanPriority = vlanPriority; self->vlanId = vlanId; self->appId = appId; @@ -630,7 +648,8 @@ DataObject_create(const char* name, ModelNode* parent, int arrayElements) { DataObject* self = (DataObject*) GLOBAL_MALLOC(sizeof(DataObject)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->modelType = DataObjectModelType; self->firstChild = NULL; @@ -640,21 +659,23 @@ DataObject_create(const char* name, ModelNode* parent, int arrayElements) self->elementCount = arrayElements; self->arrayIndex = -1; - if (arrayElements > 0) { + if (arrayElements > 0) + { int i; - for (i = 0; i < arrayElements; i++) { + for (i = 0; i < arrayElements; i++) + { DataObject* arrayElement = (DataObject*) GLOBAL_MALLOC(sizeof(DataObject)); - if (self) { - self->name = NULL; - self->modelType = DataObjectModelType; - self->firstChild = NULL; - self->parent = parent; - self->sibling = NULL; + if (arrayElement) { + arrayElement->name = NULL; + arrayElement->modelType = DataObjectModelType; + arrayElement->firstChild = NULL; + arrayElement->parent = (ModelNode*) self; + arrayElement->sibling = NULL; - self->elementCount = 0; - self->arrayIndex = i; + arrayElement->elementCount = 0; + arrayElement->arrayIndex = i; DataObject_addChild(self, (ModelNode*) arrayElement); } @@ -703,7 +724,8 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type { DataAttribute* self = (DataAttribute*) GLOBAL_MALLOC(sizeof(DataAttribute)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->elementCount = arrayElements; self->arrayIndex = -1; @@ -717,13 +739,16 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type self->triggerOptions = triggerOptions; self->sAddr = sAddr; - if (arrayElements > 0) { + if (arrayElements > 0) + { int i; - for (i = 0; i < arrayElements; i++) { + for (i = 0; i < arrayElements; i++) + { DataAttribute* arrayElement = (DataAttribute*) GLOBAL_MALLOC(sizeof(DataAttribute)); - if (arrayElement) { + if (arrayElement) + { arrayElement->name = NULL; arrayElement->elementCount = 0; arrayElement->arrayIndex = i; @@ -785,7 +810,8 @@ DataSet_create(const char* name, LogicalNode* parent) { DataSet* self = (DataSet*) GLOBAL_MALLOC(sizeof(DataSet)); - if (self) { + if (self) + { LogicalDevice* ld = (LogicalDevice*) parent->parent; self->name = StringUtils_createString(3, parent->name, "$", name); @@ -831,11 +857,12 @@ DataSet_addEntry(DataSet* self, DataSetEntry* newEntry) if (self->fcdas == NULL) self->fcdas = newEntry; - else { + else + { DataSetEntry* lastEntry = self->fcdas; - while (lastEntry != NULL) { - + while (lastEntry != NULL) + { if (lastEntry->sibling == NULL) { lastEntry->sibling = newEntry; break; @@ -851,21 +878,24 @@ DataSetEntry_create(DataSet* dataSet, const char* variable, int index, const cha { DataSetEntry* self = (DataSetEntry*) GLOBAL_MALLOC(sizeof(DataSetEntry)); - if (self) { + if (self) + { char variableName[130]; StringUtils_copyStringMax(variableName, 130, variable); char* separator = strchr(variableName, '/'); - if (separator != NULL) { + if (separator != NULL) + { *separator = 0; self->variableName = StringUtils_copyString(separator + 1); self->logicalDeviceName = StringUtils_copyString(variableName); self->isLDNameDynamicallyAllocated = true; } - else { + else + { self->variableName = StringUtils_copyString(variable); self->logicalDeviceName = dataSet->logicalDeviceName; self->isLDNameDynamicallyAllocated = false; @@ -891,14 +921,15 @@ DataSetEntry_create(DataSet* dataSet, const char* variable, int index, const cha static void ModelNode_destroy(ModelNode* modelNode) { - if (modelNode) { - + if (modelNode) + { if (modelNode->name) GLOBAL_FREEMEM(modelNode->name); ModelNode* currentChild = modelNode->firstChild; - while (currentChild != NULL) { + while (currentChild != NULL) + { ModelNode* nextChild = currentChild->sibling; ModelNode_destroy(currentChild); @@ -906,7 +937,8 @@ ModelNode_destroy(ModelNode* modelNode) currentChild = nextChild; } - if (modelNode->modelType == DataAttributeModelType) { + if (modelNode->modelType == DataAttributeModelType) + { DataAttribute* dataAttribute = (DataAttribute*) modelNode; if (dataAttribute->mmsValue != NULL) { @@ -929,8 +961,8 @@ IedModel_destroy(IedModel* model) LogicalDevice* ld = model->firstChild; - while (ld != NULL) { - + while (ld != NULL) + { if (ld->name) GLOBAL_FREEMEM(ld->name); @@ -939,7 +971,8 @@ IedModel_destroy(IedModel* model) LogicalNode* ln = (LogicalNode*) ld->firstChild; - while (ln != NULL) { + while (ln != NULL) + { GLOBAL_FREEMEM(ln->name); /* delete all data objects */ @@ -960,7 +993,6 @@ IedModel_destroy(IedModel* model) GLOBAL_FREEMEM(currentLn); } - LogicalDevice* currentLd = ld; ld = (LogicalDevice*) ld->sibling; @@ -1092,4 +1124,3 @@ IedModel_destroy(IedModel* model) GLOBAL_FREEMEM(model); } } - diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 9593de48..55b6409e 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -808,12 +808,14 @@ ModelNode_getChildWithIdx(ModelNode* self, int idx) { ModelNode* foundElement = NULL; - if (self->modelType == DataObjectModelType || self->modelType == DataAttributeModelType) { + if (self->modelType == DataObjectModelType || self->modelType == DataAttributeModelType) + { ModelNode* nextNode = self->firstChild; int currentIdx = 0; - while (nextNode) { + while (nextNode) + { if (currentIdx == idx) { foundElement = nextNode; break; @@ -845,14 +847,17 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint ModelNode* matchingNode = NULL; - while (nextNode != NULL) { + while (nextNode != NULL) + { int nodeNameLen = strlen(nextNode->name); - if (nodeNameLen == nameElementLength) { - if (memcmp(nextNode->name, name, nodeNameLen) == 0) { - + if (nodeNameLen == nameElementLength) + { + if (memcmp(nextNode->name, name, nodeNameLen) == 0) + { if (separator == NULL) { - if (nextNode->modelType == DataAttributeModelType) { + if (nextNode->modelType == DataAttributeModelType) + { DataAttribute* da = (DataAttribute*) nextNode; if (da->fc == fc) { @@ -861,9 +866,10 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint } } } - else { - - if (nextNode->modelType == DataAttributeModelType) { + else + { + if (nextNode->modelType == DataAttributeModelType) + { DataAttribute* da = (DataAttribute*) nextNode; if (da->fc == fc) { diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 2bb8ddbeb70a96e73b38d3b1b054fd8150bd7e81..51e83774a58706e93db31c714ed857db6d08ca1d 100644 GIT binary patch delta 10271 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 delta 10015 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 0) { + /* data object is an array */ + for (int i = 0; i < dataObject.getCount(); i++) { + output.print("[" + i + "]{\n"); + + exportDataObjectChild(output, dataObject, isTransient); + + output.print("}\n"); + } + } + else { + exportDataObjectChild(output, dataObject, isTransient); + } + + } + + private void printDataAttributeValue(PrintStream output, DataAttribute dataAttribute, boolean isTransient) + { + if (dataAttribute.isBasicAttribute()) { + DataModelValue value = dataAttribute.getValue(); + + /* if no value is given use default value for type if present */ + if (value == null) { + value = dataAttribute.getDefinition().getValue(); + + if (value != null) + if (value.getValue() == null) + value.updateEnumOrdValue(ied.getTypeDeclarations()); + } + + if (value != null) { + + switch (dataAttribute.getType()) { + case ENUMERATED: + case INT8: + case INT16: + case INT32: + case INT64: + output.print("=" + value.getIntValue()); + break; + case INT8U: + case INT16U: + case INT24U: + case INT32U: + output.print("=" + value.getLongValue()); + break; + case BOOLEAN: + { + Boolean boolVal = (Boolean) value.getValue(); + + if (boolVal.booleanValue()) + output.print("=1"); + } + break; + case UNICODE_STRING_255: + output.print("=\"" + value.getValue()+ "\""); + break; + case CURRENCY: + case VISIBLE_STRING_32: + case VISIBLE_STRING_64: + case VISIBLE_STRING_129: + case VISIBLE_STRING_255: + case VISIBLE_STRING_65: + output.print("=\"" + value.getValue()+ "\""); + break; + case FLOAT32: + case FLOAT64: + output.print("=" + value.getValue()); + break; + case TIMESTAMP: + case ENTRY_TIME: + output.print("=" + value.getLongValue()); + break; + + default: + System.out.println("Unknown default value for " + dataAttribute.getName() + " type: " + dataAttribute.getType()); + break; + } + + } + + output.println(";"); + } + else { + output.println("{"); + + for (DataAttribute subDataAttribute : dataAttribute.getSubDataAttributes()) { + exportDataAttribute(output, subDataAttribute, isTransient); + } + + output.println("}"); + } + } + private void exportDataAttribute(PrintStream output, DataAttribute dataAttribute, boolean isTransient) { output.print("DA(" + dataAttribute.getName() + " "); @@ -493,83 +588,22 @@ public class DynamicModelGenerator { else output.print("0"); - output.print(")"); - - if (dataAttribute.isBasicAttribute()) { - DataModelValue value = dataAttribute.getValue(); - - /* if no value is given use default value for type if present */ - if (value == null) { - value = dataAttribute.getDefinition().getValue(); - - if (value != null) - if (value.getValue() == null) - value.updateEnumOrdValue(ied.getTypeDeclarations()); - } - - if (value != null) { - - switch (dataAttribute.getType()) { - case ENUMERATED: - case INT8: - case INT16: - case INT32: - case INT64: - output.print("=" + value.getIntValue()); - break; - case INT8U: - case INT16U: - case INT24U: - case INT32U: - output.print("=" + value.getLongValue()); - break; - case BOOLEAN: - { - Boolean boolVal = (Boolean) value.getValue(); - - if (boolVal.booleanValue()) - output.print("=1"); - } - break; - case UNICODE_STRING_255: - output.print("=\"" + value.getValue()+ "\""); - break; - case CURRENCY: - case VISIBLE_STRING_32: - case VISIBLE_STRING_64: - case VISIBLE_STRING_129: - case VISIBLE_STRING_255: - case VISIBLE_STRING_65: - output.print("=\"" + value.getValue()+ "\""); - break; - case FLOAT32: - case FLOAT64: - output.print("=" + value.getValue()); - break; - case TIMESTAMP: - case ENTRY_TIME: - output.print("=" + value.getLongValue()); - break; - - default: - System.out.println("Unknown default value for " + dataAttribute.getName() + " type: " + dataAttribute.getType()); - break; - } - - } - - output.println(";"); - } - else { - output.println("{"); + output.print(")"); - for (DataAttribute subDataAttribute : dataAttribute.getSubDataAttributes()) { - exportDataAttribute(output, subDataAttribute, isTransient); + if (dataAttribute.getCount() > 0) { + output.print("{\n"); + + for (int i = 0; i < dataAttribute.getCount(); i++) { + output.print("[" + i + "]"); + + printDataAttributeValue(output, dataAttribute, isTransient); } - output.println("}"); + output.print("}\n"); + } + else { + printDataAttributeValue(output, dataAttribute, isTransient); } - } public static void main(String[] args) throws FileNotFoundException { From 23ae3c8ff3674b97e73ccf598036671b45ea7032 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 1 Apr 2024 10:32:19 +0100 Subject: [PATCH 30/53] - some code formatting --- src/iec61850/server/mms_mapping/mms_mapping.c | 6 +- src/iec61850/server/mms_mapping/reporting.c | 216 +++++++++++------- 2 files changed, 136 insertions(+), 86 deletions(-) diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 3371e502..90d863fa 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -4244,10 +4244,12 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, int flag) bool modelLocked = self->isModelLocked; - while ((element = LinkedList_getNext(element)) != NULL) { + while ((element = LinkedList_getNext(element)) != NULL) + { ReportControl* rc = (ReportControl*) element->data; - if (rc->enabled || (rc->buffered && rc->dataSet != NULL)) { + if (rc->enabled || (rc->buffered && rc->dataSet != NULL)) + { int index; switch (flag) { diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 33143eb1..0bb683f1 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1,7 +1,7 @@ /* * reporting.c * - * Copyright 2013-2023 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -185,7 +185,8 @@ purgeBuf(ReportControl* rc) static void deleteDataSetValuesShadowBuffer(ReportControl* self) { - if (self->bufferedDataSetValues != NULL) { + if (self->bufferedDataSetValues != NULL) + { assert(self->dataSet != NULL); int dataSetSize = DataSet_getSize(self->dataSet); @@ -2911,13 +2912,14 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ int dataBlockSize = 0; - if (isIntegrity || isGI) { - + if (isIntegrity || isGI) + { DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas; int i; - for (i = 0; i < inclusionBitStringSize; i++) { + for (i = 0; i < inclusionBitStringSize; i++) + { /* don't need reason for inclusion in GI or integrity report */ int encodedSize; @@ -2925,7 +2927,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ if (dataSetEntry->value) { encodedSize = MmsValue_encodeMmsData(dataSetEntry->value, NULL, 0, false); } - else { + else + { MmsValue _errVal; _errVal.type = MMS_DATA_ACCESS_ERROR; _errVal.value.dataAccessError = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; @@ -2940,17 +2943,19 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ bufferEntrySize += MemoryAllocator_getAlignedSize(sizeof(int) + dataBlockSize); /* add aligned_size(LEN + DATA) */ } - else { /* other trigger reason */ + else + { + /* other trigger reason */ bufferEntrySize += inclusionFieldSize; int reasonForInclusionSize = 0; int i; - for (i = 0; i < inclusionBitStringSize; i++) { - - if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) { - + for (i = 0; i < inclusionBitStringSize; i++) + { + if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) + { reasonForInclusionSize++; assert(reportControl->bufferedDataSetValues[i] != NULL); @@ -2985,13 +2990,15 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ if (DEBUG_IED_SERVER) printf("IED_SERVER: number of reports in report buffer: %i\n", buffer->reportsCount); - if (buffer->lastEnqueuedReport == NULL) { /* buffer is empty - we start at the beginning of the memory block */ + if (buffer->lastEnqueuedReport == NULL) + { + /* buffer is empty - we start at the beginning of the memory block */ entryBufPos = buffer->memoryBlock; buffer->oldestReport = (ReportBufferEntry*) entryBufPos; buffer->nextToTransmit = (ReportBufferEntry*) entryBufPos; } - else { - + else + { assert(buffer->lastEnqueuedReport != NULL); assert(buffer->oldestReport != NULL); @@ -3003,12 +3010,16 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ if (DEBUG_IED_SERVER) printf ("IED_SERVER: Last buffer offset: %i\n", (int) ((uint8_t*) buffer->lastEnqueuedReport - buffer->memoryBlock)); - if (buffer->lastEnqueuedReport == buffer->oldestReport) { /* --> buffer->reportsCount == 1 */ + if (buffer->lastEnqueuedReport == buffer->oldestReport) + { + /* --> buffer->reportsCount == 1 */ assert(buffer->reportsCount == 1); entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength); - if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */ + if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) + { + /* buffer overflow */ entryBufPos = buffer->memoryBlock; #if (DEBUG_IED_SERVER == 1) @@ -3022,7 +3033,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ buffer->oldestReport->next = NULL; buffer->nextToTransmit = NULL; } - else { + else + { if (buffer->nextToTransmit == buffer->oldestReport) buffer->nextToTransmit = buffer->lastEnqueuedReport; @@ -3031,17 +3043,22 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ } } - else if (buffer->lastEnqueuedReport > buffer->oldestReport) { + else if (buffer->lastEnqueuedReport > buffer->oldestReport) + { entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength); - if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */ + if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) + { + /* buffer overflow */ entryBufPos = buffer->memoryBlock; /* remove old reports until enough space for new entry is available */ - while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) { + while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) + { assert(buffer->oldestReport != NULL); - if (buffer->nextToTransmit == buffer->oldestReport) { + if (buffer->nextToTransmit == buffer->oldestReport) + { buffer->nextToTransmit = buffer->oldestReport->next; buffer->isOverflow = true; overflow = true; @@ -3056,7 +3073,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ buffer->reportsCount--; - if (buffer->oldestReport == NULL) { + if (buffer->oldestReport == NULL) + { buffer->oldestReport = (ReportBufferEntry*) entryBufPos; buffer->oldestReport->next = NULL; break; @@ -3066,14 +3084,18 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ buffer->lastEnqueuedReport->next = (ReportBufferEntry*) entryBufPos; } - else if (buffer->lastEnqueuedReport < buffer->oldestReport) { + else if (buffer->lastEnqueuedReport < buffer->oldestReport) + { entryBufPos = (uint8_t*) ((uint8_t*) buffer->lastEnqueuedReport + buffer->lastEnqueuedReport->entryLength); - if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) { /* buffer overflow */ + if ((entryBufPos + bufferEntrySize) > (buffer->memoryBlock + buffer->memoryBlockSize)) + { + /* buffer overflow */ entryBufPos = buffer->memoryBlock; /* remove older reports in upper buffer part */ - while ((uint8_t*) buffer->oldestReport > buffer->memoryBlock) { + while ((uint8_t*) buffer->oldestReport > buffer->memoryBlock) + { assert(buffer->oldestReport != NULL); if (buffer->nextToTransmit == buffer->oldestReport) { @@ -3093,13 +3115,15 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ } /* remove older reports in lower buffer part that will be overwritten by new report */ - while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) { + while ((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) + { if (buffer->oldestReport == NULL) break; assert(buffer->oldestReport != NULL); - if (buffer->nextToTransmit == buffer->oldestReport) { + if (buffer->nextToTransmit == buffer->oldestReport) + { buffer->nextToTransmit = buffer->oldestReport->next; buffer->isOverflow = true; overflow = true; @@ -3115,15 +3139,17 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ buffer->reportsCount--; } } - else { - while (((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) && ((uint8_t*) buffer->oldestReport != buffer->memoryBlock)) { - + else + { + while (((entryBufPos + bufferEntrySize) > (uint8_t*) buffer->oldestReport) && ((uint8_t*) buffer->oldestReport != buffer->memoryBlock)) + { if (buffer->oldestReport == NULL) break; assert(buffer->oldestReport != NULL); - if (buffer->nextToTransmit == buffer->oldestReport) { + if (buffer->nextToTransmit == buffer->oldestReport) + { buffer->nextToTransmit = buffer->oldestReport->next; buffer->isOverflow = true; overflow = true; @@ -3154,7 +3180,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ entry->timeOfEntry = timeOfEntry; - if (isBuffered) { + if (isBuffered) + { /* ENTRY_ID is set to system time in ms! */ uint64_t entryId = timeOfEntry; @@ -3173,7 +3200,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ printf(" at pos %p\n", entryStartPos); #endif - if (reportControl->enabled == false) { + if (reportControl->enabled == false) + { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(reportControl->rcbValuesLock); #endif @@ -3200,7 +3228,8 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ entryBufPos += MemoryAllocator_getAlignedSize(sizeof(ReportBufferEntry)); - if (isIntegrity || isGI) { + if (isIntegrity || isGI) + { DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas; /* encode LEN */ @@ -3210,12 +3239,13 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ /* encode DATA */ int i; - for (i = 0; i < inclusionBitStringSize; i++) { - + for (i = 0; i < inclusionBitStringSize; i++) + { if (dataSetEntry->value) { entryBufPos += MmsValue_encodeMmsData(dataSetEntry->value, entryBufPos, 0, true); } - else { + else + { MmsValue _errVal; _errVal.type = MMS_DATA_ACCESS_ERROR; _errVal.value.dataAccessError = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; @@ -3225,9 +3255,9 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ dataSetEntry = dataSetEntry->sibling; } - } - else { + else + { /* encode inclusion bit string */ inclusionFieldStatic.value.bitString.buf = entryBufPos; memset(entryBufPos, 0, inclusionFieldSize); @@ -3240,10 +3270,10 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ /* encode DATA */ int i; - for (i = 0; i < inclusionBitStringSize; i++) { - - if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) { - + for (i = 0; i < inclusionBitStringSize; i++) + { + if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) + { /* update inclusion bit string for report entry */ MmsValue_setBitStringBit(inclusionField, i, true); @@ -3251,13 +3281,14 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ entryBufPos += MmsValue_encodeMmsData(reportControl->bufferedDataSetValues[i], entryBufPos, 0, true); } - } /* encode REASON */ - for (i = 0; i < inclusionBitStringSize; i++) { + for (i = 0; i < inclusionBitStringSize; i++) + { - if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) { + if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) + { *entryBufPos = (uint8_t) reportControl->inclusionFlags[i]; entryBufPos ++; } @@ -3286,10 +3317,12 @@ exit_function: Semaphore_post(buffer->lock); #endif - if (reportControl->server) { + if (reportControl->server) + { MmsMapping* mmsMapping = reportControl->server->mmsMapping; - if (mmsMapping->rcbEventHandler) { + if (mmsMapping->rcbEventHandler) + { if (overflow) { mmsMapping->rcbEventHandler(mmsMapping->rcbEventHandlerParameter, reportControl->rcb, NULL, RCB_EVENT_OVERFLOW, NULL, DATA_ACCESS_ERROR_SUCCESS); } @@ -3980,11 +4013,12 @@ Reporting_activateBufferedReports(MmsMapping* self) static void processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) { - if ((rc->enabled) || (rc->isBuffering)) { - - if (rc->triggerOps & TRG_OPT_GI) { - if (rc->gi) { - + if ((rc->enabled) || (rc->isBuffering)) + { + if (rc->triggerOps & TRG_OPT_GI) + { + if (rc->gi) + { /* send current events in event buffer before GI report */ if (rc->triggered) { rc->triggered = false; @@ -3999,12 +4033,12 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) } } - if (rc->triggerOps & TRG_OPT_INTEGRITY) { - - if (rc->intgPd > 0) { - - if (currentTimeInMs >= rc->nextIntgReportTime) { - + if (rc->triggerOps & TRG_OPT_INTEGRITY) + { + if (rc->intgPd > 0) + { + if (currentTimeInMs >= rc->nextIntgReportTime) + { /* send current events in event buffer before integrity report */ if (rc->triggered) { enqueueReport(rc, false, false, currentTimeInMs); @@ -4019,8 +4053,8 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) } /* check for system time change effects */ - if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd)) { - + if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd)) + { if (rc->server->syncIntegrityReportTimes) { rc->nextIntgReportTime = getNextRoundedStartTime(currentTimeInMs, rc->intgPd); } @@ -4033,9 +4067,11 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) rc->triggered = false; } - else { + else + { /* check for system time change effects */ - if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd)) { + if ((rc->nextIntgReportTime < currentTimeInMs) || (rc->nextIntgReportTime > currentTimeInMs + rc->intgPd)) + { if (rc->server->syncIntegrityReportTimes) { rc->nextIntgReportTime = getNextRoundedStartTime(currentTimeInMs, rc->intgPd); } @@ -4047,9 +4083,10 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) } } - if (rc->triggered) { - if (currentTimeInMs >= rc->reportTime) { - + if (rc->triggered) + { + if (currentTimeInMs >= rc->reportTime) + { enqueueReport(rc, false, false, currentTimeInMs); rc->triggered = false; @@ -4065,11 +4102,12 @@ Reporting_processReportEvents(MmsMapping* self, uint64_t currentTimeInMs) Semaphore_wait(self->isModelLockedMutex); #endif - if (self->isModelLocked == false) { - + if (self->isModelLocked == false) + { LinkedList element = self->reportControls; - while ((element = LinkedList_getNext(element)) != NULL ) { + while ((element = LinkedList_getNext(element)) != NULL ) + { ReportControl* rc = (ReportControl*) element->data; ReportControl_lockNotify(rc); @@ -4093,11 +4131,12 @@ Reporting_sendReports(MmsMapping* self, MmsServerConnection connection) { LinkedList element = LinkedList_getNext(self->reportControls); - while (element) { + while (element) + { ReportControl* rc = (ReportControl*) LinkedList_getData(element); - if (rc->clientConnection == connection) { - + if (rc->clientConnection == connection) + { ReportControl_lockNotify(rc); if (rc->enabled) { @@ -4124,8 +4163,10 @@ static void copyValuesToReportBuffer(ReportControl* self) { int i; - for (i = 0; i < self->dataSet->elementCount; i++) { - if (self->inclusionFlags[i] & REPORT_CONTROL_NOT_UPDATED) { + for (i = 0; i < self->dataSet->elementCount; i++) + { + if (self->inclusionFlags[i] & REPORT_CONTROL_NOT_UPDATED) + { copySingleValueToReportBuffer(self, i); /* clear not-updated flag */ @@ -4142,13 +4183,14 @@ Reporting_processReportEventsAfterUnlock(MmsMapping* self) uint64_t currentTime = Hal_getTimeInMs(); - while ((element = LinkedList_getNext(element)) != NULL ) { + while ((element = LinkedList_getNext(element)) != NULL ) + { ReportControl* rc = (ReportControl*) element->data; ReportControl_lockNotify(rc); - if ((rc->enabled) || (rc->isBuffering)) { - + if ((rc->enabled) || (rc->isBuffering)) + { if (rc->triggered) { copyValuesToReportBuffer(rc); @@ -4166,10 +4208,13 @@ ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag, { ReportControl_lockNotify(self); - if (self->inclusionFlags[dataSetEntryIndex] & flag) { /* report for this data set entry is already pending (bypass BufTm) */ + if (self->inclusionFlags[dataSetEntryIndex] & flag) + { + /* report for this data set entry is already pending (bypass BufTm and send report immediately) */ self->reportTime = Hal_getTimeInMs(); - if (modelLocked) { + if (modelLocked) + { /* buffer all relevant values */ copyValuesToReportBuffer(self); } @@ -4177,19 +4222,21 @@ ReportControl_valueUpdated(ReportControl* self, int dataSetEntryIndex, int flag, processEventsForReport(self, self->reportTime); } - if (modelLocked) { + if (modelLocked) + { /* set flag to update values when report is to be sent or data model unlocked */ self->inclusionFlags[dataSetEntryIndex] = self->inclusionFlags[dataSetEntryIndex] | flag | REPORT_CONTROL_NOT_UPDATED; - } - else { + else + { self->inclusionFlags[dataSetEntryIndex] = flag; /* buffer value for report */ copySingleValueToReportBuffer(self, dataSetEntryIndex); } - if (self->triggered == false) { + if (self->triggered == false) + { uint64_t currentTime = Hal_getTimeInMs(); MmsValue_setBinaryTime(self->timeOfEntry, currentTime); @@ -4218,7 +4265,8 @@ ReportControlBlock_getRptEna(ReportControlBlock* self) char* ReportControlBlock_getRptID(ReportControlBlock* self) { - if (self->trgOps & 64) { + if (self->trgOps & 64) + { ReportControl* rc = (ReportControl*)(self->sibling); #if (CONFIG_MMS_THREADLESS_STACK != 1) From 37060d92d882989defe100ec6c91c92efc1a12bb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 8 Apr 2024 17:46:43 +0100 Subject: [PATCH 31/53] - GOOSE publisher: fixed - publisher parameters not set correctly (I6LLCV-76) --- src/goose/goose_publisher.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/goose/goose_publisher.c b/src/goose/goose_publisher.c index 60bacd43..46ed8feb 100644 --- a/src/goose/goose_publisher.c +++ b/src/goose/goose_publisher.c @@ -219,17 +219,17 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char* uint16_t appId; if (parameters) { - dstAddr = defaultDstAddr; - priority = CONFIG_GOOSE_DEFAULT_PRIORITY; - vlanId = CONFIG_GOOSE_DEFAULT_VLAN_ID; - appId = CONFIG_GOOSE_DEFAULT_APPID; - } - else { dstAddr = parameters->dstAddress; priority = parameters->vlanPriority; vlanId = parameters->vlanId; appId = parameters->appId; } + else { + dstAddr = defaultDstAddr; + priority = CONFIG_GOOSE_DEFAULT_PRIORITY; + vlanId = CONFIG_GOOSE_DEFAULT_VLAN_ID; + appId = CONFIG_GOOSE_DEFAULT_APPID; + } if (interfaceID) self->ethernetSocket = Ethernet_createSocket(interfaceID, dstAddr); From 569c4a5c20c475bf34894849c4bc64b37f857ef1 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 May 2024 10:03:25 +0100 Subject: [PATCH 32/53] - .NET API: Added toString method for IedClientError (calling native function IedClientError_toString) --- dotnet/example2/WriteValueExample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/example2/WriteValueExample.cs b/dotnet/example2/WriteValueExample.cs index eb8b0228..b3ea3c04 100644 --- a/dotnet/example2/WriteValueExample.cs +++ b/dotnet/example2/WriteValueExample.cs @@ -36,7 +36,7 @@ namespace example2 } catch (IedConnectionException e) { - Console.WriteLine("IED connection excepion: " + e.Message); + Console.WriteLine("IED connection exception: " + e.Message + " err: " + e.GetIedClientError().ToString()); } // release all resources - do NOT use the object after this call!! From 5c3fd679a8740eef5092d1cb8e6bc403e2028e1e Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 May 2024 14:48:45 +0100 Subject: [PATCH 33/53] - code format changes - IED server: added function IedServer_ignoreReadAccess --- src/iec61850/client/client_report_control.c | 45 +- src/iec61850/client/ied_connection.c | 877 +++++++++++------- src/iec61850/inc/iec61850_server.h | 9 + .../inc_private/ied_connection_private.h | 11 +- src/iec61850/inc_private/ied_server_private.h | 3 +- src/iec61850/server/impl/client_connection.c | 13 +- src/iec61850/server/impl/ied_server.c | 36 +- src/iec61850/server/mms_mapping/control.c | 563 ++++++----- src/iec61850/server/mms_mapping/mms_mapping.c | 68 +- 9 files changed, 1024 insertions(+), 601 deletions(-) diff --git a/src/iec61850/client/client_report_control.c b/src/iec61850/client/client_report_control.c index 6affd50e..65967cdb 100644 --- a/src/iec61850/client/client_report_control.c +++ b/src/iec61850/client/client_report_control.c @@ -528,39 +528,44 @@ readObjectHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_GetRCBValuesHandler handler = (IedConnection_GetRCBValuesHandler) call->callback; ClientReportControlBlock updateRcb = (ClientReportControlBlock) call->specificParameter; char* rcbReference = (char*) call->specificParameter2.pointer; - - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), NULL); } - else { - - if (value == NULL) { + else + { + if (value == NULL) + { handler(invokeId, call->callbackParameter, IED_ERROR_OBJECT_DOES_NOT_EXIST, NULL); } - else { - if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) { + else + { + if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) + { if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: getRCBValues returned data-access-error!\n"); handler(invokeId, call->callbackParameter, iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)), NULL); } - else { - + else + { ClientReportControlBlock returnRcb = updateRcb; if (returnRcb == NULL) returnRcb = ClientReportControlBlock_create(rcbReference); - if (clientReportControlBlock_updateValues(returnRcb, value)) { + if (clientReportControlBlock_updateValues(returnRcb, value)) + { handler(invokeId, call->callbackParameter, IED_ERROR_OK, returnRcb); } - else { + else + { if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: getRCBValues returned wrong type!\n"); @@ -569,19 +574,18 @@ readObjectHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV if (updateRcb == NULL) ClientReportControlBlock_destroy(returnRcb); } - } MmsValue_delete(value); } - } GLOBAL_FREEMEM(rcbReference); iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -598,7 +602,8 @@ IedConnection_getRCBValuesAsync(IedConnection self, IedClientError* error, const char* domainName = MmsMapping_getMmsDomainFromObjectReference(rcbReference, domainId); - if (domainName == NULL) { + if (domainName == NULL) + { *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; return 0; } @@ -608,7 +613,8 @@ IedConnection_getRCBValuesAsync(IedConnection self, IedClientError* error, const IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -627,7 +633,8 @@ IedConnection_getRCBValuesAsync(IedConnection self, IedClientError* error, const *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { GLOBAL_FREEMEM(call->specificParameter2.pointer); iedConnection_releaseOutstandingCall(self, call); return 0; diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 014f204f..cf363c3b 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -1,7 +1,7 @@ /* * ied_connection.c * - * Copyright 2013-2023 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -47,7 +47,8 @@ struct sClientDataSet MmsValue* dataSetValues; /* MmsValue instance of type MMS_ARRAY */ }; -struct sFileDirectoryEntry { +struct sFileDirectoryEntry +{ char* fileName; uint32_t fileSize; uint64_t lastModified; @@ -271,8 +272,10 @@ iedConnection_allocateOutstandingCall(IedConnection self) int i = 0; - for (i = 0; i < OUTSTANDING_CALLS; i++) { - if (self->outstandingCalls[i].used == false) { + for (i = 0; i < OUTSTANDING_CALLS; i++) + { + if (self->outstandingCalls[i].used == false) + { self->outstandingCalls[i].used = true; call = &(self->outstandingCalls[i]); break; @@ -303,8 +306,10 @@ iedConnection_lookupOutstandingCall(IedConnection self, uint32_t invokeId) int i = 0; - for (i = 0; i < OUTSTANDING_CALLS; i++) { - if ((self->outstandingCalls[i].used) && (self->outstandingCalls[i].invokeId == invokeId)) { + for (i = 0; i < OUTSTANDING_CALLS; i++) + { + if ((self->outstandingCalls[i].used) && (self->outstandingCalls[i].invokeId == invokeId)) + { call = &(self->outstandingCalls[i]); break; } @@ -1121,7 +1126,8 @@ IedConnection_getServerDirectoryAsync(IedConnection self, IedClientError* error, MmsConnection_getDomainNamesAsync(self->connection, &(call->invokeId), &err, continueAfter, result, getNameListHandler, self); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(err); iedConnection_releaseOutstandingCall(self, call); @@ -1153,7 +1159,8 @@ IedConnection_getLogicalDeviceVariablesAsync(IedConnection self, IedClientError* MmsConnection_getDomainVariableNamesAsync(self->connection, &(call->invokeId), &err, ldName, continueAfter, result, getNameListHandler, self); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(err); iedConnection_releaseOutstandingCall(self, call); @@ -1185,7 +1192,8 @@ IedConnection_getLogicalDeviceDataSetsAsync(IedConnection self, IedClientError* MmsConnection_getDomainVariableListNamesAsync(self->connection, &(call->invokeId), &err, ldName, continueAfter, result, getNameListHandler, self); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(err); iedConnection_releaseOutstandingCall(self, call); @@ -1206,15 +1214,16 @@ readObjectHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_ReadObjectHandler handler = (IedConnection_ReadObjectHandler) call->callback; handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), value); iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -1235,14 +1244,16 @@ IedConnection_readObjectAsync(IedConnection self, IedClientError* error, const c domainId = MmsMapping_getMmsDomainFromObjectReference(objRef, domainIdBuffer); itemId = MmsMapping_createMmsVariableNameFromObjectReference(objRef, fc, itemIdBuffer); - if ((domainId == NULL) || (itemId == NULL)) { + if ((domainId == NULL) || (itemId == NULL)) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -1255,15 +1266,18 @@ IedConnection_readObjectAsync(IedConnection self, IedClientError* error, const c /* check if item ID contains an array "(..)" */ char* brace = strchr(itemId, '('); - if (brace) { + if (brace) + { char* secondBrace = strchr(brace, ')'); - if (secondBrace) { + if (secondBrace) + { char* endPtr; int index = (int) strtol(brace + 1, &endPtr, 10); - if (endPtr == secondBrace) { + if (endPtr == secondBrace) + { char* component = NULL; if (strlen(secondBrace + 1) > 1) @@ -1282,8 +1296,8 @@ IedConnection_readObjectAsync(IedConnection self, IedClientError* error, const c else MmsConnection_readVariableAsync(self->connection, &(call->invokeId), &err, domainId, itemId, readObjectHandlerInternal, self); - if ((err != MMS_ERROR_NONE) || (*error != IED_ERROR_OK)) { - + if ((err != MMS_ERROR_NONE) || (*error != IED_ERROR_OK)) + { if (err != MMS_ERROR_NONE) { *error = iedConnection_mapMmsErrorToIedError(err); } @@ -1296,7 +1310,6 @@ IedConnection_readObjectAsync(IedConnection self, IedClientError* error, const c return call->invokeId; } - MmsValue* IedConnection_readObject(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc) @@ -1311,7 +1324,8 @@ IedConnection_readObject(IedConnection self, IedClientError* error, const char* domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer); itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer); - if ((domainId == NULL) || (itemId == NULL)) { + if ((domainId == NULL) || (itemId == NULL)) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -1321,15 +1335,18 @@ IedConnection_readObject(IedConnection self, IedClientError* error, const char* /* check if item ID contains an array "(..)" */ char* brace = strchr(itemId, '('); - if (brace) { + if (brace) + { char* secondBrace = strchr(brace, ')'); - if (secondBrace) { + if (secondBrace) + { char* endPtr; int index = (int) strtol(brace + 1, &endPtr, 10); - if (endPtr == secondBrace) { + if (endPtr == secondBrace) + { char* component = NULL; if (strlen(secondBrace + 1) > 1) @@ -1363,10 +1380,12 @@ IedConnection_readBooleanValue(IedConnection self, IedClientError* error, const bool retVal = false; - if (value != NULL) { + if (value != NULL) + { if (MmsValue_getType(value) == MMS_BOOLEAN) retVal = MmsValue_getBoolean(value); - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1386,10 +1405,12 @@ IedConnection_readFloatValue(IedConnection self, IedClientError* error, const ch float retVal = 0.f; - if (value != NULL) { + if (value != NULL) + { if (MmsValue_getType(value) == MMS_FLOAT) retVal = MmsValue_toFloat(value); - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1409,10 +1430,12 @@ IedConnection_readStringValue(IedConnection self, IedClientError* error, const c char* retVal = NULL; - if (value != NULL) { + if (value != NULL) + { if ((MmsValue_getType(value) == MMS_VISIBLE_STRING) || (MmsValue_getType(value) == MMS_STRING)) retVal = StringUtils_copyString(MmsValue_toString(value)); - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1432,10 +1455,12 @@ IedConnection_readInt32Value(IedConnection self, IedClientError* error, const ch int32_t retVal = 0; - if (value != NULL) { + if (value != NULL) + { if ((MmsValue_getType(value) == MMS_INTEGER) || (MmsValue_getType(value) == MMS_UNSIGNED)) retVal = MmsValue_toInt32(value); - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1455,10 +1480,12 @@ IedConnection_readUnsigned32Value(IedConnection self, IedClientError* error, con uint32_t retVal = 0; - if (value != NULL) { + if (value != NULL) + { if ((MmsValue_getType(value) == MMS_INTEGER) || (MmsValue_getType(value) == MMS_UNSIGNED)) retVal = MmsValue_toUint32(value); - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1478,10 +1505,12 @@ IedConnection_readInt64Value(IedConnection self, IedClientError* error, const ch int64_t retVal = 0; - if (value != NULL) { + if (value != NULL) + { if ((MmsValue_getType(value) == MMS_INTEGER) || (MmsValue_getType(value) == MMS_UNSIGNED)) retVal = MmsValue_toInt64(value); - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1502,15 +1531,17 @@ IedConnection_readTimestampValue(IedConnection self, IedClientError* error, cons Timestamp* retVal = timeStamp; - if (value != NULL) { - if (MmsValue_getType(value) == MMS_UTC_TIME) { - + if (value != NULL) + { + if (MmsValue_getType(value) == MMS_UTC_TIME) + { if (retVal == NULL) retVal = (Timestamp*) GLOBAL_MALLOC(sizeof(Timestamp)); memcpy(retVal->val, value->value.utcTime, 8); } - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1531,12 +1562,13 @@ IedConnection_readQualityValue(IedConnection self, IedClientError* error, const Quality quality = QUALITY_VALIDITY_GOOD; - if (value != NULL) { - + if (value != NULL) + { if ((MmsValue_getType(value) == MMS_BIT_STRING) && (MmsValue_getBitStringSize(value) == 13)) { quality = Quality_fromMmsValue(value); } - else { + else + { if (MmsValue_getType(value) == MMS_DATA_ACCESS_ERROR) *error = iedConnection_mapDataAccessErrorToIedError(MmsValue_getDataAccessError(value)); else @@ -1563,7 +1595,8 @@ IedConnection_writeObject(IedConnection self, IedClientError* error, const char* domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer); itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer); - if ((domainId == NULL) || (itemId == NULL)) { + if ((domainId == NULL) || (itemId == NULL)) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return; } @@ -1573,15 +1606,18 @@ IedConnection_writeObject(IedConnection self, IedClientError* error, const char* /* check if item ID contains an array "(..)" */ char* brace = strchr(itemId, '('); - if (brace) { + if (brace) + { char* secondBrace = strchr(brace, ')'); - if (secondBrace) { + if (secondBrace) + { char* endPtr; int index = (int) strtol(brace + 1, &endPtr, 10); - if (endPtr == secondBrace) { + if (endPtr == secondBrace) + { char* component = NULL; if (strlen(secondBrace + 1) > 1) @@ -1599,7 +1635,8 @@ IedConnection_writeObject(IedConnection self, IedClientError* error, const char* else *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; } - else { + else + { MmsConnection_writeVariable(self->connection, &mmsError, domainId, itemId, value); *error = iedConnection_mapMmsErrorToIedError(mmsError); @@ -1613,8 +1650,8 @@ writeVariableHandler(uint32_t invokeId, void* parameter, MmsError err, MmsDataAc IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler) call->callback; IedClientError iedError = iedConnection_mapMmsErrorToIedError(err); @@ -1626,7 +1663,8 @@ writeVariableHandler(uint32_t invokeId, void* parameter, MmsError err, MmsDataAc iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -1647,14 +1685,16 @@ IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const domainId = MmsMapping_getMmsDomainFromObjectReference(objectReference, domainIdBuffer); itemId = MmsMapping_createMmsVariableNameFromObjectReference(objectReference, fc, itemIdBuffer); - if ((domainId == NULL) || (itemId == NULL)) { + if ((domainId == NULL) || (itemId == NULL)) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -1668,7 +1708,8 @@ IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const /* check if item ID contains an array "(..)" */ char* brace = strchr(itemId, '('); - if (brace) { + if (brace) + { char* secondBrace = strchr(brace, ')'); if (secondBrace) { @@ -1676,7 +1717,8 @@ IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const int index = (int) strtol(brace + 1, &endPtr, 10); - if (endPtr == secondBrace) { + if (endPtr == secondBrace) + { char* component = NULL; if (strlen(secondBrace + 1) > 1) @@ -1695,13 +1737,15 @@ IedConnection_writeObjectAsync(IedConnection self, IedClientError* error, const else *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; } - else { + else + { MmsConnection_writeVariableAsync(self->connection, &(call->invokeId), &err, domainId, itemId, value, writeVariableHandler, self); *error = iedConnection_mapMmsErrorToIedError(err); } - if (*error != IED_ERROR_OK) { + if (*error != IED_ERROR_OK) + { iedConnection_releaseOutstandingCall(self, call); return 0; } @@ -1742,7 +1786,6 @@ IedConnection_writeInt32Value(IedConnection self, IedClientError* error, const c IedConnection_writeObject(self, error, objectReference, fc, &mmsValue); } - void IedConnection_writeUnsigned32Value(IedConnection self, IedClientError* error, const char* objectReference, FunctionalConstraint fc, uint32_t value) @@ -1811,9 +1854,10 @@ IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error LinkedList logicalDeviceNames = MmsConnection_getDomainNames(self->connection, &mmsError); - if (logicalDeviceNames != NULL) { - - if (self->logicalDevices != NULL) { + if (logicalDeviceNames) + { + if (self->logicalDevices) + { LinkedList_destroyDeep(self->logicalDevices, (LinkedListValueDeleteFunction) ICLogicalDevice_destroy); self->logicalDevices = NULL; } @@ -1822,20 +1866,23 @@ IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error LinkedList logicalDevices = LinkedList_create(); - while (logicalDevice != NULL) { + while (logicalDevice) + { char* name = (char*) logicalDevice->data; LinkedList variables = MmsConnection_getDomainVariableNames(self->connection, &mmsError, name); - if (variables != NULL) { + if (variables) + { ICLogicalDevice* icLogicalDevice = ICLogicalDevice_create(name); ICLogicalDevice_setVariableList(icLogicalDevice, variables); LinkedList_add(logicalDevices, icLogicalDevice); } - else { + else + { if (error) *error = iedConnection_mapMmsErrorToIedError(mmsError); break; @@ -1844,10 +1891,12 @@ IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error logicalDevice = LinkedList_getNext(logicalDevice); } - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { LinkedList_destroyDeep(logicalDevices, (LinkedListValueDeleteFunction) ICLogicalDevice_destroy); } - else { + else + { self->logicalDevices = logicalDevices; } @@ -1862,19 +1911,22 @@ IedConnection_getLogicalDeviceList(IedConnection self, IedClientError* error) { *error = IED_ERROR_OK; - if (self->logicalDevices == NULL) { + if (self->logicalDevices == NULL) + { IedConnection_getDeviceModelFromServer(self, error); if (*error != IED_ERROR_OK) return NULL; } - if (self->logicalDevices != NULL) { + if (self->logicalDevices) + { LinkedList logicalDevice = LinkedList_getNext(self->logicalDevices); LinkedList logicalDeviceList = LinkedList_create(); - while (logicalDevice != NULL) { + while (logicalDevice) + { ICLogicalDevice* icLogicalDevice = (ICLogicalDevice*) logicalDevice->data; char* logicalDeviceName = StringUtils_copyString(icLogicalDevice->name); @@ -1889,7 +1941,8 @@ IedConnection_getLogicalDeviceList(IedConnection self, IedClientError* error) return logicalDeviceList; } - else { + else + { if (error) *error = IED_ERROR_UNKNOWN; @@ -1920,19 +1973,22 @@ IedConnection_getFileDirectory(IedConnection self, IedClientError* error, const bool moreFollows = false; - do { + do + { moreFollows = MmsConnection_getFileDirectory(self->connection, &mmsError, directoryName, continueAfter, mmsFileDirectoryHandler, fileNames); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); LinkedList_destroyDeep(fileNames, (LinkedListValueDeleteFunction) FileDirectoryEntry_destroy); return NULL; } - if (moreFollows) { + if (moreFollows) + { FileDirectoryEntry lastDirectoryEntry = (FileDirectoryEntry) LinkedList_getData(LinkedList_getLastElement(fileNames)); @@ -1957,7 +2013,8 @@ IedConnection_getFileDirectoryEx(IedConnection self, IedClientError* error, cons bool moreFollowsInternal = MmsConnection_getFileDirectory(self->connection, &mmsError, directoryName, continueAfter, mmsFileDirectoryHandler, fileNames); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); LinkedList_destroyDeep(fileNames, (LinkedListValueDeleteFunction) FileDirectoryEntry_destroy); @@ -1978,9 +2035,10 @@ fileDirectoryHandlerEx(uint32_t invokeId, void* parameter, MmsError err, char* f IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - - if (call->specificParameter2.getFileDirectory.cont) { + if (call) + { + if (call->specificParameter2.getFileDirectory.cont) + { IedConnection_FileDirectoryEntryHandler handler = (IedConnection_FileDirectoryEntryHandler) call->callback; call->specificParameter2.getFileDirectory.cont = @@ -1990,7 +2048,8 @@ fileDirectoryHandlerEx(uint32_t invokeId, void* parameter, MmsError err, char* f if (filename == NULL) iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -2004,7 +2063,8 @@ IedConnection_getFileDirectoryAsyncEx(IedConnection self, IedClientError* error, IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -2018,7 +2078,8 @@ IedConnection_getFileDirectoryAsyncEx(IedConnection self, IedClientError* error, *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); return 0; } @@ -2058,7 +2119,8 @@ IedConnection_getFile(IedConnection self, IedClientError* error, const char* fil int32_t frsmId = MmsConnection_fileOpen(self->connection, &mmsError, fileName, 0, &fileSize, NULL); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); return 0; } @@ -2069,17 +2131,20 @@ IedConnection_getFile(IedConnection self, IedClientError* error, const char* fil clientFileReadHandler.retVal = true; clientFileReadHandler.byteReceived = 0; - while (true) { + while (true) + { bool moreFollows = MmsConnection_fileRead(self->connection, &mmsError, frsmId, mmsFileReadHandler, &clientFileReadHandler); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); return 0; } - if (clientFileReadHandler.retVal == false) { + if (clientFileReadHandler.retVal == false) + { *error = IED_ERROR_UNKNOWN; break; } @@ -2100,7 +2165,8 @@ mmsConnectionFileCloseHandler (uint32_t invokeId, void* parameter, MmsError mmsE { (void)success; - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: failed to close file error: %i (mms-error: %i)\n", iedConnection_mapMmsErrorToIedError(mmsError), mmsError); } @@ -2109,10 +2175,12 @@ mmsConnectionFileCloseHandler (uint32_t invokeId, void* parameter, MmsError mmsE IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { + if (call) + { iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -2126,46 +2194,53 @@ mmsConnectionFileReadHandler (uint32_t invokeId, void* parameter, MmsError mmsEr IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_GetFileAsyncHandler handler = (IedConnection_GetFileAsyncHandler) call->callback; - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError); handler(call->specificParameter2.getFileInfo.originalInvokeId, call->callbackParameter, err, invokeId, NULL, 0, false); - if (mmsError != MMS_ERROR_SERVICE_TIMEOUT) { + if (mmsError != MMS_ERROR_SERVICE_TIMEOUT) + { /* close file */ MmsConnection_fileCloseAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileCloseHandler, self); if (mmsError != MMS_ERROR_NONE) iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: getFile timeout -> stop download\n"); iedConnection_releaseOutstandingCall(self, call); } } - else { + else + { bool cont = handler(call->specificParameter2.getFileInfo.originalInvokeId, call->callbackParameter, IED_ERROR_OK, invokeId, buffer, byteReceived, moreFollows); - if ((moreFollows == false) || (cont == false)) { + if ((moreFollows == false) || (cont == false)) + { /* close file */ MmsConnection_fileCloseAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileCloseHandler, self); if (mmsError != MMS_ERROR_NONE) iedConnection_releaseOutstandingCall(self, call); } - else { + else + { /* send next read request */ MmsConnection_fileReadAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileReadHandler, self); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError); handler(invokeId, call->callbackParameter, err, invokeId, NULL, 0, false); @@ -2173,16 +2248,16 @@ mmsConnectionFileReadHandler (uint32_t invokeId, void* parameter, MmsError mmsEr /* close file */ MmsConnection_fileCloseAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileCloseHandler, self); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); } - } } } - } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -2198,24 +2273,27 @@ mmsConnectionFileOpenHandler (uint32_t invokeId, void* parameter, MmsError mmsEr IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_GetFileAsyncHandler handler = (IedConnection_GetFileAsyncHandler) call->callback; call->specificParameter2.getFileInfo.originalInvokeId = invokeId; - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError); handler(invokeId, call->callbackParameter, err, invokeId, NULL, 0, false); iedConnection_releaseOutstandingCall(self, call); } - else { + else + { call->specificParameter2.getFileInfo.originalInvokeId = invokeId; MmsConnection_fileReadAsync(self->connection, &(call->invokeId), &mmsError, frsmId, mmsConnectionFileReadHandler, self); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError); handler(invokeId, call->callbackParameter, err, invokeId, NULL, 0, false); @@ -2227,9 +2305,9 @@ mmsConnectionFileOpenHandler (uint32_t invokeId, void* parameter, MmsError mmsEr iedConnection_releaseOutstandingCall(self, call); } } - } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -2244,7 +2322,8 @@ IedConnection_getFileAsync(IedConnection self, IedClientError* error, const char IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -2256,7 +2335,8 @@ IedConnection_getFileAsync(IedConnection self, IedClientError* error, const char *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); return 0; } @@ -2298,14 +2378,16 @@ deleteFileAndSetFileHandler (uint32_t invokeId, void* parameter, MmsError mmsErr IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { + if (call) + { IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler) call->callback; handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(mmsError)); iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -2319,7 +2401,8 @@ IedConnection_setFileAsync(IedConnection self, IedClientError* error, const char IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -2331,7 +2414,8 @@ IedConnection_setFileAsync(IedConnection self, IedClientError* error, const char *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); return 0; } @@ -2371,7 +2455,8 @@ IedConnection_deleteFileAsync(IedConnection self, IedClientError* error, const c *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); return 0; } @@ -2403,15 +2488,18 @@ IedConnection_getLogicalDeviceDirectory(IedConnection self, IedClientError* erro LinkedList logicalDevice = LinkedList_getNext(self->logicalDevices); - while (logicalDevice != NULL) { + while (logicalDevice) + { ICLogicalDevice* device = (ICLogicalDevice*) logicalDevice->data; - if (strcmp(device->name, logicalDeviceName) == 0) { + if (strcmp(device->name, logicalDeviceName) == 0) + { LinkedList logicalNodeNames = LinkedList_create(); LinkedList variable = LinkedList_getNext(device->variables); - while (variable != NULL) { + while (variable) + { char* variableName = (char*) variable->data; if (strchr(variableName, '$') == NULL) @@ -2436,7 +2524,8 @@ addToStringSet(LinkedList set, char* string) { LinkedList element = set; - while (LinkedList_getNext(element) != NULL) { + while (LinkedList_getNext(element) != NULL) + { if (strcmp((char*) LinkedList_getNext(element)->data, string) == 0) return false; @@ -2452,21 +2541,25 @@ addVariablesWithFc(char* fc, char* lnName, LinkedList variables, LinkedList lnDi { LinkedList variable = LinkedList_getNext(variables); - while (variable != NULL) { + while (variable) + { char* variableName = (char*) variable->data; char* fcPos = strchr(variableName, '$'); - if (fcPos != NULL) { + if (fcPos != NULL) + { if (memcmp(fcPos + 1, fc, 2) != 0) goto next_element; int lnNameLen = (int)(fcPos - variableName); - if (strncmp(variableName, lnName, lnNameLen) == 0) { + if (strncmp(variableName, lnName, lnNameLen) == 0) + { char* fcEndPos = strchr(fcPos + 1, '$'); - if (fcEndPos != NULL) { + if (fcEndPos != NULL) + { char* nameEndPos = strchr(fcEndPos + 1, '$'); if (nameEndPos == NULL) @@ -2491,7 +2584,8 @@ getLogicalNodeDirectoryLogs(IedConnection self, IedClientError* error, const cha LinkedList journals = MmsConnection_getDomainJournals(mmsCon, &mmsError, logicalDeviceName); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); return NULL; } @@ -2500,17 +2594,19 @@ getLogicalNodeDirectoryLogs(IedConnection self, IedClientError* error, const cha LinkedList journal = LinkedList_getNext(journals); - while (journal != NULL) { - + while (journal) + { char* journalName = (char*) LinkedList_getData(journal); char* logName = strchr(journalName, '$'); - if (logName != NULL) { + if (logName) + { logName[0] = 0; logName += 1; - if (strcmp(journalName, logicalNodeName) == 0) { + if (strcmp(journalName, logicalNodeName) == 0) + { char* log = StringUtils_copyString(logName); LinkedList_add(logs, (void*) log); } @@ -2534,7 +2630,8 @@ getLogicalNodeDirectoryDataSets(IedConnection self, IedClientError* error, const LinkedList dataSets = MmsConnection_getDomainVariableListNames(mmsCon, &mmsError, logicalDeviceName); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); return NULL; } @@ -2543,16 +2640,19 @@ getLogicalNodeDirectoryDataSets(IedConnection self, IedClientError* error, const LinkedList dataSet = LinkedList_getNext(dataSets); - while (dataSet != NULL) { + while (dataSet) + { char* dataSetName = (char*) LinkedList_getData(dataSet); char* lnDataSetName = strchr(dataSetName, '$'); - if (lnDataSetName != NULL) { + if (lnDataSetName) + { lnDataSetName[0] = 0; lnDataSetName += 1; - if (strcmp(dataSetName, logicalNodeName) == 0) { + if (strcmp(dataSetName, logicalNodeName) == 0) + { char* lnDataSet = StringUtils_copyString(lnDataSetName); LinkedList_add(lnDataSets, (void*) lnDataSet); } @@ -2572,7 +2672,8 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, { *error = IED_ERROR_OK; - if (strlen(logicalNodeReference) > 129) { + if (strlen(logicalNodeReference) > 129) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2583,7 +2684,8 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, char* ldSep = strchr(lnRefCopy, '/'); - if (ldSep == NULL) { + if (ldSep == NULL) + { *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; return NULL; } @@ -2612,10 +2714,12 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, ICLogicalDevice* ld = NULL; - while (device != NULL) { + while (device) + { ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data; - if (strcmp(logicalDeviceName, ldCandidate->name) == 0) { + if (strcmp(logicalDeviceName, ldCandidate->name) == 0) + { ld = ldCandidate; break; } @@ -2623,25 +2727,28 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, device = LinkedList_getNext(device); } - if (ld == NULL) { + if (ld == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } LinkedList lnDirectory = LinkedList_create(); - switch (acsiClass) { - + switch (acsiClass) + { case ACSI_CLASS_DATA_OBJECT: { LinkedList variable = LinkedList_getNext(ld->variables); - while (variable != NULL) { + while (variable) + { char* variableName = (char*) variable->data; char* fcPos = strchr(variableName, '$'); - if (fcPos != NULL) { + if (fcPos) + { if (memcmp(fcPos + 1, "RP", 2) == 0) goto next_element; @@ -2653,13 +2760,16 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, int lnNameLen = (int)(fcPos - variableName); - if (strncmp(variableName, logicalNodeName, lnNameLen) == 0) { + if (strncmp(variableName, logicalNodeName, lnNameLen) == 0) + { char* fcEndPos = strchr(fcPos + 1, '$'); - if (fcEndPos != NULL) { + if (fcEndPos) + { char* nameEndPos = strchr(fcEndPos + 1, '$'); - if (nameEndPos == NULL) { + if (nameEndPos == NULL) + { char* dataObjectName = StringUtils_copyString(fcEndPos + 1); if (!addToStringSet(lnDirectory, dataObjectName)) @@ -2680,7 +2790,8 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, { LinkedList variable = LinkedList_getNext(ld->variables); - while (variable != NULL) { + while (variable) + { char* variableName = (char*) variable->data; if (strcmp(variableName, "LLN0$SP$SGCB") == 0) @@ -2723,7 +2834,8 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, { *error = IED_ERROR_OK; - if (strlen(logicalNodeReference) > 129) { + if (strlen(logicalNodeReference) > 129) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2740,7 +2852,8 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, char* ldSep = strchr(lnRefCopy, '/'); - if (ldSep == NULL) { + if (ldSep == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2757,10 +2870,12 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, ICLogicalDevice* ld = NULL; - while (device != NULL) { + while (device) + { ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data; - if (strcmp(logicalDeviceName, ldCandidate->name) == 0) { + if (strcmp(logicalDeviceName, ldCandidate->name) == 0) + { ld = ldCandidate; break; } @@ -2768,7 +2883,8 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, device = LinkedList_getNext(device); } - if (ld == NULL) { + if (ld == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2780,15 +2896,18 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, LinkedList lnDirectory = LinkedList_create(); - while (variable != NULL) { + while (variable) + { char* variableName = (char*) variable->data; char* fcPos = strchr(variableName, '$'); - if (fcPos != NULL) { + if (fcPos) + { int lnNameLen = (int)(fcPos - variableName); - if (strncmp(variableName, logicalNodeName, lnNameLen) == 0) { + if (strncmp(variableName, logicalNodeName, lnNameLen) == 0) + { LinkedList_add(lnDirectory, StringUtils_copyString(fcPos + 1)); } } @@ -2806,7 +2925,8 @@ getDataDirectory(IedConnection self, IedClientError* error, { *error = IED_ERROR_OK; - if (strlen(dataReference) > 129) { + if (strlen(dataReference) > 129) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2831,7 +2951,8 @@ getDataDirectory(IedConnection self, IedClientError* error, char* logicalNodeNameEnd = strchr(logicalNodeName, '.'); - if (logicalNodeNameEnd == NULL) { + if (logicalNodeNameEnd == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2842,7 +2963,8 @@ getDataDirectory(IedConnection self, IedClientError* error, int dataNamePartLen = (int)strlen(dataNamePart); - if (dataNamePartLen < 1) { + if (dataNamePartLen < 1) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2855,10 +2977,12 @@ getDataDirectory(IedConnection self, IedClientError* error, ICLogicalDevice* ld = NULL; - while (device != NULL) { + while (device) + { ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data; - if (strcmp(logicalDeviceName, ldCandidate->name) == 0) { + if (strcmp(logicalDeviceName, ldCandidate->name) == 0) + { ld = ldCandidate; break; } @@ -2866,7 +2990,8 @@ getDataDirectory(IedConnection self, IedClientError* error, device = LinkedList_getNext(device); } - if (ld == NULL) { + if (ld == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2875,18 +3000,20 @@ getDataDirectory(IedConnection self, IedClientError* error, LinkedList dataDirectory = LinkedList_create(); - while (variable != NULL) { + while (variable) + { char* variableName = (char*) variable->data; char* fcPos = strchr(variableName, '$'); - if (fcPos != NULL) { + if (fcPos) + { int lnNameLen = (int)(fcPos - variableName); - if (logicalNodeNameLen == lnNameLen) { - - if (memcmp(variableName, logicalNodeName, lnNameLen) == 0) { - + if (logicalNodeNameLen == lnNameLen) + { + if (memcmp(variableName, logicalNodeName, lnNameLen) == 0) + { /* ok we are in the correct logical node */ /* skip FC */ @@ -2902,10 +3029,10 @@ getDataDirectory(IedConnection self, IedClientError* error, if (remainingLen <= dataNamePartLen) goto next_variable; - if (remainingPart[dataNamePartLen] == '$') { - - if (memcmp(dataNamePart, remainingPart, dataNamePartLen) == 0) { - + if (remainingPart[dataNamePartLen] == '$') + { + if (memcmp(dataNamePart, remainingPart, dataNamePartLen) == 0) + { char* subElementName = remainingPart + dataNamePartLen + 1; char* subElementNameSep = strchr(subElementName, '$'); @@ -2915,7 +3042,8 @@ getDataDirectory(IedConnection self, IedClientError* error, char* elementName; - if (withFc) { + if (withFc) + { int elementNameLen = (int)strlen(subElementName); elementName = (char*) GLOBAL_MALLOC(elementNameLen + 5); @@ -2944,7 +3072,6 @@ getDataDirectory(IedConnection self, IedClientError* error, *error = IED_ERROR_OK; return dataDirectory; - } LinkedList @@ -2965,14 +3092,16 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, { *error = IED_ERROR_OK; - if (strlen(dataReference) > 129) { + if (strlen(dataReference) > 129) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } char* fcString = FunctionalConstraint_toString(fc); - if (fcString == NULL) { + if (fcString == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -2997,7 +3126,8 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, char* logicalNodeNameEnd = strchr(logicalNodeName, '.'); - if (logicalNodeNameEnd == NULL) { + if (logicalNodeNameEnd == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -3008,7 +3138,8 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, int dataNamePartLen = (int)strlen(dataNamePart); - if (dataNamePartLen < 1) { + if (dataNamePartLen < 1) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -3021,10 +3152,12 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, ICLogicalDevice* ld = NULL; - while (device != NULL) { + while (device) + { ICLogicalDevice* ldCandidate = (ICLogicalDevice*) device->data; - if (strcmp(logicalDeviceName, ldCandidate->name) == 0) { + if (strcmp(logicalDeviceName, ldCandidate->name) == 0) + { ld = ldCandidate; break; } @@ -3032,7 +3165,8 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, device = LinkedList_getNext(device); } - if (ld == NULL) { + if (ld == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -3041,18 +3175,20 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, LinkedList dataDirectory = LinkedList_create(); - while (variable != NULL) { + while (variable) + { char* variableName = (char*) variable->data; char* fcPos = strchr(variableName, '$'); - if (fcPos != NULL) { + if (fcPos) + { int lnNameLen = (int)(fcPos - variableName); - if (logicalNodeNameLen == lnNameLen) { - - if (memcmp(variableName, logicalNodeName, lnNameLen) == 0) { - + if (logicalNodeNameLen == lnNameLen) + { + if (memcmp(variableName, logicalNodeName, lnNameLen) == 0) + { /* ok we are in the correct logical node */ /* skip FC */ @@ -3071,10 +3207,10 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, if (remainingLen <= dataNamePartLen) goto next_variable; - if (remainingPart[dataNamePartLen] == '$') { - - if (memcmp(dataNamePart, remainingPart, dataNamePartLen) == 0) { - + if (remainingPart[dataNamePartLen] == '$') + { + if (memcmp(dataNamePart, remainingPart, dataNamePartLen) == 0) + { char* subElementName = remainingPart + dataNamePartLen + 1; char* subElementNameSep = strchr(subElementName, '$'); @@ -3103,7 +3239,6 @@ getDataDirectoryByFc(IedConnection self, IedClientError* error, *error = IED_ERROR_OK; return dataDirectory; - } @@ -3124,9 +3259,10 @@ IedConnection_createDataSet(IedConnection self, IedClientError* error, const cha const char* itemId; bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3134,17 +3270,20 @@ IedConnection_createDataSet(IedConnection self, IedClientError* error, const cha else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } int domainIdLength = (int)strlen(domainId); - if ((strlen(dataSetReference) - domainIdLength - 1) > 32) { + if ((strlen(dataSetReference) - domainIdLength - 1) > 32) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3154,7 +3293,8 @@ IedConnection_createDataSet(IedConnection self, IedClientError* error, const cha itemId = itemIdRef; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } @@ -3165,12 +3305,13 @@ IedConnection_createDataSet(IedConnection self, IedClientError* error, const cha LinkedList dataSetElement = LinkedList_getNext(dataSetElements); - while (dataSetElement != NULL) { - + while (dataSetElement) + { MmsVariableAccessSpecification* dataSetEntry = MmsMapping_ObjectReferenceToVariableAccessSpec((char*) dataSetElement->data); - if (dataSetEntry == NULL) { + if (dataSetEntry == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto cleanup_list; } @@ -3208,8 +3349,10 @@ IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const cha int dataSetReferenceLength = (int)strlen(dataSetReference); - if (dataSetReference[0] != '@') { - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3217,16 +3360,18 @@ IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const cha else StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference); } - else { - - if (MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainId) == NULL) { + else + { + if (MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainId) == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } const char* itemIdString = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdString) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdString) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3236,8 +3381,10 @@ IedConnection_deleteDataSet(IedConnection self, IedClientError* error, const cha StringUtils_replace(itemId, '.', '$'); } } - else { - if (dataSetReferenceLength > 33) { + else + { + if (dataSetReferenceLength > 33) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3267,13 +3414,14 @@ deleteNamedVariableListHandler(uint32_t invokeId, void* parameter, MmsError mmsE IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_GenericServiceHandler handler = (IedConnection_GenericServiceHandler)call->callback; IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError); - if (err == IED_ERROR_OK) { + if (err == IED_ERROR_OK) + { if (success == false) err = IED_ERROR_ACCESS_DENIED; } @@ -3282,7 +3430,8 @@ deleteNamedVariableListHandler(uint32_t invokeId, void* parameter, MmsError mmsE iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -3301,9 +3450,10 @@ IedConnection_deleteDataSetAsync(IedConnection self, IedClientError* error, cons int dataSetReferenceLength = (int)strlen(dataSetReference); - if (dataSetReference[0] != '@') { - if ((dataSetReference[0] == '/') - || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3311,17 +3461,18 @@ IedConnection_deleteDataSetAsync(IedConnection self, IedClientError* error, cons else StringUtils_copyStringMax(itemId, DATA_SET_MAX_NAME_LENGTH + 1, dataSetReference); } - else { - - if (MmsMapping_getMmsDomainFromObjectReference(dataSetReference, - domainId) == NULL) { + else + { + if (MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainId) == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } const char *itemIdString = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdString) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdString) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } @@ -3331,8 +3482,10 @@ IedConnection_deleteDataSetAsync(IedConnection self, IedClientError* error, cons StringUtils_replace(itemId, '.', '$'); } } - else { - if (dataSetReferenceLength > 33) { + else + { + if (dataSetReferenceLength > 33) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } @@ -3344,7 +3497,8 @@ IedConnection_deleteDataSetAsync(IedConnection self, IedClientError* error, cons MmsError mmsError; - if ((domainId == NULL) || (itemId[0] == 0)) { + if ((domainId == NULL) || (itemId[0] == 0)) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } @@ -3360,14 +3514,17 @@ IedConnection_deleteDataSetAsync(IedConnection self, IedClientError* error, cons call->callbackParameter = parameter; call->invokeId = 0; - if (isAssociationSpecific) { + if (isAssociationSpecific) + { MmsConnection_deleteAssociationSpecificNamedVariableListAsync(self->connection, &(call->invokeId), &mmsError, itemId, deleteNamedVariableListHandler, self); } - else { + else + { MmsConnection_deleteNamedVariableListAsync(self->connection, &(call->invokeId), &mmsError, domainId, itemId, deleteNamedVariableListHandler, self); } - if (*error != IED_ERROR_OK) { + if (*error != IED_ERROR_OK) + { iedConnection_releaseOutstandingCall(self, call); return 0; } @@ -3388,7 +3545,8 @@ createDataSetAsyncHandler(uint32_t invokeId, void* parameter, MmsError mmsError, IedClientError err = iedConnection_mapMmsErrorToIedError(mmsError); - if (err == IED_ERROR_OK) { + if (err == IED_ERROR_OK) + { if (success == false) err = IED_ERROR_ACCESS_DENIED; } @@ -3397,7 +3555,8 @@ createDataSetAsyncHandler(uint32_t invokeId, void* parameter, MmsError mmsError, iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -3411,7 +3570,8 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; goto exit_function; } @@ -3427,9 +3587,10 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons const char* itemId; bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3437,17 +3598,20 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } int domainIdLength = (int)strlen(domainId); - if ((strlen(dataSetReference) - domainIdLength - 1) > 32) { + if ((strlen(dataSetReference) - domainIdLength - 1) > 32) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3457,7 +3621,8 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons itemId = itemIdRef; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } @@ -3466,12 +3631,13 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons LinkedList dataSetElement = LinkedList_getNext(dataSetElements); - while (dataSetElement != NULL) { - + while (dataSetElement) + { MmsVariableAccessSpecification* dataSetEntry = MmsMapping_ObjectReferenceToVariableAccessSpec((char*) dataSetElement->data); - if (dataSetEntry == NULL) { + if (dataSetEntry == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto cleanup_list; } @@ -3481,11 +3647,13 @@ IedConnection_createDataSetAsync(IedConnection self, IedClientError* error, cons dataSetElement = LinkedList_getNext(dataSetElement); } - if (isAssociationSpecific) { + if (isAssociationSpecific) + { MmsConnection_defineNamedVariableListAssociationSpecificAsync(self->connection, &(call->invokeId), &mmsError, itemId, dataSetEntries, createDataSetAsyncHandler, self); } - else { + else + { MmsConnection_defineNamedVariableListAsync(self->connection, &(call->invokeId), &mmsError, domainId, itemId, dataSetEntries, createDataSetAsyncHandler, self); } @@ -3525,8 +3693,10 @@ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, con bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3534,17 +3704,20 @@ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, con else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } const char* itemIdRef = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdRef) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdRef) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3554,7 +3727,8 @@ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, con itemId = itemIdRefInBuffer; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } @@ -3570,13 +3744,14 @@ IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, con entries = MmsConnection_readNamedVariableListDirectory(self->connection, &mmsError, domainId, itemId, &deletable); - if (mmsError == MMS_ERROR_NONE) { - + if (mmsError == MMS_ERROR_NONE) + { LinkedList entry = LinkedList_getNext(entries); dataSetMembers = LinkedList_create(); - while (entry) { + while (entry) + { MmsVariableAccessSpecification* varAccessSpec = (MmsVariableAccessSpecification*)LinkedList_getData(entry); char* objectReference = MmsMapping_varAccessSpecToObjectReference(varAccessSpec); @@ -3607,17 +3782,20 @@ getDataSetDirectoryAsyncHandler(uint32_t invokeId, void* parameter, MmsError mms IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { + if (call) + { LinkedList dataSetMembers = NULL; if (mmsError != MMS_ERROR_NONE) err = iedConnection_mapMmsErrorToIedError(mmsError); - if (specs) { + if (specs) + { dataSetMembers = LinkedList_create(); LinkedList specElem = LinkedList_getNext(specs); - while (specElem) { + while (specElem) + { MmsVariableAccessSpecification* varAccessSpec = (MmsVariableAccessSpecification*)LinkedList_getData(specElem); char* objectReference = MmsMapping_varAccessSpecToObjectReference(varAccessSpec); @@ -3648,7 +3826,8 @@ IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -3665,8 +3844,10 @@ IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3674,17 +3855,20 @@ IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } const char* itemIdRef = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdRef) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdRef) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3694,7 +3878,8 @@ IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error itemId = itemIdRefInBuffer; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } @@ -3709,12 +3894,14 @@ IedConnection_getDataSetDirectoryAsync(IedConnection self, IedClientError* error exit_function: - if (*error != IED_ERROR_OK) { + if (*error != IED_ERROR_OK) + { iedConnection_releaseOutstandingCall(self, call); return 0; } - else { + else + { return call->invokeId; } } @@ -3731,9 +3918,10 @@ IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3741,17 +3929,20 @@ IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3762,7 +3953,8 @@ IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const itemId = itemIdRef; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } @@ -3778,18 +3970,21 @@ IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const dataSetVal = MmsConnection_readNamedVariableListValues(self->connection, &mmsError, domainId, itemId, true); - if (dataSetVal == NULL) { + if (dataSetVal == NULL) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); goto exit_function; } else *error = IED_ERROR_OK; - if (dataSet == NULL) { + if (dataSet == NULL) + { dataSet = ClientDataSet_create(dataSetReference); ClientDataSet_setDataSetValues(dataSet, dataSetVal); } - else { + else + { MmsValue* dataSetValues = ClientDataSet_getValues(dataSet); MmsValue_update(dataSetValues, dataSetVal); MmsValue_delete(dataSetVal); @@ -3815,11 +4010,13 @@ getDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV if (value) { - if (dataSet == NULL) { + if (dataSet == NULL) + { dataSet = ClientDataSet_create(dataSetReference); ClientDataSet_setDataSetValues(dataSet, value); } - else { + else + { MmsValue* dataSetValues = ClientDataSet_getValues(dataSet); MmsValue_update(dataSetValues, value); } @@ -3834,7 +4031,8 @@ getDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -3852,9 +4050,10 @@ IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3862,17 +4061,20 @@ IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } @@ -3883,14 +4085,16 @@ IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, itemId = itemIdRef; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -3915,8 +4119,8 @@ IedConnection_readDataSetValuesAsync(IedConnection self, IedClientError* error, *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { - + if (err != MMS_ERROR_NONE) + { GLOBAL_FREEMEM(call->specificParameter2.pointer); iedConnection_releaseOutstandingCall(self, call); @@ -3939,9 +4143,10 @@ IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, cons bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -3949,17 +4154,20 @@ IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, cons else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; goto exit_function; } @@ -3970,7 +4178,8 @@ IedConnection_writeDataSetValues(IedConnection self, IedClientError* error, cons itemId = itemIdRef; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } @@ -3992,15 +4201,16 @@ writeDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, Li IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_WriteDataSetHandler handler = (IedConnection_WriteDataSetHandler) call->callback; handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), accessResults); iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -4018,9 +4228,10 @@ IedConnection_writeDataSetValuesAsync(IedConnection self, IedClientError* error, bool isAssociationSpecific = false; - if (dataSetReference[0] != '@') { - - if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) { + if (dataSetReference[0] != '@') + { + if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) + { domainId = NULL; if (dataSetReference[0] == '/') @@ -4028,17 +4239,20 @@ IedConnection_writeDataSetValuesAsync(IedConnection self, IedClientError* error, else itemId = dataSetReference; } - else { + else + { domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); - if (domainId == NULL) { + if (domainId == NULL) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1; - if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) { + if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } @@ -4049,14 +4263,16 @@ IedConnection_writeDataSetValuesAsync(IedConnection self, IedClientError* error, itemId = itemIdRef; } } - else { + else + { itemId = dataSetReference + 1; isAssociationSpecific = true; } IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -4070,7 +4286,8 @@ IedConnection_writeDataSetValuesAsync(IedConnection self, IedClientError* error, *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); return 0; @@ -4092,8 +4309,8 @@ IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const ch char* logDomain = logRef; char* logName = strchr(logRef, '/'); - if (logName != NULL) { - + if (logName) + { logName[0] = 0; logName++; @@ -4109,18 +4326,19 @@ IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const ch MmsValue_delete(startTimeMms); MmsValue_delete(endTimeMms); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); return NULL; } else return journalEntries; } - else { + else + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } - } static void @@ -4130,15 +4348,16 @@ readJournalHandler(uint32_t invokeId, void* parameter, MmsError err, LinkedList IedConnectionOutstandingCall call = iedConnection_lookupOutstandingCall(self, invokeId); - if (call) { - + if (call) + { IedConnection_QueryLogHandler handler = (IedConnection_QueryLogHandler) call->callback; handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), journalEntries, moreFollows); iedConnection_releaseOutstandingCall(self, call); } - else { + else + { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: internal error - no matching outstanding call!\n"); } @@ -4155,14 +4374,15 @@ IedConnection_queryLogByTimeAsync(IedConnection self, IedClientError* error, con char* logDomain = logRef; char* logName = strchr(logRef, '/'); - if (logName != NULL) { - + if (logName != NULL) + { logName[0] = 0; logName++; IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -4186,18 +4406,19 @@ IedConnection_queryLogByTimeAsync(IedConnection self, IedClientError* error, con *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); return 0; } return call->invokeId; } - else { + else + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } - } uint32_t @@ -4211,14 +4432,15 @@ IedConnection_queryLogAfterAsync(IedConnection self, IedClientError* error, cons char* logDomain = logRef; char* logName = strchr(logRef, '/'); - if (logName != NULL) { - + if (logName) + { logName[0] = 0; logName++; IedConnectionOutstandingCall call = iedConnection_allocateOutstandingCall(self); - if (call == NULL) { + if (call == NULL) + { *error = IED_ERROR_OUTSTANDING_CALL_LIMIT_REACHED; return 0; } @@ -4238,14 +4460,16 @@ IedConnection_queryLogAfterAsync(IedConnection self, IedClientError* error, cons *error = iedConnection_mapMmsErrorToIedError(err); - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { iedConnection_releaseOutstandingCall(self, call); return 0; } return call->invokeId; } - else { + else + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return 0; } @@ -4264,8 +4488,8 @@ IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const cha char* logDomain = logRef; char* logName = strchr(logRef, '/'); - if (logName != NULL) { - + if (logName) + { logName[0] = 0; logName++; @@ -4277,14 +4501,16 @@ IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const cha MmsValue_delete(timeStampMms); - if (mmsError != MMS_ERROR_NONE) { + if (mmsError != MMS_ERROR_NONE) + { *error = iedConnection_mapMmsErrorToIedError(mmsError); return NULL; } else return journalEntries; } - else { + else + { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } @@ -4358,4 +4584,3 @@ FileDirectoryEntry_getLastModified(FileDirectoryEntry self) { return self->lastModified; } - diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index e059175f..04a81b78 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -2010,6 +2010,15 @@ typedef bool LIB61850_API void IedServer_setControlBlockAccessHandler(IedServer self, IedServer_ControlBlockAccessHandler handler, void* parameter); +/** + * \brief Temporarily ignore read requests (for testing purposes) + * + * \param self the instance of IedServer to operate on. + * \param ignore true to ignore read requests, false to handle read requests. +*/ +LIB61850_API void +IedServer_ingoreReadAccess(IedServer self, bool ignore); + /**@}*/ /**@}*/ diff --git a/src/iec61850/inc_private/ied_connection_private.h b/src/iec61850/inc_private/ied_connection_private.h index e571ca8e..f0f48247 100644 --- a/src/iec61850/inc_private/ied_connection_private.h +++ b/src/iec61850/inc_private/ied_connection_private.h @@ -1,7 +1,7 @@ /* * ied_connection_private.h * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -34,14 +34,16 @@ typedef struct sIedConnectionOutstandingCall* IedConnectionOutstandingCall; -struct sIedConnectionOutstandingCall { +struct sIedConnectionOutstandingCall +{ bool used; uint32_t invokeId; void* callback; void* callbackParameter; void* specificParameter; /* function/service specific parameter */ - union { + union + { void* pointer; struct { uint32_t originalInvokeId; @@ -81,7 +83,8 @@ struct sIedConnection uint8_t timeQuality; }; -struct sClientReportControlBlock { +struct sClientReportControlBlock +{ char* objectReference; bool isBuffered; diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index d5555659..8dc5fede 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -81,10 +81,11 @@ struct sIedServer uint8_t timeQuality; /* user settable time quality for internally updated times */ + bool ignoreReadAccess; /* when true don't answer read request (for test purposes) */ + bool running; }; - LIB61850_INTERNAL IEC61850_ServiceError private_IedServer_convertMmsDataAccessErrorToServiceError(MmsDataAccessError mmsError); diff --git a/src/iec61850/server/impl/client_connection.c b/src/iec61850/server/impl/client_connection.c index dbca770a..6eac160d 100644 --- a/src/iec61850/server/impl/client_connection.c +++ b/src/iec61850/server/impl/client_connection.c @@ -1,7 +1,7 @@ /* * client_connection.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -32,8 +32,8 @@ #include "libiec61850_platform_includes.h" -struct sClientConnection { - +struct sClientConnection +{ #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore tasksCountMutex; #endif @@ -47,7 +47,8 @@ private_ClientConnection_create(void* serverConnectionHandle) { ClientConnection self = (ClientConnection) GLOBAL_MALLOC(sizeof(struct sClientConnection)); - if (self) { + if (self) + { #if (CONFIG_MMS_THREADLESS_STACK != 1) self->tasksCountMutex = Semaphore_create(1); #endif @@ -62,7 +63,8 @@ private_ClientConnection_create(void* serverConnectionHandle) void private_ClientConnection_destroy(ClientConnection self) { - if (self) { + if (self) + { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_destroy(self->tasksCountMutex); #endif @@ -123,7 +125,6 @@ private_ClientConnection_getServerConnectionHandle(ClientConnection self) return self->serverConnectionHandle; } - const char* ClientConnection_getPeerAddress(ClientConnection self) { diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 8c83ddc0..c718e2c8 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -540,17 +540,18 @@ updateDataSetsWithCachedValues(IedServer self) } } - IedServer IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguration, IedServerConfig serverConfiguration) { IedServer self = (IedServer) GLOBAL_CALLOC(1, sizeof(struct sIedServer)); - if (self) { + if (self) + { self->model = dataModel; self->running = false; self->localIpAddress = NULL; + self->ignoreReadAccess = false; #if (CONFIG_IEC61850_EDITION_1 == 1) self->edition = IEC_61850_EDITION_1; @@ -561,7 +562,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) self->logServiceEnabled = true; - if (serverConfiguration) { + if (serverConfiguration) + { self->logServiceEnabled = serverConfiguration->enableLogService; self->edition = serverConfiguration->edition; } @@ -574,7 +576,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio #endif /* (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) */ #if (CONFIG_IEC61850_REPORT_SERVICE == 1) - if (serverConfiguration) { + if (serverConfiguration) + { self->reportBufferSizeBRCBs = serverConfiguration->reportBufferSize; self->reportBufferSizeURCBs = serverConfiguration->reportBufferSizeURCBs; self->enableBRCBResvTms = serverConfiguration->enableResvTmsForBRCB; @@ -582,7 +585,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->syncIntegrityReportTimes = serverConfiguration->syncIntegrityReportTimes; self->rcbSettingsWritable = serverConfiguration->reportSettingsWritable; } - else { + else + { self->reportBufferSizeBRCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->reportBufferSizeURCBs = CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE; self->enableOwnerForRCB = false; @@ -602,11 +606,13 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio #endif #if (CONFIG_IEC61850_SETTING_GROUPS == 1) - if (serverConfiguration) { + if (serverConfiguration) + { self->enableEditSG = serverConfiguration->enableEditSG; self->hasSGCBResvTms = serverConfiguration->enableResvTmsForSGCB; } - else { + else + { self->enableEditSG = true; self->hasSGCBResvTms = true; } @@ -614,14 +620,15 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio self->mmsMapping = MmsMapping_create(dataModel, self); - if (self->mmsMapping) { - + if (self->mmsMapping) + { self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping); self->mmsServer = MmsServer_create(self->mmsDevice, tlsConfiguration); #if (CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME == 1) - if (serverConfiguration) { + if (serverConfiguration) + { MmsServer_enableFileService(self->mmsServer, serverConfiguration->enableFileService); MmsServer_enableDynamicNamedVariableListService(self->mmsServer, serverConfiguration->enableDynamicDataSetService); MmsServer_setMaxAssociationSpecificDataSets(self->mmsServer, serverConfiguration->maxAssociationSpecificDataSets); @@ -668,7 +675,8 @@ IedServer_createWithConfig(IedModel* dataModel, TLSConfiguration tlsConfiguratio IedServer_setTimeQuality(self, true, false, false, 10); } - else { + else + { IedServer_destroy(self); self = NULL; } @@ -1992,3 +2000,9 @@ IedServer_setControlBlockAccessHandler(IedServer self, IedServer_ControlBlockAcc self->mmsMapping->controlBlockAccessHandler = handler; self->mmsMapping->controlBlockAccessHandlerParameter = parameter; } + +void +IedServer_ingoreReadAccess(IedServer self, bool ignore) +{ + self->ignoreReadAccess = ignore; +} diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 6643bb91..34fb18d5 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -74,7 +74,8 @@ ControlObject_unselect(ControlObject* self, MmsServerConnection connection, MmsM static MmsValue* getOperParameterCtlNum(MmsValue* operParameters) { - if (MmsValue_getType(operParameters) == MMS_STRUCTURE) { + if (MmsValue_getType(operParameters) == MMS_STRUCTURE) + { if (MmsValue_getArraySize(operParameters) == 7) return MmsValue_getElement(operParameters, 3); else if (MmsValue_getArraySize(operParameters) == 6) @@ -87,7 +88,8 @@ getOperParameterCtlNum(MmsValue* operParameters) static MmsValue* getCancelParameterCtlNum(MmsValue* operParameters) { - if (MmsValue_getType(operParameters) == MMS_STRUCTURE) { + if (MmsValue_getType(operParameters) == MMS_STRUCTURE) + { if (MmsValue_getArraySize(operParameters) == 6) return MmsValue_getElement(operParameters, 3); else if (MmsValue_getArraySize(operParameters) == 5) @@ -100,7 +102,8 @@ getCancelParameterCtlNum(MmsValue* operParameters) static MmsValue* getCancelParameterOrigin(MmsValue* operParameters) { - if (MmsValue_getType(operParameters) == MMS_STRUCTURE) { + if (MmsValue_getType(operParameters) == MMS_STRUCTURE) + { if (MmsValue_getArraySize(operParameters) == 6) return MmsValue_getElement(operParameters, 2); else if (MmsValue_getArraySize(operParameters) == 5) @@ -211,11 +214,12 @@ getCancelParameterTime(MmsValue* operParameters) static void copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject, IEC61850_ServiceType serviceType) { - if (controlObject->ctlVal) { - + if (controlObject->ctlVal) + { ControlTrkInstance trkInst = NULL; - switch (controlObject->cdc) { + switch (controlObject->cdc) + { case CST_SPCTRK: trkInst = self->spcTrk; break; @@ -247,7 +251,8 @@ copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject break; } - if (trkInst) { + if (trkInst) + { if (trkInst->ctlVal) MmsValue_update(trkInst->ctlVal->mmsValue, controlObject->ctlVal); @@ -265,16 +270,20 @@ copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject MmsValue* operVal = NULL; - if (serviceType == IEC61850_SERVICE_TYPE_SELECT_WITH_VALUES) { + if (serviceType == IEC61850_SERVICE_TYPE_SELECT_WITH_VALUES) + { if (controlObject->sbow) operVal = controlObject->sbow; } - else if (serviceType == IEC61850_SERVICE_TYPE_OPERATE) { + else if (serviceType == IEC61850_SERVICE_TYPE_OPERATE) + { if (controlObject->oper) operVal = controlObject->oper; } - else if (serviceType == IEC61850_SERVICE_TYPE_CANCEL) { - if (controlObject->cancel) { + else if (serviceType == IEC61850_SERVICE_TYPE_CANCEL) + { + if (controlObject->cancel) + { operVal = controlObject->cancel; if (trkInst->Test) { MmsValue_update(trkInst->Test->mmsValue, getCancelParameterTest(operVal)); @@ -286,8 +295,8 @@ copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject } } - if (operVal) { - + if (operVal) + { if (trkInst->Test) { MmsValue_update(trkInst->Test->mmsValue, getOperParameterTest(operVal)); } @@ -300,7 +309,6 @@ copyControlValuesToTrackingObject(MmsMapping* self, ControlObject* controlObject MmsValue_update(trkInst->T->mmsValue, getOperParameterTime(operVal)); } } - } } } @@ -310,8 +318,8 @@ convertCheckHandlerResultToServiceError(CheckHandlerResult controlHandlerResult) { IEC61850_ServiceError serviceError; - switch (controlHandlerResult) { - + switch (controlHandlerResult) + { case CONTROL_HARDWARE_FAULT: serviceError = IEC61850_SERVICE_ERROR_FAILED_DUE_TO_SERVER_CONSTRAINT; break; @@ -352,7 +360,8 @@ updateGenericTrackingObjectValues(MmsMapping* self, ControlObject* controlObject ServiceTrkInstance trkInst = NULL; - if (controlObject->ctlVal) { + if (controlObject->ctlVal) + { switch(controlObject->cdc) { case CST_SPCTRK: @@ -389,7 +398,8 @@ updateGenericTrackingObjectValues(MmsMapping* self, ControlObject* controlObject } } - if (trkInst) { + if (trkInst) + { if (trkInst->serviceType) MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType); @@ -460,7 +470,8 @@ getState(ControlObject* self) static void setStSeld(ControlObject* self, bool value) { - if (self->stSeld) { + if (self->stSeld) + { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->pendingEventsLock); #endif @@ -479,7 +490,8 @@ setStSeld(ControlObject* self, bool value) static void updateSboTimeoutValue(ControlObject* self) { - if (self->sboTimeout != NULL) { + if (self->sboTimeout != NULL) + { uint32_t sboTimeoutVal = MmsValue_toInt32(self->sboTimeout); if (DEBUG_IED_SERVER) @@ -506,7 +518,8 @@ selectObject(ControlObject* self, uint64_t selectTime, MmsServerConnection conne updateNextControlTimeout(mmsMapping, selectTime); - if (self->selectStateChangedHandler) { + if (self->selectStateChangedHandler) + { self->selectStateChangedHandler((ControlAction) self, self->selectStateChangedHandlerParameter, true, @@ -517,7 +530,8 @@ selectObject(ControlObject* self, uint64_t selectTime, MmsServerConnection conne static void unselectObject(ControlObject* self, SelectStateChangedReason reason, MmsMapping* mmsMapping) { - if (getState(self) != STATE_UNSELECTED) { + if (getState(self) != STATE_UNSELECTED) + { setState(self, STATE_UNSELECTED); setStSeld(self, false); @@ -525,7 +539,8 @@ unselectObject(ControlObject* self, SelectStateChangedReason reason, MmsMapping* /* trigger timeout check in next cycle to update the next timeout value */ mmsMapping->nextControlTimeout = 0; - if (self->selectStateChangedHandler) { + if (self->selectStateChangedHandler) + { self->selectStateChangedHandler((ControlAction) self, self->selectStateChangedHandlerParameter, false, @@ -540,11 +555,14 @@ unselectObject(ControlObject* self, SelectStateChangedReason reason, MmsMapping* static void checkSelectTimeout(ControlObject* self, uint64_t currentTime, MmsMapping* mmsMapping) { - if ((self->ctlModel == 2) || (self->ctlModel == 4)) { - - if (getState(self) == STATE_READY) { - if (self->selectTimeout > 0) { - if (currentTime > (self->selectTime + self->selectTimeout)) { + if ((self->ctlModel == 2) || (self->ctlModel == 4)) + { + if (getState(self) == STATE_READY) + { + if (self->selectTimeout > 0) + { + if (currentTime > (self->selectTime + self->selectTimeout)) + { if (DEBUG_IED_SERVER) printf("IED_SERVER: select-timeout (timeout-val = %u) for control %s/%s.%s\n", self->selectTimeout, MmsDomain_getName(self->mmsDomain), self->lnName, self->name); @@ -562,7 +580,8 @@ checkSelectTimeout(ControlObject* self, uint64_t currentTime, MmsMapping* mmsMap static void setOpRcvd(ControlObject* self, bool value) { - if (self->opRcvd) { + if (self->opRcvd) + { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->pendingEventsLock); #endif @@ -581,13 +600,16 @@ setOpRcvd(ControlObject* self, bool value) static void setOpOk(ControlObject* self, bool value, uint64_t currentTimeInMs) { - if (self->opOk) { + if (self->opOk) + { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->pendingEventsLock); #endif - if (value) { - if (self->tOpOk) { + if (value) + { + if (self->tOpOk) + { MmsValue* timestamp = self->tOpOk->mmsValue; MmsValue_setUtcTimeMsEx(timestamp, currentTimeInMs, self->iedServer->timeQuality); @@ -607,7 +629,8 @@ setOpOk(ControlObject* self, bool value, uint64_t currentTimeInMs) static bool isSboClassOperateOnce(ControlObject* self) { - if (self->sboClass != NULL) { + if (self->sboClass != NULL) + { if (MmsValue_toInt32(self->sboClass) == 1) return false; else @@ -620,8 +643,8 @@ isSboClassOperateOnce(ControlObject* self) static MmsValue* getOperParameterOperTime(MmsValue* operParameters) { - if (MmsValue_getType(operParameters) == MMS_STRUCTURE) { - + if (MmsValue_getType(operParameters) == MMS_STRUCTURE) + { if (MmsValue_getArraySize(operParameters) == 7) return MmsValue_getElement(operParameters, 1); } @@ -659,12 +682,14 @@ exitControlTask(ControlObject* self) static void abortControlOperation(ControlObject* self, bool unconditional, SelectStateChangedReason reason, MmsMapping* mmsMapping) { - if ((self->ctlModel == 2) || (self->ctlModel == 4)) { - - if (unconditional) { + if ((self->ctlModel == 2) || (self->ctlModel == 4)) + { + if (unconditional) + { unselectObject(self, reason, mmsMapping); } - else { + else + { if (isSboClassOperateOnce(self)) unselectObject(self, reason, mmsMapping); else @@ -694,7 +719,8 @@ operateControl(ControlObject* self, MmsValue* value, uint64_t currentTime, bool static void resetAddCause(ControlObject* self) { - if (self) { + if (self) + { self->addCauseValue = ADD_CAUSE_UNKNOWN; MmsValue_setInt32(self->addCause, self->addCauseValue); @@ -710,8 +736,8 @@ executeStateMachine: state = getState(controlObject); - switch (state) { - + switch (state) + { case STATE_WAIT_FOR_SELECT: { controlObject->isSelect = 1; @@ -724,12 +750,14 @@ executeStateMachine: controlObject->isSelect = 0; - if (checkHandlerResult != CONTROL_WAITING_FOR_SELECT) { - - if (controlObject->ctlModel == 2) { + if (checkHandlerResult != CONTROL_WAITING_FOR_SELECT) + { + if (controlObject->ctlModel == 2) + { LinkedList values = LinkedList_create(); - if (checkHandlerResult == CONTROL_ACCEPTED) { + if (checkHandlerResult == CONTROL_ACCEPTED) + { LinkedList_add(values, controlObject->sbo); selectObject(controlObject, Hal_getTimeInMs(), controlObject->mmsConnection, self); @@ -738,7 +766,8 @@ executeStateMachine: updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT, IEC61850_SERVICE_ERROR_NO_ERROR); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ } - else { + else + { LinkedList_add(values, &emptyString); setState(controlObject, STATE_UNSELECTED); @@ -753,9 +782,10 @@ executeStateMachine: LinkedList_destroyStatic(values); } - else if (controlObject->ctlModel == 4) { - if (checkHandlerResult == CONTROL_ACCEPTED) { - + else if (controlObject->ctlModel == 4) + { + if (checkHandlerResult == CONTROL_ACCEPTED) + { selectObject(controlObject, Hal_getTimeInMs(), controlObject->mmsConnection, self); if (controlObject->ctlNumSt) @@ -773,8 +803,8 @@ executeStateMachine: if (DEBUG_IED_SERVER) printf("IED_SERVER: SBOw - selected successful\n"); } - else { - + else + { setState(controlObject, STATE_UNSELECTED); ControlObject_sendLastApplError(controlObject, controlObject->mmsConnection, "SBOw", @@ -793,13 +823,15 @@ executeStateMachine: printf("IED_SERVER: SBOw - select rejected by application!\n"); } } - else { + else + { /* ERROR: invalid internal state! */ setState(controlObject, STATE_WAIT_FOR_SELECT); } } - else { + else + { updateNextControlTimeout(self, Hal_getTimeInMs() + 100); } @@ -818,19 +850,23 @@ executeStateMachine: controlObject->errorValue = CONTROL_ERROR_NO_ERROR; controlObject->addCauseValue = ADD_CAUSE_BLOCKED_BY_SYNCHROCHECK; - if (controlObject->waitForExecutionHandler != NULL) { + if (controlObject->waitForExecutionHandler != NULL) + { dynamicCheckResult = controlObject->waitForExecutionHandler((ControlAction) controlObject, controlObject->waitForExecutionHandlerParameter, controlObject->ctlVal, controlObject->testMode, controlObject->synchroCheck); } - if (dynamicCheckResult == CONTROL_RESULT_FAILED) { - if ((controlObject->errorValue != CONTROL_ERROR_NO_ERROR) || (controlObject->addCauseValue != ADD_CAUSE_UNKNOWN)) { + if (dynamicCheckResult == CONTROL_RESULT_FAILED) + { + if ((controlObject->errorValue != CONTROL_ERROR_NO_ERROR) || (controlObject->addCauseValue != ADD_CAUSE_UNKNOWN)) + { ControlObject_sendLastApplError(controlObject, controlObject->mmsConnection, "Oper", controlObject->errorValue, controlObject->addCauseValue, controlObject->ctlNum, controlObject->origin, false); } - if (!isTimeActivatedControl) { + if (!isTimeActivatedControl) + { MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId, DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED, true); @@ -838,7 +874,8 @@ executeStateMachine: updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_OPERATE, IEC61850_SERVICE_ERROR_ACCESS_NOT_ALLOWED_IN_CURRENT_STATE); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ } - else { + else + { #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_TIME_ACTIVATED_OPERATE, IEC61850_SERVICE_ERROR_ACCESS_NOT_ALLOWED_IN_CURRENT_STATE); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ @@ -849,8 +886,10 @@ executeStateMachine: abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED, self); exitControlTask(controlObject); } - else if (dynamicCheckResult == CONTROL_RESULT_OK) { - if (isTimeActivatedControl) { + else if (dynamicCheckResult == CONTROL_RESULT_OK) + { + if (isTimeActivatedControl) + { ControlObject_sendCommandTerminationPositive(controlObject); #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) @@ -862,7 +901,8 @@ executeStateMachine: MmsValue_setUtcTime(operTm, 0); MmsValue_setUtcTimeQuality(operTm, self->iedServer->timeQuality); } - else { + else + { MmsServerConnection_sendWriteResponse(controlObject->mmsConnection, controlObject->operateInvokeId, DATA_ACCESS_ERROR_SUCCESS, true); @@ -877,7 +917,8 @@ executeStateMachine: goto executeStateMachine; } - else { + else + { updateNextControlTimeout(self, Hal_getTimeInMs() + 10); } } @@ -889,11 +930,12 @@ executeStateMachine: ControlHandlerResult result = operateControl(controlObject, controlObject->ctlVal, currentTime, controlObject->testMode); - if (result != CONTROL_RESULT_WAITING) { - - if (result == CONTROL_RESULT_OK) { - - if ((controlObject->ctlModel == 4) || (controlObject->ctlModel == 3)) { + if (result != CONTROL_RESULT_WAITING) + { + if (result == CONTROL_RESULT_OK) + { + if ((controlObject->ctlModel == 4) || (controlObject->ctlModel == 3)) + { ControlObject_sendCommandTerminationPositive(controlObject); #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) @@ -903,9 +945,10 @@ executeStateMachine: abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATED, self); } - else { - - if ((controlObject->ctlModel == 4) || (controlObject->ctlModel == 3)) { + else + { + if ((controlObject->ctlModel == 4) || (controlObject->ctlModel == 3)) + { if (DEBUG_IED_SERVER) printf("IED_SERVER: operate failed!\n"); @@ -925,7 +968,8 @@ executeStateMachine: resetAddCause(controlObject); } - else { + else + { updateNextControlTimeout(self, currentTimeInMs + 10); } } @@ -949,7 +993,8 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* self->stateLock = Semaphore_create(1); self->pendingEventsLock = Semaphore_create(1); - if ((self->stateLock == NULL) || (self->pendingEventsLock == NULL)) { + if ((self->stateLock == NULL) || (self->pendingEventsLock == NULL)) + { ControlObject_destroy(self); self = NULL; goto exit_function; @@ -958,7 +1003,8 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* self->name = StringUtils_copyString(name); - if (self->name == NULL) { + if (self->name == NULL) + { ControlObject_destroy(self); self = NULL; goto exit_function; @@ -970,20 +1016,24 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char* MmsVariableSpecification* ctlValSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "ctlVal", NULL); - if (ctlValSpec) { + if (ctlValSpec) + { self->ctlVal = MmsValue_newDefaultValue(ctlValSpec); } - else { + else + { if (DEBUG_IED_SERVER) printf("IED_SERVER: control object %s/%s.%s has no ctlVal element!\n", domain->domainName, lnName, name); } MmsVariableSpecification* originSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "origin", NULL); - if (originSpec) { + if (originSpec) + { self->origin = MmsValue_newDefaultValue(originSpec); } - else { + else + { if (DEBUG_IED_SERVER) printf("IED_SERVER: control object %s/%s.%s has no origin element!\n", domain->domainName, lnName, name); } @@ -1015,7 +1065,8 @@ ControlObject_initialize(ControlObject* self) MmsValue* ctlModel = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, ctlModelName); - if (ctlModel == NULL) { + if (ctlModel == NULL) + { if (DEBUG_IED_SERVER) printf("IED_SERVER: No control model found for variable %s\n", ctlModelName); } @@ -1028,7 +1079,8 @@ ControlObject_initialize(ControlObject* self) self->ctlNumSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, ctlNumName); - if (self->ctlNumSt == NULL) { + if (self->ctlNumSt == NULL) + { /* for APC */ ctlNumName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$MX$", self->name, "$ctlNum"); @@ -1039,7 +1091,8 @@ ControlObject_initialize(ControlObject* self) self->originSt = MmsServer_getValueFromCache(mmsServer, self->mmsDomain, originName); - if (self->originSt == NULL) { + if (self->originSt == NULL) + { /* for APC */ originName = StringUtils_createStringInBuffer(strBuf, 130, 4, self->lnName, "$MX$", self->name, "$origin"); @@ -1053,7 +1106,8 @@ ControlObject_initialize(ControlObject* self) updateSboTimeoutValue(self); - if (self->sbo) { + if (self->sbo) + { char* controlObjectReference = StringUtils_createStringInBuffer(strBuf, 130, 5, self->mmsDomain->domainName, "/", self->lnName, "$CO$", self->name); @@ -1154,7 +1208,8 @@ ControlObject_initialize(ControlObject* self) self->stSeld = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, stSeldName); - if ((self->stSeld) && (self->stSeld->type != IEC61850_BOOLEAN)) { + if ((self->stSeld) && (self->stSeld->type != IEC61850_BOOLEAN)) + { self->stSeld = NULL; if (DEBUG_IED_SERVER) @@ -1165,7 +1220,8 @@ ControlObject_initialize(ControlObject* self) self->opRcvd = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, opRcvdName); - if ((self->opRcvd) && (self->opRcvd->type != IEC61850_BOOLEAN)) { + if ((self->opRcvd) && (self->opRcvd->type != IEC61850_BOOLEAN)) + { self->opRcvd = NULL; if (DEBUG_IED_SERVER) @@ -1176,7 +1232,8 @@ ControlObject_initialize(ControlObject* self) self->opOk = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, opOkName); - if ((self->opOk) && (self->opOk->type != IEC61850_BOOLEAN)) { + if ((self->opOk) && (self->opOk->type != IEC61850_BOOLEAN)) + { self->opOk = NULL; if (DEBUG_IED_SERVER) @@ -1187,7 +1244,8 @@ ControlObject_initialize(ControlObject* self) self->tOpOk = (DataAttribute*) IedModel_getModelNodeByObjectReference(self->iedServer->model, tOpOkName); - if ((self->tOpOk) && (self->tOpOk->type != IEC61850_TIMESTAMP)) { + if ((self->tOpOk) && (self->tOpOk->type != IEC61850_TIMESTAMP)) + { self->tOpOk = NULL; if (DEBUG_IED_SERVER) @@ -1197,7 +1255,8 @@ ControlObject_initialize(ControlObject* self) self->error = MmsValue_newIntegerFromInt32(0); self->addCause = MmsValue_newIntegerFromInt32(0); - if (ctlModel != NULL) { + if (ctlModel != NULL) + { int ctlModelVal = MmsValue_toInt32(ctlModel); if (DEBUG_IED_SERVER) @@ -1225,44 +1284,50 @@ ControlObject_handlePendingEvents(ControlObject* self) Semaphore_wait(self->pendingEventsLock); #endif - if (self->pendingEvents > 0) { - - if (self->pendingEvents & PENDING_EVENT_SELECTED) { + if (self->pendingEvents > 0) + { + if (self->pendingEvents & PENDING_EVENT_SELECTED) + { if (self->stSeld) IedServer_updateBooleanAttributeValue(self->iedServer, self->stSeld, true); self->pendingEvents &= ~(PENDING_EVENT_SELECTED); } - if (self->pendingEvents & PENDING_EVENT_UNSELECTED) { + if (self->pendingEvents & PENDING_EVENT_UNSELECTED) + { if (self->stSeld) IedServer_updateBooleanAttributeValue(self->iedServer, self->stSeld, false); self->pendingEvents &= ~(PENDING_EVENT_UNSELECTED); } - if (self->pendingEvents & PENDING_EVENT_OP_RCVD_TRUE) { + if (self->pendingEvents & PENDING_EVENT_OP_RCVD_TRUE) + { if (self->opRcvd) IedServer_updateBooleanAttributeValue(self->iedServer, self->opRcvd, true); self->pendingEvents &= ~(PENDING_EVENT_OP_RCVD_TRUE); } - if (self->pendingEvents & PENDING_EVENT_OP_RCVD_FALSE) { + if (self->pendingEvents & PENDING_EVENT_OP_RCVD_FALSE) + { if (self->opRcvd) IedServer_updateBooleanAttributeValue(self->iedServer, self->opRcvd, false); self->pendingEvents &= ~(PENDING_EVENT_OP_RCVD_FALSE); } - if (self->pendingEvents & PENDING_EVENT_OP_OK_TRUE) { + if (self->pendingEvents & PENDING_EVENT_OP_OK_TRUE) + { if (self->opOk) IedServer_updateBooleanAttributeValue(self->iedServer, self->opOk, true); self->pendingEvents &= ~(PENDING_EVENT_OP_OK_TRUE); } - if (self->pendingEvents & PENDING_EVENT_OP_OK_FALSE) { + if (self->pendingEvents & PENDING_EVENT_OP_OK_FALSE) + { if (self->opOk) IedServer_updateBooleanAttributeValue(self->iedServer, self->opOk, false); @@ -1278,7 +1343,8 @@ ControlObject_handlePendingEvents(ControlObject* self) void ControlObject_destroy(ControlObject* self) { - if (self) { + if (self) + { if (self->mmsValue) MmsValue_delete(self->mmsValue); @@ -1357,7 +1423,8 @@ ControlObject_getMmsValue(ControlObject* self) bool ControlObject_unselect(ControlObject* self, MmsServerConnection connection, MmsMapping* mmsMapping) { - if (self->mmsConnection == connection) { + if (self->mmsConnection == connection) + { abortControlOperation(self, true, SELECT_STATE_REASON_DISCONNECTED, mmsMapping); return true; } @@ -1415,29 +1482,32 @@ ControlObject_updateControlModel(ControlObject* self, ControlModel value, DataOb void Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) { - if (currentTimeInMs >= self->nextControlTimeout) { - + if (currentTimeInMs >= self->nextControlTimeout) + { /* invalidate nextControlTimeout */ self->nextControlTimeout = (uint64_t) 0xFFFFFFFFFFFFFFFFLLU; LinkedList element = LinkedList_getNext(self->controlObjects); - while (element != NULL) { + while (element != NULL) + { ControlObject* controlObject = (ControlObject*) element->data; - if (controlObject->state != STATE_UNSELECTED) { - - if ((controlObject->ctlModel == 1) || (controlObject->ctlModel == 3)) { - if (controlObject->state == STATE_READY) { + if (controlObject->state != STATE_UNSELECTED) + { + if ((controlObject->ctlModel == 1) || (controlObject->ctlModel == 3)) + { + if (controlObject->state == STATE_READY) + { element = LinkedList_getNext(element); continue; } } - if (controlObject->state == STATE_WAIT_FOR_ACTIVATION_TIME) { - - if (controlObject->operateTime <= currentTimeInMs) { - + if (controlObject->state == STATE_WAIT_FOR_ACTIVATION_TIME) + { + if (controlObject->operateTime <= currentTimeInMs) + { /* enter state Perform Test */ setOpRcvd(controlObject, true); @@ -1448,7 +1518,9 @@ Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) CheckHandlerResult checkResult = CONTROL_ACCEPTED; - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + if (controlObject->checkHandler != NULL) + { + /* perform operative tests */ controlObject->errorValue = CONTROL_ERROR_NO_ERROR; controlObject->addCauseValue = ADD_CAUSE_BLOCKED_BY_INTERLOCKING; @@ -1458,8 +1530,8 @@ Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) controlObject->interlockCheck); } - if (checkResult == CONTROL_ACCEPTED) { - + if (checkResult == CONTROL_ACCEPTED) + { if (DEBUG_IED_SERVER) printf("IED_SERVER: time activated operate: command accepted\n"); @@ -1468,8 +1540,8 @@ Control_processControlActions(MmsMapping* self, uint64_t currentTimeInMs) executeControlTask(self, controlObject, currentTimeInMs); } - else { - + else + { ControlObject_sendLastApplError(controlObject, controlObject->mmsConnection, "Oper", controlObject->errorValue, controlObject->addCauseValue, controlObject->ctlNum, controlObject->origin, false); @@ -1512,12 +1584,16 @@ Control_lookupControlObject(MmsMapping* self, MmsDomain* domain, char* lnName, c { LinkedList element = LinkedList_getNext(self->controlObjects); - while (element != NULL) { + while (element != NULL) + { ControlObject* controlObject = (ControlObject*) element->data; - if (ControlObject_getDomain(controlObject) == domain) { - if (strcmp(ControlObject_getLNName(controlObject), lnName) == 0) { - if (strcmp(ControlObject_getName(controlObject), objectName) == 0) { + if (ControlObject_getDomain(controlObject) == domain) + { + if (strcmp(ControlObject_getLNName(controlObject), lnName) == 0) + { + if (strcmp(ControlObject_getName(controlObject), objectName) == 0) + { return controlObject; } } @@ -1532,8 +1608,10 @@ Control_lookupControlObject(MmsMapping* self, MmsDomain* domain, char* lnName, c static MmsValue* getCtlVal(MmsValue* operParameters) { - if (MmsValue_getType(operParameters) == MMS_STRUCTURE) { - if (MmsValue_getArraySize(operParameters) > 5) { + if (MmsValue_getType(operParameters) == MMS_STRUCTURE) + { + if (MmsValue_getArraySize(operParameters) > 5) + { return MmsValue_getElement(operParameters, 0); } } @@ -1678,7 +1756,8 @@ ControlObject_sendLastApplError(ControlObject* self, MmsServerConnection connect StringUtils_createStringInBuffer(ctlObj, 130, 7, MmsDomain_getName(self->mmsDomain), "/", self->lnName, "$CO$", self->name, "$", ctlVariable); - if (DEBUG_IED_SERVER) { + if (DEBUG_IED_SERVER) + { printf("IED_SERVER: sendLastApplError:\n"); printf("IED_SERVER: control object: %s\n", ctlObj); printf("IED_SERVER: ctlNum: %u\n", MmsValue_toUint32(ctlNum)); @@ -1728,7 +1807,8 @@ doesElementEquals(char* element, char* name) { int i = 0; - while (name[i] != 0) { + while (name[i] != 0) + { if (element[i] == 0) return false; @@ -1773,13 +1853,14 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia char* varName = MmsMapping_getNextNameElement(objectName); - if (varName != NULL) { - + if (varName != NULL) + { bool foundVar = false; char* nextVarName = varName; - do { + do + { if (doesElementEquals(varName, "Oper") || doesElementEquals(varName, "SBO") || doesElementEquals(varName, "SBOw") || @@ -1806,24 +1887,28 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia ControlObject* controlObject = Control_lookupControlObject(self, domain, lnName, objectName); - if (controlObject != NULL) { - - if (varName != NULL) { + if (controlObject != NULL) + { + if (varName != NULL) + { if (strcmp(varName, "Oper") == 0) value = controlObject->oper; else if (strcmp(varName, "SBOw") == 0) value = controlObject->sbow; - else if (strcmp(varName, "SBO") == 0) { - if (controlObject->ctlModel == 2) { - + else if (strcmp(varName, "SBO") == 0) + { + if (controlObject->ctlModel == 2) + { uint64_t currentTime = Hal_getTimeInMs(); value = &emptyString; - if (isDirectAccess == true) { + if (isDirectAccess == true) + { checkSelectTimeout(controlObject, currentTime, self); - if (getState(controlObject) == STATE_UNSELECTED) { + if (getState(controlObject) == STATE_UNSELECTED) + { CheckHandlerResult checkResult = CONTROL_ACCEPTED; /* opRcvd must not be set here! */ @@ -1831,7 +1916,9 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia controlObject->addCauseValue = ADD_CAUSE_UNKNOWN; controlObject->mmsConnection = connection; - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + if (controlObject->checkHandler != NULL) + { + /* perform operative tests */ controlObject->isSelect = 1; @@ -1841,7 +1928,8 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia controlObject->isSelect = 0; } - if (checkResult == CONTROL_ACCEPTED) { + if (checkResult == CONTROL_ACCEPTED) + { selectObject(controlObject, currentTime, connection, self); value = controlObject->sbo; @@ -1849,13 +1937,15 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT, IEC61850_SERVICE_ERROR_NO_ERROR); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ } - else if (checkResult == CONTROL_WAITING_FOR_SELECT) { + else if (checkResult == CONTROL_WAITING_FOR_SELECT) + { controlObject->mmsConnection = connection; controlObject->operateInvokeId = MmsServerConnection_getLastInvokeId(connection); setState(controlObject, STATE_WAIT_FOR_SELECT); value = &delayedResponse; } - else { + else + { #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) updateGenericTrackingObjectValues(self, controlObject, IEC61850_SERVICE_TYPE_SELECT, convertCheckHandlerResultToServiceError(checkResult)); @@ -1863,9 +1953,9 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia } } } - } - else { + else + { if (DEBUG_IED_SERVER) printf("IED_SERVER: select not applicable for control model %u\n", controlObject->ctlModel); @@ -1875,7 +1965,8 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia else if (strcmp(varName, "Cancel") == 0) value = controlObject->cancel; - else { + else + { value = MmsValue_getSubElement(ControlObject_getMmsValue(controlObject), ControlObject_getTypeSpec(controlObject), varName); } @@ -1884,7 +1975,8 @@ Control_readAccessControlObject(MmsMapping* self, MmsDomain* domain, char* varia value = ControlObject_getMmsValue(controlObject); } } - else { + else + { if (DEBUG_IED_SERVER) printf("IED_SERVER: Control object not found %s/%s.%s\n", domain->domainName, lnName, objectName); } @@ -1958,13 +2050,14 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char char* varName = MmsMapping_getNextNameElement(objectName); - if (varName != NULL) { - + if (varName != NULL) + { bool foundVar = false; char* nextVarName = varName; - do { + do + { if (doesElementEquals(varName, "Oper") || doesElementEquals(varName, "SBO") || doesElementEquals(varName, "SBOw") || @@ -1989,31 +2082,36 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char if (DEBUG_IED_SERVER) printf("IED_SERVER: write access control: objectName: (%s) varName: (%s)\n", objectName, varName); - if (varName == NULL) { + if (varName == NULL) + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto free_and_return; } controlObject = Control_lookupControlObject(self, domain, lnName, objectName); - if (controlObject == NULL) { + if (controlObject == NULL) + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto free_and_return; } - if (controlObject->ctlModel == CONTROL_MODEL_STATUS_ONLY) { + if (controlObject->ctlModel == CONTROL_MODEL_STATUS_ONLY) + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto free_and_return; } - if (strcmp(varName, "SBOw") == 0) { /* select with value */ - + if (strcmp(varName, "SBOw") == 0) /* select with value */ + { serviceType = IEC61850_SERVICE_TYPE_SELECT_WITH_VALUES; - if (controlObject->ctlModel == 4) { - - if (controlObject->sbow) { - if (MmsValue_update(controlObject->sbow, value) == false) { + if (controlObject->ctlModel == 4) + { + if (controlObject->sbow) + { + if (MmsValue_update(controlObject->sbow, value) == false) + { if (DEBUG_IED_SERVER) printf("IED_SERVER: SBOw - type mismatch\n"); } @@ -2021,14 +2119,15 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char MmsValue* ctlVal = getCtlVal(value); - if (ctlVal != NULL) { - + if (ctlVal != NULL) + { MmsValue* ctlNum = getOperParameterCtlNum(value); MmsValue* origin = getOperParameterOrigin(value); MmsValue* check = getOperParameterCheck(value); MmsValue* test = getOperParameterTest(value); - if (checkValidityOfOriginParameter(origin) == false) { + if (checkValidityOfOriginParameter(origin) == false) + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, @@ -2046,9 +2145,10 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char checkSelectTimeout(controlObject, currentTime, self); - if (state != STATE_UNSELECTED) { - - if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) { + if (state != STATE_UNSELECTED) + { + if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) + { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; ControlObject_sendLastApplError(controlObject, connection, "SBOw", @@ -2060,7 +2160,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } - else { + else + { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; ControlObject_sendLastApplError(controlObject, connection, "SBOw", CONTROL_ERROR_NO_ERROR, @@ -2072,8 +2173,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } } - else { - + else + { CheckHandlerResult checkResult = CONTROL_ACCEPTED; /* opRcvd must not be set here! */ @@ -2088,7 +2189,9 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char controlObject->testMode = testCondition; - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + if (controlObject->checkHandler != NULL) + { + /* perform operative tests */ controlObject->isSelect = 1; @@ -2100,7 +2203,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char controlObject->isSelect = 0; } - if (checkResult == CONTROL_ACCEPTED) { + if (checkResult == CONTROL_ACCEPTED) + { selectObject(controlObject, currentTime, connection, self); indication = DATA_ACCESS_ERROR_SUCCESS; @@ -2108,7 +2212,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char if (DEBUG_IED_SERVER) printf("IED_SERVER: SBOw - selected successful\n"); } - else if (checkResult == CONTROL_WAITING_FOR_SELECT) { + else if (checkResult == CONTROL_WAITING_FOR_SELECT) + { controlObject->mmsConnection = connection; controlObject->operateInvokeId = MmsServerConnection_getLastInvokeId(connection); @@ -2122,7 +2227,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char indication = DATA_ACCESS_ERROR_NO_RESPONSE; } - else { + else + { indication = (MmsDataAccessError) checkResult; ControlObject_sendLastApplError(controlObject, connection, "SBOw", controlObject->errorValue, @@ -2135,17 +2241,19 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char } } } - else { + else + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; } } - else { + else + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto free_and_return; } } - else if (strcmp(varName, "Oper") == 0) { - + else if (strcmp(varName, "Oper") == 0) + { serviceType = IEC61850_SERVICE_TYPE_OPERATE; MmsValue* ctlVal = getCtlVal(value); @@ -2156,19 +2264,22 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char MmsValue* timeParameter = getOperParameterTime(value); if ((ctlVal == NULL) || (test == NULL) || (ctlNum == NULL) || (origin == NULL) || (check == NULL) - || (timeParameter == NULL)) { + || (timeParameter == NULL)) + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto free_and_return; } - if (checkValidityOfOriginParameter(origin) == false) { + if (checkValidityOfOriginParameter(origin) == false) + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; ControlObject_sendLastApplError(controlObject, connection, "Oper", CONTROL_ERROR_NO_ERROR, ADD_CAUSE_INCONSISTENT_PARAMETERS, ctlNum, origin, true); - if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) { + if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) + { unselectObject(controlObject, SELECT_STATE_REASON_OPERATE_FAILED, self); } @@ -2181,7 +2292,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char int state = getState(controlObject); - if (state == STATE_WAIT_FOR_ACTIVATION_TIME) { + if (state == STATE_WAIT_FOR_ACTIVATION_TIME) + { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; ControlObject_sendLastApplError(controlObject, connection, "Oper", @@ -2190,15 +2302,17 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } - else if (state == STATE_READY) { - + else if (state == STATE_READY) + { bool interlockCheck = MmsValue_getBitStringBit(check, 1); bool synchroCheck = MmsValue_getBitStringBit(check, 0); bool testCondition = MmsValue_getBoolean(test); - if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) { - if (controlObject->mmsConnection != connection) { + if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) + { + if (controlObject->mmsConnection != connection) + { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; if (DEBUG_IED_SERVER) printf("IED_SERVER: Oper - operate from wrong client connection!\n"); @@ -2209,7 +2323,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } - if (controlObject->ctlModel == 4) { /* select-before-operate with enhanced security */ + 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) && @@ -2237,24 +2352,29 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char MmsValue* operTm = getOperParameterOperTime(value); - if (operTm != NULL) { + if (operTm != NULL) + { controlObject->operateTime = MmsValue_getUtcTimeInMs(operTm); - if (controlObject->operateTime > currentTime) { + if (controlObject->operateTime > currentTime) + { controlObject->timeActivatedOperate = true; controlObject->synchroCheck = synchroCheck; controlObject->interlockCheck = interlockCheck; controlObject->mmsConnection = connection; CheckHandlerResult checkResult = CONTROL_ACCEPTED; - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + if (controlObject->checkHandler != NULL) + { + /* perform operative tests */ checkResult = controlObject->checkHandler((ControlAction) controlObject, controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck); } - if (checkResult == CONTROL_ACCEPTED) { + if (checkResult == CONTROL_ACCEPTED) + { initiateControlTask(controlObject); setState(controlObject, STATE_WAIT_FOR_ACTIVATION_TIME); @@ -2266,19 +2386,21 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char indication = DATA_ACCESS_ERROR_SUCCESS; } - else { + else + { indication = (MmsDataAccessError) checkResult; } } } - else{ + else + { controlObject->operateTime = 0; } MmsValue_update(controlObject->oper, value); - if (controlObject->timeActivatedOperate == false) { - + if (controlObject->timeActivatedOperate == false) + { CheckHandlerResult checkResult = CONTROL_ACCEPTED; /* enter state Perform Test */ @@ -2288,13 +2410,16 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char controlObject->addCauseValue = ADD_CAUSE_UNKNOWN; controlObject->mmsConnection = connection; - if (controlObject->checkHandler != NULL) { /* perform operative tests */ + if (controlObject->checkHandler != NULL) + { + /* perform operative tests */ checkResult = controlObject->checkHandler((ControlAction) controlObject, controlObject->checkHandlerParameter, ctlVal, testCondition, interlockCheck); } - if (checkResult == CONTROL_ACCEPTED) { + if (checkResult == CONTROL_ACCEPTED) + { indication = DATA_ACCESS_ERROR_NO_RESPONSE; controlObject->mmsConnection = connection; @@ -2310,7 +2435,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char updateNextControlTimeout(self, currentTime); } - else { + else + { indication = (MmsDataAccessError) checkResult; /* leave state Perform Test */ @@ -2318,7 +2444,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char abortControlOperation(controlObject, false, SELECT_STATE_REASON_OPERATE_FAILED, self); - if ((controlObject->ctlModel == 3) || (controlObject->ctlModel == 4)) { + if ((controlObject->ctlModel == 3) || (controlObject->ctlModel == 4)) + { ControlObject_sendLastApplError(controlObject, connection, "Oper", controlObject->errorValue, controlObject->addCauseValue, ctlNum, origin, true); @@ -2327,7 +2454,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char } } - else if (state == STATE_UNSELECTED) { + else if (state == STATE_UNSELECTED) + { if (DEBUG_IED_SERVER) printf("IED_SERVER: Oper failed - control not selected!\n"); @@ -2340,7 +2468,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } - else if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) { + else if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) + { if (DEBUG_IED_SERVER) printf("IED_SERVER: Oper failed - control already being executed!\n"); @@ -2353,8 +2482,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } } - else if (strcmp(varName, "Cancel") == 0) { - + else if (strcmp(varName, "Cancel") == 0) + { serviceType = IEC61850_SERVICE_TYPE_CANCEL; int state = getState(controlObject); @@ -2365,14 +2494,16 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char MmsValue* ctlNum = getCancelParameterCtlNum(value); MmsValue* origin = getCancelParameterOrigin(value); - if ((ctlNum == NULL) || (origin == NULL)) { + if ((ctlNum == NULL) || (origin == NULL)) + { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; if (DEBUG_IED_SERVER) printf("IED_SERVER: Invalid cancel message!\n"); goto free_and_return; } - if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) { + if ((state == STATE_OPERATE) || (state == STATE_WAIT_FOR_EXECUTION)) + { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; ControlObject_sendLastApplError(controlObject, connection, "Cancel", @@ -2382,26 +2513,32 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } - if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) { - if (state != STATE_UNSELECTED) { - if (controlObject->mmsConnection == connection) { + if ((controlObject->ctlModel == 2) || (controlObject->ctlModel == 4)) + { + if (state != STATE_UNSELECTED) + { + if (controlObject->mmsConnection == connection) + { indication = DATA_ACCESS_ERROR_SUCCESS; unselectObject(controlObject, SELECT_STATE_REASON_CANCELED, self); goto free_and_return; } - else { + else + { indication = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; ControlObject_sendLastApplError(controlObject, connection, "Cancel", CONTROL_ERROR_NO_ERROR, ADD_CAUSE_LOCKED_BY_OTHER_CLIENT, ctlNum, origin, true); } } - else { + else + { indication = DATA_ACCESS_ERROR_SUCCESS; } } - if (controlObject->timeActivatedOperate) { + if (controlObject->timeActivatedOperate) + { controlObject->timeActivatedOperate = false; abortControlOperation(controlObject, false, SELECT_STATE_REASON_CANCELED, self); @@ -2414,14 +2551,18 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char free_and_return: #if (CONFIG_IEC61850_SERVICE_TRACKING == 1) - if (controlObject) { - if (serviceError == IEC61850_SERVICE_ERROR_NO_ERROR) { - if (indication != DATA_ACCESS_ERROR_NO_RESPONSE) { + if (controlObject) + { + if (serviceError == IEC61850_SERVICE_ERROR_NO_ERROR) + { + if (indication != DATA_ACCESS_ERROR_NO_RESPONSE) + { updateGenericTrackingObjectValues(self, controlObject, serviceType, private_IedServer_convertMmsDataAccessErrorToServiceError(indication)); } } - else { + else + { updateGenericTrackingObjectValues(self, controlObject, serviceType, serviceError); } } @@ -2453,7 +2594,8 @@ ControlAction_getOrCat(ControlAction self) { ControlObject* controlObject = (ControlObject*) self; - if (controlObject->origin) { + if (controlObject->origin) + { MmsValue* orCat = MmsValue_getElement(controlObject->origin, 0); if (orCat) { @@ -2469,10 +2611,12 @@ ControlAction_getOrIdent(ControlAction self, int* orIdentSize) { ControlObject* controlObject = (ControlObject*) self; - if (controlObject->origin) { + if (controlObject->origin) + { MmsValue* orIdent = MmsValue_getElement(controlObject->origin, 1); - if (orIdent) { + if (orIdent) + { if (MmsValue_getType(orIdent) == MMS_OCTET_STRING) { *orIdentSize = MmsValue_getOctetStringSize(orIdent); return MmsValue_getOctetStringBuffer(orIdent); @@ -2547,4 +2691,3 @@ ControlAction_getControlTime(ControlAction self) } #endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */ - diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 90d863fa..a14ed58f 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -3186,7 +3186,6 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo } #endif - #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) /* GOOSE control blocks - GO */ if (isGooseControlBlock(separator)) { @@ -3213,8 +3212,8 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo #if (CONFIG_IEC61850_REPORT_SERVICE == 1) /* Report control blocks - BR, RP */ - if (isReportControlBlock(separator)) { - + if (isReportControlBlock(separator)) + { LinkedList reportControls = self->reportControls; LinkedList nextElement = reportControls; @@ -3235,11 +3234,12 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo else variableIdLen = strlen(variableId); - while ((nextElement = LinkedList_getNext(nextElement)) != NULL) { + while ((nextElement = LinkedList_getNext(nextElement)) != NULL) + { ReportControl* rc = (ReportControl*) nextElement->data; - if (rc->domain == domain) { - + if (rc->domain == domain) + { int parentLNNameStrLen = strlen(rc->parentLN->name); if (parentLNNameStrLen != lnNameLength) @@ -3248,7 +3248,8 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo if (memcmp(rc->parentLN->name, variableId, parentLNNameStrLen) != 0) continue; - if (strlen(rc->name) == variableIdLen) { + if (strlen(rc->name) == variableIdLen) + { if (strncmp(variableId, rc->name, variableIdLen) == 0) { char* elementName = MmsMapping_getNextNameElement(reportName); @@ -3665,15 +3666,26 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS if (DEBUG_IED_SERVER) printf("IED_SERVER: mmsReadAccessHandler: Requested %s\n", variableId); + if (self->iedServer->ignoreReadAccess) + { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: mmsReadAccessHandler - ignore request\n"); + + return DATA_ACCESS_ERROR_NO_RESPONSE; + } + char* separator = strchr(variableId, '$'); #if (CONFIG_IEC61850_SETTING_GROUPS == 1) - if (separator) { - if (isFunctionalConstraint("SE", separator)) { + if (separator) + { + if (isFunctionalConstraint("SE", separator)) + { SettingGroup* sg = getSettingGroupByMmsDomain(self, domain); - if (sg != NULL) { + if (sg != NULL) + { if (sg->sgcb->editSG == 0) return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; } @@ -3695,7 +3707,8 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS { FunctionalConstraint fc = IEC61850_FC_NONE; - if (separator != NULL) { + if (separator != NULL) + { fc = FunctionalConstraint_fromString(separator + 1); if (fc == IEC61850_FC_BR || fc == IEC61850_FC_US || @@ -3716,23 +3729,27 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS { char* doStart = strchr(separator + 1, '$'); - if (doStart != NULL) { - + if (doStart != NULL) + { char* doEnd = strchr(doStart + 1, '$'); - if (doEnd == NULL) { + if (doEnd == NULL) + { StringUtils_copyStringToBuffer(doStart + 1, str); } - else { + else + { doEnd--; StringUtils_createStringFromBufferInBuffer(str, (uint8_t*) (doStart + 1), doEnd - doStart); } - if (fc == IEC61850_FC_SP) { + if (fc == IEC61850_FC_SP) + { if (!strcmp(str, "SGCB")) { - if (self->controlBlockAccessHandler) { + if (self->controlBlockAccessHandler) + { ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); @@ -3747,10 +3764,10 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS ModelNode* dobj = ModelNode_getChild((ModelNode*) ln, str); - if (dobj != NULL) { - - if (dobj->modelType == DataObjectModelType) { - + if (dobj != NULL) + { + if (dobj->modelType == DataObjectModelType) + { ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); @@ -3759,7 +3776,8 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS } } } - else { + else + { ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); @@ -3769,10 +3787,12 @@ mmsReadAccessHandler (void* parameter, MmsDomain* domain, char* variableId, MmsS } } } - else { + else + { LogicalNode* ln = LogicalDevice_getLogicalNode(ld, variableId); - if (ln != NULL) { + if (ln != NULL) + { ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); return self->readAccessHandler(ld, ln, NULL, fc, clientConnection, From 8e49a72f8b238f955175267ed820f95889d236e2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 May 2024 14:56:05 +0100 Subject: [PATCH 34/53] - IED connection: Fixed memory leak and memory handling problem in function IedConnection_readDataSetValuesAsync (LIB61850-439) --- src/iec61850/client/ied_connection.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index cf363c3b..338df359 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -4013,7 +4013,7 @@ getDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV if (dataSet == NULL) { dataSet = ClientDataSet_create(dataSetReference); - ClientDataSet_setDataSetValues(dataSet, value); + ClientDataSet_setDataSetValues(dataSet, MmsValue_clone(value)); } else { @@ -4021,12 +4021,12 @@ getDataSetHandlerInternal(uint32_t invokeId, void* parameter, MmsError err, MmsV MmsValue_update(dataSetValues, value); } - if (dataSetReference) - GLOBAL_FREEMEM(dataSetReference); - MmsValue_delete(value); } + if (dataSetReference) + GLOBAL_FREEMEM(dataSetReference); + handler(invokeId, call->callbackParameter, iedConnection_mapMmsErrorToIedError(err), dataSet); iedConnection_releaseOutstandingCall(self, call); From 1add5fd7a053a88f834189473a7d4a0968127d7d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 May 2024 15:16:59 +0100 Subject: [PATCH 35/53] - fixed typo in function name IedServer_ignoreReadAccess --- src/iec61850/inc/iec61850_server.h | 6 ++++-- src/iec61850/server/impl/ied_server.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 04a81b78..d33d1a00 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -815,7 +815,9 @@ LIB61850_API void IedServer_setConnectionIndicationHandler(IedServer self, IedConnectionIndicationHandler handler, void* parameter); /** - * \brief Ignore all requests from clients + * \brief Ignore all requests from clients (for testing purposes) + * + * NOTE: This function will block all client requests on MMS layer * * \param self the instance of IedServer to configure. * \param enable when true all requests from clients will be ignored @@ -2017,7 +2019,7 @@ IedServer_setControlBlockAccessHandler(IedServer self, IedServer_ControlBlockAcc * \param ignore true to ignore read requests, false to handle read requests. */ LIB61850_API void -IedServer_ingoreReadAccess(IedServer self, bool ignore); +IedServer_ignoreReadAccess(IedServer self, bool ignore); /**@}*/ diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index c718e2c8..2acecedd 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -2002,7 +2002,7 @@ IedServer_setControlBlockAccessHandler(IedServer self, IedServer_ControlBlockAcc } void -IedServer_ingoreReadAccess(IedServer self, bool ignore) +IedServer_ignoreReadAccess(IedServer self, bool ignore) { self->ignoreReadAccess = ignore; } From 0b57f143b5890837e173e3474097f639d1c63c46 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 May 2024 15:18:42 +0100 Subject: [PATCH 36/53] - .NET API: added function IedServer.IgnoreClientRequests and IedServer.IgnoreReadAccess --- dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 4bb653bb..a39a8a2f 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -557,7 +557,13 @@ namespace IEC61850 public delegate void StateChangedHandler(IedConnection connection, IedConnectionState newState); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_installStateChangedHandler(IntPtr connection, InternalStateChangedHandler handler, IntPtr parameter); + static extern void IedConnection_installStateChangedHandler(IntPtr connection, InternalStateChangedHandler handler, IntPtr parameter); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_ignoreReadAccess(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool ignore); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedServer_ignoreClientRequests(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool ignore); /********************* * Async functions @@ -1948,6 +1954,24 @@ namespace IEC61850 } } + /// + /// Ignore all MMS requests from clients (for testing purposes) + /// + /// when true all requests from clients will be ignored + public void IgnoreClientRequests(bool ignore) + { + IedServer_ignoreClientRequests(connection, ignore); + } + + /// + /// Temporarily ignore read requests (for testing purposes) + /// + /// true to ignore read requests, false to handle read requests. + public void IgnoreReadAccess(bool ignore) + { + IedServer_ignoreReadAccess(connection, ignore); + } + /// /// Read the values of a data set (GetDataSetValues service). /// @@ -2981,6 +3005,19 @@ namespace IEC61850 IED_STATE_CLOSING = 3 } + public static class IedClientErrorExtension + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedClientError_toString(int err); + + public static string ToString(this IedClientError err) + { + string stringVal = Marshal.PtrToStringAnsi(IedClientError_toString((int)err)); + + return stringVal; + } + } + /// /// Error codes for client side functions /// @@ -3066,6 +3103,9 @@ namespace IEC61850 /** Received an invalid response message from the server */ IED_ERROR_MALFORMED_MESSAGE = 34, + /** Service was not executed because required resource is still in use */ + IED_ERROR_OBJECT_CONSTRAINT_CONFLICT = 35, + /** Service not implemented */ IED_ERROR_SERVICE_NOT_IMPLEMENTED = 98, From 790e3e6714919f4902733082ccfc32fbff71b187 Mon Sep 17 00:00:00 2001 From: Federico Francescon Date: Thu, 9 May 2024 17:10:35 +0200 Subject: [PATCH 37/53] fix: ssl renegotiation causing handshake failure (#494) * feat: added semaphore around `TLSSocket_performHandshake` * fix: improved error checking in TLS read and write * removed useless semaphore for renegotiation lock * added some tls debug and cleared the session renegotiation events * using mbedtls API instead of using internals * fixed deadlock situation with TLSSocket_read * test fix sonarcloud minor notice * still some sonarcloud minor things --------- Co-authored-by: Federico Francescon --- hal/tls/mbedtls/tls_mbedtls.c | 93 +++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/hal/tls/mbedtls/tls_mbedtls.c b/hal/tls/mbedtls/tls_mbedtls.c index e2f152d0..06b98234 100644 --- a/hal/tls/mbedtls/tls_mbedtls.c +++ b/hal/tls/mbedtls/tls_mbedtls.c @@ -37,9 +37,9 @@ #endif #if (CONFIG_DEBUG_TLS == 1) -#define DEBUG_PRINT(appId, fmt, ...) fprintf(stderr, "%s: " fmt, appId, ## __VA_ARGS__); +#define DEBUG_PRINT(appId, fmt, ...) fprintf(stderr, "%s: " fmt, appId, ## __VA_ARGS__) #else -#define DEBUG_PRINT(fmt, ...) {do {} while(0);} +#define DEBUG_PRINT(fmt, ...) do {} while(0) #endif @@ -265,7 +265,7 @@ TLSConfiguration_create() mbedtls_ssl_conf_authmode(&(self->conf), MBEDTLS_SSL_VERIFY_REQUIRED); - mbedtls_ssl_conf_renegotiation(&(self->conf), MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION); + mbedtls_ssl_conf_renegotiation(&(self->conf), MBEDTLS_SSL_RENEGOTIATION_ENABLED); /* static int hashes[] = {3,4,5,6,7,8,0}; */ /* mbedtls_ssl_conf_sig_hashes(&(self->conf), hashes); */ @@ -297,7 +297,7 @@ TLSConfiguration_create() void TLSConfiguration_setClientMode(TLSConfiguration self) { - self->conf.endpoint = MBEDTLS_SSL_IS_CLIENT; + mbedtls_ssl_conf_endpoint(&(self->conf), MBEDTLS_SSL_IS_CLIENT); } void @@ -852,20 +852,26 @@ TLSSocket_performHandshake(TLSSocket self) { int ret = mbedtls_ssl_renegotiate(&(self->ssl)); - if (ret == 0) { + if (ret == 0 || ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE || + ret == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS || ret == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) { if (getTLSVersion(self->ssl.major_ver, self->ssl.minor_ver) < TLS_VERSION_TLS_1_2) { raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_WARNING, TLS_EVENT_CODE_WRN_INSECURE_TLS_VERSION, "Warning: Insecure TLS version", self); } + + DEBUG_PRINT("TLS", "TLSSocket_performHandshake Success -> ret=%i\n", ret); + raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INFO, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "TLS session renegotiation completed", self); return true; } else { DEBUG_PRINT("TLS", "TLSSocket_performHandshake failed -> ret=%i\n", ret); - if (self->tlsConfig->eventHandler) { - uint32_t flags = mbedtls_ssl_get_verify_result(&(self->ssl)); + raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_WARNING, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "Alarm: TLS session renegotiation failed", self); - createSecurityEvents(self->tlsConfig, ret, flags, self); + /* mbedtls_ssl_renegotiate mandates to reset the ssl session in case of errors */ + ret = mbedtls_ssl_session_reset(&(self->ssl)); + if (ret != 0) { + DEBUG_PRINT("TLS", "mbedtls_ssl_session_reset failed -> ret: -0x%X\n", -ret); } return false; @@ -898,16 +904,10 @@ startRenegotiationIfRequired(TLSSocket self) if (Hal_getTimeInMs() <= self->lastRenegotiationTime + self->tlsConfig->renegotiationTimeInMs) return true; - raiseSecurityEvent(self->tlsConfig, TLS_SEC_EVT_INFO, TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION, "Info: session renegotiation started", self); - - if (TLSSocket_performHandshake(self) == false) { - DEBUG_PRINT("TLS", " renegotiation failed\n"); + if (TLSSocket_performHandshake(self) == false) return false; - } - DEBUG_PRINT("TLS", " started renegotiation\n"); self->lastRenegotiationTime = Hal_getTimeInMs(); - return true; } @@ -920,22 +920,30 @@ TLSSocket_read(TLSSocket self, uint8_t* buf, int size) return -1; } - int ret = mbedtls_ssl_read(&(self->ssl), buf, size); - - if ((ret == MBEDTLS_ERR_SSL_WANT_READ) || (ret == MBEDTLS_ERR_SSL_WANT_WRITE)) - return 0; + int len = 0; + while (len < size) { + int ret = mbedtls_ssl_read(&(self->ssl), (buf + len), (size - len)); + if (ret > 0) { + len += ret; + continue; + } - if (ret < 0) { + switch (ret) { + case 0: // falling through + case MBEDTLS_ERR_SSL_WANT_READ: + case MBEDTLS_ERR_SSL_WANT_WRITE: + case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS: + case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS: + // Known "good" cases indicating the read is done + return len; - switch (ret) - { case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: DEBUG_PRINT("TLS", " connection was closed gracefully\n"); - return -1; + break; case MBEDTLS_ERR_NET_CONN_RESET: DEBUG_PRINT("TLS", " connection was reset by peer\n"); - return -1; + break; default: DEBUG_PRINT("TLS", " mbedtls_ssl_read returned -0x%x\n", -ret); @@ -945,19 +953,23 @@ TLSSocket_read(TLSSocket self, uint8_t* buf, int size) createSecurityEvents(self->tlsConfig, ret, flags, self); } + } - return -1; + int reset_err = mbedtls_ssl_session_reset(&(self->ssl)); + if (0 != reset_err) { + DEBUG_PRINT("TLS", "mbedtls_ssl_session_reset failed -0x%X\n", -reset_err); } + + return ret; } - return ret; + return len; } int TLSSocket_write(TLSSocket self, uint8_t* buf, int size) { - int ret; - int len = size; + int len = 0; checkForCRLUpdate(self); @@ -965,22 +977,26 @@ TLSSocket_write(TLSSocket self, uint8_t* buf, int size) return -1; } - while ((ret = mbedtls_ssl_write(&(self->ssl), buf, len)) <= 0) + while (len < size) { - if (ret == MBEDTLS_ERR_NET_CONN_RESET) - { - DEBUG_PRINT("TLS", "peer closed the connection\n"); - return -1; + int ret = mbedtls_ssl_write(&(self->ssl), (buf + len), (size -len)); + if ((ret == MBEDTLS_ERR_SSL_WANT_READ) || (ret == MBEDTLS_ERR_SSL_WANT_WRITE) || + (ret == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS) || (ret == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS)) { + continue; } - if ((ret != MBEDTLS_ERR_SSL_WANT_READ) && (ret != MBEDTLS_ERR_SSL_WANT_WRITE)) - { - DEBUG_PRINT("TLS", "mbedtls_ssl_write returned %d\n", ret); + if (ret < 0) { + DEBUG_PRINT("TLS", "mbedtls_ssl_write returned -0x%X\n", -ret); + + if (0 != (ret = mbedtls_ssl_session_reset(&(self->ssl)))) { + DEBUG_PRINT("TLS", "mbedtls_ssl_session_reset failed -0x%X\n", -ret); + } + return -1; } - } - len = ret; + len += ret; + } return len; } @@ -1008,6 +1024,7 @@ TLSSocket_close(TLSSocket self) if (self->peerCert) GLOBAL_FREEMEM(self->peerCert); + GLOBAL_FREEMEM(self); } From 69b7b28e8406723a03df6c57500a9e699d2823c3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 17 May 2024 16:50:49 +0100 Subject: [PATCH 38/53] - HAL socket: added missing initialization of address structure --- hal/socket/linux/socket_linux.c | 1 + hal/socket/win32/socket_win32.c | 1 + 2 files changed, 2 insertions(+) diff --git a/hal/socket/linux/socket_linux.c b/hal/socket/linux/socket_linux.c index c188c24f..fffc72ae 100644 --- a/hal/socket/linux/socket_linux.c +++ b/hal/socket/linux/socket_linux.c @@ -816,6 +816,7 @@ int UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize) { struct sockaddr_storage remoteAddress; + memset(&remoteAddress, 0, sizeof(struct sockaddr_storage)); socklen_t structSize = sizeof(struct sockaddr_storage); int result = recvfrom(self->fd, msg, msgSize, MSG_DONTWAIT, (struct sockaddr*)&remoteAddress, &structSize); diff --git a/hal/socket/win32/socket_win32.c b/hal/socket/win32/socket_win32.c index 65be99d7..5b8c448f 100644 --- a/hal/socket/win32/socket_win32.c +++ b/hal/socket/win32/socket_win32.c @@ -749,6 +749,7 @@ int UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize) { struct sockaddr_storage remoteAddress; + memset(&remoteAddress, 0, sizeof(struct sockaddr_storage)); socklen_t structSize = sizeof(struct sockaddr_storage); int result = recvfrom(self->fd, (char*) msg, msgSize, 0, (struct sockaddr*)&remoteAddress, &structSize); From 58939c3209462ff829ea74639ffa22533c298b2f Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 20 May 2024 11:21:53 +0100 Subject: [PATCH 39/53] - code format change --- src/mms/iso_client/iso_client_connection.c | 239 +++++++++++---------- 1 file changed, 129 insertions(+), 110 deletions(-) diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c index 4f37176b..ee2202a3 100644 --- a/src/mms/iso_client/iso_client_connection.c +++ b/src/mms/iso_client/iso_client_connection.c @@ -3,7 +3,7 @@ * * Client side representation of the ISO stack (COTP, session, presentation, ACSE) * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -146,7 +146,8 @@ IsoClientConnection_create(IsoConnectionParameters parameters, IsoIndicationCall { IsoClientConnection self = (IsoClientConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoClientConnection)); - if (self) { + if (self) + { self->parameters = parameters; self->callback = callback; self->callbackParameter = callbackParameter; @@ -196,7 +197,8 @@ sendConnectionRequestMessage(IsoClientConnection self) int socketExtensionBufferSize = CONFIG_MMS_MAXIMUM_PDU_SIZE + 1000; uint8_t* socketExtensionBuffer = NULL; - if (self->cotpConnection) { + if (self->cotpConnection) + { /* Destroy existing handle set when connection is reused */ if (self->cotpConnection->handleSet) Handleset_destroy(self->cotpConnection->handleSet); @@ -205,19 +207,20 @@ sendConnectionRequestMessage(IsoClientConnection self) socketExtensionBuffer = self->cotpConnection->socketExtensionBuffer; } - if (socketExtensionBuffer == NULL) { + if (socketExtensionBuffer == NULL) + { socketExtensionBuffer = (uint8_t*)GLOBAL_MALLOC(socketExtensionBufferSize); } - if (socketExtensionBuffer) { - + if (socketExtensionBuffer) + { /* COTP (ISO transport) handshake */ CotpConnection_init(self->cotpConnection, self->socket, self->receiveBuffer, self->cotpReadBuffer, self->cotpWriteBuffer, socketExtensionBuffer, socketExtensionBufferSize); #if (CONFIG_MMS_SUPPORT_TLS == 1) - if (self->parameters->tlsConfiguration) { - + if (self->parameters->tlsConfiguration) + { TLSConfiguration_setClientMode(self->parameters->tlsConfiguration); /* create TLSSocket and start TLS authentication */ @@ -225,8 +228,8 @@ sendConnectionRequestMessage(IsoClientConnection self) if (tlsSocket) self->cotpConnection->tlsSocket = tlsSocket; - else { - + else + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: TLS handshake failed!\n"); @@ -244,7 +247,8 @@ sendConnectionRequestMessage(IsoClientConnection self) else return true; } - else { + else + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Failed to allocate socket extension buffer\n"); @@ -298,14 +302,14 @@ sendAcseInitiateRequest(IsoClientConnection self) Semaphore_post(self->transmitBufferMutex); } - static void releaseSocket(IsoClientConnection self) { - if (self->socket) { - + if (self->socket) + { #if (CONFIG_MMS_SUPPORT_TLS == 1) - if (self->cotpConnection->tlsSocket) { + if (self->cotpConnection->tlsSocket) + { TLSSocket_close(self->cotpConnection->tlsSocket); self->cotpConnection->tlsSocket = NULL; } @@ -345,29 +349,34 @@ IsoClientConnection_handleConnection(IsoClientConnection self) { SocketState socketState = Socket_checkAsyncConnectState(self->socket); - if (socketState == SOCKET_STATE_CONNECTED) { - if (sendConnectionRequestMessage(self)) { + if (socketState == SOCKET_STATE_CONNECTED) + { + if (sendConnectionRequestMessage(self)) + { self->nextReadTimeout = Hal_getTimeInMs() + self->readTimeoutInMs; nextState = INT_STATE_WAIT_FOR_COTP_CONNECT_RESP; } - else { + else + { IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } } - else if (socketState == SOCKET_STATE_FAILED) { + else if (socketState == SOCKET_STATE_FAILED) + { IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } - else { - + else + { /* check connect timeout */ uint64_t currentTime = Hal_getTimeInMs(); - if (currentTime > self->nextReadTimeout) { + if (currentTime > self->nextReadTimeout) + { IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; @@ -385,8 +394,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self) { uint64_t currentTime = Hal_getTimeInMs(); - if (currentTime > self->nextReadTimeout) { - + if (currentTime > self->nextReadTimeout) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Timeout waiting for COTP CR\n"); @@ -396,15 +405,16 @@ IsoClientConnection_handleConnection(IsoClientConnection self) nextState = INT_STATE_CLOSE_ON_ERROR; } - else { - + else + { TpktState packetState = CotpConnection_readToTpktBuffer(self->cotpConnection); - if (packetState == TPKT_PACKET_COMPLETE) { - + if (packetState == TPKT_PACKET_COMPLETE) + { CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); - if (cotpIndication != COTP_CONNECT_INDICATION) { + if (cotpIndication != COTP_CONNECT_INDICATION) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Unexpected COTP state (%i)\n", cotpIndication); @@ -414,7 +424,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self) nextState = INT_STATE_CLOSE_ON_ERROR; } - else { + else + { sendAcseInitiateRequest(self); self->nextReadTimeout = Hal_getTimeInMs() + self->readTimeoutInMs; @@ -422,7 +433,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self) nextState = INT_STATE_WAIT_FOR_ACSE_RESP; } } - else if (packetState == TPKT_ERROR) { + else if (packetState == TPKT_ERROR) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Error receiving COTP message\n"); @@ -435,7 +447,6 @@ IsoClientConnection_handleConnection(IsoClientConnection self) else { waits = true; } - } } break; @@ -444,8 +455,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self) { uint64_t currentTime = Hal_getTimeInMs(); - if (currentTime > self->nextReadTimeout) { - + if (currentTime > self->nextReadTimeout) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Timeout waiting for ACSE initiate response\n"); @@ -453,15 +464,16 @@ IsoClientConnection_handleConnection(IsoClientConnection self) nextState = INT_STATE_CLOSE_ON_ERROR; } - else { - + else + { TpktState packetState = CotpConnection_readToTpktBuffer(self->cotpConnection); - if (packetState == TPKT_PACKET_COMPLETE) { - + if (packetState == TPKT_PACKET_COMPLETE) + { CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); - if (cotpIndication != COTP_DATA_INDICATION) { + if (cotpIndication != COTP_DATA_INDICATION) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Unexpected COTP state (%i)\n", cotpIndication); @@ -469,67 +481,71 @@ IsoClientConnection_handleConnection(IsoClientConnection self) nextState = INT_STATE_CLOSE_ON_ERROR; } - else { - + else + { /* parse ACSE response */ - IsoSessionIndication sessionIndication; - - sessionIndication = - IsoSession_parseMessage(self->session, CotpConnection_getPayload(self->cotpConnection)); + IsoSessionIndication sessionIndication; - if (sessionIndication != SESSION_CONNECT) { - if (DEBUG_ISO_CLIENT) - printf("ISO_CLIENT: IsoClientConnection_associate: no session connect indication\n"); + sessionIndication = + IsoSession_parseMessage(self->session, CotpConnection_getPayload(self->cotpConnection)); - self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); - - nextState = INT_STATE_CLOSE_ON_ERROR; - } - else { - - if (IsoPresentation_parseAcceptMessage(self->presentation, IsoSession_getUserData(self->session)) == false) { - - if (DEBUG_ISO_CLIENT) - printf("ISO_CLIENT: IsoClientConnection_associate: no presentation ok indication\n"); - - self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); - - nextState = INT_STATE_CLOSE_ON_ERROR; - } - else { - - AcseIndication acseIndication = AcseConnection_parseMessage(&(self->acseConnection), &self->presentation->nextPayload); - - if (acseIndication != ACSE_ASSOCIATE) { - if (DEBUG_ISO_CLIENT) - printf("ISO_CLIENT: IsoClientConnection_associate: no ACSE_ASSOCIATE indication\n"); - - self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); - - nextState = INT_STATE_CLOSE_ON_ERROR; - } - else { - - ByteBuffer_wrap(self->receivePayloadBuffer, self->acseConnection.userDataBuffer, - self->acseConnection.userDataBufferSize, self->acseConnection.userDataBufferSize); + if (sessionIndication != SESSION_CONNECT) + { + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: IsoClientConnection_associate: no session connect indication\n"); - setState(self, STATE_CONNECTED); - nextState = INT_STATE_WAIT_FOR_DATA_MSG; + self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); - if (self->callback(ISO_IND_ASSOCIATION_SUCCESS, self->callbackParameter, self->receivePayloadBuffer) == false) { - nextState = INT_STATE_CLOSE_ON_ERROR; - } + nextState = INT_STATE_CLOSE_ON_ERROR; + } + else + { + if (IsoPresentation_parseAcceptMessage(self->presentation, IsoSession_getUserData(self->session)) == false) + { + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: no presentation accept indication\n"); - } + self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); - } + nextState = INT_STATE_CLOSE_ON_ERROR; + } + else + { + AcseIndication acseIndication = AcseConnection_parseMessage(&(self->acseConnection), &self->presentation->nextPayload); + + if (acseIndication != ACSE_ASSOCIATE) + { + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: no ACSE_ASSOCIATE indication\n"); + + self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); + + nextState = INT_STATE_CLOSE_ON_ERROR; + } + else + { + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: ACSE AARE - association accepted\n"); + + ByteBuffer_wrap(self->receivePayloadBuffer, self->acseConnection.userDataBuffer, + self->acseConnection.userDataBufferSize, self->acseConnection.userDataBufferSize); + + setState(self, STATE_CONNECTED); + nextState = INT_STATE_WAIT_FOR_DATA_MSG; + + if (self->callback(ISO_IND_ASSOCIATION_SUCCESS, self->callbackParameter, self->receivePayloadBuffer) == false) { + nextState = INT_STATE_CLOSE_ON_ERROR; + } + } + } - CotpConnection_resetPayload(self->cotpConnection); - } + CotpConnection_resetPayload(self->cotpConnection); + } } } - else if (packetState == TPKT_ERROR) { + else if (packetState == TPKT_ERROR) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Error receiving COTP message\n"); @@ -540,7 +556,6 @@ IsoClientConnection_handleConnection(IsoClientConnection self) else { waits = true; } - } } break; @@ -552,8 +567,8 @@ IsoClientConnection_handleConnection(IsoClientConnection self) if (packetState == TPKT_ERROR) { nextState = INT_STATE_CLOSE_ON_ERROR; } - else if (packetState == TPKT_PACKET_COMPLETE) { - + else if (packetState == TPKT_PACKET_COMPLETE) + { CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); switch (cotpIndication) { @@ -576,23 +591,24 @@ IsoClientConnection_handleConnection(IsoClientConnection self) IsoSession_parseMessage(self->session, CotpConnection_getPayload(self->cotpConnection)); - if (sessionIndication != SESSION_DATA) { + if (sessionIndication != SESSION_DATA) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT_CONNECTION: Invalid session message\n"); nextState = INT_STATE_CLOSE_ON_ERROR; } - else { - - if (!IsoPresentation_parseUserData(self->presentation, IsoSession_getUserData(self->session))) { - + else + { + if (!IsoPresentation_parseUserData(self->presentation, IsoSession_getUserData(self->session))) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT_CONNECTION: Invalid presentation message\n"); nextState = INT_STATE_CLOSE_ON_ERROR; } - else { - + else + { self->callback(ISO_IND_DATA, self->callbackParameter, &(self->presentation->nextPayload)); @@ -659,7 +675,6 @@ IsoClientConnection_handleConnection(IsoClientConnection self) return waits; } - bool IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTimeoutInMs, uint32_t readTimeoutInMs) { @@ -669,7 +684,8 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim self->socket = TcpSocket_create(); - if (self->socket == NULL) { + if (self->socket == NULL) + { Semaphore_post(self->tickMutex); return false; } @@ -697,8 +713,8 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim Socket_bind(self->socket, self->parameters->localIpAddress, self->parameters->localTcpPort); } - if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) { - + if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) + { Socket_destroy(self->socket); self->socket = NULL; @@ -718,7 +734,8 @@ IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTim void IsoClientConnection_sendMessage(IsoClientConnection self, ByteBuffer* payloadBuffer) { - if (getState(self) == STATE_CONNECTED) { + if (getState(self) == STATE_CONNECTED) + { struct sBufferChain payloadBCMemory; BufferChain payload = &payloadBCMemory; @@ -743,7 +760,8 @@ IsoClientConnection_sendMessage(IsoClientConnection self, ByteBuffer* payloadBuf if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: IsoClientConnection_sendMessage: send message failed!\n"); } - else { + else + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Not connected --> cannot send message\n"); } @@ -762,7 +780,8 @@ IsoClientConnection_close(IsoClientConnection self) eIsoClientInternalState intState = getIntState(self); - if ((intState != INT_STATE_IDLE) && (intState != INT_STATE_ERROR) && (intState != INT_STATE_CLOSE_ON_ERROR)) { + if ((intState != INT_STATE_IDLE) && (intState != INT_STATE_ERROR) && (intState != INT_STATE_CLOSE_ON_ERROR)) + { setIntState(self, INT_STATE_CLOSING_CONNECTION); Semaphore_post(self->tickMutex); @@ -783,8 +802,8 @@ IsoClientConnection_destroy(IsoClientConnection self) int state = getState(self); - if (state == STATE_CONNECTED) { - + if (state == STATE_CONNECTED) + { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: call IsoClientConnection_close\n"); @@ -798,7 +817,8 @@ IsoClientConnection_destroy(IsoClientConnection self) if (self->receiveBuffer != NULL) GLOBAL_FREEMEM(self->receiveBuffer); - if (self->cotpConnection != NULL) { + if (self->cotpConnection != NULL) + { if (self->cotpConnection->handleSet != NULL) Handleset_destroy(self->cotpConnection->handleSet); @@ -910,7 +930,6 @@ IsoClientConnection_release(IsoClientConnection self) Semaphore_post(self->transmitBufferMutex); } - ByteBuffer* IsoClientConnection_allocateTransmitBuffer(IsoClientConnection self) { From 98d6be43546a152ab17da6ec6fc8631487685ee3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 31 May 2024 17:52:34 +0100 Subject: [PATCH 40/53] - MMS client: fixed - getNameList task can get stuck in while loop when message cannot be sent (LIB61850-347) --- .../iso_mms/client/mms_client_connection.c | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 0ba12801..9f983b60 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1975,7 +1975,8 @@ mmsClient_getNameListSingleRequestAsync( void* parameter, LinkedList nameList) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -1991,8 +1992,8 @@ mmsClient_getNameListSingleRequestAsync( if (associationSpecific) mmsClient_createMmsGetNameListRequestAssociationSpecific(invokeId, payload, continueAfter); - else { - + else + { if (objectClass == MMS_OBJECT_CLASS_DOMAIN) mmsClient_createMmsGetNameListRequestVMDspecific(invokeId, payload, continueAfter); @@ -2036,7 +2037,6 @@ getNameListHandler(uint32_t invokeId, void* parameter, MmsError mmsError, Linked Semaphore_post(parameters->sem); } - static LinkedList /* */ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, const char* domainId, @@ -2059,7 +2059,8 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, mmsClient_getNameListSingleRequestAsync(self, NULL, &err, domainId, objectClass, associationSpecific, NULL, getNameListHandler, ¶meter, NULL); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); err = parameter.err; list = parameter.nameList; @@ -2068,7 +2069,8 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, Semaphore_destroy(parameter.sem); - while (moreFollows) { + while (moreFollows) + { parameter.sem = Semaphore_create(1); char* continueAfter = NULL; @@ -2081,12 +2083,18 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, mmsClient_getNameListSingleRequestAsync(self, NULL, &err, domainId, objectClass, associationSpecific, continueAfter, getNameListHandler, ¶meter, list); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); err = parameter.err; list = parameter.nameList; moreFollows = parameter.moreFollows; } + else + { + /* exit look when message cannot be sent */ + moreFollows = false; + } Semaphore_destroy(parameter.sem); } @@ -2094,8 +2102,10 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, if (mmsError) *mmsError = err; - if (err != MMS_ERROR_NONE) { - if (list) { + if (err != MMS_ERROR_NONE) + { + if (list) + { LinkedList_destroy(list); list = NULL; } @@ -2188,7 +2198,6 @@ MmsConnection_getVariableListNamesAssociationSpecificAsync(MmsConnection self, u continueAfter, handler, parameter, NULL); } - struct readNVParameters { Semaphore sem; From 6dc6431be1ae670ffcdb124f02a80348b547c0d4 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 31 May 2024 18:17:52 +0100 Subject: [PATCH 41/53] - code format update --- .../iso_mms/client/mms_client_connection.c | 832 +++++++++++------- 1 file changed, 523 insertions(+), 309 deletions(-) diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 9f983b60..3b5ed25a 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -65,7 +65,8 @@ getConnectionState(MmsConnection self) static void handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) { - if (self->reportHandler != NULL) { + if (self->reportHandler) + { MmsPdu_t* mmsPdu = NULL; /* allow asn1c to allocate structure */ if (DEBUG_MMS_CLIENT) @@ -74,12 +75,13 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu, (void**) &mmsPdu, ByteBuffer_getBuffer(message), ByteBuffer_getSize(message)); - if (rval.code == RC_OK) { + if (rval.code == RC_OK) + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received report (size:%i)\n", (int) rval.consumed); - if (mmsPdu->present == MmsPdu_PR_unconfirmedPDU) { - + if (mmsPdu->present == MmsPdu_PR_unconfirmedPDU) + { if (mmsPdu->choice.unconfirmedPDU.unconfirmedService.present == UnconfirmedService_PR_informationReport) { @@ -121,7 +123,8 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) int listSize = report->listOfAccessResult.list.count; int variableSpecSize = report->variableAccessSpecification.choice.listOfVariable.list.count; - if (listSize != variableSpecSize) { + if (listSize != variableSpecSize) + { if (DEBUG_MMS_CLIENT) printf("report contains wrong number of access results\n"); return; @@ -131,7 +134,8 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) report->listOfAccessResult.list.array, listSize, false); int i; - for (i = 0; i < variableSpecSize; i++) { + for (i = 0; i < variableSpecSize; i++) + { if (report->variableAccessSpecification.choice.listOfVariable.list.array[i]->variableSpecification.present == VariableSpecification_PR_name) { @@ -146,7 +150,8 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) report->variableAccessSpecification.choice.listOfVariable.list.array[i] ->variableSpecification.choice.name.choice.vmdspecific.buf; - if (nameSize < 129) { + if (nameSize < 129) + { char variableListName[129]; memcpy(variableListName, buffer, nameSize); variableListName[nameSize] = 0; @@ -167,8 +172,8 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) } } else if (report->variableAccessSpecification.choice.listOfVariable.list.array[i] - ->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) { - + ->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) + { int domainNameSize = report->variableAccessSpecification.choice.listOfVariable.list.array[i] ->variableSpecification.choice.name.choice.domainspecific.domainId.size; @@ -177,7 +182,8 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) report->variableAccessSpecification.choice.listOfVariable.list.array[i] ->variableSpecification.choice.name.choice.domainspecific.itemId.size; - if ((domainNameSize < 65) && (itemNameSize < 65)) { + if ((domainNameSize < 65) && (itemNameSize < 65)) + { char domainNameStr[65]; char itemNameStr[65]; @@ -216,7 +222,8 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) if (values != NULL) MmsValue_delete(values); } - else { + else + { /* Ignore */ if (DEBUG_MMS_CLIENT) printf("unrecognized information report\n"); @@ -226,7 +233,8 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) } } - else { + else + { if (DEBUG_MMS_CLIENT) printf("handleUnconfirmedMmsPdu: error parsing PDU at %u\n", (uint32_t) rval.consumed); } @@ -259,9 +267,12 @@ checkForOutstandingCall(MmsConnection self, uint32_t invokeId) Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) { - if (self->outstandingCalls[i].isUsed) { - if (self->outstandingCalls[i].invokeId == invokeId) { + for (i = 0; i < OUTSTANDING_CALLS; i++) + { + if (self->outstandingCalls[i].isUsed) + { + if (self->outstandingCalls[i].invokeId == invokeId) + { Semaphore_post(self->outstandingCallsLock); return &(self->outstandingCalls[i]); } @@ -280,8 +291,10 @@ addToOutstandingCalls(MmsConnection self, uint32_t invokeId, eMmsOutstandingCall Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) { - if (self->outstandingCalls[i].isUsed == false) { + for (i = 0; i < OUTSTANDING_CALLS; i++) + { + if (self->outstandingCalls[i].isUsed == false) + { self->outstandingCalls[i].isUsed = true; self->outstandingCalls[i].invokeId = invokeId; self->outstandingCalls[i].timeout = Hal_getTimeInMs() + self->requestTimeout; @@ -306,9 +319,12 @@ removeFromOutstandingCalls(MmsConnection self, uint32_t invokeId) Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) { - if (self->outstandingCalls[i].isUsed) { - if (self->outstandingCalls[i].invokeId == invokeId) { + for (i = 0; i < OUTSTANDING_CALLS; i++) + { + if (self->outstandingCalls[i].isUsed) + { + if (self->outstandingCalls[i].invokeId == invokeId) + { self->outstandingCalls[i].isUsed = false; break; } @@ -325,16 +341,18 @@ mmsClient_getMatchingObtainFileRequest(MmsConnection self, const char* filename) Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) { - if (self->outstandingCalls[i].isUsed) { - - if (self->outstandingCalls[i].type == MMS_CALL_TYPE_OBTAIN_FILE) { - + for (i = 0; i < OUTSTANDING_CALLS; i++) + { + if (self->outstandingCalls[i].isUsed) + { + if (self->outstandingCalls[i].type == MMS_CALL_TYPE_OBTAIN_FILE) + { char* storedFilename = (char*) self->outstandingCalls[i].internalParameter.ptr; - if (storedFilename) { - - if (!strcmp(filename, storedFilename)) { + if (storedFilename) + { + if (!strcmp(filename, storedFilename)) + { Semaphore_post(self->outstandingCallsLock); return &(self->outstandingCalls[i]); } @@ -352,7 +370,8 @@ static void sendMessage(MmsConnection self, ByteBuffer* message) { #if (CONFIG_MMS_RAW_MESSAGE_LOGGING == 1) - if (self->rawMmsMessageHandler != NULL) { + if (self->rawMmsMessageHandler != NULL) + { MmsRawMessageHandler handler = (MmsRawMessageHandler) self->rawMmsMessageHandler; handler(self->rawMmsMessageHandlerParameter, message->buffer, message->size, false); } @@ -365,8 +384,8 @@ static MmsError sendAsyncRequest(MmsConnection self, uint32_t invokeId, ByteBuffer* message, eMmsOutstandingCallType type, void* userCallback, void* userParameter, MmsClientInternalParameter internalParameter) { - if (addToOutstandingCalls(self, invokeId, type, userCallback, userParameter, internalParameter) == false) { - + if (addToOutstandingCalls(self, invokeId, type, userCallback, userParameter, internalParameter) == false) + { /* message cannot be sent - release resources */ IsoClientConnection_releaseTransmitBuffer(self->isoClient); @@ -531,7 +550,8 @@ parseServiceError(uint8_t* buffer, int bufPos, int maxLength, MmsServiceError* e int endPos = bufPos + maxLength; int length; - while (bufPos < endPos) { + while (bufPos < endPos) + { uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, endPos); @@ -592,7 +612,8 @@ mmsMsg_parseConfirmedErrorPDU(uint8_t* buffer, int bufPos, int maxBufPos, uint32 int endPos = bufPos + length; - while (bufPos < endPos) { + while (bufPos < endPos) + { tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); @@ -653,7 +674,8 @@ mmsMsg_parseRejectPDU(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t* invo int endPos = bufPos + length; - while (bufPos < endPos) { + while (bufPos < endPos) + { tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); @@ -661,13 +683,16 @@ mmsMsg_parseRejectPDU(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t* invo if (bufPos < 0) goto exit_error; - if (tag == 0x80) { /* invoke id */ + if (tag == 0x80) + { + /* invoke id */ if (hasInvokeId) *hasInvokeId = true; if (invokeId != NULL) *invokeId = BerDecoder_decodeUint32(buffer, length, bufPos); } - else if (tag > 0x80 && tag < 0x8c) { + else if (tag > 0x80 && tag < 0x8c) + { *rejectType = tag - 0x80; *rejectReason = BerDecoder_decodeInt32(buffer, length, bufPos); } @@ -690,15 +715,19 @@ exit_error: static void handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, MmsOutstandingCall outstandingCall, MmsError err) { - if (outstandingCall->type == MMS_CALL_TYPE_READ_VARIABLE) { - + if (outstandingCall->type == MMS_CALL_TYPE_READ_VARIABLE) + { MmsConnection_ReadVariableHandler handler = (MmsConnection_ReadVariableHandler) outstandingCall->userCallback; if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL); - else { - if (response) { + } + else + { + if (response) + { MmsValue* value = mmsClient_parseReadResponse(response, NULL, false); if (value == NULL) @@ -709,15 +738,19 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } } - else if (outstandingCall->type == MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES) { - + else if (outstandingCall->type == MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES) + { MmsConnection_ReadVariableHandler handler = (MmsConnection_ReadVariableHandler) outstandingCall->userCallback; if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL); - else { - if (response) { + } + else + { + if (response) + { MmsValue* value = mmsClient_parseReadResponse(response, NULL, true); if (value == NULL) @@ -725,19 +758,21 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M handler(outstandingCall->invokeId, outstandingCall->userParameter, err, value); } - } } - else if (outstandingCall->type == MMS_CALL_TYPE_WRITE_VARIABLE) { - + else if (outstandingCall->type == MMS_CALL_TYPE_WRITE_VARIABLE) + { MmsConnection_WriteVariableHandler handler = (MmsConnection_WriteVariableHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, DATA_ACCESS_ERROR_NO_RESPONSE); } - else { - if (response) { + else + { + if (response) + { MmsDataAccessError daError = mmsClient_parseWriteResponse(response, bufPos, &err); handler(outstandingCall->invokeId, outstandingCall->userParameter, err, daError); @@ -745,16 +780,19 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } } - else if (outstandingCall->type == MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES) { - + else if (outstandingCall->type == MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES) + { MmsConnection_WriteMultipleVariablesHandler handler = (MmsConnection_WriteMultipleVariablesHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL); } - else { - if (response) { + else + { + if (response) + { LinkedList accessResults = NULL; mmsClient_parseWriteMultipleItemsResponse(response, bufPos, &err, -1, &accessResults); @@ -763,15 +801,19 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } } } - else if (outstandingCall->type == MMS_CALL_TYPE_READ_NVL_DIRECTORY) { + else if (outstandingCall->type == MMS_CALL_TYPE_READ_NVL_DIRECTORY) + { MmsConnection_ReadNVLDirectoryHandler handler = (MmsConnection_ReadNVLDirectoryHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL, false); } - else { - if (response) { + else + { + if (response) + { bool deletable = false; LinkedList accessSpec = mmsClient_parseGetNamedVariableListAttributesResponse(response, &deletable); @@ -783,15 +825,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } } } - else if (outstandingCall->type == MMS_CALL_TYPE_DEFINE_NVL) { - + else if (outstandingCall->type == MMS_CALL_TYPE_DEFINE_NVL) + { MmsConnection_GenericServiceHandler handler = (MmsConnection_GenericServiceHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, false); } - else { + else + { bool success = false; if (!mmsClient_parseDefineNamedVariableResponse(response, NULL)) @@ -802,15 +846,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M handler(outstandingCall->invokeId, outstandingCall->userParameter, err, success); } } - else if (outstandingCall->type == MMS_CALL_TYPE_DELETE_NVL) { - + else if (outstandingCall->type == MMS_CALL_TYPE_DELETE_NVL) + { MmsConnection_GenericServiceHandler handler = (MmsConnection_GenericServiceHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, false); } - else { + else + { bool success = false; long numberMatched = 0; @@ -820,8 +866,11 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M success = true; if (numberMatched == 0) + { err = MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT; - else { + } + else + { if (numberDeleted == 0) err = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; } @@ -829,14 +878,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M handler(outstandingCall->invokeId, outstandingCall->userParameter, err, success); } } - else if (outstandingCall->type == MMS_CALL_TYPE_GET_VAR_ACCESS_ATTR) { + else if (outstandingCall->type == MMS_CALL_TYPE_GET_VAR_ACCESS_ATTR) + { MmsConnection_GetVariableAccessAttributesHandler handler = (MmsConnection_GetVariableAccessAttributesHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL); } - else { + else + { MmsVariableSpecification* typeSpec = mmsClient_parseGetVariableAccessAttributesResponse(response, NULL); if (typeSpec == NULL) @@ -845,14 +897,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M handler(outstandingCall->invokeId, outstandingCall->userParameter, err, typeSpec); } } - else if (outstandingCall->type == MMS_CALL_TYPE_GET_SERVER_STATUS) { + else if (outstandingCall->type == MMS_CALL_TYPE_GET_SERVER_STATUS) + { MmsConnection_GetServerStatusHandler handler = (MmsConnection_GetServerStatusHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, 0, 0); } - else { + else + { int vmdLogicalStatus; int vmdPhysicalStatus; @@ -862,15 +917,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M handler(outstandingCall->invokeId, outstandingCall->userParameter, err, vmdLogicalStatus, vmdPhysicalStatus); } } - else if (outstandingCall->type == MMS_CALL_TYPE_IDENTIFY) { + else if (outstandingCall->type == MMS_CALL_TYPE_IDENTIFY) + { MmsConnection_IdentifyHandler handler = (MmsConnection_IdentifyHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL, NULL, NULL); } - else { - + else + { if (mmsClient_parseIdentifyResponse(self, response, bufPos, outstandingCall->invokeId, handler, outstandingCall->userParameter) == false) { @@ -880,15 +937,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } } - else if (outstandingCall->type == MMS_CALL_TYPE_READ_JOURNAL) { - + else if (outstandingCall->type == MMS_CALL_TYPE_READ_JOURNAL) + { MmsConnection_ReadJournalHandler handler = (MmsConnection_ReadJournalHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL, false); } - else { + else + { bool moreFollows = false; LinkedList entries = NULL; @@ -900,15 +959,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } } } - else if (outstandingCall->type == MMS_CALL_TYPE_GET_NAME_LIST) { - + else if (outstandingCall->type == MMS_CALL_TYPE_GET_NAME_LIST) + { MmsConnection_GetNameListHandler handler = (MmsConnection_GetNameListHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL, false); } - else { + else + { LinkedList nameList = (LinkedList) outstandingCall->internalParameter.ptr; bool moreFollows = mmsClient_parseGetNameListResponse(&nameList, response); @@ -921,15 +982,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } } } - else if (outstandingCall->type == MMS_CALL_TYPE_FILE_OPEN) { - + else if (outstandingCall->type == MMS_CALL_TYPE_FILE_OPEN) + { MmsConnection_FileOpenHandler handler = (MmsConnection_FileOpenHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, 0, 0, 0); } - else { + else + { int32_t frsmId; uint32_t fileSize; uint64_t lastModified; @@ -939,21 +1002,25 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M { handler(outstandingCall->invokeId, outstandingCall->userParameter, MMS_ERROR_PARSING_RESPONSE, 0, 0, 0); } - else { + else + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, frsmId, fileSize, lastModified); } } } - else if (outstandingCall->type == MMS_CALL_TYPE_FILE_READ) { + else if (outstandingCall->type == MMS_CALL_TYPE_FILE_READ) + { MmsConnection_FileReadHandler handler = (MmsConnection_FileReadHandler) outstandingCall->userCallback; int32_t frsmId = outstandingCall->internalParameter.i32; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, frsmId, NULL, 0, false); } - else { + else + { bool moreFollows; if (mmsMsg_parseFileReadResponse(ByteBuffer_getBuffer(response), bufPos, ByteBuffer_getSize(response), outstandingCall->invokeId, frsmId, &moreFollows, @@ -971,7 +1038,8 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M MmsConnection_GenericServiceHandler handler = (MmsConnection_GenericServiceHandler) outstandingCall->userCallback; - if (outstandingCall->type == MMS_CALL_TYPE_OBTAIN_FILE) { + if (outstandingCall->type == MMS_CALL_TYPE_OBTAIN_FILE) + { if (outstandingCall->internalParameter.ptr) GLOBAL_FREEMEM(outstandingCall->internalParameter.ptr); } @@ -983,14 +1051,17 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M handler(outstandingCall->invokeId, outstandingCall->userParameter, err, true); } } - else if (outstandingCall->type == MMS_CALL_TYPE_GET_FILE_DIR) { + else if (outstandingCall->type == MMS_CALL_TYPE_GET_FILE_DIR) + { MmsConnection_FileDirectoryHandler handler = (MmsConnection_FileDirectoryHandler) outstandingCall->userCallback; - if (err != MMS_ERROR_NONE) { + if (err != MMS_ERROR_NONE) + { handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL, 0, 0, false); } - else { + else + { if (mmsClient_parseFileDirectoryResponse(response, bufPos, outstandingCall->invokeId, handler, outstandingCall->userParameter) == false) handler(outstandingCall->invokeId, outstandingCall->userParameter, MMS_ERROR_PARSING_RESPONSE, NULL, 0, 0, false); } @@ -1009,24 +1080,24 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (indication != ISO_IND_TICK) printf("MMS_CLIENT: mmsIsoCallback called with indication %i\n", indication); - if (indication == ISO_IND_TICK) { - + if (indication == ISO_IND_TICK) + { /* check timeouts */ uint64_t currentTime = Hal_getTimeInMs(); int i = 0; - for (i = 0; i < OUTSTANDING_CALLS; i++) { - + for (i = 0; i < OUTSTANDING_CALLS; i++) + { Semaphore_wait(self->outstandingCallsLock); - if (self->outstandingCalls[i].isUsed) { - + if (self->outstandingCalls[i].isUsed) + { Semaphore_post(self->outstandingCallsLock); - if (currentTime > self->outstandingCalls[i].timeout) { - + if (currentTime > self->outstandingCalls[i].timeout) + { if (self->outstandingCalls[i].type != MMS_CALL_TYPE_NONE) handleAsyncResponse(self, NULL, 0, &(self->outstandingCalls[i]), MMS_ERROR_SERVICE_TIMEOUT); @@ -1042,8 +1113,10 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) } } - if (self->concludeHandler) { - if (currentTime > self->concludeTimeout) { + if (self->concludeHandler) + { + if (currentTime > self->concludeTimeout) + { self->concludeHandler(self->concludeHandlerParameter, MMS_ERROR_SERVICE_TIMEOUT, false); self->concludeHandler = NULL; } @@ -1052,7 +1125,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) return true; } - if (indication == ISO_IND_CLOSED) { + if (indication == ISO_IND_CLOSED) + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: mmsIsoCallback: Connection lost or closed by client!\n"); @@ -1066,12 +1140,12 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) { int i; - for (i = 0; i < OUTSTANDING_CALLS; i++) { - + for (i = 0; i < OUTSTANDING_CALLS; i++) + { Semaphore_wait(self->outstandingCallsLock); - if (self->outstandingCalls[i].isUsed) { - + if (self->outstandingCalls[i].isUsed) + { Semaphore_post(self->outstandingCallsLock); if (self->outstandingCalls[i].type != MMS_CALL_TYPE_NONE) @@ -1089,7 +1163,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) return true; } - if (indication == ISO_IND_ASSOCIATION_FAILED) { + if (indication == ISO_IND_ASSOCIATION_FAILED) + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: mmsIsoCallback: association failed!\n"); @@ -1097,7 +1172,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) return false; } - if (payload != NULL) { + if (payload != NULL) + { if (ByteBuffer_getSize(payload) < 1) { return false; } @@ -1106,7 +1182,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) uint8_t* buf = ByteBuffer_getBuffer(payload); #if (CONFIG_MMS_RAW_MESSAGE_LOGGING == 1) - if (self->rawMmsMessageHandler != NULL) { + if (self->rawMmsMessageHandler != NULL) + { MmsRawMessageHandler handler = (MmsRawMessageHandler) self->rawMmsMessageHandler; handler(self->rawMmsMessageHandlerParameter, buf, payload->size, true); } @@ -1117,20 +1194,25 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: MMS-PDU: %02x\n", tag); - if (tag == 0xa9) { /* initiate response PDU */ - - if (indication == ISO_IND_ASSOCIATION_SUCCESS) { + if (tag == 0xa9) + { + /* initiate response PDU */ - if (mmsClient_parseInitiateResponse(self, payload)) { + if (indication == ISO_IND_ASSOCIATION_SUCCESS) + { + if (mmsClient_parseInitiateResponse(self, payload)) + { setConnectionState(self, MMS_CONNECTION_STATE_CONNECTED); } - else { + else + { setConnectionState(self, MMS_CONNECTION_STATE_CLOSING); goto exit_with_error; } } - else { + else + { setConnectionState(self, MMS_CONNECTION_STATE_CLOSING); if (DEBUG_MMS_CLIENT) @@ -1139,7 +1221,9 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) return false; } } - else if (tag == 0xaa) { /* initiate error PDU */ + else if (tag == 0xaa) + { + /* initiate error PDU */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received initiate error PDU\n"); @@ -1148,36 +1232,48 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) return false; } - else if (tag == 0xa3) { /* unconfirmed PDU */ + else if (tag == 0xa3) + { + /* unconfirmed PDU */ handleUnconfirmedMmsPdu(self, payload); } - else if (tag == 0x8b) { /* conclude request PDU */ + else if (tag == 0x8b) + { + /* conclude request PDU */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received conclude.request\n"); /* TODO block all new user requests? */ } - else if (tag == 0x8c) { /* conclude response PDU */ + else if (tag == 0x8c) + { + /* conclude response PDU */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received conclude.response+\n"); - if (self->concludeHandler) { + if (self->concludeHandler) + { self->concludeHandler(self->concludeHandlerParameter, MMS_ERROR_NONE, true); self->concludeHandler = NULL; } IsoClientConnection_release(self->isoClient); } - else if (tag == 0x8d) { /* conclude error PDU */ + else if (tag == 0x8d) + { + /* conclude error PDU */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received conclude.reponse-\n"); - if (self->concludeHandler) { + if (self->concludeHandler) + { self->concludeHandler(self->concludeHandlerParameter, MMS_ERROR_CONCLUDE_REJECTED, false); self->concludeHandler = NULL; } } - else if (tag == 0xa2) { /* confirmed error PDU */ + else if (tag == 0xa2) + { + /* confirmed error PDU */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: Confirmed error PDU!\n"); @@ -1187,37 +1283,43 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) MmsServiceError serviceError = { 0, 0 }; - if (mmsMsg_parseConfirmedErrorPDU(payload->buffer, 0, payload->size, &invokeId, &hasInvokeId, &serviceError) < 0) { + if (mmsMsg_parseConfirmedErrorPDU(payload->buffer, 0, payload->size, &invokeId, &hasInvokeId, &serviceError) < 0) + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: Error parsing confirmedErrorPDU!\n"); goto exit_with_error; } - else { - - if (hasInvokeId) { + else + { + if (hasInvokeId) + { MmsOutstandingCall call = checkForOutstandingCall(self, invokeId); - if (call) { - + if (call) + { MmsError err = convertServiceErrorToMmsError(serviceError); - if (call->type != MMS_CALL_TYPE_NONE) { + if (call->type != MMS_CALL_TYPE_NONE) + { handleAsyncResponse(self, NULL, 0, call, err); } - else { + else + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: internal problem (unexpected call type - error PDU)\n"); } } - else { + else + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: server sent unexpected confirmed error PDU!\n"); return false; } } - else { + else + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: server sent confirmed error PDU without invoke ID!\n"); @@ -1226,7 +1328,9 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) } } - else if (tag == 0xa4) { /* reject PDU */ + else if (tag == 0xa4) + { + /* reject PDU */ if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: reject PDU!\n"); @@ -1236,23 +1340,25 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) int rejectType; int rejectReason; - if (mmsMsg_parseRejectPDU(payload->buffer, 0, payload->size, &invokeId, &hasInvokeId, &rejectType, &rejectReason) >= 0) { - + if (mmsMsg_parseRejectPDU(payload->buffer, 0, payload->size, &invokeId, &hasInvokeId, &rejectType, &rejectReason) >= 0) + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: reject PDU invokeID: %u type: %i reason: %i\n", invokeId, rejectType, rejectReason); - if (hasInvokeId) { + if (hasInvokeId) + { MmsOutstandingCall call = checkForOutstandingCall(self, invokeId); - if (call) { - + if (call) + { MmsError err = convertRejectCodesToMmsError(rejectType, rejectReason); - if (call->type != MMS_CALL_TYPE_NONE) { + if (call->type != MMS_CALL_TYPE_NONE) + { handleAsyncResponse(self, NULL, 0, call, err); } - else { - + else + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: internal problem (unexpected call type - reject PDU)\n"); } @@ -1264,12 +1370,13 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) else { return false; } - } else goto exit_with_error; } - else if (tag == 0xa1) { /* confirmed response PDU */ + else if (tag == 0xa1) + { + /* confirmed response PDU */ int length; int bufPos = 1; @@ -1278,7 +1385,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (bufPos < 0) goto exit_with_error; - if (buf[bufPos++] == 0x02) { + if (buf[bufPos++] == 0x02) + { int invokeIdLength; bufPos = BerDecoder_decodeLength(buf, &invokeIdLength, bufPos, payload->size); @@ -1296,17 +1404,20 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) MmsOutstandingCall call = checkForOutstandingCall(self, invokeId); - if (call) { - - if (call->type != MMS_CALL_TYPE_NONE) { + if (call) + { + if (call->type != MMS_CALL_TYPE_NONE) + { handleAsyncResponse(self, payload, bufPos, call, MMS_ERROR_NONE); } - else { + else + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: internal problem (unexpected call type - confirmed response PDU)\n"); } } - else { + else + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: unexpected message from server!\n"); @@ -1317,8 +1428,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) goto exit_with_error; } #if (MMS_OBTAIN_FILE_SERVICE == 1) - else if (tag == 0xa0) { - + else if (tag == 0xa0) + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: received confirmed request PDU (size=%i)\n", payload->size); @@ -1334,13 +1445,14 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) bool hasInvokeId = false; uint32_t invokeId = 0; - while (bufPos < payload->size) { - + while (bufPos < payload->size) + { uint8_t nestedTag = buf[bufPos++]; bool extendedTag = false; - if ((nestedTag & 0x1f) == 0x1f) { + if ((nestedTag & 0x1f) == 0x1f) + { extendedTag = true; nestedTag = buf[bufPos++]; } @@ -1349,9 +1461,10 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) if (bufPos < 0) goto exit_with_error; - if (extendedTag) { - - if (hasInvokeId == false) { + if (extendedTag) + { + if (hasInvokeId == false) + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: invalid message received - missing invoke ID!\n"); @@ -1438,7 +1551,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) } #endif /* (MMS_OBTAIN_FILE_SERVICE == 1) */ - else { + else + { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT: unknown message type\n"); @@ -1464,7 +1578,8 @@ connectionHandlingThread(void* parameter) { MmsConnection self = (MmsConnection) parameter; - while (self->connectionThreadRunning) { + while (self->connectionThreadRunning) + { if (MmsConnection_tick(self)) Thread_sleep(10); } @@ -1483,8 +1598,8 @@ MmsConnection_createInternal(TLSConfiguration tlsConfig, bool createThread) MmsConnection self = (MmsConnection) GLOBAL_CALLOC(1, sizeof(struct sMmsConnection)); - if (self) { - + if (self) + { self->parameters.dataStructureNestingLevel = -1; self->parameters.maxServOutstandingCalled = -1; self->parameters.maxServOutstandingCalling = -1; @@ -1559,43 +1674,49 @@ MmsConnection_createNonThreaded(TLSConfiguration tlsConfig) void MmsConnection_destroy(MmsConnection self) { + if (self) + { #if (CONFIG_MMS_THREADLESS_STACK == 0) - if (self->createThread) { - if (self->connectionHandlingThread) { - if (self->connectionThreadRunning) { - self->connectionThreadRunning = false; - Thread_destroy(self->connectionHandlingThread); - self->connectionHandlingThread = NULL; + if (self->createThread) + { + if (self->connectionHandlingThread) + { + if (self->connectionThreadRunning) + { + self->connectionThreadRunning = false; + Thread_destroy(self->connectionHandlingThread); + self->connectionHandlingThread = NULL; + } } } - } #endif - if (self->isoClient != NULL) - IsoClientConnection_destroy(self->isoClient); + if (self->isoClient != NULL) + IsoClientConnection_destroy(self->isoClient); - if (self->isoParameters != NULL) - IsoConnectionParameters_destroy(self->isoParameters); + if (self->isoParameters != NULL) + IsoConnectionParameters_destroy(self->isoParameters); - Semaphore_destroy(self->nextInvokeIdLock); + Semaphore_destroy(self->nextInvokeIdLock); - Semaphore_destroy(self->outstandingCallsLock); + Semaphore_destroy(self->outstandingCallsLock); - Semaphore_destroy(self->associationStateLock); + Semaphore_destroy(self->associationStateLock); - GLOBAL_FREEMEM(self->outstandingCalls); + GLOBAL_FREEMEM(self->outstandingCalls); #if (MMS_OBTAIN_FILE_SERVICE == 1) #if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) - if (self->filestoreBasepath != NULL) - GLOBAL_FREEMEM(self->filestoreBasepath); + if (self->filestoreBasepath != NULL) + GLOBAL_FREEMEM(self->filestoreBasepath); #endif - /* Close outstanding open files */ - mmsClient_closeOutstandingOpenFiles(self); + /* Close outstanding open files */ + mmsClient_closeOutstandingOpenFiles(self); #endif - GLOBAL_FREEMEM(self); + GLOBAL_FREEMEM(self); + } } void @@ -1603,7 +1724,8 @@ MmsConnection_setFilestoreBasepath(MmsConnection self, const char* basepath) { #if (MMS_OBTAIN_FILE_SERVICE == 1) #if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) - if (self->filestoreBasepath != NULL) { + if (self->filestoreBasepath != NULL) + { GLOBAL_FREEMEM(self->filestoreBasepath); self->filestoreBasepath = NULL; } @@ -1744,22 +1866,25 @@ MmsConnection_connect(MmsConnection self, MmsError* mmsError, const char* server MmsConnection_connectAsync(self, &err, serverName, serverPort); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(conParams.sem); - if (conParams.state == MMS_CONNECTION_STATE_CONNECTED) { + if (conParams.state == MMS_CONNECTION_STATE_CONNECTED) + { *mmsError = MMS_ERROR_NONE; success = true; } - else { + else + { *mmsError = MMS_ERROR_CONNECTION_REJECTED; } if (conParams.originalHandler) conParams.originalHandler(self, conParams.originalParameter, conParams.state); - } - else { + else + { *mmsError = err; } @@ -1774,7 +1899,8 @@ MmsConnection_connect(MmsConnection self, MmsError* mmsError, const char* server void MmsConnection_connectAsync(MmsConnection self, MmsError* mmsError, const char* serverName, int serverPort) { - if (serverPort == -1) { + if (serverPort == -1) + { #if (CONFIG_MMS_SUPPORT_TLS == 1) if (self->isoParameters->tlsConfiguration) serverPort = 3782; @@ -1786,9 +1912,10 @@ MmsConnection_connectAsync(MmsConnection self, MmsError* mmsError, const char* s } #if (CONFIG_MMS_THREADLESS_STACK == 0) - if (self->createThread) { - if (self->connectionHandlingThread == NULL) { - + if (self->createThread) + { + if (self->connectionHandlingThread == NULL) + { self->connectionHandlingThread = Thread_create(connectionHandlingThread, self, false); self->connectionThreadRunning = true; Thread_start(self->connectionHandlingThread); @@ -1806,13 +1933,15 @@ MmsConnection_connectAsync(MmsConnection self, MmsError* mmsError, const char* s mmsClient_createInitiateRequest(self, payload); #if (CONFIG_MMS_RAW_MESSAGE_LOGGING == 1) - if (self->rawMmsMessageHandler != NULL) { + if (self->rawMmsMessageHandler != NULL) + { MmsRawMessageHandler handler = (MmsRawMessageHandler) self->rawMmsMessageHandler; handler(self->rawMmsMessageHandlerParameter, payload->buffer, payload->size, false); } #endif /* (CONFIG_MMS_RAW_MESSAGE_LOGGING == 1) */ - if (IsoClientConnection_associateAsync(self->isoClient, self->connectTimeout, self->requestTimeout)) { + if (IsoClientConnection_associateAsync(self->isoClient, self->connectTimeout, self->requestTimeout)) + { setConnectionState(self, MMS_CONNECTION_STATE_CONNECTING); *mmsError = MMS_ERROR_NONE; } @@ -1841,7 +1970,8 @@ MmsConnection_abortAsync(MmsConnection self, MmsError* mmsError) { self->connectionLostHandler = NULL; - if (getConnectionState(self) == MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) == MMS_CONNECTION_STATE_CONNECTED) + { IsoClientConnection_abortAsync(self->isoClient); *mmsError = MMS_ERROR_NONE; } @@ -1859,14 +1989,16 @@ MmsConnection_abort(MmsConnection self, MmsError* mmsError) bool success = false; - if (getConnectionState(self) == MMS_CONNECTION_STATE_CONNECTED) { - + if (getConnectionState(self) == MMS_CONNECTION_STATE_CONNECTED) + { IsoClientConnection_abortAsync(self->isoClient); uint64_t timeout = Hal_getTimeInMs() + self->requestTimeout; - while (Hal_getTimeInMs() < timeout) { - if (getConnectionState(self) == MMS_CONNECTION_STATE_CLOSED) { + while (Hal_getTimeInMs() < timeout) + { + if (getConnectionState(self) == MMS_CONNECTION_STATE_CLOSED) + { success = true; break; } @@ -1874,10 +2006,10 @@ MmsConnection_abort(MmsConnection self, MmsError* mmsError) Thread_sleep(10); } } - } - if (success == false) { + if (success == false) + { IsoClientConnection_close(self->isoClient); *mmsError = MMS_ERROR_SERVICE_TIMEOUT; } @@ -1919,7 +2051,8 @@ MmsConnection_conclude(MmsConnection self, MmsError* mmsError) MmsConnection_concludeAsync(self, &err, concludeHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); err = parameter.err; } @@ -1933,7 +2066,8 @@ MmsConnection_conclude(MmsConnection self, MmsError* mmsError) void MmsConnection_concludeAsync(MmsConnection self, MmsError* mmsError, MmsConnection_ConcludeAbortHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } @@ -2092,7 +2226,7 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, } else { - /* exit look when message cannot be sent */ + /* exit loop when message cannot be sent */ moreFollows = false; } @@ -2223,7 +2357,8 @@ void MmsConnection_readVariableAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, const char* domainId, const char* itemId, MmsConnection_ReadVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; @@ -2268,7 +2403,8 @@ MmsConnection_readVariable(MmsConnection self, MmsError* mmsError, MmsConnection_readVariableAsync(self, NULL, &err, domainId, itemId, readVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); value = parameter.value; @@ -2288,7 +2424,8 @@ MmsConnection_readVariableComponentAsync(MmsConnection self, uint32_t* usedInvok const char* domainId, const char* itemId, const char* componentId, MmsConnection_ReadVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; @@ -2333,7 +2470,8 @@ MmsConnection_readVariableComponent(MmsConnection self, MmsError* mmsError, MmsConnection_readVariableComponentAsync(self, NULL, &err, domainId, itemId, componentId, readVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); value = parameter.value; @@ -2367,7 +2505,8 @@ MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError, MmsConnection_readArrayElementsAsync(self, NULL, &err, domainId, itemId, startIndex, numberOfElements, readVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); value = parameter.value; @@ -2387,7 +2526,8 @@ MmsConnection_readArrayElementsAsync(MmsConnection self, uint32_t* usedInvokeId, uint32_t startIndex, uint32_t numberOfElements, MmsConnection_ReadVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2433,7 +2573,8 @@ MmsConnection_readSingleArrayElementWithComponent(MmsConnection self, MmsError* MmsConnection_readSingleArrayElementWithComponentAsync(self, NULL, &err, domainId, itemId, index, componentId, readVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); value = parameter.value; @@ -2455,7 +2596,8 @@ MmsConnection_readSingleArrayElementWithComponentAsync(MmsConnection self, uint3 uint32_t index, const char* componentId, MmsConnection_ReadVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2501,7 +2643,8 @@ MmsConnection_readMultipleVariables(MmsConnection self, MmsError* mmsError, MmsConnection_readMultipleVariablesAsync(self, NULL, &err, domainId, items, readVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); value = parameter.value; @@ -2521,7 +2664,8 @@ MmsConnection_readMultipleVariablesAsync(MmsConnection self, uint32_t* usedInvok const char* domainId, LinkedList /**/items, MmsConnection_ReadVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2534,7 +2678,8 @@ MmsConnection_readMultipleVariablesAsync(MmsConnection self, uint32_t* usedInvok if (usedInvokeId) *usedInvokeId = invokeId; - if (mmsClient_createReadRequestMultipleValues(invokeId, domainId, items, payload) > 0) { + if (mmsClient_createReadRequestMultipleValues(invokeId, domainId, items, payload) > 0) + { MmsClientInternalParameter intParam; intParam.ptr = NULL; @@ -2543,7 +2688,8 @@ MmsConnection_readMultipleVariablesAsync(MmsConnection self, uint32_t* usedInvok if (mmsError) *mmsError = err; } - else { + else + { if (mmsError) *mmsError = MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE; } @@ -2571,7 +2717,8 @@ MmsConnection_readNamedVariableListValues(MmsConnection self, MmsError* mmsError MmsConnection_readNamedVariableListValuesAsync(self, NULL, &err, domainId, listName, specWithResult, readVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); value = parameter.value; @@ -2591,7 +2738,8 @@ MmsConnection_readNamedVariableListValuesAsync(MmsConnection self, uint32_t* use const char* domainId, const char* listName, bool specWithResult, MmsConnection_ReadVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2639,7 +2787,8 @@ MmsConnection_readNamedVariableListValuesAssociationSpecific( MmsConnection_readNamedVariableListValuesAssociationSpecificAsync(self, NULL, &err, listName, specWithResult, readVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); value = parameter.value; @@ -2659,7 +2808,8 @@ MmsConnection_readNamedVariableListValuesAssociationSpecificAsync(MmsConnection const char* listName, bool specWithResult, MmsConnection_ReadVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2729,7 +2879,8 @@ MmsConnection_readNamedVariableListDirectory(MmsConnection self, MmsError* mmsEr MmsConnection_readNamedVariableListDirectoryAsync(self, NULL, &err, domainId, listName, readNVLDirectoryHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(waitForResponse); err = parameter.err; specs = parameter.specs; @@ -2751,7 +2902,8 @@ MmsConnection_readNamedVariableListDirectoryAsync(MmsConnection self, uint32_t* const char* domainId, const char* listName, MmsConnection_ReadNVLDirectoryHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2798,7 +2950,8 @@ MmsConnection_readNamedVariableListDirectoryAssociationSpecific(MmsConnection se MmsConnection_readNamedVariableListDirectoryAssociationSpecificAsync(self, NULL, &err, listName, readNVLDirectoryHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(waitForResponse); err = parameter.err; specs = parameter.specs; @@ -2820,7 +2973,8 @@ MmsConnection_readNamedVariableListDirectoryAssociationSpecificAsync(MmsConnecti const char* listName, MmsConnection_ReadNVLDirectoryHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2885,7 +3039,8 @@ MmsConnection_defineNamedVariableList(MmsConnection self, MmsError* mmsError, MmsConnection_defineNamedVariableListAsync(self, NULL, &err, domainId, listName, variableSpecs, defineNVLHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; } @@ -2901,7 +3056,8 @@ MmsConnection_defineNamedVariableListAsync(MmsConnection self, uint32_t* usedInv const char* listName, LinkedList variableSpecs, MmsConnection_GenericServiceHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -2944,7 +3100,8 @@ MmsConnection_defineNamedVariableListAssociationSpecific(MmsConnection self, MmsConnection_defineNamedVariableListAssociationSpecificAsync(self, NULL, &err, listName, variableSpecs, defineNVLHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; } @@ -2960,7 +3117,8 @@ MmsConnection_defineNamedVariableListAssociationSpecificAsync(MmsConnection self const char* listName, LinkedList variableSpecs, MmsConnection_GenericServiceHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3004,7 +3162,8 @@ MmsConnection_deleteNamedVariableList(MmsConnection self, MmsError* mmsError, MmsConnection_deleteNamedVariableListAsync(self, NULL, &err, domainId, listName, defineNVLHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; isDeleted = parameter.success; @@ -3022,7 +3181,8 @@ void MmsConnection_deleteNamedVariableListAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, const char* domainId, const char* listName, MmsConnection_GenericServiceHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3065,7 +3225,8 @@ MmsConnection_deleteAssociationSpecificNamedVariableList(MmsConnection self, MmsConnection_deleteAssociationSpecificNamedVariableListAsync(self, NULL, &err, listName, defineNVLHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; isDeleted = parameter.success; @@ -3083,7 +3244,8 @@ void MmsConnection_deleteAssociationSpecificNamedVariableListAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, const char* listName, MmsConnection_GenericServiceHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3148,7 +3310,8 @@ MmsConnection_getVariableAccessAttributes(MmsConnection self, MmsError* mmsError MmsConnection_getVariableAccessAttributesAsync(self, NULL, &err, domainId, itemId, getAccessAttrHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; typeSpec = parameter.typeSpec; @@ -3167,7 +3330,8 @@ MmsConnection_getVariableAccessAttributesAsync(MmsConnection self, uint32_t* use const char* domainId, const char* itemId, MmsConnection_GetVariableAccessAttributesHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3237,7 +3401,8 @@ MmsConnection_identify(MmsConnection self, MmsError* mmsError) MmsConnection_identifyAsync(self, NULL, &err, identifyHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; identity = parameter.identify; @@ -3255,7 +3420,8 @@ void MmsConnection_identifyAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, MmsConnection_IdentifyHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3323,7 +3489,8 @@ MmsConnection_getServerStatus(MmsConnection self, MmsError* mmsError, int* vmdLo MmsConnection_getServerStatusAsync(self, NULL, &err, extendedDerivation, getServerStatusHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -3344,7 +3511,8 @@ void MmsConnection_getServerStatusAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, bool extendedDerivation, MmsConnection_GetServerStatusHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3374,7 +3542,8 @@ exit_function: static void MmsJournalVariable_destroy(MmsJournalVariable self) { - if (self != NULL) { + if (self) + { GLOBAL_FREEMEM(self->tag); MmsValue_delete(self->value); GLOBAL_FREEMEM(self); @@ -3384,7 +3553,8 @@ MmsJournalVariable_destroy(MmsJournalVariable self) void MmsJournalEntry_destroy(MmsJournalEntry self) { - if (self != NULL) { + if (self) + { MmsValue_delete(self->entryID); MmsValue_delete(self->occurenceTime); LinkedList_destroyDeep(self->journalVariables, @@ -3464,7 +3634,8 @@ MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const MmsConnection_readJournalTimeRangeAsync(self, NULL, &err, domainId, itemId, startTime, endTime, readJournalHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -3485,15 +3656,16 @@ void MmsConnection_readJournalTimeRangeAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, const char* domainId, const char* itemId, MmsValue* startTime, MmsValue* endTime, MmsConnection_ReadJournalHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } if ((MmsValue_getType(startTime) != MMS_BINARY_TIME) || - (MmsValue_getType(endTime) != MMS_BINARY_TIME)) { - + (MmsValue_getType(endTime) != MMS_BINARY_TIME)) + { if (mmsError) *mmsError = MMS_ERROR_INVALID_ARGUMENTS; goto exit_function; @@ -3537,7 +3709,8 @@ MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, cons MmsConnection_readJournalStartAfterAsync(self, NULL, &err, domainId, itemId, timeSpecification, entrySpecification, readJournalHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -3558,15 +3731,16 @@ void MmsConnection_readJournalStartAfterAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError* mmsError, const char* domainId, const char* itemId, MmsValue* timeSpecification, MmsValue* entrySpecification, MmsConnection_ReadJournalHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; } if ((MmsValue_getType(timeSpecification) != MMS_BINARY_TIME) || - (MmsValue_getType(entrySpecification) != MMS_OCTET_STRING)) { - + (MmsValue_getType(entrySpecification) != MMS_OCTET_STRING)) + { if (mmsError) *mmsError = MMS_ERROR_INVALID_ARGUMENTS; goto exit_function; @@ -3639,7 +3813,8 @@ MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filen MmsConnection_fileOpenAsync(self, NULL, &err, filename, initialPosition, fileOpenHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -3672,7 +3847,8 @@ MmsConnection_fileOpenAsync(MmsConnection self, uint32_t* usedInvokeId, MmsError { #if (MMS_FILE_SERVICE == 1) - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3744,7 +3920,8 @@ MmsConnection_fileClose(MmsConnection self, MmsError* mmsError, int32_t frsmId) MmsConnection_fileCloseAsync(self, NULL, &err, frsmId, fileOperationHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -3768,7 +3945,8 @@ MmsConnection_fileCloseAsync(MmsConnection self, uint32_t* usedInvokeId, MmsErro { #if (MMS_FILE_SERVICE == 1) - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3819,7 +3997,8 @@ MmsConnection_fileDelete(MmsConnection self, MmsError* mmsError, const char* fil MmsConnection_fileDeleteAsync(self, NULL, &err, fileName, fileOperationHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -3844,7 +4023,8 @@ MmsConnection_fileDeleteAsync(MmsConnection self, uint32_t* usedInvokeId, MmsErr { #if (MMS_FILE_SERVICE == 1) - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -3926,7 +4106,8 @@ MmsConnection_fileRead(MmsConnection self, MmsError* mmsError, int32_t frsmId, M MmsConnection_fileReadAsync(self, NULL, &err, frsmId, fileReadHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -4006,7 +4187,8 @@ getFileDirHandler(uint32_t invokeId, void* parameter, MmsError mmsError, char* f parameters->err = mmsError; - if ((mmsError != MMS_ERROR_NONE) || (filename == NULL)) { + if ((mmsError != MMS_ERROR_NONE) || (filename == NULL)) + { parameters->moreFollows = moreFollows; /* last call --> unblock user thread */ @@ -4037,7 +4219,8 @@ MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const cha MmsConnection_getFileDirectoryAsync(self, NULL, &err, fileSpecification, continueAfter, getFileDirHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; moreFollows = parameter.moreFollows; @@ -4064,7 +4247,8 @@ MmsConnection_getFileDirectoryAsync(MmsConnection self, uint32_t* usedInvokeId, { #if (MMS_FILE_SERVICE == 1) - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4115,7 +4299,8 @@ MmsConnection_fileRename(MmsConnection self, MmsError* mmsError, const char* cur MmsConnection_fileRenameAsync(self, NULL, &err, currentFileName, newFileName, fileOperationHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -4139,7 +4324,8 @@ MmsConnection_fileRenameAsync(MmsConnection self, uint32_t* usedInvokeId, MmsErr { #if (MMS_FILE_SERVICE == 1) - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4190,7 +4376,8 @@ MmsConnection_obtainFile(MmsConnection self, MmsError* mmsError, const char* sou MmsConnection_obtainFileAsync(self, NULL, &err, sourceFile, destinationFile, fileOperationHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -4222,7 +4409,8 @@ MmsConnection_obtainFileAsync(MmsConnection self, uint32_t* usedInvokeId, MmsErr { #if (MMS_FILE_SERVICE == 1) - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4291,7 +4479,8 @@ MmsConnection_writeVariable(MmsConnection self, MmsError* mmsError, MmsConnection_writeVariableAsync(self, NULL, &err, domainId, itemId, value, writeVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -4310,7 +4499,8 @@ MmsConnection_writeVariableAsync(MmsConnection self, uint32_t* usedInvokeId, Mms const char* domainId, const char* itemId, MmsValue* value, MmsConnection_WriteVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4354,7 +4544,8 @@ MmsConnection_writeSingleArrayElementWithComponent(MmsConnection self, MmsError* MmsConnection_writeSingleArrayElementWithComponentAsync(self, NULL, &err, domainId, itemId, arrayIndex, componentId, value, writeVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -4374,7 +4565,8 @@ MmsConnection_writeSingleArrayElementWithComponentAsync(MmsConnection self, uint uint32_t arrayIndex, const char* componentId, MmsValue* value, MmsConnection_WriteVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4419,7 +4611,8 @@ MmsConnection_writeVariableComponent(MmsConnection self, MmsError* mmsError, MmsConnection_writeVariableComponentAsync(self, NULL, &err, domainId, itemId, componentId, value, writeVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -4438,7 +4631,8 @@ MmsConnection_writeVariableComponentAsync(MmsConnection self, uint32_t* usedInvo const char* domainId, const char* itemId, const char* componentId, MmsValue* value, MmsConnection_WriteVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4504,8 +4698,8 @@ MmsConnection_writeMultipleVariables(MmsConnection self, MmsError* mmsError, con MmsConnection_writeMultipleVariablesAsync(self, NULL, &err, domainId, items, values, writeMultipleVariablesHandler, ¶meter); - if (err == MMS_ERROR_NONE) { - + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); err = parameter.err; @@ -4531,7 +4725,8 @@ MmsConnection_writeMultipleVariablesAsync(MmsConnection self, uint32_t* usedInvo LinkedList /**/ items, LinkedList /* */ values, MmsConnection_WriteMultipleVariablesHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4544,7 +4739,8 @@ MmsConnection_writeMultipleVariablesAsync(MmsConnection self, uint32_t* usedInvo if (usedInvokeId) *usedInvokeId = invokeId; - if (mmsClient_createWriteMultipleItemsRequest(invokeId, domainId, items, values, payload) != -1) { + if (mmsClient_createWriteMultipleItemsRequest(invokeId, domainId, items, values, payload) != -1) + { MmsClientInternalParameter intParam; intParam.ptr = NULL; @@ -4553,7 +4749,8 @@ MmsConnection_writeMultipleVariablesAsync(MmsConnection self, uint32_t* usedInvo if (mmsError) *mmsError = err; } - else { + else + { *mmsError = MMS_ERROR_RESOURCE_OTHER; } @@ -4579,7 +4776,8 @@ MmsConnection_writeArrayElements(MmsConnection self, MmsError* mmsError, MmsConnection_writeArrayElementsAsync(self, NULL, &err, domainId, itemId, index, numberOfElements, value, writeVariableHandler, ¶meter); - if (err == MMS_ERROR_NONE) { + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.waitForResponse); err = parameter.err; @@ -4599,7 +4797,8 @@ MmsConnection_writeArrayElementsAsync(MmsConnection self, uint32_t* usedInvokeId MmsValue* value, MmsConnection_WriteVariableHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4643,8 +4842,8 @@ MmsConnection_writeNamedVariableList(MmsConnection self, MmsError* mmsError, boo MmsConnection_writeNamedVariableListAsync(self, NULL, &err, isAssociationSpecific, domainId, itemId, values, writeMultipleVariablesHandler, ¶meter); - if (err == MMS_ERROR_NONE) { - + if (err == MMS_ERROR_NONE) + { Semaphore_wait(parameter.sem); err = parameter.err; @@ -4654,7 +4853,8 @@ MmsConnection_writeNamedVariableList(MmsConnection self, MmsError* mmsError, boo else LinkedList_destroyDeep(parameter.result, (LinkedListValueDeleteFunction) MmsValue_delete); } - else { + else + { if (accessResults) *accessResults = NULL; } @@ -4670,7 +4870,8 @@ MmsConnection_writeNamedVariableListAsync(MmsConnection self, uint32_t* usedInvo const char* domainId, const char* itemId, LinkedList /* */values, MmsConnection_WriteMultipleVariablesHandler handler, void* parameter) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4700,7 +4901,8 @@ exit_function: void MmsConnection_sendRawData(MmsConnection self, MmsError* mmsError, uint8_t* buffer, int bufSize) { - if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) { + if (getConnectionState(self) != MMS_CONNECTION_STATE_CONNECTED) + { if (mmsError) *mmsError = MMS_ERROR_CONNECTION_LOST; goto exit_function; @@ -4722,16 +4924,19 @@ exit_function: void MmsServerIdentity_destroy(MmsServerIdentity* self) { - if (self->modelName != NULL) - GLOBAL_FREEMEM(self->modelName); + if (self) + { + if (self->modelName != NULL) + GLOBAL_FREEMEM(self->modelName); - if (self->vendorName != NULL) - GLOBAL_FREEMEM(self->vendorName); + if (self->vendorName != NULL) + GLOBAL_FREEMEM(self->vendorName); - if (self->revision != NULL) - GLOBAL_FREEMEM(self->revision); + if (self->revision != NULL) + GLOBAL_FREEMEM(self->revision); - GLOBAL_FREEMEM(self); + GLOBAL_FREEMEM(self); + } } MmsVariableAccessSpecification* @@ -4740,10 +4945,13 @@ MmsVariableAccessSpecification_create(char* domainId, char* itemId) MmsVariableAccessSpecification* self = (MmsVariableAccessSpecification*) GLOBAL_MALLOC(sizeof(MmsVariableAccessSpecification)); - self->domainId = domainId; - self->itemId = itemId; - self->arrayIndex = -1; - self->componentName = NULL; + if (self) + { + self->domainId = domainId; + self->itemId = itemId; + self->arrayIndex = -1; + self->componentName = NULL; + } return self; } @@ -4755,10 +4963,13 @@ MmsVariableAccessSpecification_createAlternateAccess(char* domainId, char* itemI MmsVariableAccessSpecification* self = (MmsVariableAccessSpecification*) GLOBAL_MALLOC(sizeof(MmsVariableAccessSpecification)); - self->domainId = domainId; - self->itemId = itemId; - self->arrayIndex = index; - self->componentName = componentName; + if (self) + { + self->domainId = domainId; + self->itemId = itemId; + self->arrayIndex = index; + self->componentName = componentName; + } return self; } @@ -4766,14 +4977,17 @@ MmsVariableAccessSpecification_createAlternateAccess(char* domainId, char* itemI void MmsVariableAccessSpecification_destroy(MmsVariableAccessSpecification* self) { - if (self->domainId != NULL) - GLOBAL_FREEMEM((void*) self->domainId); + if (self) + { + if (self->domainId != NULL) + GLOBAL_FREEMEM((void*) self->domainId); - if (self->itemId != NULL) - GLOBAL_FREEMEM((void*) self->itemId); + if (self->itemId != NULL) + GLOBAL_FREEMEM((void*) self->itemId); - if (self->componentName != NULL) - GLOBAL_FREEMEM((void*) self->componentName); + if (self->componentName != NULL) + GLOBAL_FREEMEM((void*) self->componentName); - GLOBAL_FREEMEM(self); + GLOBAL_FREEMEM(self); + } } From 05f32320e18b6ef2d08da91b48d28e21f30ebdf4 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 3 Jun 2024 15:36:15 +0100 Subject: [PATCH 42/53] - added function to get timestamp of received command (ControlAction_getT) (LIB61850-422) --- config/stack_config.h | 2 +- config/stack_config.h.cmake | 2 +- dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs | 18 +++++++++++++++++- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 15 +++++++++++++++ src/iec61850/inc/iec61850_server.h | 12 +++++++++++- src/iec61850/inc_private/control.h | 4 +++- src/iec61850/server/mms_mapping/control.c | 19 ++++++++++++++++++- 7 files changed, 66 insertions(+), 6 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index 2559069c..bc3045d0 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -170,7 +170,7 @@ /* allow application to set server identity (for MMS identity service) at runtime */ #define CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY 1 -/* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ +/* Force memory alignment - required for some platforms (requires more memory for buffered reporting) */ #define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 /* overwrite default results for MMS identify service */ diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 437d87d9..adfeca96 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -160,7 +160,7 @@ /* allow application to set server identity (for MMS identity service) at runtime */ #define CONFIG_IEC61850_SUPPORT_SERVER_IDENTITY 1 -/* Force memory alignment - required for some platforms (required more memory for buffered reporting) */ +/* Force memory alignment - required for some platforms (requires more memory for buffered reporting) */ #define CONFIG_IEC61850_FORCE_MEMORY_ALIGNMENT 1 /* default results for MMS identify service */ diff --git a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs index 818276c4..980d9ceb 100644 --- a/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850CommonAPI.cs @@ -1,7 +1,7 @@ /* * IEC61850CommonAPI.cs * - * Copyright 2014-2017 Michael Zillgith + * Copyright 2014-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -454,6 +454,9 @@ namespace IEC61850 SetByMmsUtcTime (mmsUtcTime); } + /// + /// Initializes a new instance of the class. + /// public Timestamp() { self = Timestamp_create (); @@ -461,6 +464,19 @@ namespace IEC61850 responsibleForDeletion = true; } + /// + /// Initializes a new instance of the class. + /// + public Timestamp(Timestamp other) : this() + { + SetTimeInSeconds (other.GetTimeInSeconds ()); + SetSubsecondPrecision (other.GetSubsecondPrecision ()); + SetFractionOfSecondPart (other.GetFractionOfSecondPart ()); + SetLeapSecondKnow(other.IsLeapSecondKnown()); + SetClockFailure(other.HasClockFailure()); + SetClockNotSynchronized(other.IsClockNotSynchronized()); + } + public Timestamp(byte[] value) { self = Timestamp_createFromByteArray (value); diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 97adae02..8a0c5bf3 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -1874,6 +1874,9 @@ namespace IEC61850 [return: MarshalAs(UnmanagedType.I1)] static extern bool ControlAction_getInterlockCheck(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ControlAction_getT(IntPtr self); + private IntPtr self; private IedServer.ControlHandlerInfo info; private IedServer iedServer; @@ -2003,6 +2006,18 @@ namespace IEC61850 { return ControlAction_getInterlockCheck(self); } + + /// + /// Gets the time (paramter T) of the control action + /// + public Timestamp GetT() + { + IntPtr tPtr = ControlAction_getT(self); + + Timestamp t = new Timestamp(tPtr, false); + + return new Timestamp(t); + } } public delegate void GoCBEventHandler(MmsGooseControlBlock goCB, int cbEvent, object parameter); diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index d33d1a00..6dc69524 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -1449,7 +1449,7 @@ LIB61850_API DataObject* ControlAction_getControlObject(ControlAction self); /** - * \brief Gets the time of the control, if it's a timeActivatedControl, returns 0, if it's not. + * \brief Gets the time of the control (attribute "operTm"), if it's a timeActivatedControl, returns 0, if it's not. * * \param self the control action instance * @@ -1458,6 +1458,16 @@ ControlAction_getControlObject(ControlAction self); LIB61850_API uint64_t ControlAction_getControlTime(ControlAction self); +/** + * \brief Gets the time (attribute "T") of the last received control action (Oper or Select) + * + * \param self the control action instance + * + * \return the time of the last received control action + */ +LIB61850_API Timestamp* +ControlAction_getT(ControlAction self); + /** * \brief Control model callback to perform the static tests (optional). * diff --git a/src/iec61850/inc_private/control.h b/src/iec61850/inc_private/control.h index 66cb6706..adf56731 100644 --- a/src/iec61850/inc_private/control.h +++ b/src/iec61850/inc_private/control.h @@ -1,7 +1,7 @@ /* * control.h * - * Copyright 2013-2019 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -90,6 +90,8 @@ struct sControlObject MmsValue* origin; MmsValue* timestamp; + Timestamp T; + MmsValue* ctlNumSt; MmsValue* originSt; diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 34fb18d5..07ef4324 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -169,6 +169,7 @@ getCancelParameterTest(MmsValue* operParameters) return NULL; } +/* access the MmsValue of Oper.T or SBOw.T */ static MmsValue* getOperParameterTime(MmsValue* operParameters) { @@ -182,7 +183,7 @@ getOperParameterTime(MmsValue* operParameters) timeParameter = MmsValue_getElement(operParameters, 3); } - if (timeParameter != NULL) + if (timeParameter) if ((MmsValue_getType(timeParameter) == MMS_UTC_TIME) || (MmsValue_getType(timeParameter) == MMS_BINARY_TIME)) return timeParameter; @@ -2125,6 +2126,7 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char MmsValue* origin = getOperParameterOrigin(value); MmsValue* check = getOperParameterCheck(value); MmsValue* test = getOperParameterTest(value); + MmsValue* t = getOperParameterTime(value); if (checkValidityOfOriginParameter(origin) == false) { @@ -2139,6 +2141,11 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } + if (t) + { + Timestamp_fromMmsValue(&(controlObject->T), t); + } + int state = getState(controlObject); uint64_t currentTime = Hal_getTimeInMs(); @@ -2270,6 +2277,8 @@ Control_writeAccessControlObject(MmsMapping* self, MmsDomain* domain, const char goto free_and_return; } + Timestamp_fromMmsValue(&(controlObject->T), timeParameter); + if (checkValidityOfOriginParameter(origin) == false) { indication = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; @@ -2690,4 +2699,12 @@ ControlAction_getControlTime(ControlAction self) return controlObject->operateTime; } +Timestamp* +ControlAction_getT(ControlAction self) +{ + ControlObject* controlObject = (ControlObject*) self; + + return &(controlObject->T); +} + #endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */ From 7d03b582a9ab0bab777f3434f0c7ab6962850359 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 5 Jun 2024 08:01:38 +0100 Subject: [PATCH 43/53] - MmsValue_decodeMmsData: add support for empty visible-string, mms-string, and octet-string values (#506) --- src/mms/iso_mms/server/mms_access_result.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/mms/iso_mms/server/mms_access_result.c b/src/mms/iso_mms/server/mms_access_result.c index b17461f0..95dcef8a 100644 --- a/src/mms/iso_mms/server/mms_access_result.c +++ b/src/mms/iso_mms/server/mms_access_result.c @@ -171,9 +171,12 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu if (bufPos < 0) goto exit_with_error; - /* if not indefinite length end tag, data length must be > 0 */ - if ((tag != 0) && (dataLength == 0)) - goto exit_with_error; + /* if not indefinite length end tag, visible-string, mms-string, or octet-string, data length must be > 0 */ + if (tag != 0) + { + if (tag != 0x8a && tag != 0x90 && tag != 0x89 && dataLength == 0) + goto exit_with_error; + } switch (tag) { @@ -192,8 +195,8 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu int i; - for (i = 0; i < elementCount; i++) { - + for (i = 0; i < elementCount; i++) + { int elementLength; int newBufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos + 1, dataEndBufPos); @@ -304,7 +307,8 @@ MmsValue_decodeMmsData(uint8_t* buffer, int bufPos, int bufferLength, int* endBu break; case 0x91: /* MMS_UTC_TIME */ - if (dataLength == 8) { + if (dataLength == 8) + { value = MmsValue_newUtcTime(0); MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); bufPos += dataLength; From 4791c14f19faec4e1fd9f09c74a7c100d5218bae Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 5 Jun 2024 08:02:55 +0100 Subject: [PATCH 44/53] - code format update --- src/mms/iso_mms/common/mms_value.c | 53 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index 549619a8..f88e232b 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -1463,13 +1463,15 @@ MmsValue_newOctetString(int size, int maxSize) { MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - if (self) { + if (self) + { self->type = MMS_OCTET_STRING; self->value.octetString.size = size; self->value.octetString.maxSize = maxSize; self->value.octetString.buf = (uint8_t*) GLOBAL_CALLOC(1, abs(maxSize)); - if (self->value.octetString.buf == NULL) { + if ((maxSize != 0) && (self->value.octetString.buf == NULL)) + { GLOBAL_FREEMEM(self); self = NULL; } @@ -1677,14 +1679,16 @@ exit_function: } static void -setVisibleStringValue(MmsValue* self, const char* string) +setVisibleStringValue(MmsValue* self, const char* value) { - if (self->value.visibleString.buf != NULL) { - if (string != NULL) { - - int newStringSize = strlen(string); + if (self->value.visibleString.buf != NULL) + { + if (value != NULL) + { + int newStringSize = strlen(value); - if (newStringSize > self->value.visibleString.size) { + if (newStringSize > self->value.visibleString.size) + { GLOBAL_FREEMEM(self->value.visibleString.buf); self->value.visibleString.buf = (char*) GLOBAL_MALLOC(newStringSize + 1); @@ -1694,7 +1698,7 @@ setVisibleStringValue(MmsValue* self, const char* string) self->value.visibleString.size = newStringSize; } - StringUtils_copyStringMax(self->value.visibleString.buf, self->value.visibleString.size + 1, string); + StringUtils_copyStringMax(self->value.visibleString.buf, self->value.visibleString.size + 1, value); } else self->value.visibleString.buf[0] = 0; @@ -1906,7 +1910,8 @@ MmsValue_newStringFromByteArray(const uint8_t* byteArray, int size, MmsType type self->value.visibleString.buf = StringUtils_createStringFromBuffer(byteArray, size); - if (self->value.visibleString.buf == NULL) { + if (self->value.visibleString.buf == NULL) + { GLOBAL_FREEMEM(self); self = NULL; } @@ -2008,17 +2013,20 @@ MmsValue_createArray(const MmsVariableSpecification* elementType, int size) self->value.structure.size = size; self->value.structure.components = (MmsValue**) GLOBAL_CALLOC(size, sizeof(MmsValue*)); - if (self->value.structure.components == NULL) { + if (self->value.structure.components == NULL) + { GLOBAL_FREEMEM(self); self = NULL; goto exit_function; } int i; - for (i = 0; i < size; i++) { + for (i = 0; i < size; i++) + { self->value.structure.components[i] = MmsValue_newDefaultValue(elementType); - if (self->value.structure.components[i] == NULL) { + if (self->value.structure.components[i] == NULL) + { MmsValue_delete(self); self = NULL; goto exit_function; @@ -2102,9 +2110,10 @@ MmsValue_setDeletable(MmsValue* self) void MmsValue_setDeletableRecursive(MmsValue* self) { - if (self != NULL) { - - if ((MmsValue_getType(self) == MMS_ARRAY) || (MmsValue_getType(self) == MMS_STRUCTURE)) { + if (self) + { + if ((MmsValue_getType(self) == MMS_ARRAY) || (MmsValue_getType(self) == MMS_STRUCTURE)) + { int i; int elementCount = MmsValue_getArraySize(self); @@ -2179,7 +2188,8 @@ MmsValue_getTypeString(MmsValue* self) const char* MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) { - if (self == NULL) { + if (self == NULL) + { StringUtils_copyStringMax(buffer, bufferSize, "(null)"); return buffer; @@ -2240,7 +2250,8 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) int size = MmsValue_getBitStringSize(self); /* fill buffer with zeros */ - if (size + 1 > bufferSize) { + if (size + 1 > bufferSize) + { memset(buffer, 0, bufferSize); size = bufferSize - 1; @@ -2250,7 +2261,8 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) } int i; - for (i = 0; i < size; i++) { + for (i = 0; i < size; i++) + { if (MmsValue_getBitStringBit(self, i)) buffer[bufPos++] = '1'; else @@ -2290,7 +2302,8 @@ MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) int size = MmsValue_getOctetStringSize(self); int bufPos = 0; int i; - for (i = 0; i < size; i++) { + for (i = 0; i < size; i++) + { snprintf(buffer + bufPos, bufferSize - bufPos, "%02x", self->value.octetString.buf[i]); bufPos += 2; From ac1734905397cb4414c67c351d415b5124d0d072 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 12 Jun 2024 11:07:51 +0100 Subject: [PATCH 45/53] - GOOSE receiver: added additional length and plausibility checks to fix #509 --- src/goose/goose_receiver.c | 63 +++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index 164ddca8..843b5239 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -662,10 +662,12 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) uint32_t numberOfDatSetEntries = 0; - if (buffer[bufPos++] == 0x61) { + if (buffer[bufPos++] == 0x61) + { int gooseLength; bufPos = BerDecoder_decodeLength(buffer, &gooseLength, bufPos, apduLength); - if (bufPos < 0) { + if (bufPos < 0) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); return 0; @@ -673,12 +675,14 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) int gooseEnd = bufPos + gooseLength; - while (bufPos < gooseEnd) { + while (bufPos < gooseEnd) + { int elementLength; uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, apduLength); - if (bufPos < 0) { + if (bufPos < 0) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); return 0; @@ -696,7 +700,8 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) { LinkedList element = LinkedList_getNext(self->subscriberList); - while (element != NULL) { + while (element) + { GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element); if (subscriber->isObserver) @@ -713,8 +718,10 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) matchingSubscriber = subscriber; break; } - else if (subscriber->goCBRefLen == elementLength) { - if (memcmp(subscriber->goCBRef, buffer + bufPos, elementLength) == 0) { + else if (subscriber->goCBRefLen == elementLength) + { + if (memcmp(subscriber->goCBRef, buffer + bufPos, elementLength) == 0) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: gocbRef is matching!\n"); matchingSubscriber = subscriber; @@ -832,15 +839,17 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) bufPos += elementLength; } - if (matchingSubscriber != NULL) { - + if (matchingSubscriber != NULL) + { matchingSubscriber->timeAllowedToLive = timeAllowedToLive; matchingSubscriber->ndsCom = ndsCom; matchingSubscriber->simulation = simulation; - if (matchingSubscriber->dataSetValuesSelfAllocated) { + if (matchingSubscriber->dataSetValuesSelfAllocated) + { /* when confRev changed replaced old data set */ - if ((matchingSubscriber->dataSetValues != NULL) && (matchingSubscriber->confRev != confRev)) { + if ((matchingSubscriber->dataSetValues != NULL) && (matchingSubscriber->confRev != confRev)) + { MmsValue_delete(matchingSubscriber->dataSetValues); matchingSubscriber->dataSetValues = NULL; } @@ -866,7 +875,8 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) if (matchingSubscriber->dataSetValues == NULL) matchingSubscriber->dataSetValues = parseAllDataUnknownValue(matchingSubscriber, dataSetBufferAddress, dataSetBufferLength, false); - else { + else + { GooseParseError parseError = parseAllData(dataSetBufferAddress, dataSetBufferLength, matchingSubscriber->dataSetValues); if (parseError != GOOSE_PARSE_ERROR_NO_ERROR) { @@ -876,7 +886,8 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) matchingSubscriber->parseError = parseError; } - if (matchingSubscriber->stNum == stNum) { + if (matchingSubscriber->stNum == stNum) + { if (matchingSubscriber->sqNum >= sqNum) { isValid = false; } @@ -920,13 +931,18 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes) uint8_t priority = 0; uint16_t vlanId = 0; bool vlanSet = false; + /* check for VLAN tag */ - if ((buffer[bufPos] == 0x81) && (buffer[bufPos + 1] == 0x00)) { + if ((buffer[bufPos] == 0x81) && (buffer[bufPos + 1] == 0x00)) + { priority = buffer[bufPos + 2] & 0xF8 >> 5; vlanId = ((buffer[bufPos + 2] & 0x07) << 8) + buffer[bufPos + 3]; vlanSet = true; bufPos += 4; /* skip VLAN tag */ headerLength += 4; + + if (numbytes < (22 + 4)) + return; } /* check for GOOSE Ethertype */ @@ -956,13 +972,22 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes) int apduLength = length - 8; - if (numbytes < length + headerLength) { + if (apduLength < 0) + { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: Invalid length field\n"); + return; + } + + if (numbytes < length + headerLength) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Invalid PDU size\n"); return; } - if (DEBUG_GOOSE_SUBSCRIBER) { + if (DEBUG_GOOSE_SUBSCRIBER) + { printf("GOOSE_SUBSCRIBER: GOOSE message:\nGOOSE_SUBSCRIBER: ----------------\n"); printf("GOOSE_SUBSCRIBER: DST-MAC: %02x:%02x:%02x:%02x:%02X:%02X\n", dstMac[0], dstMac[1], dstMac[2], dstMac[3], dstMac[4], dstMac[5]); @@ -974,7 +999,8 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes) /* check if there is an interested subscriber */ LinkedList element = LinkedList_getNext(self->subscriberList); - while (element != NULL) { + while (element) + { GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element); if (subscriber->isObserver) @@ -990,7 +1016,8 @@ parseGooseMessage(GooseReceiver self, uint8_t* buffer, int numbytes) } if (((subscriber->appId == -1) || (subscriber->appId == appId)) && - (!subscriber->dstMacSet || (memcmp(subscriber->dstMac, dstMac,6) == 0))) { + (!subscriber->dstMacSet || (memcmp(subscriber->dstMac, dstMac,6) == 0))) + { subscriberFound = true; break; } From 501dffe6d0564147981acd4b7dd59702d9781a5b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 12 Jun 2024 12:06:46 +0100 Subject: [PATCH 46/53] - ACSE: fixed out-of-bounds read in parseAarqPdu function (LIB61850-441)(#512) --- src/mms/iso_acse/acse.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mms/iso_acse/acse.c b/src/mms/iso_acse/acse.c index 40ecafe0..a16b4d74 100644 --- a/src/mms/iso_acse/acse.c +++ b/src/mms/iso_acse/acse.c @@ -263,13 +263,17 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) int authMechLen = 0; bool userInfoValid = false; - while (bufPos < maxBufPos) { + while (bufPos < maxBufPos) + { uint8_t tag = buffer[bufPos++]; int len; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); - if (bufPos < 0) { + if (len == 0) + continue; + + if ((bufPos < 0) || (bufPos + len > maxBufPos)) { if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); return ACSE_ASSOCIATE_FAILED; From d0f52a28241d5f9e5d3afeb18d695ba7bbc0f9f1 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 12 Jun 2024 12:24:32 +0100 Subject: [PATCH 47/53] - fixed out-of-bound read in parseAarePdu function (LIB61850-442)(#513) --- src/mms/iso_acse/acse.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/mms/iso_acse/acse.c b/src/mms/iso_acse/acse.c index a16b4d74..65390969 100644 --- a/src/mms/iso_acse/acse.c +++ b/src/mms/iso_acse/acse.c @@ -126,7 +126,10 @@ parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxB bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); - if (bufPos < 0) { + if (len == 0) + continue; + + if ((bufPos < 0) || (bufPos + len > maxBufPos)) { *userInfoValid = false; return -1; } @@ -186,8 +189,15 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) int len; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); - if (bufPos < 0) + + if (len == 0) + continue; + + if ((bufPos < 0) || (bufPos + len > maxBufPos)) { + if (DEBUG_ACSE) + printf("ACSE: Invalid PDU!\n"); return ACSE_ERROR; + } switch (tag) { From b2b84bbbbc1fa9294c1188b1c960ad1bbc6a1b4b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 12 Jun 2024 12:53:37 +0100 Subject: [PATCH 48/53] - code format updates --- src/goose/goose_publisher.c | 107 ++++++++++-------- src/goose/goose_receiver.c | 211 +++++++++++++++++++++++------------ src/goose/goose_subscriber.c | 14 ++- src/mms/iso_acse/acse.c | 81 +++++++++----- 4 files changed, 263 insertions(+), 150 deletions(-) diff --git a/src/goose/goose_publisher.c b/src/goose/goose_publisher.c index 46ed8feb..d1ec7e20 100644 --- a/src/goose/goose_publisher.c +++ b/src/goose/goose_publisher.c @@ -1,7 +1,7 @@ /* * goose_publisher.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -67,12 +67,14 @@ GoosePublisher_createEx(CommParameters* parameters, const char* interfaceID, boo if (self) { - if (prepareGooseBuffer(self, parameters, interfaceID, useVlanTag)) { + if (prepareGooseBuffer(self, parameters, interfaceID, useVlanTag)) + { self->timestamp = MmsValue_newUtcTimeByMsTime(Hal_getTimeInMs()); GoosePublisher_reset(self); } - else { + else + { GoosePublisher_destroy(self); self = NULL; } @@ -218,13 +220,15 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char* uint16_t vlanId; uint16_t appId; - if (parameters) { + if (parameters) + { dstAddr = parameters->dstAddress; priority = parameters->vlanPriority; vlanId = parameters->vlanId; appId = parameters->appId; } - else { + else + { dstAddr = defaultDstAddr; priority = CONFIG_GOOSE_DEFAULT_PRIORITY; vlanId = CONFIG_GOOSE_DEFAULT_VLAN_ID; @@ -236,53 +240,64 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char* else self->ethernetSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, dstAddr); - if (self->ethernetSocket) { + if (self->ethernetSocket) + { self->buffer = (uint8_t*) GLOBAL_MALLOC(GOOSE_MAX_MESSAGE_SIZE); - memcpy(self->buffer, dstAddr, 6); - memcpy(self->buffer + 6, srcAddr, 6); + if (self->buffer) + { + memcpy(self->buffer, dstAddr, 6); + memcpy(self->buffer + 6, srcAddr, 6); - int bufPos = 12; + int bufPos = 12; - if (useVlanTags) { - /* Priority tag - IEEE 802.1Q */ - self->buffer[bufPos++] = 0x81; - self->buffer[bufPos++] = 0x00; + if (useVlanTags) + { + /* Priority tag - IEEE 802.1Q */ + self->buffer[bufPos++] = 0x81; + self->buffer[bufPos++] = 0x00; - uint8_t tci1 = priority << 5; - tci1 += vlanId / 256; + uint8_t tci1 = priority << 5; + tci1 += vlanId / 256; - uint8_t tci2 = vlanId % 256; + uint8_t tci2 = vlanId % 256; - self->buffer[bufPos++] = tci1; /* Priority + VLAN-ID */ - self->buffer[bufPos++] = tci2; /* VLAN-ID */ - } + self->buffer[bufPos++] = tci1; /* Priority + VLAN-ID */ + self->buffer[bufPos++] = tci2; /* VLAN-ID */ + } - /* EtherType GOOSE */ - self->buffer[bufPos++] = 0x88; - self->buffer[bufPos++] = 0xB8; + /* EtherType GOOSE */ + self->buffer[bufPos++] = 0x88; + self->buffer[bufPos++] = 0xB8; - /* APPID */ - self->buffer[bufPos++] = appId / 256; - self->buffer[bufPos++] = appId % 256; + /* APPID */ + self->buffer[bufPos++] = appId / 256; + self->buffer[bufPos++] = appId % 256; - self->lengthField = bufPos; + self->lengthField = bufPos; - /* Length */ - self->buffer[bufPos++] = 0x00; - self->buffer[bufPos++] = 0x08; + /* Length */ + self->buffer[bufPos++] = 0x00; + self->buffer[bufPos++] = 0x08; - /* Reserved1 */ - self->buffer[bufPos++] = 0x00; - self->buffer[bufPos++] = 0x00; + /* Reserved1 */ + self->buffer[bufPos++] = 0x00; + self->buffer[bufPos++] = 0x00; - /* Reserved2 */ - self->buffer[bufPos++] = 0x00; - self->buffer[bufPos++] = 0x00; + /* Reserved2 */ + self->buffer[bufPos++] = 0x00; + self->buffer[bufPos++] = 0x00; - self->payloadStart = bufPos; + self->payloadStart = bufPos; - return true; + return true; + } + else + { + if (DEBUG_GOOSE_PUBLISHER) + printf("GOOSE_PUBLISHER: Failed to allocate buffer\n"); + return false; + } } else { return false; @@ -290,8 +305,8 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, const char* } static int32_t -createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffer, size_t maxPayloadSize) { - +createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffer, size_t maxPayloadSize) +{ /* Step 1 - calculate length fields */ uint32_t goosePduLength = 0; @@ -326,13 +341,16 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe LinkedList element = LinkedList_getNext(dataSetValues); - while (element) { + while (element) + { MmsValue* dataSetEntry = (MmsValue*) element->data; - if (dataSetEntry) { + if (dataSetEntry) + { dataSetSize += MmsValue_encodeMmsData(dataSetEntry, NULL, 0, false); } - else { + else + { /* TODO encode MMS NULL */ if (DEBUG_GOOSE_PUBLISHER) printf("GOOSE_PUBLISHER: NULL value in data set!\n"); @@ -399,7 +417,8 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe /* Encode data set entries */ element = LinkedList_getNext(dataSetValues); - while (element) { + while (element) + { MmsValue* dataSetEntry = (MmsValue*) element->data; if (dataSetEntry) { @@ -453,7 +472,7 @@ GoosePublisher_publishAndDump(GoosePublisher self, LinkedList dataSet, char *msg int rc = GoosePublisher_publish(self, dataSet); if (rc == 0) - { + { int copied = self->payloadStart + self->payloadLength; if (bufSize < copied) diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index 843b5239..1c6abac2 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -1,7 +1,7 @@ /* * goose_receiver.c * - * Copyright 2014-2022 Michael Zillgith + * Copyright 2014-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -63,7 +63,8 @@ GooseReceiver_createEx(uint8_t* buffer) { GooseReceiver self = (GooseReceiver) GLOBAL_MALLOC(sizeof(struct sGooseReceiver)); - if (self != NULL) { + if (self != NULL) + { self->running = false; self->stop = false; self->interfaceId = NULL; @@ -124,9 +125,20 @@ static void createNewStringFromBufferElement(MmsValue* value, uint8_t* bufferSrc, int elementLength) { value->value.visibleString.buf = (char*) GLOBAL_MALLOC(elementLength + 1); - memcpy(value->value.visibleString.buf, bufferSrc, elementLength); - value->value.visibleString.buf[elementLength] = 0; - value->value.visibleString.size = elementLength; + + if (value->value.visibleString.buf) + { + memcpy(value->value.visibleString.buf, bufferSrc, elementLength); + value->value.visibleString.buf[elementLength] = 0; + value->value.visibleString.size = elementLength; + } + else + { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: failed to allocate memory for visible string\n"); + + value->value.visibleString.size = 0; + } } static GooseParseError @@ -141,24 +153,28 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) GooseParseError pe = GOOSE_PARSE_ERROR_NO_ERROR; uint8_t tag; - while (bufPos < allDataLength) { + while (bufPos < allDataLength) + { tag = buffer[bufPos++]; - if (elementIndex > maxIndex) { + if (elementIndex > maxIndex) + { pe = GOOSE_PARSE_ERROR_OVERFLOW; break; /* from while */ } MmsValue* value = MmsValue_getElement(dataSetValues, elementIndex); - if (value == NULL) { + if (value == NULL) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: type mismatch (element %i not found)\n", elementIndex); return GOOSE_PARSE_ERROR_TYPE_MISMATCH; } bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); - if (bufPos < 0) { + if (bufPos < 0) + { pe = GOOSE_PARSE_ERROR_TAGDECODE; break; /* from while */ } @@ -173,7 +189,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) case 0xa1: /* array */ if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found array\n"); - if (MmsValue_getType(value) == MMS_ARRAY) { + if (MmsValue_getType(value) == MMS_ARRAY) + { if (parseAllData(buffer + bufPos, elementLength, value) != GOOSE_PARSE_ERROR_NO_ERROR) pe = GOOSE_PARSE_ERROR_SUBLEVEL; } @@ -185,7 +202,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) case 0xa2: /* structure */ if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found structure\n"); - if (MmsValue_getType(value) == MMS_STRUCTURE) { + if (MmsValue_getType(value) == MMS_STRUCTURE) + { if (parseAllData(buffer + bufPos, elementLength, value) != GOOSE_PARSE_ERROR_NO_ERROR) pe = GOOSE_PARSE_ERROR_SUBLEVEL; } @@ -208,18 +226,23 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x84: /* BIT STRING */ - if (MmsValue_getType(value) == MMS_BIT_STRING) { + if (MmsValue_getType(value) == MMS_BIT_STRING) + { int padding = buffer[bufPos]; - if (padding > 7) { + if (padding > 7) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: invalid bit-string (padding not plausible)\n"); pe = GOOSE_PARSE_ERROR_INVALID_PADDING; } - else { + else + { int bitStringLength = (8 * (elementLength - 1)) - padding; - if (bitStringLength == value->value.bitString.size) { + + if (bitStringLength == value->value.bitString.size) + { memcpy(value->value.bitString.buf, buffer + bufPos + 1, elementLength - 1); } @@ -234,8 +257,10 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x85: /* integer */ - if (MmsValue_getType(value) == MMS_INTEGER) { - if (elementLength <= value->value.integer->maxSize) { + if (MmsValue_getType(value) == MMS_INTEGER) + { + if (elementLength <= value->value.integer->maxSize) + { value->value.integer->size = elementLength; memcpy(value->value.integer->octets, buffer + bufPos, elementLength); } @@ -249,8 +274,10 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x86: /* unsigned integer */ - if (MmsValue_getType(value) == MMS_UNSIGNED) { - if (elementLength <= value->value.integer->maxSize) { + if (MmsValue_getType(value) == MMS_UNSIGNED) + { + if (elementLength <= value->value.integer->maxSize) + { value->value.integer->size = elementLength; memcpy(value->value.integer->octets, buffer + bufPos, elementLength); } @@ -264,7 +291,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x87: /* Float */ - if (MmsValue_getType(value) == MMS_FLOAT) { + if (MmsValue_getType(value) == MMS_FLOAT) + { if (elementLength == 9) { MmsValue_setDouble(value, BerDecoder_decodeDouble(buffer, bufPos)); } @@ -281,15 +309,19 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x89: /* octet string */ - if (MmsValue_getType(value) == MMS_OCTET_STRING) { - if (elementLength <= abs(value->value.octetString.maxSize)) { + if (MmsValue_getType(value) == MMS_OCTET_STRING) + { + if (elementLength <= abs(value->value.octetString.maxSize)) + { value->value.octetString.size = elementLength; memcpy(value->value.octetString.buf, buffer + bufPos, elementLength); } - else { + else + { uint8_t* newBuf = (uint8_t*)GLOBAL_MALLOC(elementLength); - if (newBuf) { + if (newBuf) + { memcpy(newBuf, buffer + bufPos, elementLength); uint8_t* oldBuf = value->value.octetString.buf; @@ -300,7 +332,6 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) GLOBAL_FREEMEM(oldBuf); } - } } else { @@ -309,14 +340,17 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x8a: /* visible string */ - if (MmsValue_getType(value) == MMS_VISIBLE_STRING) { - - if (value->value.visibleString.buf != NULL) { - if ((int32_t) value->value.visibleString.size >= elementLength) { + if (MmsValue_getType(value) == MMS_VISIBLE_STRING) + { + if (value->value.visibleString.buf != NULL) + { + if ((int32_t) value->value.visibleString.size >= elementLength) + { memcpy(value->value.visibleString.buf, buffer + bufPos, elementLength); value->value.visibleString.buf[elementLength] = 0; } - else { + else + { GLOBAL_FREEMEM(value->value.visibleString.buf); createNewStringFromBufferElement(value, buffer + bufPos, elementLength); @@ -324,7 +358,6 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) } else createNewStringFromBufferElement(value, buffer + bufPos, elementLength); - } else { pe = GOOSE_PARSE_ERROR_TYPE_MISMATCH; @@ -332,7 +365,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x8c: /* binary time */ - if (MmsValue_getType(value) == MMS_BINARY_TIME) { + if (MmsValue_getType(value) == MMS_BINARY_TIME) + { if ((elementLength == 4) || (elementLength == 6)) { memcpy(value->value.binaryTime.buf, buffer + bufPos, elementLength); } @@ -343,7 +377,8 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) break; case 0x91: /* Utctime */ - if (elementLength == 8) { + if (elementLength == 8) + { if (MmsValue_getType(value) == MMS_UTC_TIME) { MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); } @@ -370,13 +405,15 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) elementIndex++; } - if (elementIndex <= maxIndex) { + if (elementIndex <= maxIndex) + { if (pe == GOOSE_PARSE_ERROR_NO_ERROR) { pe = GOOSE_PARSE_ERROR_UNDERFLOW; } } - if (DEBUG_GOOSE_SUBSCRIBER) { + if (DEBUG_GOOSE_SUBSCRIBER) + { switch (pe) { case GOOSE_PARSE_ERROR_UNKNOWN_TAG: printf("GOOSE_SUBSCRIBER: Found unkown tag %02x!\n", tag); @@ -419,11 +456,14 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt MmsValue* dataSetValues = NULL; - while (bufPos < allDataLength) { + while (bufPos < allDataLength) + { uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); - if (bufPos < 0) { + + if (bufPos < 0) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); return 0; @@ -474,11 +514,14 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt elementIndex = 0; bufPos = 0; - while (bufPos < allDataLength) { + while (bufPos < allDataLength) + { uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); - if (bufPos < 0) { + + if (bufPos < 0) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); return 0; @@ -528,22 +571,26 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt case 0x84: /* BIT STRING */ { - if (elementLength > 1) { + if (elementLength > 1) + { int padding = buffer[bufPos]; int rawBitLength = (elementLength - 1) * 8; - if (padding > 7) { + if (padding > 7) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: invalid bit-string (padding not plausible)\n"); goto exit_with_error; } - else { + else + { value = MmsValue_newBitString(rawBitLength - padding); memcpy(value->value.bitString.buf, buffer + bufPos + 1, elementLength - 1); } } - else { + else + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: invalid bit-string\n"); @@ -553,13 +600,15 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt break; case 0x85: /* integer */ - if (elementLength > 8) { - if (DEBUG_GOOSE_SUBSCRIBER) - printf("GOOSE_SUBSCRIBER: unsupported integer size(%i)\n", elementLength); + if (elementLength > 8) + { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: unsupported integer size(%i)\n", elementLength); - goto exit_with_error; + goto exit_with_error; } - else { + else + { value = MmsValue_newInteger(elementLength * 8); memcpy(value->value.integer->octets, buffer + bufPos, elementLength); value->value.integer->size = elementLength; @@ -568,13 +617,15 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt break; case 0x86: /* unsigned integer */ - if (elementLength > 8) { + if (elementLength > 8) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: unsupported unsigned size(%i)\n", elementLength); goto exit_with_error; } - else { + else + { value = MmsValue_newUnsigned(elementLength * 8); memcpy(value->value.integer->octets, buffer + bufPos, elementLength); value->value.integer->size = elementLength; @@ -610,13 +661,16 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt break; case 0x91: /* Utctime */ - if (elementLength == 8) { + if (elementLength == 8) + { value = MmsValue_newUtcTime(0); MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); } else - if (DEBUG_GOOSE_SUBSCRIBER) - printf("GOOSE_SUBSCRIBER: UTCTime element is of wrong size!\n"); + { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: UTCTime element is of wrong size!\n"); + } break; default: @@ -627,7 +681,8 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt bufPos += elementLength; - if (value != NULL) { + if (value != NULL) + { MmsValue_setElement(dataSetValues, elementIndex, value); elementIndex++; } @@ -706,11 +761,13 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) if (subscriber->isObserver) { - if (elementLength > 129) { + if (elementLength > 129) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: gocbRef too long!\n"); } - else { + else + { memcpy(subscriber->goCBRef, buffer + bufPos, elementLength); subscriber->goCBRef[elementLength] = 0; } @@ -751,12 +808,15 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found dataSet\n"); { - if (matchingSubscriber) { - if (elementLength > 129) { + if (matchingSubscriber) + { + if (elementLength > 129) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: datSet too long!\n"); } - else { + else + { memcpy(matchingSubscriber->datSet, buffer + bufPos, elementLength); matchingSubscriber->datSet[elementLength] = 0; } @@ -768,12 +828,15 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found goId\n"); { - if (matchingSubscriber) { - if (elementLength > 129) { + if (matchingSubscriber) + { + if (elementLength > 129) + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: goId too long!\n"); } - else { + else + { memcpy(matchingSubscriber->goId, buffer + bufPos, elementLength); matchingSubscriber->goId[elementLength] = 0; } @@ -859,14 +922,16 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) if (timestampBufPos) MmsValue_setUtcTimeByBuffer(matchingSubscriber->timestamp, timestampBufPos); - else { + else + { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: GOOSE message has no time stamp\n"); MmsValue_setUtcTime(matchingSubscriber->timestamp, 0); } - if (matchingSubscriber->isObserver && matchingSubscriber->dataSetValues != NULL) { + if (matchingSubscriber->isObserver && matchingSubscriber->dataSetValues != NULL) + { MmsValue_delete(matchingSubscriber->dataSetValues); matchingSubscriber->dataSetValues = NULL; } @@ -1119,7 +1184,8 @@ GooseReceiver_stop(GooseReceiver self) void GooseReceiver_destroy(GooseReceiver self) { - if (self) { + if (self) + { #if (CONFIG_MMS_THREADLESS_STACK == 0) if ((self->thread != NULL) && (GooseReceiver_isRunning(self))) GooseReceiver_stop(self); @@ -1147,7 +1213,8 @@ GooseReceiver_startThreadless(GooseReceiver self) else self->ethSocket = Ethernet_createSocket(self->interfaceId, NULL); - if (self->ethSocket != NULL) { + if (self->ethSocket != NULL) + { Ethernet_setProtocolFilter(self->ethSocket, ETH_P_GOOSE); /* set multicast addresses for subscribers */ @@ -1155,14 +1222,17 @@ GooseReceiver_startThreadless(GooseReceiver self) LinkedList element = LinkedList_getNext(self->subscriberList); - while (element != NULL) { + while (element != NULL) + { GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element); - if (subscriber->dstMacSet == false) { + if (subscriber->dstMacSet == false) + { /* no destination MAC address defined -> we have to switch to all multicast mode */ Ethernet_setMode(self->ethSocket, ETHERNET_SOCKET_MODE_ALL_MULTICAST); } - else { + else + { Ethernet_addMulticastAddress(self->ethSocket, subscriber->dstMac); } @@ -1192,7 +1262,8 @@ GooseReceiver_tick(GooseReceiver self) { int packetSize = Ethernet_receivePacket(self->ethSocket, self->buffer, ETH_BUFFER_LENGTH); - if (packetSize > 0) { + if (packetSize > 0) + { parseGooseMessage(self, self->buffer, packetSize); return true; } diff --git a/src/goose/goose_subscriber.c b/src/goose/goose_subscriber.c index 64227127..019c2b7e 100644 --- a/src/goose/goose_subscriber.c +++ b/src/goose/goose_subscriber.c @@ -1,7 +1,7 @@ /* * goose_subscriber.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -40,7 +40,8 @@ GooseSubscriber_create(char* goCbRef, MmsValue* dataSetValues) { GooseSubscriber self = (GooseSubscriber) GLOBAL_CALLOC(1, sizeof(struct sGooseSubscriber)); - if (self) { + if (self) + { StringUtils_copyStringMax(self->goCBRef, 130, goCbRef); self->goCBRefLen = strlen(goCbRef); @@ -97,7 +98,8 @@ GooseSubscriber_setAppId(GooseSubscriber self, uint16_t appId) void GooseSubscriber_destroy(GooseSubscriber self) { - if (self) { + if (self) + { MmsValue_delete(self->timestamp); if (self->dataSetValuesSelfAllocated) @@ -120,19 +122,19 @@ GooseSubscriber_getAppId(GooseSubscriber self) return self->appId; } -char * +char* GooseSubscriber_getGoId(GooseSubscriber self) { return self->goId; } -char * +char* GooseSubscriber_getGoCbRef(GooseSubscriber self) { return self->goCBRef; } -char * +char* GooseSubscriber_getDataSet(GooseSubscriber self) { return self->datSet; diff --git a/src/mms/iso_acse/acse.c b/src/mms/iso_acse/acse.c index 65390969..9f5b4186 100644 --- a/src/mms/iso_acse/acse.c +++ b/src/mms/iso_acse/acse.c @@ -1,7 +1,7 @@ /* * acse.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -43,9 +43,10 @@ checkAuthMechanismName(uint8_t* authMechanism, int authMechLen) { AcseAuthenticationMechanism authenticationMechanism = ACSE_AUTH_NONE; - if (authMechanism != NULL) { - - if (authMechLen == 3) { + if (authMechanism != NULL) + { + if (authMechLen == 3) + { if (memcmp(auth_mech_password_oid, authMechanism, 3) == 0) { authenticationMechanism = ACSE_AUTH_PASSWORD; } @@ -64,11 +65,13 @@ authenticateClient(AcseConnection* self, AcseAuthenticationMechanism mechanism, authParameter->mechanism = mechanism; - if (mechanism == ACSE_AUTH_PASSWORD) { + if (mechanism == ACSE_AUTH_PASSWORD) + { authParameter->value.password.octetString = authValue; authParameter->value.password.passwordLength = authValueLen; } - else if (mechanism == ACSE_AUTH_TLS) { + else if (mechanism == ACSE_AUTH_TLS) + { authParameter->value.certificate.buf = authValue; authParameter->value.certificate.length = authValueLen; } @@ -81,15 +84,15 @@ checkAuthentication(AcseConnection* self, uint8_t* authMechanism, int authMechLe { self->securityToken = NULL; - if (self->authenticator != NULL) { - + if (self->authenticator != NULL) + { AcseAuthenticationMechanism mechanism = checkAuthMechanismName(authMechanism, authMechLen); - if (mechanism == ACSE_AUTH_NONE) { - + if (mechanism == ACSE_AUTH_NONE) + { #if (CONFIG_MMS_SUPPORT_TLS == 1) - if (self->tlsSocket) { - + if (self->tlsSocket) + { int certLen; uint8_t* certBuf = TLSSocket_getPeerCertificate(self->tlsSocket, &certLen); @@ -120,7 +123,8 @@ parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxB bool hasindirectReference = false; bool isDataValid = false; - while (bufPos < maxBufPos) { + while (bufPos < maxBufPos) + { uint8_t tag = buffer[bufPos++]; int len; @@ -129,7 +133,8 @@ parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxB if (len == 0) continue; - if ((bufPos < 0) || (bufPos + len > maxBufPos)) { + if ((bufPos < 0) || (bufPos + len > maxBufPos)) + { *userInfoValid = false; return -1; } @@ -158,7 +163,8 @@ parseUserInformation(AcseConnection* self, uint8_t* buffer, int bufPos, int maxB } } - if (DEBUG_ACSE) { + if (DEBUG_ACSE) + { if (!hasindirectReference) printf("ACSE: User data has no indirect reference!\n"); @@ -184,7 +190,8 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) uint32_t result = 99; - while (bufPos < maxBufPos) { + while (bufPos < maxBufPos) + { uint8_t tag = buffer[bufPos++]; int len; @@ -193,7 +200,8 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) if (len == 0) continue; - if ((bufPos < 0) || (bufPos + len > maxBufPos)) { + if ((bufPos < 0) || (bufPos + len > maxBufPos)) + { if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); return ACSE_ERROR; @@ -222,12 +230,14 @@ parseAarePdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) break; case 0xbe: /* user information */ - if (buffer[bufPos] != 0x28) { + if (buffer[bufPos] != 0x28) + { if (DEBUG_ACSE) printf("ACSE: invalid user info\n"); bufPos += len; } - else { + else + { bufPos++; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); @@ -283,7 +293,8 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) if (len == 0) continue; - if ((bufPos < 0) || (bufPos + len > maxBufPos)) { + if ((bufPos < 0) || (bufPos + len > maxBufPos)) + { if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); return ACSE_ASSOCIATE_FAILED; @@ -304,7 +315,9 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) case 0xa6: /* calling AP title */ { - if (buffer[bufPos] == 0x06) { /* ap-title-form2 */ + if (buffer[bufPos] == 0x06) + { + /* ap-title-form2 */ int innerLength = buffer[bufPos + 1]; @@ -317,7 +330,9 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) case 0xa7: /* calling AE qualifier */ { - if (buffer[bufPos] == 0x02) { /* ae-qualifier-form2 */ + if (buffer[bufPos] == 0x02) + { + /* ae-qualifier-form2 */ int innerLength = buffer[bufPos + 1]; @@ -342,7 +357,8 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) bufPos++; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); - if (bufPos < 0) { + if (bufPos < 0) + { if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); return ACSE_ASSOCIATE_FAILED; @@ -354,17 +370,20 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) break; case 0xbe: /* user information */ - if (buffer[bufPos] != 0x28) { + if (buffer[bufPos] != 0x28) + { if (DEBUG_ACSE) printf("ACSE: invalid user info\n"); bufPos += len; } - else { + else + { bufPos++; bufPos = BerDecoder_decodeLength(buffer, &len, bufPos, maxBufPos); - if (bufPos < 0) { + if (bufPos < 0) + { if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); return ACSE_ASSOCIATE_FAILED; @@ -372,7 +391,8 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) bufPos = parseUserInformation(self, buffer, bufPos, bufPos + len, &userInfoValid); - if (bufPos < 0) { + if (bufPos < 0) + { if (DEBUG_ACSE) printf("ACSE: Invalid PDU!\n"); return ACSE_ASSOCIATE_FAILED; @@ -392,14 +412,16 @@ parseAarqPdu(AcseConnection* self, uint8_t* buffer, int bufPos, int maxBufPos) } } - if (checkAuthentication(self, authMechanism, authMechLen, authValue, authValueLen) == false) { + if (checkAuthentication(self, authMechanism, authMechLen, authValue, authValueLen) == false) + { if (DEBUG_ACSE) printf("ACSE: parseAarqPdu: check authentication failed!\n"); return ACSE_ASSOCIATE_FAILED; } - if (userInfoValid == false) { + if (userInfoValid == false) + { if (DEBUG_ACSE) printf("ACSE: parseAarqPdu: user info invalid!\n"); @@ -848,4 +870,3 @@ AcseConnection_createReleaseResponseMessage(AcseConnection* self, BufferChain wr writeBuffer->length = 2; writeBuffer->nextPart = NULL; } - From 462e9c38512dff3e9172efd22bd41ee300ee0280 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Wed, 3 Jul 2024 17:33:33 +0200 Subject: [PATCH 49/53] -added function to set the number of outstanding calls- IedConnection_setMaxOutstandingCalls (LIB61850-433) --- config/stack_config.h | 14 ++++++ config/stack_config.h.cmake | 14 ++++++ dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 45 ++++++++++++------- src/iec61850/client/ied_connection.c | 34 +++++++++++--- src/iec61850/inc/iec61850_client.h | 10 +++++ .../inc_private/ied_connection_private.h | 1 + src/mms/inc/mms_client_connection.h | 10 +++++ src/mms/inc_private/mms_client_internal.h | 2 + src/mms/inc_private/mms_common_internal.h | 2 - .../iso_mms/client/mms_client_connection.c | 36 +++++++++++---- src/mms/iso_mms/client/mms_client_initiate.c | 22 +++++---- .../iso_mms/server/mms_association_service.c | 12 ++--- 12 files changed, 153 insertions(+), 49 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index bc3045d0..36d0b246 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -240,10 +240,24 @@ /* enable to configure MmsServer at runtime */ #define CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME 1 +/* Define the default number of the maximum outstanding calls allowed by the caller (client) */ +#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING 5 + +/* Define the default number of the maximum outstanding calls allowed by the calling endpoint (server) */ +#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED 5 + /************************************************************************************ * Check configuration for consistency - DO NOT MODIFY THIS PART! ************************************************************************************/ +#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING < 1) +#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING must be greater than 0!" +#endif + +#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED < 1) +#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED must be greater than 0!" +#endif + #if (MMS_JOURNAL_SERVICE != 1) #if (CONFIG_IEC61850_LOG_SERVICE == 1) diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index adfeca96..c61f6e26 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -229,10 +229,24 @@ /* enable to configure MmsServer at runtime */ #define CONFIG_MMS_SERVER_CONFIG_SERVICES_AT_RUNTIME 1 +/* Define the default number of the maximum outstanding calls allowed by the caller (client) */ +#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING 5 + +/* Define the default number of the maximum outstanding calls allowed by the calling endpoint (server) */ +#define CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED 5 + /************************************************************************************ * Check configuration for consistency - DO NOT MODIFY THIS PART! ************************************************************************************/ +#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING < 1) +#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING must be greater than 0!" +#endif + +#if (CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED < 1) +#error "Invalid configuration: CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED must be greater than 0!" +#endif + #if (MMS_JOURNAL_SERVICE != 1) #if (CONFIG_IEC61850_LOG_SERVICE == 1) diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index a39a8a2f..38db124c 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -431,6 +431,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 IedConnection_getRequestTimeout(IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_setMaxOutstandingCalls(IntPtr self, int calling, int called); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_setTimeQuality(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool leapSecondKnown, [MarshalAs(UnmanagedType.I1)] bool clockFailure, [MarshalAs(UnmanagedType.I1)] bool clockNotSynchronized, int subsecondPrecision); @@ -557,11 +560,11 @@ namespace IEC61850 public delegate void StateChangedHandler(IedConnection connection, IedConnectionState newState); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedConnection_installStateChangedHandler(IntPtr connection, InternalStateChangedHandler handler, IntPtr parameter); - + static extern void IedConnection_installStateChangedHandler(IntPtr connection, InternalStateChangedHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void IedServer_ignoreReadAccess(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool ignore); - + static extern void IedServer_ignoreReadAccess(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool ignore); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedServer_ignoreClientRequests(IntPtr self, [MarshalAs(UnmanagedType.I1)] bool ignore); @@ -821,6 +824,16 @@ namespace IEC61850 } } + /// + /// Set the maximum number outstanding calls allowed for this connection + /// + /// the maximum outstanding calls allowed by the caller (client) + /// the maximum outstanding calls allowed by the called endpoint (server) + public void SetMaxOutstandingCalls(int calling, int called) + { + IedConnection_setMaxOutstandingCalls(connection, calling, called); + } + /// /// Gets or sets the maximum size if a PDU (has to be set before calling connect!). /// @@ -1954,22 +1967,22 @@ namespace IEC61850 } } - /// - /// Ignore all MMS requests from clients (for testing purposes) - /// + /// + /// Ignore all MMS requests from clients (for testing purposes) + /// /// when true all requests from clients will be ignored - public void IgnoreClientRequests(bool ignore) - { - IedServer_ignoreClientRequests(connection, ignore); + public void IgnoreClientRequests(bool ignore) + { + IedServer_ignoreClientRequests(connection, ignore); } - /// - /// Temporarily ignore read requests (for testing purposes) - /// + /// + /// Temporarily ignore read requests (for testing purposes) + /// /// true to ignore read requests, false to handle read requests. - public void IgnoreReadAccess(bool ignore) - { - IedServer_ignoreReadAccess(connection, ignore); + public void IgnoreReadAccess(bool ignore) + { + IedServer_ignoreReadAccess(connection, ignore); } /// diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 338df359..23ccf6ff 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -33,7 +33,6 @@ #define DEFAULT_CONNECTION_TIMEOUT 10000 #define DATA_SET_MAX_NAME_LENGTH 64 /* is 32 according to standard! */ -#define OUTSTANDING_CALLS 12 typedef struct sICLogicalDevice { @@ -272,7 +271,7 @@ iedConnection_allocateOutstandingCall(IedConnection self) int i = 0; - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { if (self->outstandingCalls[i].used == false) { @@ -306,7 +305,7 @@ iedConnection_lookupOutstandingCall(IedConnection self, uint32_t invokeId) int i = 0; - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { if ((self->outstandingCalls[i].used) && (self->outstandingCalls[i].invokeId == invokeId)) { @@ -717,7 +716,8 @@ createNewConnectionObject(TLSConfiguration tlsConfig, bool useThreads) self->reportHandlerMutex = Semaphore_create(1); self->outstandingCallsLock = Semaphore_create(1); - self->outstandingCalls = (IedConnectionOutstandingCall) GLOBAL_CALLOC(OUTSTANDING_CALLS, sizeof(struct sIedConnectionOutstandingCall)); + self->maxOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED; + self->outstandingCalls = (IedConnectionOutstandingCall) GLOBAL_CALLOC(CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED, sizeof(struct sIedConnectionOutstandingCall)); self->connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; @@ -762,6 +762,29 @@ IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, in IsoConnectionParameters_setLocalTcpParameters(isoP, localIpAddress, localPort); } +void +IedConnection_setMaxOutstandingCalls(IedConnection self, int calling, int called) +{ + if (calling < 1) + calling = 1; + + if (called < 1) + called = 1; + + if (self->outstandingCalls) + { + GLOBAL_FREEMEM(self->outstandingCalls); + } + + self->maxOutstandingCalled = called; + self->outstandingCalls = (IedConnectionOutstandingCall)GLOBAL_CALLOC(called, sizeof(struct sIedConnectionOutstandingCall)); + + if (self->connection) + { + MmsConnnection_setMaxOutstandingCalls(self->connection, calling, called); + } +} + void IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs) { @@ -771,7 +794,8 @@ IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs) void IedConnection_setRequestTimeout(IedConnection self, uint32_t timeoutInMs) { - if (self->connection) { + if (self->connection) + { MmsConnection_setRequestTimeout(self->connection, timeoutInMs); } } diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 06efd917..5946bd00 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -266,6 +266,16 @@ IedConnection_setLocalAddress(IedConnection self, const char* localIpAddress, in LIB61850_API void IedConnection_setConnectTimeout(IedConnection self, uint32_t timeoutInMs); +/** + * \brief Set the maximum number outstanding calls allowed for this connection + * + * \param self the connection object + * \param calling the maximum outstanding calls allowed by the caller (client) + * \param called the maximum outstanding calls allowed by the called endpoint (server) + */ +LIB61850_API void +IedConnection_setMaxOutstandingCalls(IedConnection self, int calling, int called); + /** * \brief set the request timeout in ms * diff --git a/src/iec61850/inc_private/ied_connection_private.h b/src/iec61850/inc_private/ied_connection_private.h index f0f48247..52467747 100644 --- a/src/iec61850/inc_private/ied_connection_private.h +++ b/src/iec61850/inc_private/ied_connection_private.h @@ -71,6 +71,7 @@ struct sIedConnection Semaphore outstandingCallsLock; IedConnectionOutstandingCall outstandingCalls; + int maxOutstandingCalled; IedConnectionClosedHandler connectionCloseHandler; void* connectionClosedParameter; diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 86f12c9b..7447b33b 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -160,6 +160,16 @@ MmsConnection_setFilestoreBasepath(MmsConnection self, const char* basepath); LIB61850_API void MmsConnection_setRequestTimeout(MmsConnection self, uint32_t timeoutInMs); +/** + * \brief Set the maximum number outstanding calls allowed for this connection + * + * \param self MmsConnection instance to operate on + * \param calling the maximum outstanding calls allowed by the caller (client) + * \param called the maximum outstanding calls allowed by the called endpoint (server) + */ +LIB61850_API void +MmsConnnection_setMaxOutstandingCalls(MmsConnection self, int calling, int called); + /** * \brief Get the request timeout in ms for this connection * diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index aa0e60f3..5a7b2242 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -95,6 +95,8 @@ struct sMmsConnection { Semaphore outstandingCallsLock; MmsOutstandingCall outstandingCalls; + int maxOutstandingCalled; + int maxOutstandingCalling; uint32_t requestTimeout; uint32_t connectTimeout; diff --git a/src/mms/inc_private/mms_common_internal.h b/src/mms/inc_private/mms_common_internal.h index 0c508fbf..61faea58 100644 --- a/src/mms/inc_private/mms_common_internal.h +++ b/src/mms/inc_private/mms_common_internal.h @@ -30,8 +30,6 @@ #include "byte_buffer.h" #include "mms_server.h" -#define DEFAULT_MAX_SERV_OUTSTANDING_CALLING 5 -#define DEFAULT_MAX_SERV_OUTSTANDING_CALLED 5 #define DEFAULT_DATA_STRUCTURE_NESTING_LEVEL 10 typedef struct sMmsOutstandingCall* MmsOutstandingCall; diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 3b5ed25a..e92f8046 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -37,7 +37,6 @@ #define CONFIG_MMS_CONNECTION_DEFAULT_TIMEOUT 5000 #define CONFIG_MMS_CONNECTION_DEFAULT_CONNECT_TIMEOUT 10000 -#define OUTSTANDING_CALLS 10 static void setConnectionState(MmsConnection self, MmsConnectionState newState) @@ -267,7 +266,7 @@ checkForOutstandingCall(MmsConnection self, uint32_t invokeId) Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { if (self->outstandingCalls[i].isUsed) { @@ -291,7 +290,7 @@ addToOutstandingCalls(MmsConnection self, uint32_t invokeId, eMmsOutstandingCall Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { if (self->outstandingCalls[i].isUsed == false) { @@ -319,7 +318,7 @@ removeFromOutstandingCalls(MmsConnection self, uint32_t invokeId) Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { if (self->outstandingCalls[i].isUsed) { @@ -341,7 +340,7 @@ mmsClient_getMatchingObtainFileRequest(MmsConnection self, const char* filename) Semaphore_wait(self->outstandingCallsLock); - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { if (self->outstandingCalls[i].isUsed) { @@ -1088,7 +1087,7 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) int i = 0; - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { Semaphore_wait(self->outstandingCallsLock); @@ -1140,7 +1139,7 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) { int i; - for (i = 0; i < OUTSTANDING_CALLS; i++) + for (i = 0; i < self->maxOutstandingCalled; i++) { Semaphore_wait(self->outstandingCallsLock); @@ -1617,7 +1616,9 @@ MmsConnection_createInternal(TLSConfiguration tlsConfig, bool createThread) self->concludeHandlerParameter = NULL; self->concludeTimeout = 0; - self->outstandingCalls = (MmsOutstandingCall) GLOBAL_CALLOC(OUTSTANDING_CALLS, sizeof(struct sMmsOutstandingCall)); + self->maxOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING; + self->maxOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED; + self->outstandingCalls = (MmsOutstandingCall) GLOBAL_CALLOC(CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED, sizeof(struct sMmsOutstandingCall)); self->isoParameters = IsoConnectionParameters_create(); @@ -1783,6 +1784,25 @@ MmsConnection_setRequestTimeout(MmsConnection self, uint32_t timeoutInMs) self->requestTimeout = timeoutInMs; } +void +MmsConnnection_setMaxOutstandingCalls(MmsConnection self, int calling, int called) +{ + if (calling < 1) + calling = 1; + + if (called < 1) + called = 1; + + if (self->outstandingCalls) + { + GLOBAL_FREEMEM(self->outstandingCalls); + } + + self->maxOutstandingCalling = calling; + self->maxOutstandingCalled = called; + self->outstandingCalls = (MmsOutstandingCall)GLOBAL_CALLOC(called, sizeof(struct sMmsOutstandingCall)); +} + uint32_t MmsConnection_getRequestTimeout(MmsConnection self) { diff --git a/src/mms/iso_mms/client/mms_client_initiate.c b/src/mms/iso_mms/client/mms_client_initiate.c index fdd9b94e..8eb930de 100644 --- a/src/mms/iso_mms/client/mms_client_initiate.c +++ b/src/mms/iso_mms/client/mms_client_initiate.c @@ -39,18 +39,16 @@ static uint8_t servicesSupported[] = { 0xee, 0x1c, 0x00, 0x00, 0x04, 0x08, 0x00, void mmsClient_createInitiateRequest(MmsConnection self, ByteBuffer* message) { - int maxServerOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING; - int maxServerOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED; int dataStructureNestingLevel = DEFAULT_DATA_STRUCTURE_NESTING_LEVEL; uint32_t localDetailSize = BerEncoder_UInt32determineEncodedSize(self->parameters.maxPduSize); uint32_t proposedMaxServerOutstandingCallingSize = - BerEncoder_UInt32determineEncodedSize(maxServerOutstandingCalling); + BerEncoder_UInt32determineEncodedSize(self->maxOutstandingCalling); uint32_t proposedMaxServerOutstandingCalledSize = - BerEncoder_UInt32determineEncodedSize(maxServerOutstandingCalled); + BerEncoder_UInt32determineEncodedSize(self->maxOutstandingCalled); uint32_t dataStructureNestingLevelSize = BerEncoder_UInt32determineEncodedSize(dataStructureNestingLevel); @@ -76,11 +74,11 @@ mmsClient_createInitiateRequest(MmsConnection self, ByteBuffer* message) /* proposedMaxServerOutstandingCalling */ bufPos = BerEncoder_encodeTL(0x81, proposedMaxServerOutstandingCallingSize, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(maxServerOutstandingCalling, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(self->maxOutstandingCalling, buffer, bufPos); /* proposedMaxServerOutstandingCalled */ bufPos = BerEncoder_encodeTL(0x82, proposedMaxServerOutstandingCalledSize, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(maxServerOutstandingCalled, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(self->maxOutstandingCalled, buffer, bufPos); /* proposedDataStructureNestingLevel */ bufPos = BerEncoder_encodeTL(0x83, dataStructureNestingLevelSize, buffer, bufPos); @@ -169,8 +167,8 @@ mmsClient_parseInitiateResponse(MmsConnection self, ByteBuffer* response) { self->parameters.maxPduSize = CONFIG_MMS_MAXIMUM_PDU_SIZE; self->parameters.dataStructureNestingLevel = DEFAULT_DATA_STRUCTURE_NESTING_LEVEL; - self->parameters.maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED; - self->parameters.maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING; + self->parameters.maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED; + self->parameters.maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING; int bufPos = 1; /* ignore tag - already checked */ @@ -203,16 +201,16 @@ mmsClient_parseInitiateResponse(MmsConnection self, ByteBuffer* response) case 0x81: /* proposed-max-serv-outstanding-calling */ self->parameters.maxServOutstandingCalling = BerDecoder_decodeUint32(buffer, length, bufPos); - if (self->parameters.maxServOutstandingCalling > DEFAULT_MAX_SERV_OUTSTANDING_CALLING) - self->parameters.maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING; + if (self->parameters.maxServOutstandingCalling > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING) + self->parameters.maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING; break; case 0x82: /* proposed-max-serv-outstanding-called */ self->parameters.maxServOutstandingCalled = BerDecoder_decodeUint32(buffer, length, bufPos); - if (self->parameters.maxServOutstandingCalled > DEFAULT_MAX_SERV_OUTSTANDING_CALLED) - self->parameters.maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED; + if (self->parameters.maxServOutstandingCalled > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED) + self->parameters.maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED; break; case 0x83: /* proposed-data-structure-nesting-level */ diff --git a/src/mms/iso_mms/server/mms_association_service.c b/src/mms/iso_mms/server/mms_association_service.c index 83a32f3e..667f365d 100644 --- a/src/mms/iso_mms/server/mms_association_service.c +++ b/src/mms/iso_mms/server/mms_association_service.c @@ -330,9 +330,9 @@ parseInitiateRequestPdu(MmsServerConnection self, uint8_t* buffer, int bufPos, i self->dataStructureNestingLevel = DEFAULT_DATA_STRUCTURE_NESTING_LEVEL; - self->maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED; + self->maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED; - self->maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING; + self->maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING; self->negotiatedParameterCBC[0] = 0; self->negotiatedParameterCBC[1] = 0; @@ -367,16 +367,16 @@ parseInitiateRequestPdu(MmsServerConnection self, uint8_t* buffer, int bufPos, i case 0x81: /* proposed-max-serv-outstanding-calling */ self->maxServOutstandingCalling = BerDecoder_decodeUint32(buffer, length, bufPos); - if (self->maxServOutstandingCalling > DEFAULT_MAX_SERV_OUTSTANDING_CALLING) - self->maxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING; + if (self->maxServOutstandingCalling > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING) + self->maxServOutstandingCalling = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLING; break; case 0x82: /* proposed-max-serv-outstanding-called */ self->maxServOutstandingCalled = BerDecoder_decodeUint32(buffer, length, bufPos); - if (self->maxServOutstandingCalled > DEFAULT_MAX_SERV_OUTSTANDING_CALLED) - self->maxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED; + if (self->maxServOutstandingCalled > CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED) + self->maxServOutstandingCalled = CONFIG_DEFAULT_MAX_SERV_OUTSTANDING_CALLED; break; case 0x83: /* proposed-data-structure-nesting-level */ From 2d787755ecfd7e158e8a2331481eb515b1c50112 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 4 Jul 2024 18:12:07 +0100 Subject: [PATCH 50/53] - small code change to make code compile with VS2022 --- src/iec61850/server/mms_mapping/mms_mapping.c | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index a14ed58f..1435df2d 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2499,43 +2499,6 @@ writeAccessGooseControlBlock(MmsMapping* self, MmsDomain* domain, const char* va #endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ -#if 0 -static MmsValue* -checkIfValueBelongsToModelNode(DataAttribute* dataAttribute, MmsValue* value, MmsValue* newValue) -{ - if (dataAttribute->mmsValue == value) - return newValue; - - DataAttribute* child = (DataAttribute*) dataAttribute->firstChild; - - while (child != NULL) { - MmsValue* tmpValue = checkIfValueBelongsToModelNode(child, value, newValue); - - if (tmpValue != NULL) - return tmpValue; - else - child = (DataAttribute*) child->sibling; - } - - if (MmsValue_getType(value) == MMS_STRUCTURE) { - int elementCount = MmsValue_getArraySize(value); - - int i; - for (i = 0; i < elementCount; i++) { - MmsValue* childValue = MmsValue_getElement(value, i); - MmsValue* childNewValue = MmsValue_getElement(newValue, i); - - MmsValue* tmpValue = checkIfValueBelongsToModelNode(dataAttribute, childValue, childNewValue); - - if (tmpValue != NULL) - return tmpValue; - } - } - - return NULL; -} -#endif - static FunctionalConstraint getFunctionalConstraintForWritableNode(char* separator) { @@ -2626,7 +2589,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, /* Access control based on functional constraint */ - char* separator = strchr(variableId, '$'); + char* separator = (char*)strchr(variableId, '$'); if (separator == NULL) return DATA_ACCESS_ERROR_INVALID_ADDRESS; From affe0ed1d0bc9c526445d79fa5d012d949c154e0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 9 Jul 2024 09:40:38 +0100 Subject: [PATCH 51/53] - code format updates --- hal/ethernet/linux/ethernet_linux.c | 61 ++++++++++++++++------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index eaf1897a..ebf66c4d 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -1,7 +1,7 @@ /* * ethernet_linux.c * - * Copyright 2013-2022 Michael Zillgith + * Copyright 2013-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -57,7 +57,8 @@ EthernetHandleSet_new(void) { EthernetHandleSet result = (EthernetHandleSet) GLOBAL_MALLOC(sizeof(struct sEthernetHandleSet)); - if (result != NULL) { + if (result != NULL) + { result->handles = NULL; result->nhandles = 0; } @@ -68,8 +69,8 @@ EthernetHandleSet_new(void) void EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock) { - if (self != NULL && sock != NULL) { - + if (self != NULL && sock != NULL) + { int i = self->nhandles++; self->handles = realloc(self->handles, self->nhandles * sizeof(struct pollfd)); @@ -82,12 +83,14 @@ EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock) void EthernetHandleSet_removeSocket(EthernetHandleSet self, const EthernetSocket sock) { - if ((self != NULL) && (sock != NULL)) { - + if ((self != NULL) && (sock != NULL)) + { int i; - for (i = 0; i < self->nhandles; i++) { - if (self->handles[i].fd == sock->rawSocket) { + for (i = 0; i < self->nhandles; i++) + { + if (self->handles[i].fd == sock->rawSocket) + { memmove(&self->handles[i], &self->handles[i+1], sizeof(struct pollfd) * (self->nhandles - i - 1)); self->nhandles--; return; @@ -128,7 +131,8 @@ getInterfaceIndex(int sock, const char* deviceName) strncpy(ifr.ifr_name, deviceName, IFNAMSIZ - 1); - if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) { + if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) + { if (DEBUG_SOCKET) printf("ETHERNET_LINUX: Failed to get interface index"); return -1; @@ -157,7 +161,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr) int i; - for(i = 0; i < 6; i++ ) + for (i = 0; i < 6; i++ ) { addr[i] = (unsigned char)buffer.ifr_hwaddr.sa_data[i]; } @@ -168,10 +172,12 @@ Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress) { EthernetSocket ethernetSocket = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket)); - if (ethernetSocket) { + if (ethernetSocket) + { ethernetSocket->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); - if (ethernetSocket->rawSocket == -1) { + if (ethernetSocket->rawSocket == -1) + { if (DEBUG_SOCKET) printf("Error creating raw socket!\n"); GLOBAL_FREEMEM(ethernetSocket); @@ -183,7 +189,8 @@ Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress) int ifcIdx = getInterfaceIndex(ethernetSocket->rawSocket, interfaceId); - if (ifcIdx == -1) { + if (ifcIdx == -1) + { Ethernet_destroySocket(ethernetSocket); return NULL; } @@ -209,10 +216,10 @@ Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress) void Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) { - if (ethSocket) { - - if (mode == ETHERNET_SOCKET_MODE_PROMISC) { - + if (ethSocket) + { + if (mode == ETHERNET_SOCKET_MODE_PROMISC) + { struct ifreq ifr; if (ioctl (ethSocket->rawSocket, SIOCGIFFLAGS, &ifr) == -1) @@ -222,7 +229,6 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) return; } - ifr.ifr_flags |= IFF_PROMISC; if (ioctl (ethSocket->rawSocket, SIOCSIFFLAGS, &ifr) == -1) { @@ -231,7 +237,8 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) return; } } - else if (mode == ETHERNET_SOCKET_MODE_ALL_MULTICAST) { + else if (mode == ETHERNET_SOCKET_MODE_ALL_MULTICAST) + { struct ifreq ifr; if (ioctl (ethSocket->rawSocket, SIOCGIFFLAGS, &ifr) == -1) @@ -241,7 +248,6 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) return; } - ifr.ifr_flags |= IFF_ALLMULTI; if (ioctl (ethSocket->rawSocket, SIOCSIFFLAGS, &ifr) == -1) { @@ -250,10 +256,12 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) return; } } - else if (mode == ETHERNET_SOCKET_MODE_HOST_ONLY) { + else if (mode == ETHERNET_SOCKET_MODE_HOST_ONLY) + { ethSocket->socketAddress.sll_pkttype = PACKET_HOST; } - else if (mode == ETHERNET_SOCKET_MODE_MULTICAST) { + else if (mode == ETHERNET_SOCKET_MODE_MULTICAST) + { ethSocket->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST; } } @@ -277,8 +285,8 @@ Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress int res = setsockopt(ethSocket->rawSocket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); - - if (res != 0) { + if (res != 0) + { if (DEBUG_SOCKET) printf("ETHERNET_LINUX: Setting multicast address failed"); } @@ -313,12 +321,12 @@ Ethernet_setProtocolFilter(EthernetSocket ethSocket, uint16_t etherType) } } - /* non-blocking receive */ int Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize) { - if (self->isBind == false) { + if (self->isBind == false) + { if (bind(self->rawSocket, (struct sockaddr*) &self->socketAddress, sizeof(self->socketAddress)) == 0) self->isBind = true; else @@ -347,4 +355,3 @@ Ethernet_isSupported() { return true; } - From c37cc76f9b42a4333ad4eaf998a5fd9cc3139706 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 9 Jul 2024 10:12:26 +0100 Subject: [PATCH 52/53] - Linux Ethernet Socket: set interface to promisc mode by default --- hal/ethernet/linux/ethernet_linux.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index ebf66c4d..9ad6da8d 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -208,6 +208,8 @@ Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress) memcpy(ethernetSocket->socketAddress.sll_addr, destAddress, 6); ethernetSocket->isBind = false; + + Ethernet_setMode(ethernetSocket, ETHERNET_SOCKET_MODE_PROMISC); } return ethernetSocket; From 1ed5ab3a4e2c5120eb22ef2ad5f07b71f472f2da Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 9 Jul 2024 10:16:00 +0100 Subject: [PATCH 53/53] - ethernet_linux: renamed ethernetSocket to self --- hal/ethernet/linux/ethernet_linux.c | 79 +++++++++++++++-------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/hal/ethernet/linux/ethernet_linux.c b/hal/ethernet/linux/ethernet_linux.c index 9ad6da8d..bb989600 100644 --- a/hal/ethernet/linux/ethernet_linux.c +++ b/hal/ethernet/linux/ethernet_linux.c @@ -170,61 +170,61 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr) EthernetSocket Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress) { - EthernetSocket ethernetSocket = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket)); + EthernetSocket self = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket)); - if (ethernetSocket) + if (self) { - ethernetSocket->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + self->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); - if (ethernetSocket->rawSocket == -1) + if (self->rawSocket == -1) { if (DEBUG_SOCKET) printf("Error creating raw socket!\n"); - GLOBAL_FREEMEM(ethernetSocket); + GLOBAL_FREEMEM(self); return NULL; } - ethernetSocket->socketAddress.sll_family = PF_PACKET; - ethernetSocket->socketAddress.sll_protocol = htons(ETH_P_ALL); + self->socketAddress.sll_family = PF_PACKET; + self->socketAddress.sll_protocol = htons(ETH_P_ALL); - int ifcIdx = getInterfaceIndex(ethernetSocket->rawSocket, interfaceId); + int ifcIdx = getInterfaceIndex(self->rawSocket, interfaceId); if (ifcIdx == -1) { - Ethernet_destroySocket(ethernetSocket); + Ethernet_destroySocket(self); return NULL; } - ethernetSocket->socketAddress.sll_ifindex = ifcIdx; + self->socketAddress.sll_ifindex = ifcIdx; - ethernetSocket->socketAddress.sll_hatype = ARPHRD_ETHER; - ethernetSocket->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST; + self->socketAddress.sll_hatype = ARPHRD_ETHER; + self->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST; - ethernetSocket->socketAddress.sll_halen = ETH_ALEN; + self->socketAddress.sll_halen = ETH_ALEN; - memset(ethernetSocket->socketAddress.sll_addr, 0, 8); + memset(self->socketAddress.sll_addr, 0, 8); if (destAddress != NULL) - memcpy(ethernetSocket->socketAddress.sll_addr, destAddress, 6); + memcpy(self->socketAddress.sll_addr, destAddress, 6); - ethernetSocket->isBind = false; + self->isBind = false; - Ethernet_setMode(ethernetSocket, ETHERNET_SOCKET_MODE_PROMISC); + Ethernet_setMode(self, ETHERNET_SOCKET_MODE_PROMISC); } - return ethernetSocket; + return self; } void -Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) +Ethernet_setMode(EthernetSocket self, EthernetSocketMode mode) { - if (ethSocket) + if (self) { if (mode == ETHERNET_SOCKET_MODE_PROMISC) { struct ifreq ifr; - if (ioctl (ethSocket->rawSocket, SIOCGIFFLAGS, &ifr) == -1) + if (ioctl (self->rawSocket, SIOCGIFFLAGS, &ifr) == -1) { if (DEBUG_SOCKET) printf("ETHERNET_LINUX: Problem getting device flags"); @@ -232,7 +232,7 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) } ifr.ifr_flags |= IFF_PROMISC; - if (ioctl (ethSocket->rawSocket, SIOCSIFFLAGS, &ifr) == -1) + if (ioctl (self->rawSocket, SIOCSIFFLAGS, &ifr) == -1) { if (DEBUG_SOCKET) printf("ETHERNET_LINUX: Setting device to promiscuous mode failed"); @@ -243,7 +243,7 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) { struct ifreq ifr; - if (ioctl (ethSocket->rawSocket, SIOCGIFFLAGS, &ifr) == -1) + if (ioctl (self->rawSocket, SIOCGIFFLAGS, &ifr) == -1) { if (DEBUG_SOCKET) printf("ETHERNET_LINUX: Problem getting device flags"); @@ -251,7 +251,7 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) } ifr.ifr_flags |= IFF_ALLMULTI; - if (ioctl (ethSocket->rawSocket, SIOCSIFFLAGS, &ifr) == -1) + if (ioctl (self->rawSocket, SIOCSIFFLAGS, &ifr) == -1) { if (DEBUG_SOCKET) printf("ETHERNET_LINUX: Setting device to promiscuous mode failed"); @@ -260,22 +260,22 @@ Ethernet_setMode(EthernetSocket ethSocket, EthernetSocketMode mode) } else if (mode == ETHERNET_SOCKET_MODE_HOST_ONLY) { - ethSocket->socketAddress.sll_pkttype = PACKET_HOST; + self->socketAddress.sll_pkttype = PACKET_HOST; } else if (mode == ETHERNET_SOCKET_MODE_MULTICAST) { - ethSocket->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST; + self->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST; } } } void -Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress) +Ethernet_addMulticastAddress(EthernetSocket self, uint8_t* multicastAddress) { struct packet_mreq mreq; memset(&mreq, 0, sizeof(struct packet_mreq)); - mreq.mr_ifindex = ethSocket->socketAddress.sll_ifindex; + mreq.mr_ifindex = self->socketAddress.sll_ifindex; mreq.mr_alen = ETH_ALEN; mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_address[0] = multicastAddress[0]; @@ -285,7 +285,7 @@ Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress mreq.mr_address[4] = multicastAddress[4]; mreq.mr_address[5] = multicastAddress[5]; - int res = setsockopt(ethSocket->rawSocket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + int res = setsockopt(self->rawSocket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); if (res != 0) { @@ -295,7 +295,7 @@ Ethernet_addMulticastAddress(EthernetSocket ethSocket, uint8_t* multicastAddress } void -Ethernet_setProtocolFilter(EthernetSocket ethSocket, uint16_t etherType) +Ethernet_setProtocolFilter(EthernetSocket self, uint16_t etherType) { if (etherType == 0x88b8) { @@ -313,13 +313,13 @@ Ethernet_setProtocolFilter(EthernetSocket ethSocket, uint16_t etherType) fprog.len = sizeof(filter) / sizeof(*filter); fprog.filter = filter; - if (setsockopt(ethSocket->rawSocket, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) == -1) + if (setsockopt(self->rawSocket, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) == -1) if (DEBUG_SOCKET) printf("ETHERNET_LINUX: Applying filter failed"); } else { - ethSocket->socketAddress.sll_protocol = htons(etherType); + self->socketAddress.sll_protocol = htons(etherType); } } @@ -339,17 +339,20 @@ Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize) } void -Ethernet_sendPacket(EthernetSocket ethSocket, uint8_t* buffer, int packetSize) +Ethernet_sendPacket(EthernetSocket self, uint8_t* buffer, int packetSize) { - sendto(ethSocket->rawSocket, buffer, packetSize, - 0, (struct sockaddr*) &(ethSocket->socketAddress), sizeof(ethSocket->socketAddress)); + sendto(self->rawSocket, buffer, packetSize, + 0, (struct sockaddr*) &(self->socketAddress), sizeof(self->socketAddress)); } void -Ethernet_destroySocket(EthernetSocket ethSocket) +Ethernet_destroySocket(EthernetSocket self) { - close(ethSocket->rawSocket); - GLOBAL_FREEMEM(ethSocket); + if (self) + { + close(self->rawSocket); + GLOBAL_FREEMEM(self); + } } bool