diff --git a/CHANGELOG b/CHANGELOG index 96ca7ca2..c343f4c9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,18 @@ +Changes to version 0.9.2 +------------------------ +- client/server: support for MMS journals and IEC 61850 log service +- Abstract interface for log storage providers: logging_api.h and LogStorage class +- log storage implementation using sqlite +- server: negative delete data set response is now compatible with new test procedures (TPCL 1.1) +- FileSystem API is now intended to access the complete file system. Path translation for VMD filestore is done by MMS file service implementation (removed FileSystem_setBasePath function) +- added CDC_DPL_create function +- MMS server: fixed race condition when opening/closing connections in multi-threaded configuration. +- client: IedConnection_readObject and IedConnection_getVariableSpecification functions can now be applied to whole LNs +- client: GetLogicalNodeDirectory for data set and log ACSI classes will not use cached model data but request the information from server. This way the client can get up to date information about newly created dynamic data sets +- changed HAL thread implementation for bsd to be compatible with MacOS X 10.10 +- fixed problem with test case sSgN4: return temporarily-unavailable when no EditSG is selected +- fixed bug in ethernet_win32.c + Changes to version 0.9.1 ------------------------ - client: added function MmsConnection_getMmsConnectionParameters diff --git a/CMakeLists.txt b/CMakeLists.txt index e374c898..30066a76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,9 @@ ENABLE_TESTING() set(LIB_VERSION_MAJOR "0") set(LIB_VERSION_MINOR "9") -set(LIB_VERSION_PATCH "1") +set(LIB_VERSION_PATCH "2") + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/") # feature checks include(CheckLibraryExists) @@ -28,7 +30,7 @@ set(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS 5 CACHE STRING "Configure the maximum option(BUILD_EXAMPLES "Build the examples" ON) option(BUILD_PYTHON_BINDINGS "Build Python bindings" OFF) -option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" OFF) +option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" ON) option(CONFIG_MMS_THREADLESS_STACK "Optimize stack for threadless operation (warning: single- or multi-threaded server will not work!)" OFF) # choose the library features which shall be included @@ -38,6 +40,8 @@ option(CONFIG_IEC61850_CONTROL_SERVICE "Build with support for IEC 61850 control option(CONFIG_IEC61850_REPORT_SERVICE "Build with support for IEC 61850 reporting services" ON) +option(CONFIG_IEC61850_LOG_SERVICE "Build with support for IEC 61850 logging services" ON) + option(CONFIG_IEC61850_SETTING_GROUPS "Build with support for IEC 61850 setting group services" ON) option(CONFIG_ACTIVATE_TCP_KEEPALIVE "Activate TCP keepalive" ON) @@ -68,6 +72,7 @@ include_directories( src/mms/inc src/mms/inc_private src/mms/iso_mms/asn1c + src/logging ) set(API_HEADERS @@ -102,6 +107,7 @@ set(API_HEADERS src/goose/goose_publisher.h src/sampled_values/sv_subscriber.h src/sampled_values/sv_publisher.h + src/logging/logging_api.h ) IF(MSVC) @@ -119,7 +125,7 @@ endif(BUILD_EXAMPLES) add_subdirectory(src) -INSTALL(FILES ${API_HEADERS} DESTINATION include/libiec61850) +INSTALL(FILES ${API_HEADERS} DESTINATION include/libiec61850 COMPONENT Development) IF(BUILD_PYTHON_BINDINGS) add_subdirectory(pyiec61850) @@ -128,10 +134,6 @@ ENDIF(BUILD_PYTHON_BINDINGS) IF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") INCLUDE(InstallRequiredSystemLibraries) -SET(CPACK_SET_DESTDIR "on") -SET(CPACK_INSTALL_PREFIX "/usr") -SET(CPACK_GENERATOR "DEB") - SET(CPACK_PACKAGE_DESCRIPTION "IEC 61850 MMS/GOOSE client and server library") SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IEC 61850 MMS/GOOSE client and server library") SET(CPACK_PACKAGE_VENDOR "The libIEC61850 project") @@ -142,10 +144,8 @@ SET(CPACK_PACKAGE_VERSION_PATCH "${LIB_VERSION_PATCH}") SET(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CMAKE_SYSTEM_PROCESSOR}") SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") -SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") -SET(CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) - -SET(CPACK_COMPONENTS_ALL Libraries ApplicationData) +SET(CPACK_COMPONENTS_ALL Libraries Development Applications) +#set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CMAKE_PROJECT_NAME}") INCLUDE(CPack) ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") diff --git a/Makefile b/Makefile index 6bb5ca4a..2aec4a61 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ LIB_SOURCE_DIRS += src/mms/iso_common LIB_SOURCE_DIRS += src/mms/iso_mms/common LIB_SOURCE_DIRS += src/mms/iso_mms/asn1c LIB_SOURCE_DIRS += src/mms/iso_server + +LIB_SOURCE_DIRS += src/logging + ifndef EXCLUDE_ETHERNET_WINDOWS LIB_SOURCE_DIRS += src/goose LIB_SOURCE_DIRS += src/sampled_values @@ -41,7 +44,7 @@ LIB_SOURCE_DIRS += src/hal/filesystem/linux LIB_SOURCE_DIRS += src/hal/time/unix else ifeq ($(HAL_IMPL), BSD) LIB_SOURCE_DIRS += src/hal/socket/bsd -LIB_SOURCE_DIRS += src/hal/thread/linux +LIB_SOURCE_DIRS += src/hal/thread/bsd LIB_SOURCE_DIRS += src/hal/ethernet/bsd LIB_SOURCE_DIRS += src/hal/filesystem/linux LIB_SOURCE_DIRS += src/hal/time/unix @@ -56,6 +59,7 @@ LIB_INCLUDE_DIRS += src/goose LIB_INCLUDE_DIRS += src/sampled_values LIB_INCLUDE_DIRS += src/iec61850/inc LIB_INCLUDE_DIRS += src/iec61850/inc_private +LIB_INCLUDE_DIRS += src/logging ifeq ($(HAL_IMPL), WIN32) LIB_INCLUDE_DIRS += third_party/winpcap/Include endif @@ -97,6 +101,7 @@ LIB_API_HEADER_FILES += src/goose/goose_receiver.h LIB_API_HEADER_FILES += src/goose/goose_publisher.h LIB_API_HEADER_FILES += src/sampled_values/sv_subscriber.h LIB_API_HEADER_FILES += src/sampled_values/sv_publisher.h +LIB_API_HEADER_FILES += src/logging/logging_api.h get_sources_from_directory = $(wildcard $1/*.c) get_sources = $(foreach dir, $1, $(call get_sources_from_directory,$(dir))) diff --git a/README b/README index 5dea8a84..a4231e6b 100644 --- a/README +++ b/README @@ -85,11 +85,28 @@ Note: The ".." at the end of the command line tells cmake where to find the main To select some configuration options you can use ccmake or cmake-gui. + +Building the sqlite logging driver +----------------------------------- + +You can use the driver by including the src/logging/drivers/sqlite/log_storage_sqlite.c file into your application build. + +On Ubuntu Linux (and simpilar Linux distributions) it is enough to install the sqlite dev packages from the standard repository. For other OSes (e.g. Windows) and cross-compiling it is recomended to download the amalagation source code (from https://www.sqlite.org/download.html) of sqlite and copy them to the third_party/sqlite folder. + +On windows the cmake skript will detect the sqlite source code and also creates the example project for logging. + + C# client API -------------- A C#/.NET wrapper and examples and Visual Studio/MonoDevelop project files can be found in the dotnet folder. The examples and the C# wrapper API can be build and run on .NET or Mono. +Experimental Python bindings +---------------------------- + +The experimental Python binding can be created using SWIG with cmake. + +To enable the bindings you have to select the phyton configuration option with ccmake of cmake-gui. Commercial licenses ------------------- diff --git a/config/stack_config.h b/config/stack_config.h index 3e900e11..c414690d 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -153,12 +153,15 @@ /* default reservation time of a setting group control block in s */ #define CONFIG_IEC61850_SG_RESVTMS 300 -/* default results for MMS identify service */ -#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" -#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" -#define CONFIG_DEFAULT_MMS_REVISION "0.9.1" +/* include support for IEC 61850 log services */ +#define CONFIG_IEC61850_LOG_SERVICE 1 -/* MMS virtual file store base path - where file services are looking for files */ +/* overwrite default results for MMS identify service */ +//#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" +//#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" +//#define CONFIG_DEFAULT_MMS_REVISION "0.9.2" + +/* MMS virtual file store base path - where MMS file services are looking for files */ #define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/" /* Maximum number of open file per MMS connection (for MMS file read service) */ @@ -183,6 +186,7 @@ #define MMS_READ_SERVICE 1 #define MMS_WRITE_SERVICE 1 #define MMS_GET_NAME_LIST 1 +#define MMS_JOURNAL_SERVICE 1 #define MMS_GET_VARIABLE_ACCESS_ATTRIBUTES 1 #define MMS_DATA_SET_SERVICE 1 #define MMS_DYNAMIC_DATA_SETS 1 diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index c7ad4c4c..78bf6f88 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -142,6 +142,9 @@ /* default reservation time of a setting group control block in s */ #define CONFIG_IEC61850_SG_RESVTMS 100 +/* include support for IEC 61850 log services */ +#cmakedefine01 CONFIG_IEC61850_LOG_SERVICE + /* default results for MMS identify service */ #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" #define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" @@ -175,6 +178,7 @@ #define MMS_READ_SERVICE 1 #define MMS_WRITE_SERVICE 1 #define MMS_GET_NAME_LIST 1 +#define MMS_JOURNAL_SERVICE 1 #define MMS_GET_VARIABLE_ACCESS_ATTRIBUTES 1 #define MMS_DATA_SET_SERVICE 1 #define MMS_DYNAMIC_DATA_SETS 1 diff --git a/demos/beaglebone/beagle_demo.icd b/demos/beaglebone/beagle_demo.icd index 259c21f5..df06bd57 100644 --- a/demos/beaglebone/beagle_demo.icd +++ b/demos/beaglebone/beagle_demo.icd @@ -1,24 +1,9 @@ - -
+ +
- - - Station bus - 10 - -
-

10.0.0.2

-

255.255.255.0

-

10.0.0.1

-

0001

-

00000001

-

0001

-
-
-
-
- + + @@ -30,17 +15,13 @@ - - + + - - - - @@ -63,22 +44,12 @@ - - - status-only - - - - - - status-only - - + direct-with-normal-security @@ -86,7 +57,7 @@ - sbo-with-normal-security + direct-with-normal-security @@ -96,7 +67,7 @@ - direct-with-enhanced-security + direct-with-normal-security @@ -112,20 +83,20 @@ - + - + - + @@ -167,16 +138,22 @@ + + + + + + - - + + direct-with-normal-security - + @@ -194,7 +171,7 @@ - + status-only @@ -233,31 +210,25 @@ - - - - - - + - - + - + - + @@ -265,15 +236,7 @@ - - - - - - - - - + @@ -286,15 +249,6 @@ - - - - - - - - - @@ -305,12 +259,21 @@ - + + + + + + + + + + on @@ -320,6 +283,12 @@ off + + Ok + Warning + Alarm + + unknown forward @@ -330,9 +299,6 @@ status-only direct-with-normal-security - sbo-with-normal-security - direct-with-enhanced-security - sbo-with-enhanced-security diff --git a/demos/beaglebone/beagle_demo.iid b/demos/beaglebone/beagle_demo.iid new file mode 100644 index 00000000..32b1946c --- /dev/null +++ b/demos/beaglebone/beagle_demo.iid @@ -0,0 +1,331 @@ + + +
+
+ + + Station bus + 10 + +
+

10.0.0.2

+

255.255.255.0

+

10.0.0.1

+

0001

+

00000001

+

0001

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EXT:2015 + + + + + + + + + + + + + + + + + + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + blocked + test + test/blocked + off + + + + Ok + Warning + Alarm + + + + unknown + forward + backward + both + + + + status-only + direct-with-normal-security + + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
diff --git a/demos/beaglebone/static_model.c b/demos/beaglebone/static_model.c index 83951899..50ce8fbb 100644 --- a/demos/beaglebone/static_model.c +++ b/demos/beaglebone/static_model.c @@ -1,7 +1,7 @@ /* * static_model.c * - * automatically generated from beagle_demo.icd + * automatically generated from beagle_demo.iid */ #include "static_model.h" @@ -1114,7 +1114,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_SBO = { NULL, 0, IEC61850_FC_CO, - IEC61850_VISIBLE_STRING_64, + IEC61850_VISIBLE_STRING_129, 0, NULL, 0}; @@ -1257,7 +1257,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Cancel_ctlVal = { NULL, 0, IEC61850_FC_CO, - IEC61850_INT8, + IEC61850_BOOLEAN, 0, NULL, 0}; @@ -2279,7 +2279,7 @@ DataAttribute iedModel_GenericIO_TIM_GAPC1_OpCntRs_Oper_ctlVal = { NULL, 0, IEC61850_FC_CO, - IEC61850_BOOLEAN, + IEC61850_INT32, 0, NULL, 0}; @@ -2404,6 +2404,8 @@ ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, + + IedModel iedModel = { "beagle", &iedModel_GenericIO, @@ -2412,6 +2414,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; @@ -2425,11 +2429,11 @@ iedModel_GenericIO_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0) iedModel_GenericIO_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); -iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(2); +iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); iedModel_GenericIO_GGIO1_SPCSO3_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); -iedModel_GenericIO_GGIO1_DPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(3); +iedModel_GenericIO_GGIO1_DPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); iedModel_GenericIO_TIM_GAPC1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); diff --git a/demos/beaglebone/static_model.h b/demos/beaglebone/static_model.h index a50ade81..8409861e 100644 --- a/demos/beaglebone/static_model.h +++ b/demos/beaglebone/static_model.h @@ -1,7 +1,7 @@ /* * static_model.h * - * automatically generated from beagle_demo.icd + * automatically generated from beagle_demo.iid */ #ifndef STATIC_MODEL_H_ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d5be930d..398e901d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(server_example_complex_array) add_subdirectory(server_example_threadless) add_subdirectory(server_example_61400_25) add_subdirectory(server_example_setting_groups) +add_subdirectory(server_example_logging) add_subdirectory(iec61850_client_example1) add_subdirectory(iec61850_client_example2) add_subdirectory(iec61850_client_example3) @@ -17,6 +18,7 @@ add_subdirectory(iec61850_client_example4) add_subdirectory(iec61850_client_example5) add_subdirectory(iec61850_client_example_files) add_subdirectory(iec61850_client_example_reporting) +add_subdirectory(iec61850_client_example_log) add_subdirectory(mms_client_example1) add_subdirectory(mms_client_example2) add_subdirectory(mms_client_example3) diff --git a/examples/Makefile b/examples/Makefile index 7492d071..38262219 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -10,6 +10,7 @@ EXAMPLE_DIRS += iec61850_client_example3 EXAMPLE_DIRS += iec61850_client_example4 EXAMPLE_DIRS += iec61850_client_example5 EXAMPLE_DIRS += iec61850_client_example_reporting +EXAMPLE_DIRS += iec61850_client_example_log EXAMPLE_DIRS += server_example1 EXAMPLE_DIRS += server_example2 EXAMPLE_DIRS += server_example3 @@ -23,6 +24,7 @@ EXAMPLE_DIRS += server_example_complex_array EXAMPLE_DIRS += server_example_61400_25 EXAMPLE_DIRS += server_example_threadless EXAMPLE_DIRS += server_example_setting_groups +EXAMPLE_DIRS += server_example_logging EXAMPLE_DIRS += goose_subscriber EXAMPLE_DIRS += goose_publisher EXAMPLE_DIRS += sv_subscriber @@ -44,6 +46,7 @@ MODEL_DIRS += server_example_complex_array MODEL_DIRS += server_example_61400_25 MODEL_DIRS += server_example_threadless MODEL_DIRS += server_example_setting_groups +MODEL_DIRS += server_example_logging MODEL_DIRS += iec61850_9_2_LE_example all: examples diff --git a/examples/goose_subscriber/goose_subscriber_example.c b/examples/goose_subscriber/goose_subscriber_example.c index 4295f474..a3ac5744 100644 --- a/examples/goose_subscriber/goose_subscriber_example.c +++ b/examples/goose_subscriber/goose_subscriber_example.c @@ -12,7 +12,7 @@ #include #include #include - +#include static int running = 1; @@ -28,7 +28,7 @@ gooseListener(GooseSubscriber subscriber, void* parameter) printf(" stNum: %u sqNum: %u\n", GooseSubscriber_getStNum(subscriber), GooseSubscriber_getSqNum(subscriber)); printf(" timeToLive: %u\n", GooseSubscriber_getTimeAllowedToLive(subscriber)); - printf(" timestamp: %llu\n", GooseSubscriber_getTimestamp(subscriber)); + printf(" timestamp: %"PRIu64"\n", GooseSubscriber_getTimestamp(subscriber)); MmsValue* values = GooseSubscriber_getDataSetValues(subscriber); diff --git a/examples/iec61850_9_2_LE_example/static_model.c b/examples/iec61850_9_2_LE_example/static_model.c index 5962f83d..ada63aad 100644 --- a/examples/iec61850_9_2_LE_example/static_model.c +++ b/examples/iec61850_9_2_LE_example/static_model.c @@ -947,6 +947,8 @@ SVControlBlock iedModel_MUnn_LLN0_smv0 = {&iedModel_MUnn_LLN0, "MSVCB01", "xxxxM + + IedModel iedModel = { "TEMPLATE", &iedModel_MUnn, @@ -955,6 +957,8 @@ IedModel iedModel = { NULL, &iedModel_MUnn_LLN0_smv0, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/iec61850_client_example_log/CMakeLists.txt b/examples/iec61850_client_example_log/CMakeLists.txt new file mode 100644 index 00000000..0322bd61 --- /dev/null +++ b/examples/iec61850_client_example_log/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_example_log_SRCS + client_example_log.c +) + +IF(MSVC) +set_source_files_properties(${iec61850_client_example_log_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(iec61850_client_example_log + ${iec61850_client_example_log_SRCS} +) + +target_link_libraries(iec61850_client_example_log + iec61850 +) diff --git a/examples/iec61850_client_example_log/Makefile b/examples/iec61850_client_example_log/Makefile new file mode 100644 index 00000000..4c379413 --- /dev/null +++ b/examples/iec61850_client_example_log/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_log +PROJECT_SOURCES = client_example_log.c + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) diff --git a/examples/iec61850_client_example_log/client_example_log.c b/examples/iec61850_client_example_log/client_example_log.c new file mode 100644 index 00000000..83f8e958 --- /dev/null +++ b/examples/iec61850_client_example_log/client_example_log.c @@ -0,0 +1,133 @@ +/* + * client_example_log.c + * + * This example is intended to be used with server_example_logging + */ + +#include "iec61850_client.h" + +#include +#include +#include + +//#include "hal_thread.h" + +static void +printJournalEntries(LinkedList journalEntries) +{ + char buf[1024]; + + LinkedList journalEntriesElem = LinkedList_getNext(journalEntries); + + while (journalEntriesElem != NULL) { + + MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem); + + MmsValue_printToBuffer(MmsJournalEntry_getEntryID(journalEntry), buf, 1024); + printf("EntryID: %s\n", buf); + MmsValue_printToBuffer(MmsJournalEntry_getOccurenceTime(journalEntry), buf, 1024); + printf(" occurence time: %s\n", buf); + + LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables); + + while (journalVariableElem != NULL) { + + MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem); + + printf(" variable-tag: %s\n", MmsJournalVariable_getTag(journalVariable)); + MmsValue_printToBuffer(MmsJournalVariable_getValue(journalVariable), buf, 1024); + printf(" variable-value: %s\n", buf); + + journalVariableElem = LinkedList_getNext(journalVariableElem); + } + + journalEntriesElem = LinkedList_getNext(journalEntriesElem); + } +} + +int main(int argc, char** argv) { + + char* hostname; + int tcpPort = 102; + + if (argc > 1) + hostname = argv[1]; + else + hostname = "localhost"; + + if (argc > 2) + tcpPort = atoi(argv[2]); + + char* logRef = "simpleIOGenericIO/LLN0$EventLog"; + + IedClientError error; + + IedConnection con = IedConnection_create(); + + IedConnection_connect(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) { + + /* read list of logs in LN (optional - if you don't know the existing logs) */ + LinkedList logs = IedConnection_getLogicalNodeDirectory(con, &error, "simpleIOGenericIO/LLN0", ACSI_CLASS_LOG); + + if (error == IED_ERROR_OK) { + printf("Found log in LN simpleIOGenericIO/LLN0:\n"); + + LinkedList log = LinkedList_getNext(logs); + + while (log != NULL) { + char* logName = (char*) LinkedList_getData(log); + + printf(" %s\n", logName); + + log = LinkedList_getNext(log); + } + + LinkedList_destroy(logs); + } + + /* read log control block (using the generic read function) */ + MmsValue* lcbValue = IedConnection_readObject(con, &error, "simpleIOGenericIO/LLN0.EventLog", IEC61850_FC_LG); + + if (error == IED_ERROR_OK) { + + MmsValue* oldEntryTm = MmsValue_getElement(lcbValue, 3); + MmsValue* oldEntry = MmsValue_getElement(lcbValue, 5); + + uint64_t timestamp = MmsValue_getUtcTimeInMs(oldEntryTm); + + bool moreFollows; + + /* + * read the log contents. Be aware that the logRef uses the '$' sign as separator between the LN and + * the log name! This is in contrast to the LCB object reference above. + */ + LinkedList logEntries = IedConnection_queryLogAfter(con, &error, "simpleIOGenericIO/LLN0$EventLog", oldEntry, timestamp, &moreFollows); + + if (error == IED_ERROR_OK) { + printJournalEntries(logEntries); + + LinkedList_destroyDeep(logEntries, (LinkedListValueDeleteFunction) MmsJournalEntry_destroy); + } + else + printf("QueryLog failed (error code: %i)!\n", error); + + //TODO handle moreFollows + + MmsValue_delete(lcbValue); + } + else + printf("Read LCB failed!\n"); + + + IedConnection_abort(con, &error); + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + IedConnection_destroy(con); +} + + diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index cdb9791f..28d43794 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -20,6 +20,7 @@ print_help() printf("-a specify domain for read or write command\n"); printf("-f show file list\n"); printf("-g get file attributes\n"); + printf("-j read journal\n"); } static void @@ -43,6 +44,39 @@ mmsGetFileAttributeHandler (void* parameter, char* filename, uint32_t size, uint printf("DATE: %s\n", gtString); } +static void +printJournalEntries(LinkedList journalEntries) +{ + char buf[1024]; + + LinkedList journalEntriesElem = LinkedList_getNext(journalEntries); + + while (journalEntriesElem != NULL) { + + MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem); + + MmsValue_printToBuffer(MmsJournalEntry_getEntryID(journalEntry), buf, 1024); + printf("EntryID: %s\n", buf); + MmsValue_printToBuffer(MmsJournalEntry_getOccurenceTime(journalEntry), buf, 1024); + printf(" occurence time: %s\n", buf); + + LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables); + + while (journalVariableElem != NULL) { + + MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem); + + printf(" variable-tag: %s\n", MmsJournalVariable_getTag(journalVariable)); + MmsValue_printToBuffer(MmsJournalVariable_getValue(journalVariable), buf, 1024); + printf(" variable-value: %s\n", buf); + + journalVariableElem = LinkedList_getNext(journalVariableElem); + } + + journalEntriesElem = LinkedList_getNext(journalEntriesElem); + } +} + int main(int argc, char** argv) { @@ -53,6 +87,7 @@ int main(int argc, char** argv) { char* domainName = NULL; char* variableName = NULL; char* filename = NULL; + char* journalName = NULL; int readDeviceList = 0; int getDeviceDirectory = 0; @@ -61,11 +96,11 @@ int main(int argc, char** argv) { int readVariable = 0; int showFileList = 0; int getFileAttributes = 0; - + int readJournal = 0; int c; - while ((c = getopt(argc, argv, "ifdh:p:l:t:a:r:g:")) != -1) + while ((c = getopt(argc, argv, "ifdh:p:l:t:a:r:g:j:")) != -1) switch (c) { case 'h': hostname = copyString(optarg); @@ -102,6 +137,11 @@ int main(int argc, char** argv) { filename = copyString(optarg); break; + case 'j': + readJournal = 1; + journalName = copyString(optarg); + break; + default: print_help(); return 0; @@ -146,16 +186,102 @@ int main(int argc, char** argv) { LinkedList variableList = MmsConnection_getDomainVariableNames(con, &error, domainName); - LinkedList element = variableList; + LinkedList element = LinkedList_getNext(variableList); printf("\nMMS domain variables for domain %s\n", domainName); - while ((element = LinkedList_getNext(element)) != NULL) { + while (element != NULL) { char* name = (char*) element->data; printf(" %s\n", name); + + element = LinkedList_getNext(element); + } + + LinkedList_destroy(variableList); + + variableList = MmsConnection_getDomainJournals(con, &error, domainName); + + if (variableList != NULL) { + + element = variableList; + + printf("\nMMS journals for domain %s\n", domainName); + + while ((element = LinkedList_getNext(element)) != NULL) { + char* name = (char*) element->data; + + printf(" %s\n", name); + } + + LinkedList_destroy(variableList); } + } + + if (readJournal) { + + printf(" read journal %s...\n", journalName); + + char* logDomain = journalName; + char* logName = strchr(journalName, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + uint64_t timestamp = Hal_getTimeInMs(); + + MmsValue* startTime = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(startTime, timestamp - 6000000000); + + MmsValue* endTime = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(endTime, timestamp); + + bool moreFollows; + + LinkedList journalEntries = MmsConnection_readJournalTimeRange(con, &error, logDomain, logName, startTime, endTime, + &moreFollows); + + MmsValue_delete(startTime); + MmsValue_delete(endTime); + if (journalEntries != NULL) { + + bool readNext; + + do { + readNext = false; + + LinkedList lastEntry = LinkedList_getLastElement(journalEntries); + MmsJournalEntry lastJournalEntry = (MmsJournalEntry) LinkedList_getData(lastEntry); + + MmsValue* nextEntryId = MmsValue_clone(MmsJournalEntry_getEntryID(lastJournalEntry)); + MmsValue* nextTimestamp = MmsValue_clone(MmsJournalEntry_getOccurenceTime(lastJournalEntry)); + + printJournalEntries(journalEntries); + + LinkedList_destroyDeep(journalEntries, (LinkedListValueDeleteFunction) + MmsJournalEntry_destroy); + + if (moreFollows) { + char buf[100]; + MmsValue_printToBuffer(nextEntryId, buf, 100); + + printf("READ NEXT AFTER entryID: %s ...\n", buf); + + journalEntries = MmsConnection_readJournalStartAfter(con, &error, logDomain, logName, nextTimestamp, nextEntryId, &moreFollows); + + MmsValue_delete(nextEntryId); + MmsValue_delete(nextTimestamp); + + readNext = true; + } + } while ((moreFollows == true) || (readNext == true)); + } + } + else + printf(" Invalid log name!\n"); } if (readVariable) { diff --git a/examples/server_example1/server_example1.c b/examples/server_example1/server_example1.c index 14a0b2a1..f702b1ad 100644 --- a/examples/server_example1/server_example1.c +++ b/examples/server_example1/server_example1.c @@ -49,8 +49,6 @@ int main(int argc, char** argv) { IedServer iedServer = IedServer_create(&iedModel); - // get stored values from persistent storage - // set initial measurement and status values from process /* MMS server will be instructed to start listening to client connections. */ diff --git a/examples/server_example1/static_model.c b/examples/server_example1/static_model.c index 3f4b234b..f4bb1e61 100644 --- a/examples/server_example1/static_model.c +++ b/examples/server_example1/static_model.c @@ -1596,6 +1596,8 @@ ReportControlBlock iedModel_Device1_LLN0_report0 = {&iedModel_Device1_LLN0, "LLN + + IedModel iedModel = { "SampleIED", &iedModel_Device1, @@ -1604,6 +1606,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example2/static_model.c b/examples/server_example2/static_model.c index 2e935caf..91d0dcb0 100644 --- a/examples/server_example2/static_model.c +++ b/examples/server_example2/static_model.c @@ -3586,6 +3586,8 @@ ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "r + + IedModel iedModel = { "ied1", &iedModel_Inverter, @@ -3594,6 +3596,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example3/simpleIO_direct_control.icd b/examples/server_example3/simpleIO_direct_control.icd index df5636b9..b48a383e 100644 --- a/examples/server_example3/simpleIO_direct_control.icd +++ b/examples/server_example3/simpleIO_direct_control.icd @@ -85,7 +85,15 @@ - + + + + + + + + + diff --git a/examples/server_example3/static_model.c b/examples/server_example3/static_model.c index 84faec82..07b5b4a4 100644 --- a/examples/server_example3/static_model.c +++ b/examples/server_example3/static_model.c @@ -1955,6 +1955,16 @@ ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, +extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; +extern LogControlBlock iedModel_GenericIO_LLN0_lcb1; +LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, &iedModel_GenericIO_LLN0_lcb1}; +LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", NULL, NULL, 3, 0, true, true, NULL}; + +extern Log iedModel_GenericIO_LLN0_log0; +extern Log iedModel_GenericIO_LLN0_log1; +Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1}; +Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL}; + IedModel iedModel = { "simpleIO", @@ -1964,6 +1974,8 @@ IedModel iedModel = { NULL, NULL, NULL, + &iedModel_GenericIO_LLN0_lcb0, + &iedModel_GenericIO_LLN0_log0, initializeValues }; diff --git a/examples/server_example4/static_model.c b/examples/server_example4/static_model.c index c2c99bf9..bf02f9d5 100644 --- a/examples/server_example4/static_model.c +++ b/examples/server_example4/static_model.c @@ -1779,6 +1779,8 @@ ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, + + IedModel iedModel = { "simpleIO", &iedModel_GenericIO, @@ -1787,6 +1789,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example5/static_model.c b/examples/server_example5/static_model.c index 2e935caf..91d0dcb0 100644 --- a/examples/server_example5/static_model.c +++ b/examples/server_example5/static_model.c @@ -3586,6 +3586,8 @@ ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "r + + IedModel iedModel = { "ied1", &iedModel_Inverter, @@ -3594,6 +3596,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_61400_25/static_model.c b/examples/server_example_61400_25/static_model.c index b34a03f0..3f180925 100644 --- a/examples/server_example_61400_25/static_model.c +++ b/examples/server_example_61400_25/static_model.c @@ -4134,6 +4134,8 @@ DataAttribute iedModel_WTG_WTUR1_SetTurOp_cmAcs = { + + IedModel iedModel = { "WIND", &iedModel_WTG, @@ -4142,6 +4144,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_complex_array/static_model.c b/examples/server_example_complex_array/static_model.c index 3bc9396a..76006197 100644 --- a/examples/server_example_complex_array/static_model.c +++ b/examples/server_example_complex_array/static_model.c @@ -544,6 +544,8 @@ DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency = { + + IedModel iedModel = { "test", &iedModel_ComplexArray, @@ -552,6 +554,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_config_file/vmd-filestore/model.cfg b/examples/server_example_config_file/model.cfg similarity index 95% rename from examples/server_example_config_file/vmd-filestore/model.cfg rename to examples/server_example_config_file/model.cfg index 58fdeaee..02feeb04 100644 --- a/examples/server_example_config_file/vmd-filestore/model.cfg +++ b/examples/server_example_config_file/model.cfg @@ -1,8 +1,3 @@ -Dynamic model generator -parse data type templates ... -parse IED section ... -parse communication section ... -Found connectedAP accessPoint1 for IED simpleIO MODEL(simpleIO){ LD(GenericIO){ LN(LLN0){ @@ -42,6 +37,10 @@ DE(GGIO1$MX$AnIn4); } RC(EventsRCB01 Events 0 Events 1 24 111 50 1000); RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 24 111 50 1000); +LC(EventLog Events GenericIO/LLN0$EventLog 19 0 0 1) +LC(GeneralLog - - 19 0 0 1) +LOG(GeneralLog) +LOG(EventLog) GC(gcbEvents events Events 2 0 -1 -1 ){ PA(4 273 4096 010ccd010001); } diff --git a/examples/server_example_config_file/simpleIO_direct_control_goose.icd b/examples/server_example_config_file/simpleIO_direct_control_goose.icd index e66f9a95..72552c45 100644 --- a/examples/server_example_config_file/simpleIO_direct_control_goose.icd +++ b/examples/server_example_config_file/simpleIO_direct_control_goose.icd @@ -67,7 +67,19 @@ - + + + + + + + + + + + + + diff --git a/examples/server_example_control/static_model.c b/examples/server_example_control/static_model.c index b538b20e..6921b4f4 100644 --- a/examples/server_example_control/static_model.c +++ b/examples/server_example_control/static_model.c @@ -3874,6 +3874,8 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { + + IedModel iedModel = { "simpleIO", &iedModel_GenericIO, @@ -3882,6 +3884,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_goose/static_model.c b/examples/server_example_goose/static_model.c index 69df5e7f..4c3d6c97 100644 --- a/examples/server_example_goose/static_model.c +++ b/examples/server_example_goose/static_model.c @@ -1909,6 +1909,8 @@ GSEControlBlock iedModel_GenericIO_LLN0_gse1 = {&iedModel_GenericIO_LLN0, "gcbAn + + IedModel iedModel = { "simpleIO", &iedModel_GenericIO, @@ -1917,6 +1919,8 @@ IedModel iedModel = { &iedModel_GenericIO_LLN0_gse0, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_logging/CMakeLists.txt b/examples/server_example_logging/CMakeLists.txt new file mode 100644 index 00000000..598cfaef --- /dev/null +++ b/examples/server_example_logging/CMakeLists.txt @@ -0,0 +1,41 @@ + +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/sqlite/sqlite3.h") +message("Found sqlite source code -> compile sqlite-log driver with static sqlite library") + +include_directories( + . + ${CMAKE_SOURCE_DIR}/third_party/sqlite +) + +set(server_example_SRCS + server_example_logging.c + static_model.c + ${CMAKE_SOURCE_DIR}/src/logging/drivers/sqlite/log_storage_sqlite.c +) + +set(sqlite_SRCS + ${CMAKE_SOURCE_DIR}/third_party/sqlite/sqlite3.c +) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION") + +IF(WIN32) +set_source_files_properties(${server_example_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(server_example_logging + ${server_example_SRCS} + ${sqlite_SRCS} +) + +target_link_libraries(server_example_logging + iec61850 +) + +ELSE() + +message("server-example-logging: sqlite not found") + +ENDIF() + diff --git a/examples/server_example_logging/Makefile b/examples/server_example_logging/Makefile new file mode 100644 index 00000000..7c1e389f --- /dev/null +++ b/examples/server_example_logging/Makefile @@ -0,0 +1,30 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_logging +PROJECT_SOURCES = server_example_logging.c +PROJECT_SOURCES += static_model.c +PROJECT_SOURCES += $(LIBIEC_HOME)/src/logging/drivers/sqlite/log_storage_sqlite.c + +PROJECT_ICD_FILE = simpleIO_direct_control.icd + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +LDLIBS += -lm -lsqlite3 + +CP = cp + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) + + diff --git a/examples/server_example_logging/Makefile.sqliteStatic b/examples/server_example_logging/Makefile.sqliteStatic new file mode 100644 index 00000000..65ff899a --- /dev/null +++ b/examples/server_example_logging/Makefile.sqliteStatic @@ -0,0 +1,34 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_logging +PROJECT_SOURCES = server_example_logging.c +PROJECT_SOURCES += static_model.c +PROJECT_SOURCES += $(LIBIEC_HOME)/src/logging/drivers/sqlite/log_storage_sqlite.c +PROJECT_SOURCES += $(LIBIEC_HOME)/third_party/sqlite/sqlite3.c + +PROJECT_ICD_FILE = simpleIO_direct_control.icd + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +CFLAGS += -I$(LIBIEC_HOME)/third_party/sqlite +CFLAGS += -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DHAVE_USLEEP + +LDLIBS += -lm + +CP = cp + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) + + diff --git a/examples/server_example_logging/README b/examples/server_example_logging/README new file mode 100644 index 00000000..d82ecf4a --- /dev/null +++ b/examples/server_example_logging/README @@ -0,0 +1,8 @@ +BUILD THE EXAMPLE: + +To build the logging example it is required to have sqlite present! + +If you have sqlite installed on the system (including the header files) e.g. in an Ubuntu installation with the sqlite3 package installed, you can simply use the Makefile. + +If you don't have sqlite installed you have to download the sqlite amalgamation package and install the files sqlite3.c, sqlite3.h in the libiec61850/third_party/sqlite folder and use the Makefile.sqliteStatic instead. This will build a version of the example with the sqlite code statically linked. + diff --git a/examples/server_example_logging/server_example_logging.c b/examples/server_example_logging/server_example_logging.c new file mode 100644 index 00000000..76907253 --- /dev/null +++ b/examples/server_example_logging/server_example_logging.c @@ -0,0 +1,234 @@ +/* + * server_example_logging.c + * + * - How to use a server with logging service + * - How to store arbitrary data in a log + * + */ + +#include "iec61850_server.h" +#include "hal_thread.h" +#include +#include +#include +#include + +#include "static_model.h" + +#include "logging_api.h" + +LogStorage SqliteLogStorage_createInstance(const char*); + +/* import IEC 61850 device model created from SCL-File */ +extern IedModel iedModel; + +static int running = 0; +static IedServer iedServer = NULL; + +void +sigint_handler(int signalId) +{ + running = 0; +} + +static ControlHandlerResult +controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test) +{ + if (test) + return CONTROL_RESULT_FAILED; + + if (MmsValue_getType(value) == MMS_BOOLEAN) { + printf("received binary control command: "); + + if (MmsValue_getBoolean(value)) + printf("on\n"); + else + printf("off\n"); + } + else + return CONTROL_RESULT_FAILED; + + uint64_t timeStamp = Hal_getTimeInMs(); + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO1) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO2) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO3) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO4) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal, value); + } + + return CONTROL_RESULT_OK; +} + + +static void +connectionHandler (IedServer self, ClientConnection connection, bool connected, void* parameter) +{ + if (connected) + printf("Connection opened\n"); + else + printf("Connection closed\n"); +} + +static bool +entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow) +{ +#if 0 + if (moreFollow) + printf("Found entry ID:%llu timestamp:%llu\n", entryID, timestamp); +#endif + return true; +} + +static bool +entryDataCallback (void* parameter, const char* dataRef, const uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow) +{ +#if 0 + if (moreFollow) { + printf(" EntryData: ref: %s\n", dataRef); + + MmsValue* value = MmsValue_decodeMmsData(data, 0, dataSize); + + char buffer[256]; + + MmsValue_printToBuffer(value, buffer, 256); + + printf(" value: %s\n", buffer); + + MmsValue_delete(value); + } +#endif + + return true; +} + +int +main(int argc, char** argv) +{ + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); + + iedServer = IedServer_create(&iedModel); + + /* Install handler for operate command */ + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO1); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO2); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO3); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO4); + + IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); + + LogStorage statusLog = SqliteLogStorage_createInstance("log_status.db"); + + IedServer_setLogStorage(iedServer, "GenericIO/LLN0$EventLog", statusLog); + + uint64_t entryID = LogStorage_addEntry(statusLog, Hal_getTimeInMs()); + + MmsValue* value = MmsValue_newIntegerFromInt32(123); + + uint8_t blob[256]; + + int blobSize = MmsValue_encodeMmsData(value, blob, 0, true); + + + LogStorage_addEntryData(statusLog, entryID, "simpleIOGenerioIO/GPIO1$ST$SPCSO1$stVal", blob, blobSize, 0); + + MmsValue_delete(value); + + value = MmsValue_newUtcTimeByMsTime(Hal_getTimeInMs()); + + blobSize = MmsValue_encodeMmsData(value, blob, 0, true); + + MmsValue_delete(value); + + LogStorage_addEntryData(statusLog, entryID, "simpleIOGenerioIO/GPIO1$ST$SPCSO1$t", blob, blobSize, 0); + + LogStorage_getEntries(statusLog, 0, Hal_getTimeInMs(), entryCallback, (LogEntryDataCallback) entryDataCallback, NULL); + + /* MMS server will be instructed to start listening to client connections. */ + IedServer_start(iedServer, 102); + + if (!IedServer_isRunning(iedServer)) { + printf("Starting server failed! Exit.\n"); + IedServer_destroy(iedServer); + exit(-1); + } + + running = 1; + + signal(SIGINT, sigint_handler); + + float t = 0.f; + + while (running) { + uint64_t timestamp = Hal_getTimeInMs(); + + t += 0.1f; + + float an1 = sinf(t); + float an2 = sinf(t + 1.f); + float an3 = sinf(t + 2.f); + float an4 = sinf(t + 3.f); + + IedServer_lockDataModel(iedServer); + + Timestamp iecTimestamp; + + Timestamp_clearFlags(&iecTimestamp); + Timestamp_setTimeInMilliseconds(&iecTimestamp, timestamp); + Timestamp_setLeapSecondKnown(&iecTimestamp, true); + + /* toggle clock-not-synchronized flag in timestamp */ + if (((int) t % 2) == 0) + Timestamp_setClockNotSynchronized(&iecTimestamp, true); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f, an1); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f, an2); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f, an3); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f, an4); + + IedServer_unlockDataModel(iedServer); + + Thread_sleep(100); + } + + /* stop MMS server - close TCP server socket and all client sockets */ + IedServer_stop(iedServer); + + /* Cleanup - free all resources */ + IedServer_destroy(iedServer); + + /* Release connection to database and free resources */ + LogStorage_destroy(statusLog); + +} /* main() */ diff --git a/examples/server_example_logging/simpleIO_direct_control.icd b/examples/server_example_logging/simpleIO_direct_control.icd new file mode 100644 index 00000000..216f1f76 --- /dev/null +++ b/examples/server_example_logging/simpleIO_direct_control.icd @@ -0,0 +1,306 @@ + + +
+
+ + + Station bus + 10 + +
+

10.0.0.2

+

255.255.255.0

+

10.0.0.1

+

0001

+

00000001

+

0001

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + MZ Automation + + + 0.7.3 + + + libiec61850 server example + + + + + + + + status-only + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
diff --git a/examples/server_example_logging/static_model.c b/examples/server_example_logging/static_model.c new file mode 100644 index 00000000..07b5b4a4 --- /dev/null +++ b/examples/server_example_logging/static_model.c @@ -0,0 +1,2003 @@ +/* + * static_model.c + * + * automatically generated from simpleIO_direct_control.icd + */ +#include "static_model.h" + +static void initializeValues(); + +extern DataSet iedModelds_GenericIO_LLN0_Events; +extern DataSet iedModelds_GenericIO_LLN0_Events2; +extern DataSet iedModelds_GenericIO_LLN0_Measurements; + + +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda3; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda0 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO1$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda1 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO2$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda2 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO3$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda3 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO4$stVal", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Events = { + "GenericIO", + "LLN0$Events", + 4, + &iedModelds_GenericIO_LLN0_Events_fcda0, + &iedModelds_GenericIO_LLN0_Events2 +}; + +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda3; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda0 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO1", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda1 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO2", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda2 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO3", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda3 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO4", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Events2 = { + "GenericIO", + "LLN0$Events2", + 4, + &iedModelds_GenericIO_LLN0_Events2_fcda0, + &iedModelds_GenericIO_LLN0_Measurements +}; + +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda3; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda4; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda5; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda6; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda7; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda0 = { + "GenericIO", + false, + "GGIO1$MX$AnIn1$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda1 = { + "GenericIO", + false, + "GGIO1$MX$AnIn1$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda2 = { + "GenericIO", + false, + "GGIO1$MX$AnIn2$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda3 = { + "GenericIO", + false, + "GGIO1$MX$AnIn2$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda4 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda4 = { + "GenericIO", + false, + "GGIO1$MX$AnIn3$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda5 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda5 = { + "GenericIO", + false, + "GGIO1$MX$AnIn3$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda6 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda6 = { + "GenericIO", + false, + "GGIO1$MX$AnIn4$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda7 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda7 = { + "GenericIO", + false, + "GGIO1$MX$AnIn4$q", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Measurements = { + "GenericIO", + "LLN0$Measurements", + 8, + &iedModelds_GenericIO_LLN0_Measurements_fcda0, + NULL +}; + +LogicalDevice iedModel_GenericIO = { + LogicalDeviceModelType, + "GenericIO", + (ModelNode*) &iedModel, + NULL, + (ModelNode*) &iedModel_GenericIO_LLN0 +}; + +LogicalNode iedModel_GenericIO_LLN0 = { + LogicalNodeModelType, + "LLN0", + (ModelNode*) &iedModel_GenericIO, + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, +}; + +DataObject iedModel_GenericIO_LLN0_Mod = { + DataObjectModelType, + "Mod", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_Health_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Health_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Health_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_GenericIO_LLN0, + NULL, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_vendor, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_swRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_d, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_configRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_configRev = { + DataAttributeModelType, + "configRev", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_ldNs, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs = { + DataAttributeModelType, + "ldNs", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + NULL, + NULL, + 0, + IEC61850_FC_EX, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +LogicalNode iedModel_GenericIO_LPHD1 = { + LogicalNodeModelType, + "LPHD1", + (ModelNode*) &iedModel_GenericIO, + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam, +}; + +DataObject iedModel_GenericIO_LPHD1_PhyNam = { + DataObjectModelType, + "PhyNam", + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam_vendor, + 0 +}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LPHD1_PhyHealth = { + DataObjectModelType, + "PhyHealth", + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LPHD1_Proxy = { + DataObjectModelType, + "Proxy", + (ModelNode*) &iedModel_GenericIO_LPHD1, + NULL, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +LogicalNode iedModel_GenericIO_GGIO1 = { + LogicalNodeModelType, + "GGIO1", + (ModelNode*) &iedModel_GenericIO, + NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, +}; + +DataObject iedModel_GenericIO_GGIO1_Mod = { + DataObjectModelType, + "Mod", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_q, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_vendor, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_swRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_d, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn1 = { + DataObjectModelType, + "AnIn1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn2 = { + DataObjectModelType, + "AnIn2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn3 = { + DataObjectModelType, + "AnIn3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn4 = { + DataObjectModelType, + "AnIn4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO1 = { + DataObjectModelType, + "SPCSO1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO2 = { + DataObjectModelType, + "SPCSO2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO3 = { + DataObjectModelType, + "SPCSO3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO4 = { + DataObjectModelType, + "SPCSO4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind1 = { + DataObjectModelType, + "Ind1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind2 = { + DataObjectModelType, + "Ind2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind3 = { + DataObjectModelType, + "Ind3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind4 = { + DataObjectModelType, + "Ind4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +extern ReportControlBlock iedModel_GenericIO_LLN0_report0; +extern ReportControlBlock iedModel_GenericIO_LLN0_report1; +extern ReportControlBlock iedModel_GenericIO_LLN0_report2; +extern ReportControlBlock iedModel_GenericIO_LLN0_report3; +extern ReportControlBlock iedModel_GenericIO_LLN0_report4; +extern ReportControlBlock iedModel_GenericIO_LLN0_report5; +extern ReportControlBlock iedModel_GenericIO_LLN0_report6; + +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, &iedModel_GenericIO_LLN0_report5}; +ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, &iedModel_GenericIO_LLN0_report6}; +ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, NULL}; + + + + +extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; +extern LogControlBlock iedModel_GenericIO_LLN0_lcb1; +LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, &iedModel_GenericIO_LLN0_lcb1}; +LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", NULL, NULL, 3, 0, true, true, NULL}; + +extern Log iedModel_GenericIO_LLN0_log0; +extern Log iedModel_GenericIO_LLN0_log1; +Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1}; +Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL}; + + +IedModel iedModel = { + "simpleIO", + &iedModel_GenericIO, + &iedModelds_GenericIO_LLN0_Events, + &iedModel_GenericIO_LLN0_report0, + NULL, + NULL, + NULL, + &iedModel_GenericIO_LLN0_lcb0, + &iedModel_GenericIO_LLN0_log0, + initializeValues +}; + +static void +initializeValues() +{ + +iedModel_GenericIO_LLN0_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); + +iedModel_GenericIO_LLN0_NamPlt_vendor.mmsValue = MmsValue_newVisibleString("MZ Automation"); + +iedModel_GenericIO_LLN0_NamPlt_swRev.mmsValue = MmsValue_newVisibleString("0.7.3"); + +iedModel_GenericIO_LLN0_NamPlt_d.mmsValue = MmsValue_newVisibleString("libiec61850 server example"); + +iedModel_GenericIO_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); + +iedModel_GenericIO_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_SPCSO3_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_SPCSO4_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); +} diff --git a/examples/server_example_logging/static_model.h b/examples/server_example_logging/static_model.h new file mode 100644 index 00000000..b5670e9f --- /dev/null +++ b/examples/server_example_logging/static_model.h @@ -0,0 +1,301 @@ +/* + * static_model.h + * + * automatically generated from simpleIO_direct_control.icd + */ + +#ifndef STATIC_MODEL_H_ +#define STATIC_MODEL_H_ + +#include +#include "iec61850_model.h" + +extern IedModel iedModel; +extern LogicalDevice iedModel_GenericIO; +extern LogicalNode iedModel_GenericIO_LLN0; +extern DataObject iedModel_GenericIO_LLN0_Mod; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_q; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_t; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_ctlModel; +extern DataObject iedModel_GenericIO_LLN0_Beh; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_q; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_t; +extern DataObject iedModel_GenericIO_LLN0_Health; +extern DataAttribute iedModel_GenericIO_LLN0_Health_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Health_q; +extern DataAttribute iedModel_GenericIO_LLN0_Health_t; +extern DataObject iedModel_GenericIO_LLN0_NamPlt; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_vendor; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_swRev; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_d; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_configRev; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs; +extern LogicalNode iedModel_GenericIO_LPHD1; +extern DataObject iedModel_GenericIO_LPHD1_PhyNam; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor; +extern DataObject iedModel_GenericIO_LPHD1_PhyHealth; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_q; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_t; +extern DataObject iedModel_GenericIO_LPHD1_Proxy; +extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_stVal; +extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_q; +extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_t; +extern LogicalNode iedModel_GenericIO_GGIO1; +extern DataObject iedModel_GenericIO_GGIO1_Mod; +extern DataAttribute iedModel_GenericIO_GGIO1_Mod_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Mod_t; +extern DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel; +extern DataObject iedModel_GenericIO_GGIO1_Beh; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_t; +extern DataObject iedModel_GenericIO_GGIO1_Health; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_t; +extern DataObject iedModel_GenericIO_GGIO1_NamPlt; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_vendor; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_swRev; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_d; +extern DataObject iedModel_GenericIO_GGIO1_AnIn1; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn2; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn3; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn4; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO1; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO2; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO3; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO4; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind1; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind2; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind3; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind4; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; + + + +#define IEDMODEL_GenericIO (&iedModel_GenericIO) +#define IEDMODEL_GenericIO_LLN0 (&iedModel_GenericIO_LLN0) +#define IEDMODEL_GenericIO_LLN0_Mod (&iedModel_GenericIO_LLN0_Mod) +#define IEDMODEL_GenericIO_LLN0_Mod_stVal (&iedModel_GenericIO_LLN0_Mod_stVal) +#define IEDMODEL_GenericIO_LLN0_Mod_q (&iedModel_GenericIO_LLN0_Mod_q) +#define IEDMODEL_GenericIO_LLN0_Mod_t (&iedModel_GenericIO_LLN0_Mod_t) +#define IEDMODEL_GenericIO_LLN0_Mod_ctlModel (&iedModel_GenericIO_LLN0_Mod_ctlModel) +#define IEDMODEL_GenericIO_LLN0_Beh (&iedModel_GenericIO_LLN0_Beh) +#define IEDMODEL_GenericIO_LLN0_Beh_stVal (&iedModel_GenericIO_LLN0_Beh_stVal) +#define IEDMODEL_GenericIO_LLN0_Beh_q (&iedModel_GenericIO_LLN0_Beh_q) +#define IEDMODEL_GenericIO_LLN0_Beh_t (&iedModel_GenericIO_LLN0_Beh_t) +#define IEDMODEL_GenericIO_LLN0_Health (&iedModel_GenericIO_LLN0_Health) +#define IEDMODEL_GenericIO_LLN0_Health_stVal (&iedModel_GenericIO_LLN0_Health_stVal) +#define IEDMODEL_GenericIO_LLN0_Health_q (&iedModel_GenericIO_LLN0_Health_q) +#define IEDMODEL_GenericIO_LLN0_Health_t (&iedModel_GenericIO_LLN0_Health_t) +#define IEDMODEL_GenericIO_LLN0_NamPlt (&iedModel_GenericIO_LLN0_NamPlt) +#define IEDMODEL_GenericIO_LLN0_NamPlt_vendor (&iedModel_GenericIO_LLN0_NamPlt_vendor) +#define IEDMODEL_GenericIO_LLN0_NamPlt_swRev (&iedModel_GenericIO_LLN0_NamPlt_swRev) +#define IEDMODEL_GenericIO_LLN0_NamPlt_d (&iedModel_GenericIO_LLN0_NamPlt_d) +#define IEDMODEL_GenericIO_LLN0_NamPlt_configRev (&iedModel_GenericIO_LLN0_NamPlt_configRev) +#define IEDMODEL_GenericIO_LLN0_NamPlt_ldNs (&iedModel_GenericIO_LLN0_NamPlt_ldNs) +#define IEDMODEL_GenericIO_LPHD1 (&iedModel_GenericIO_LPHD1) +#define IEDMODEL_GenericIO_LPHD1_PhyNam (&iedModel_GenericIO_LPHD1_PhyNam) +#define IEDMODEL_GenericIO_LPHD1_PhyNam_vendor (&iedModel_GenericIO_LPHD1_PhyNam_vendor) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth (&iedModel_GenericIO_LPHD1_PhyHealth) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_stVal (&iedModel_GenericIO_LPHD1_PhyHealth_stVal) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_q (&iedModel_GenericIO_LPHD1_PhyHealth_q) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_t (&iedModel_GenericIO_LPHD1_PhyHealth_t) +#define IEDMODEL_GenericIO_LPHD1_Proxy (&iedModel_GenericIO_LPHD1_Proxy) +#define IEDMODEL_GenericIO_LPHD1_Proxy_stVal (&iedModel_GenericIO_LPHD1_Proxy_stVal) +#define IEDMODEL_GenericIO_LPHD1_Proxy_q (&iedModel_GenericIO_LPHD1_Proxy_q) +#define IEDMODEL_GenericIO_LPHD1_Proxy_t (&iedModel_GenericIO_LPHD1_Proxy_t) +#define IEDMODEL_GenericIO_GGIO1 (&iedModel_GenericIO_GGIO1) +#define IEDMODEL_GenericIO_GGIO1_Mod (&iedModel_GenericIO_GGIO1_Mod) +#define IEDMODEL_GenericIO_GGIO1_Mod_q (&iedModel_GenericIO_GGIO1_Mod_q) +#define IEDMODEL_GenericIO_GGIO1_Mod_t (&iedModel_GenericIO_GGIO1_Mod_t) +#define IEDMODEL_GenericIO_GGIO1_Mod_ctlModel (&iedModel_GenericIO_GGIO1_Mod_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_Beh (&iedModel_GenericIO_GGIO1_Beh) +#define IEDMODEL_GenericIO_GGIO1_Beh_stVal (&iedModel_GenericIO_GGIO1_Beh_stVal) +#define IEDMODEL_GenericIO_GGIO1_Beh_q (&iedModel_GenericIO_GGIO1_Beh_q) +#define IEDMODEL_GenericIO_GGIO1_Beh_t (&iedModel_GenericIO_GGIO1_Beh_t) +#define IEDMODEL_GenericIO_GGIO1_Health (&iedModel_GenericIO_GGIO1_Health) +#define IEDMODEL_GenericIO_GGIO1_Health_stVal (&iedModel_GenericIO_GGIO1_Health_stVal) +#define IEDMODEL_GenericIO_GGIO1_Health_q (&iedModel_GenericIO_GGIO1_Health_q) +#define IEDMODEL_GenericIO_GGIO1_Health_t (&iedModel_GenericIO_GGIO1_Health_t) +#define IEDMODEL_GenericIO_GGIO1_NamPlt (&iedModel_GenericIO_GGIO1_NamPlt) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_vendor (&iedModel_GenericIO_GGIO1_NamPlt_vendor) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_swRev (&iedModel_GenericIO_GGIO1_NamPlt_swRev) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_d (&iedModel_GenericIO_GGIO1_NamPlt_d) +#define IEDMODEL_GenericIO_GGIO1_AnIn1 (&iedModel_GenericIO_GGIO1_AnIn1) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag (&iedModel_GenericIO_GGIO1_AnIn1_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f (&iedModel_GenericIO_GGIO1_AnIn1_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_q (&iedModel_GenericIO_GGIO1_AnIn1_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_t (&iedModel_GenericIO_GGIO1_AnIn1_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn2 (&iedModel_GenericIO_GGIO1_AnIn2) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag (&iedModel_GenericIO_GGIO1_AnIn2_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f (&iedModel_GenericIO_GGIO1_AnIn2_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_q (&iedModel_GenericIO_GGIO1_AnIn2_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_t (&iedModel_GenericIO_GGIO1_AnIn2_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn3 (&iedModel_GenericIO_GGIO1_AnIn3) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag (&iedModel_GenericIO_GGIO1_AnIn3_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f (&iedModel_GenericIO_GGIO1_AnIn3_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_q (&iedModel_GenericIO_GGIO1_AnIn3_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_t (&iedModel_GenericIO_GGIO1_AnIn3_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn4 (&iedModel_GenericIO_GGIO1_AnIn4) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag (&iedModel_GenericIO_GGIO1_AnIn4_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f (&iedModel_GenericIO_GGIO1_AnIn4_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_q (&iedModel_GenericIO_GGIO1_AnIn4_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_t (&iedModel_GenericIO_GGIO1_AnIn4_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1 (&iedModel_GenericIO_GGIO1_SPCSO1) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal (&iedModel_GenericIO_GGIO1_SPCSO1_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_q (&iedModel_GenericIO_GGIO1_SPCSO1_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper (&iedModel_GenericIO_GGIO1_SPCSO1_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO1_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_t (&iedModel_GenericIO_GGIO1_SPCSO1_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2 (&iedModel_GenericIO_GGIO1_SPCSO2) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal (&iedModel_GenericIO_GGIO1_SPCSO2_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_q (&iedModel_GenericIO_GGIO1_SPCSO2_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper (&iedModel_GenericIO_GGIO1_SPCSO2_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO2_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_t (&iedModel_GenericIO_GGIO1_SPCSO2_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3 (&iedModel_GenericIO_GGIO1_SPCSO3) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal (&iedModel_GenericIO_GGIO1_SPCSO3_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_q (&iedModel_GenericIO_GGIO1_SPCSO3_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper (&iedModel_GenericIO_GGIO1_SPCSO3_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO3_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_t (&iedModel_GenericIO_GGIO1_SPCSO3_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4 (&iedModel_GenericIO_GGIO1_SPCSO4) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal (&iedModel_GenericIO_GGIO1_SPCSO4_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_q (&iedModel_GenericIO_GGIO1_SPCSO4_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper (&iedModel_GenericIO_GGIO1_SPCSO4_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_t (&iedModel_GenericIO_GGIO1_SPCSO4_t) +#define IEDMODEL_GenericIO_GGIO1_Ind1 (&iedModel_GenericIO_GGIO1_Ind1) +#define IEDMODEL_GenericIO_GGIO1_Ind1_stVal (&iedModel_GenericIO_GGIO1_Ind1_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind1_q (&iedModel_GenericIO_GGIO1_Ind1_q) +#define IEDMODEL_GenericIO_GGIO1_Ind1_t (&iedModel_GenericIO_GGIO1_Ind1_t) +#define IEDMODEL_GenericIO_GGIO1_Ind2 (&iedModel_GenericIO_GGIO1_Ind2) +#define IEDMODEL_GenericIO_GGIO1_Ind2_stVal (&iedModel_GenericIO_GGIO1_Ind2_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind2_q (&iedModel_GenericIO_GGIO1_Ind2_q) +#define IEDMODEL_GenericIO_GGIO1_Ind2_t (&iedModel_GenericIO_GGIO1_Ind2_t) +#define IEDMODEL_GenericIO_GGIO1_Ind3 (&iedModel_GenericIO_GGIO1_Ind3) +#define IEDMODEL_GenericIO_GGIO1_Ind3_stVal (&iedModel_GenericIO_GGIO1_Ind3_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind3_q (&iedModel_GenericIO_GGIO1_Ind3_q) +#define IEDMODEL_GenericIO_GGIO1_Ind3_t (&iedModel_GenericIO_GGIO1_Ind3_t) +#define IEDMODEL_GenericIO_GGIO1_Ind4 (&iedModel_GenericIO_GGIO1_Ind4) +#define IEDMODEL_GenericIO_GGIO1_Ind4_stVal (&iedModel_GenericIO_GGIO1_Ind4_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind4_q (&iedModel_GenericIO_GGIO1_Ind4_q) +#define IEDMODEL_GenericIO_GGIO1_Ind4_t (&iedModel_GenericIO_GGIO1_Ind4_t) + +#endif /* STATIC_MODEL_H_ */ + diff --git a/examples/server_example_setting_groups/static_model.c b/examples/server_example_setting_groups/static_model.c index 2c10dc43..53fdbb3e 100644 --- a/examples/server_example_setting_groups/static_model.c +++ b/examples/server_example_setting_groups/static_model.c @@ -1108,6 +1108,8 @@ extern SettingGroupControlBlock iedModel_PROT_LLN0_sgcb; SettingGroupControlBlock iedModel_PROT_LLN0_sgcb = {&iedModel_PROT_LLN0, 1, 5, 0, false, 0, 0, NULL}; + + IedModel iedModel = { "DEMO", &iedModel_PROT, @@ -1116,6 +1118,8 @@ IedModel iedModel = { NULL, NULL, &iedModel_PROT_LLN0_sgcb, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_threadless/static_model.c b/examples/server_example_threadless/static_model.c index 1fdf97c7..4f37bdea 100644 --- a/examples/server_example_threadless/static_model.c +++ b/examples/server_example_threadless/static_model.c @@ -1851,6 +1851,10 @@ ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, +extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; +LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, NULL}; + + IedModel iedModel = { "simpleIO", @@ -1860,6 +1864,8 @@ IedModel iedModel = { NULL, NULL, NULL, + &iedModel_GenericIO_LLN0_lcb0, + NULL, initializeValues }; diff --git a/make/stack_includes.mk b/make/stack_includes.mk index 3ddcc443..f1afbeb3 100644 --- a/make/stack_includes.mk +++ b/make/stack_includes.mk @@ -8,3 +8,4 @@ INCLUDES += -I$(LIBIEC_HOME)/src/iec61850/inc_private INCLUDES += -I$(LIBIEC_HOME)/src/hal/inc INCLUDES += -I$(LIBIEC_HOME)/src/goose INCLUDES += -I$(LIBIEC_HOME)/src/sampled_values +INCLUDES += -I$(LIBIEC_HOME)/src/logging diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 266c2799..49938c32 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ set (lib_common_SRCS ./mms/iso_mms/client/mms_client_get_var_access.c ./mms/iso_mms/client/mms_client_common.c ./mms/iso_mms/client/mms_client_read.c +./mms/iso_mms/client/mms_client_journals.c ./mms/iso_mms/server/mms_read_service.c ./mms/iso_mms/server/mms_file_service.c ./mms/iso_mms/server/mms_association_service.c @@ -43,6 +44,8 @@ set (lib_common_SRCS ./mms/iso_mms/server/mms_domain.c ./mms/iso_mms/server/mms_device.c ./mms/iso_mms/server/mms_information_report.c +./mms/iso_mms/server/mms_journal.c +./mms/iso_mms/server/mms_journal_service.c ./mms/iso_mms/server/mms_server_connection.c ./mms/iso_mms/server/mms_write_service.c ./mms/iso_mms/server/mms_get_var_access_service.c @@ -73,6 +76,8 @@ set (lib_common_SRCS ./iec61850/server/mms_mapping/reporting.c ./iec61850/server/mms_mapping/mms_goose.c ./iec61850/server/mms_mapping/mms_sv.c +./iec61850/server/mms_mapping/logging.c +./logging/log_storage.c ) set (lib_asn1c_SRCS @@ -198,7 +203,7 @@ set (lib_windows_SRCS set (lib_bsd_SRCS ./hal/socket/bsd/socket_bsd.c ./hal/ethernet/bsd/ethernet_bsd.c -./hal/thread/linux/thread_linux.c +./hal/thread/bsd/thread_bsd.c ./hal/filesystem/linux/file_provider_linux.c ./hal/time/unix/time.c ) @@ -284,6 +289,24 @@ ENDIF(WIN32) include (GenerateExportHeader) +set(RES_FILES "") +if ( WIN32 ) + # Adding RC resource file for adding information to the archive + set(RES_FILES "${CMAKE_CURRENT_BINARY_DIR}/version.rc") + message(STATUS "Generating RC file : ${RES_FILES}") + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in + ${RES_FILES} + @ONLY) + if( MINGW ) + set(CMAKE_RC_COMPILER_INIT windres) + ENABLE_LANGUAGE(RC) + SET(CMAKE_RC_COMPILE_OBJECT + " -O coff -i -o ") + endif(MINGW) + set(library_SRCS ${library_SRCS} ${RES_FILES}) +endif( WIN32 ) + add_library (iec61850-shared SHARED ${library_SRCS} ) set_target_properties(iec61850-shared PROPERTIES @@ -345,10 +368,9 @@ endif() ENDIF(WITH_WPCAP) - install (TARGETS iec61850 iec61850-shared - RUNTIME DESTINATION bin - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + RUNTIME DESTINATION bin COMPONENT Applications + ARCHIVE DESTINATION lib COMPONENT Libraries + LIBRARY DESTINATION lib COMPONENT Libraries ) diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index d2639c31..97ae2751 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -15,7 +15,7 @@ #include "platform_endian.h" -#define LIBIEC61850_VERSION "0.9.1" +#define LIBIEC61850_VERSION "0.9.2" #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" diff --git a/src/doxygen.config b/src/doxygen.config index 31522e54..6dc0c632 100644 --- a/src/doxygen.config +++ b/src/doxygen.config @@ -18,7 +18,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "libIEC61850" -PROJECT_NUMBER = 0.9.1 +PROJECT_NUMBER = 0.9.2 PROJECT_BRIEF = "Open-source IEC 61850 MMS/GOOSE/SV server and client library" @@ -237,6 +237,7 @@ INPUT += "hal/inc/hal_thread.h" INPUT += "hal/inc/hal_ethernet.h" INPUT += "hal/inc/hal_filesystem.h" INPUT += "hal/inc/hal_time.h" +INPUT += "logging/logging_api.h" INPUT_ENCODING = UTF-8 diff --git a/src/hal/ethernet/win32/ethernet_win32.c b/src/hal/ethernet/win32/ethernet_win32.c index 2770df81..68911eeb 100644 --- a/src/hal/ethernet/win32/ethernet_win32.c +++ b/src/hal/ethernet/win32/ethernet_win32.c @@ -233,7 +233,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr) long interfaceIndex = strtol(interfaceId, &endPtr, 10); - if (endPtr != NULL) { + if ((*interfaceId != '\0') && (*endPtr != '\0')) { printf("Ethernet_getInterfaceMACAddress: invalid interface number %s\n", interfaceId); return; } diff --git a/src/hal/filesystem/linux/file_provider_linux.c b/src/hal/filesystem/linux/file_provider_linux.c index df20c5c5..b807319c 100644 --- a/src/hal/filesystem/linux/file_provider_linux.c +++ b/src/hal/filesystem/linux/file_provider_linux.c @@ -34,52 +34,20 @@ #include "stack_config.h" -#ifndef CONFIG_VIRTUAL_FILESTORE_BASEPATH -#define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/" -#endif - -static char fileBasePath[256]; -static bool fileBasePathInitialized = false; - struct sDirectoryHandle { DIR* handle; }; -static void -createFullPathFromFileName(char* fullPath, char* filename) -{ - if (!fileBasePathInitialized) { - strcpy(fileBasePath, CONFIG_VIRTUAL_FILESTORE_BASEPATH); - fileBasePathInitialized = true; - } - - strcpy(fullPath, fileBasePath); - - if (filename != NULL) - strcat(fullPath, filename); -} - - -void -FileSystem_setBasePath(char* basePath) -{ - strcpy(fileBasePath, basePath); - fileBasePathInitialized = true; -} FileHandle FileSystem_openFile(char* fileName, bool readWrite) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, fileName); - FileHandle newHandle = NULL; if (readWrite) - newHandle = (FileHandle) fopen(fullPath, "w"); + newHandle = (FileHandle) fopen(fileName, "w"); else - newHandle = (FileHandle) fopen(fullPath, "r"); + newHandle = (FileHandle) fopen(fileName, "r"); return newHandle; } @@ -99,11 +67,7 @@ FileSystem_closeFile(FileHandle handle) bool FileSystem_deleteFile(char* filename) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, filename); - - if (remove(fullPath) == 0) + if (remove(filename) == 0) return true; else return false; @@ -112,13 +76,7 @@ FileSystem_deleteFile(char* filename) bool FileSystem_renameFile(char* oldFilename, char* newFilename) { - char oldFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - char newFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(oldFullPath, oldFilename); - createFullPathFromFileName(newFullPath, newFilename); - - if (rename(oldFullPath, newFullPath) == 0) + if (rename(oldFilename, newFilename) == 0) return true; else return false; @@ -130,11 +88,7 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific { struct stat fileStats; - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; - - createFullPathFromFileName(fullPath, filename); - - if (stat(fullPath, &fileStats) == -1) + if (stat(filename, &fileStats) == -1) return false; if (lastModificationTimestamp != NULL) @@ -150,11 +104,7 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific DirectoryHandle FileSystem_openDirectory(char* directoryName) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, directoryName); - - DIR* dirHandle = opendir(fullPath); + DIR* dirHandle = opendir(directoryName); DirectoryHandle handle = NULL; diff --git a/src/hal/filesystem/win32/file_provider_win32.c b/src/hal/filesystem/win32/file_provider_win32.c index 95c42600..21511289 100644 --- a/src/hal/filesystem/win32/file_provider_win32.c +++ b/src/hal/filesystem/win32/file_provider_win32.c @@ -37,15 +37,6 @@ #include -#ifndef CONFIG_VIRTUAL_FILESTORE_BASEPATH -#define CONFIG_VIRTUAL_FILESTORE_BASEPATH ".\\vmd-filestore\\" -#endif - -//static char* fileBasePath = CONFIG_VIRTUAL_FILESTORE_BASEPATH; - -static char fileBasePath[256]; -static bool fileBasePathInitialized = false; - struct sDirectoryHandle { HANDLE handle; WIN32_FIND_DATAW findData; @@ -53,40 +44,16 @@ struct sDirectoryHandle { bool available; }; -static void -createFullPathFromFileName(char* fullPath, char* filename) -{ - if (!fileBasePathInitialized) { - strcpy(fileBasePath, CONFIG_VIRTUAL_FILESTORE_BASEPATH); - fileBasePathInitialized = true; - } - - strcpy(fullPath, fileBasePath); - - if (filename != NULL) - strcat(fullPath, filename); -} - -void -FileSystem_setBasePath(char* basePath) -{ - strcpy(fileBasePath, basePath); - fileBasePathInitialized = true; -} FileHandle FileSystem_openFile(char* fileName, bool readWrite) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, fileName); - FileHandle newHandle = NULL; if (readWrite) - newHandle = (FileHandle) fopen(fullPath, "wb"); + newHandle = (FileHandle) fopen(fileName, "wb"); else - newHandle = (FileHandle) fopen(fullPath, "rb"); + newHandle = (FileHandle) fopen(fileName, "rb"); return newHandle; } @@ -106,13 +73,9 @@ FileSystem_closeFile(FileHandle handle) bool FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModificationTimestamp) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; - - createFullPathFromFileName(fullPath, filename); - WIN32_FILE_ATTRIBUTE_DATA fad; - if (GetFileAttributesEx(fullPath, GetFileExInfoStandard, &fad) == 0) + if (GetFileAttributesEx(filename, GetFileExInfoStandard, &fad) == 0) return false; if (lastModificationTimestamp != NULL) { @@ -136,13 +99,12 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific DirectoryHandle FileSystem_openDirectory(char* directoryName) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, directoryName); - DirectoryHandle dirHandle = (DirectoryHandle) GLOBAL_CALLOC(1, sizeof(struct sDirectoryHandle)); - strcat(fullPath, "\\*"); + char fullPath[MAX_PATH + 1]; + + strncpy(fullPath, directoryName, MAX_PATH - 3); + strncat(fullPath, "\\*", MAX_PATH); /* convert UTF-8 path name to WCHAR */ WCHAR unicodeFullPath[MAX_PATH + 1]; @@ -201,11 +163,7 @@ getNextDirectoryEntry(DirectoryHandle directory, bool* isDirectory) bool FileSystem_deleteFile(char* filename) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, filename); - - if (remove(fullPath) == 0) + if (remove(filename) == 0) return true; else return false; @@ -214,13 +172,7 @@ FileSystem_deleteFile(char* filename) bool FileSystem_renameFile(char* oldFilename, char* newFilename) { - char oldFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - char newFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(oldFullPath, oldFilename); - createFullPathFromFileName(newFullPath, newFilename); - - if (rename(oldFullPath, newFullPath) == 0) + if (rename(oldFilename, newFilename) == 0) return true; else return false; diff --git a/src/hal/thread/bsd/thread_bsd.c b/src/hal/thread/bsd/thread_bsd.c new file mode 100644 index 00000000..df16fc9c --- /dev/null +++ b/src/hal/thread/bsd/thread_bsd.c @@ -0,0 +1,122 @@ +/** + * thread_bsd.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include +#include +#include +#include "hal_thread.h" + +#include "libiec61850_platform_includes.h" + +struct sThread { + ThreadExecutionFunction function; + void* parameter; + pthread_t pthread; + int state; + bool autodestroy; +}; + +Semaphore +Semaphore_create(int initialValue) +{ + char tmpname[] = {"/tmp/libiec61850.XXXXXX"}; + mktemp(tmpname); + Semaphore self = sem_open(tmpname, O_CREAT, 0666, initialValue); + + return self; +} + +/* Wait until semaphore value is more than zero. Then decrease the semaphore value. */ +void +Semaphore_wait(Semaphore self) +{ + sem_wait((sem_t*) self); +} + +void +Semaphore_post(Semaphore self) +{ + sem_post((sem_t*) self); +} + +void +Semaphore_destroy(Semaphore self) +{ + sem_close(self); +} + +Thread +Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy) +{ + Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread)); + + if (thread != NULL) { + thread->parameter = parameter; + thread->function = function; + thread->state = 0; + thread->autodestroy = autodestroy; + } + + return thread; +} + +static void* +destroyAutomaticThread(void* parameter) +{ + Thread thread = (Thread) parameter; + + thread->function(thread->parameter); + + GLOBAL_FREEMEM(thread); + + pthread_exit(NULL); +} + +void +Thread_start(Thread thread) +{ + if (thread->autodestroy == true) { + pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread); + pthread_detach(thread->pthread); + } + else + pthread_create(&thread->pthread, NULL, thread->function, thread->parameter); + + thread->state = 1; +} + +void +Thread_destroy(Thread thread) +{ + if (thread->state == 1) { + pthread_join(thread->pthread, NULL); + } + + GLOBAL_FREEMEM(thread); +} + +void +Thread_sleep(int millies) +{ + usleep(millies * 1000); +} diff --git a/src/iec61850/client/client_report.c b/src/iec61850/client/client_report.c index 20cc3c1b..a0b7c8af 100644 --- a/src/iec61850/client/client_report.c +++ b/src/iec61850/client/client_report.c @@ -31,6 +31,8 @@ #include "libiec61850_platform_includes.h" +#include + struct sClientReport { ReportCallbackFunction callback; @@ -405,7 +407,7 @@ private_IedConnection_handleReport(IedConnection self, MmsValue* value) matchingReport->timestamp = MmsValue_getBinaryTimeAsUtcMs(timeStampValue); if (DEBUG_IED_CLIENT) - printf("DEBUG_IED_CLIENT: report has timestamp %llu\n", matchingReport->timestamp); + printf("DEBUG_IED_CLIENT: report has timestamp %" PRIu64 "\n", matchingReport->timestamp); } inclusionIndex++; diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 7c9eaa04..da745847 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -39,7 +39,6 @@ typedef struct sICLogicalDevice { char* name; LinkedList variables; - LinkedList dataSets; } ICLogicalDevice; struct sClientDataSet @@ -165,12 +164,6 @@ ICLogicalDevice_setVariableList(ICLogicalDevice* self, LinkedList variables) self->variables = variables; } -static void -ICLogicalDevice_setDataSetList(ICLogicalDevice* self, LinkedList dataSets) -{ - self->dataSets = dataSets; -} - static void ICLogicalDevice_destroy(ICLogicalDevice* self) { @@ -179,9 +172,6 @@ ICLogicalDevice_destroy(ICLogicalDevice* self) if (self->variables != NULL) LinkedList_destroy(self->variables); - if (self->dataSets != NULL) - LinkedList_destroy(self->dataSets); - GLOBAL_FREEMEM(self); } @@ -1015,16 +1005,6 @@ IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error break; } - LinkedList dataSets = MmsConnection_getDomainVariableListNames(self->connection, - &mmsError, name); - - if (dataSets != NULL) - ICLogicalDevice_setDataSetList(icLogicalDevice, dataSets); - else { - *error = iedConnection_mapMmsErrorToIedError(mmsError); - break; - } - LinkedList_add(logicalDevices, icLogicalDevice); logicalDevice = LinkedList_getNext(logicalDevice); @@ -1306,6 +1286,91 @@ addVariablesWithFc(char* fc, char* lnName, LinkedList variables, LinkedList lnDi } } +static LinkedList +getLogicalNodeDirectoryLogs(IedConnection self, IedClientError* error, const char* logicalDeviceName, + const char* logicalNodeName) +{ + MmsConnection mmsCon = self->connection; + + MmsError mmsError; + + LinkedList journals = MmsConnection_getDomainJournals(mmsCon, &mmsError, logicalDeviceName); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + + LinkedList logs = LinkedList_create(); + + LinkedList journal = LinkedList_getNext(journals); + + while (journal != NULL) { + + char* journalName = (char*) LinkedList_getData(journal); + + char* logName = strchr(journalName, '$'); + + if (logName != NULL) { + logName[0] = 0; + logName += 1; + + if (strcmp(journalName, logicalNodeName) == 0) { + char* log = copyString(logName); + LinkedList_add(logs, (void*) log); + } + } + + journal = LinkedList_getNext(journal); + } + + LinkedList_destroy(journals); + + return logs; +} + +static LinkedList +getLogicalNodeDirectoryDataSets(IedConnection self, IedClientError* error, const char* logicalDeviceName, + const char* logicalNodeName) +{ + MmsConnection mmsCon = self->connection; + + MmsError mmsError; + + LinkedList dataSets = MmsConnection_getDomainVariableListNames(mmsCon, &mmsError, logicalDeviceName); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + + LinkedList lnDataSets = LinkedList_create(); + + LinkedList dataSet = LinkedList_getNext(dataSets); + + while (dataSet != NULL) { + char* dataSetName = (char*) LinkedList_getData(dataSet); + + char* lnDataSetName = strchr(dataSetName, '$'); + + if (lnDataSetName != NULL) { + lnDataSetName[0] = 0; + lnDataSetName += 1; + + if (strcmp(dataSetName, logicalNodeName) == 0) { + char* lnDataSet = copyString(lnDataSetName); + LinkedList_add(lnDataSets, (void*) lnDataSet); + } + } + + dataSet = LinkedList_getNext(dataSet); + } + + LinkedList_destroy(dataSets); + + return lnDataSets; +} + LinkedList /**/ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, const char* logicalNodeReference, ACSIClass acsiClass) @@ -1317,12 +1382,6 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, return NULL; } - if (self->logicalDevices == NULL) - IedConnection_getDeviceModelFromServer(self, error); - - if (*error != IED_ERROR_OK) - return NULL; - char lnRefCopy[130]; strncpy(lnRefCopy, logicalNodeReference, 129); @@ -1341,6 +1400,18 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, char* logicalNodeName = ldSep + 1; + if (acsiClass == ACSI_CLASS_LOG) + return getLogicalNodeDirectoryLogs(self, error, logicalDeviceName, logicalNodeName); + + if (acsiClass == ACSI_CLASS_DATA_SET) + return getLogicalNodeDirectoryDataSets(self, error, logicalDeviceName, logicalNodeName); + + if (self->logicalDevices == NULL) + IedConnection_getDeviceModelFromServer(self, error); + + if (*error != IED_ERROR_OK) + return NULL; + /* search for logical device */ LinkedList device = LinkedList_getNext(self->logicalDevices); @@ -1440,33 +1511,8 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, addVariablesWithFc("GO", logicalNodeName, ld->variables, lnDirectory); break; - case ACSI_CLASS_DATA_SET: - { - LinkedList dataSet = LinkedList_getNext(ld->dataSets); - - while (dataSet != NULL) { - char* dataSetName = (char*) dataSet->data; - - char* fcPos = strchr(dataSetName, '$'); - - if (fcPos == NULL) - goto next_data_set_element; - - size_t lnNameLen = fcPos - dataSetName; - - if (strlen(logicalNodeName) != lnNameLen) - goto next_data_set_element; - - if (memcmp(dataSetName, logicalNodeName, lnNameLen) != 0) - goto next_data_set_element; - - LinkedList_add(lnDirectory, copyString(fcPos + 1)); - - next_data_set_element: - - dataSet = LinkedList_getNext(dataSet); - } - } + case ACSI_CLASS_LCB: + addVariablesWithFc("LG", logicalNodeName, ld->variables, lnDirectory); break; default: @@ -2204,6 +2250,91 @@ exit_function: return dataSet; } +LinkedList /* */ +IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, + uint64_t startTime, uint64_t endTime, bool* moreFollows) +{ + MmsError mmsError; + + char logRef[130]; + + strncpy(logRef, logReference, 129); + + char* logDomain = logRef; + char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + MmsValue* startTimeMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(startTimeMms, startTime); + + MmsValue* endTimeMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(endTimeMms, endTime); + + LinkedList journalEntries = MmsConnection_readJournalTimeRange(self->connection, &mmsError, logDomain, logName, + startTimeMms, endTimeMms, moreFollows); + + MmsValue_delete(startTimeMms); + MmsValue_delete(endTimeMms); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + else + return journalEntries; + } + else { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return NULL; + } + +} + + +LinkedList /* */ +IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference, + MmsValue* entryID, uint64_t timeStamp, bool* moreFollows) +{ + MmsError mmsError; + + char logRef[130]; + + strncpy(logRef, logReference, 129); + + char* logDomain = logRef; + char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + MmsValue* timeStampMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(timeStampMms, timeStamp); + + LinkedList journalEntries = MmsConnection_readJournalStartAfter(self->connection, &mmsError, logDomain, logName, + timeStampMms, entryID, moreFollows); + + MmsValue_delete(timeStampMms); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + else + return journalEntries; + } + else { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return NULL; + } +} + + MmsConnection IedConnection_getMmsConnection(IedConnection self) diff --git a/src/iec61850/common/iec61850_common.c b/src/iec61850/common/iec61850_common.c index f8e0f016..e16eb9a1 100644 --- a/src/iec61850/common/iec61850_common.c +++ b/src/iec61850/common/iec61850_common.c @@ -133,6 +133,8 @@ FunctionalConstraint_toString(FunctionalConstraint fc) { return "RP"; case IEC61850_FC_BR: return "BR"; + case IEC61850_FC_LG: + return "LG"; default: return NULL; } @@ -212,6 +214,12 @@ FunctionalConstraint_fromString(const char* fcString) return IEC61850_FC_NONE; } + if (fcString[0] == 'L') { + if (fcString[1] == 'G') + return IEC61850_FC_LG; + return IEC61850_FC_NONE; + } + return IEC61850_FC_NONE; } diff --git a/src/iec61850/inc/iec61850_cdc.h b/src/iec61850/inc/iec61850_cdc.h index 6144bea5..139f0182 100644 --- a/src/iec61850/inc/iec61850_cdc.h +++ b/src/iec61850/inc/iec61850_cdc.h @@ -46,12 +46,20 @@ extern "C" { */ #define CDC_OPTION_PICS_SUBST (1 << 0) #define CDC_OPTION_BLK_ENA (1 << 1) + +/** Add d (description) data attribute */ #define CDC_OPTION_DESC (1 << 2) + +/** Add dU (unicode description) data attribute */ #define CDC_OPTION_DESC_UNICODE (1 << 3) +/** Add cdcNs and cdcName required when a CDC is an extension to the standard */ #define CDC_OPTION_AC_DLNDA (1 << 4) + +/** Add dataNs (data namespace) required for extended CDCs */ #define CDC_OPTION_AC_DLN (1 << 5) +/** Add the unit data attribute */ #define CDC_OPTION_UNIT (1 << 6) #define CDC_OPTION_FROZEN_VALUE (1 << 7) @@ -87,6 +95,17 @@ extern "C" { #define CDC_OPTION_ANGLE_REF (1 << 23) +/** Options that are only valid for DPL CDC */ +#define CDC_OPTION_DPL_HWREV (1 << 17) +#define CDC_OPTION_DPL_SWREV (1 << 18) +#define CDC_OPTION_DPL_SERNUM (1 << 19) +#define CDC_OPTION_DPL_MODEL (1 << 20) +#define CDC_OPTION_DPL_LOCATION (1 << 21) + +/** Add mandatory data attributes for LLN0 (e.g. LBL configRef) */ +#define CDC_OPTION_AC_LN0_M (1 << 24) +#define CDC_OPTION_AC_LN0_EX (1 << 25) +#define CDC_OPTION_AC_DLD_M (1 << 26) /** * \brief Control model types @@ -233,10 +252,54 @@ CDC_CMV_create(const char* dataObjectName, ModelNode* parent, uint32_t options); DataObject* CDC_SAV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat); - +/** + * \brief create a new LPL (Logical node name plate) CDC instance (data object) + * + * Allowed parent type is LogicalNode + * + * possible options: + * CDC_OPTION_AC_LN0_M (includes "configRev") + * CDC_OPTION_AC_LN0_EX (includes "ldNs") + * CDC_OPTION_AC_DLD_M (includes "lnNs") + * standard options: + * CDC_OPTION_DESC (includes "d") + * CDC_OPTION_DESC_UNICODE (include "du") + * CDC_OPTION_AC_DLNDA (include "cdcNs" and "cdcName") + * CDC_OPTION_AC_DLN (includes "dataNs") + * + * \param dataObjectName the name of the new object + * \param parent the parent of the new data object (either a LogicalNode or another DataObject) + * \param options bit mask to encode required optional elements + * + * \return new DataObject instance + */ DataObject* CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options); +/** + * \brief create a new DPL (Device name plate) CDC instance (data object) + * + * Allowed parent type is LogicalNode + * + * possible options: + * CDC_OPTION_DPL_HWREV (includes "hwRev") + * CDC_OPTION_DPL_SWREV (includes "swRev") + * CDC_OPTION_DPL_SERNUM (includes "serNum") + * CDC_OPTION_DPL_MODEL (includes "model") + * CDC_OPTION_DPL_LOCATION (includes "location") + * standard options: + * CDC_OPTION_AC_DLNDA (include "cdcNs" and "cdcName") + * CDC_OPTION_AC_DLN (includes "dataNs") + * + * \param dataObjectName the name of the new object + * \param parent the parent of the new data object (either a LogicalNode or another DataObject) + * \param options bit mask to encode required optional elements + * + * \return new DataObject instance + */ +DataObject* +CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options); + DataObject* CDC_HST_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint16_t maxPts); diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index b5ce446c..47d46180 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1661,7 +1661,10 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, /** * \brief returns the directory of the given logical node (LN) containing elements of the specified ACSI class * - * Implementation of the GetLogicalNodeDirectory ACSI service. + * Implementation of the GetLogicalNodeDirectory ACSI service. In contrast to the ACSI description this + * function does not always creates a request to the server. For most ACSI classes it simply accesses the + * data model that was retrieved before. An exception to this rule are the ACSI classes ACSI_CLASS_DATASET and + * ACSI_CLASS_LOG. Both always perform a request to the server. * * \param self the connection object * \param error the error code if an error occurs @@ -1744,6 +1747,58 @@ MmsVariableSpecification* IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* dataAttributeReference, FunctionalConstraint fc); +/** @} */ + +/** + * @defgroup IEC61850_CLIENT_LOG_SERVICE Log service related functions, data types, and definitions + * + * @{ + */ + +/** + * \brief Implementation of the QueryLogByTime ACSI service + * + * Read log entries from a log at the server. The log entries to read are specified by + * a starting time and an end time. If the complete range does not fit in a single MMS message + * the moreFollows flag will be set to true, to indicate that more entries are available for the + * specified time range. + * + * \param self the connection object + * \param error the error code if an error occurs + * \param logReference log object reference in the form /$ + * \param startTime as millisecond UTC timestamp + * \param endTime as millisecond UTC timestamp + * \param moreFollows (output value) indicates that more entries are available that match the specification. + * + * \return list of MmsJournalEntry objects matching the specification + */ +LinkedList /* */ +IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, + uint64_t startTime, uint64_t endTime, bool* moreFollows); + +/** + * \brief Implementation of the QueryLogAfter ACSI service + * + * Read log entries from a log at the server following the entry with the specified entryID and timestamp. + * If the complete range does not fit in a single MMS message + * the moreFollows flag will be set to true, to indicate that more entries are available for the + * specified time range. + * + * \param self the connection object + * \param error the error code if an error occurs + * \param logReference log object reference in the form /$ + * \param entryID usually the entryID of the last received entry + * \param timeStamp as millisecond UTC timestamp + * \param moreFollows (output value) indicates that more entries are available that match the specification. + * + * \return list of MmsJournalEntry objects matching the specification + */ +LinkedList /* */ +IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference, + MmsValue* entryID, uint64_t timeStamp, bool* moreFollows); + + + /** @} */ /** diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index 938c9d17..5b73f337 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -30,6 +30,7 @@ extern "C" { #include "libiec61850_common_api.h" +#include "logging_api.h" /** * @defgroup iec61850_common_api_group IEC 61850 API common parts @@ -233,6 +234,8 @@ typedef enum eFunctionalConstraint { IEC61850_FC_RP = 15, /** Buffered report */ IEC61850_FC_BR = 16, + /** Log control blocks */ + IEC61850_FC_LG = 17, /** All FCs - wildcard value */ IEC61850_FC_ALL = 99, diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index 967c764f..ee011dbe 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -146,7 +146,7 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type * \param parent the parent LN. * \param rptId of the report. If NULL the default report ID (object reference) is used. * \param isBuffered true for a buffered RCB - false for unbuffered RCB - * \param dataSetName name (object reference) of the default data set or NULL if no data + * \param dataSetName name (object reference) of the default data set or NULL if no data set * is set by default * \param confRef the configuration revision * \param trgOps the trigger options supported by this RCB (bit set) @@ -160,6 +160,38 @@ ReportControlBlock* ReportControlBlock_create(const char* name, LogicalNode* parent, char* rptId, bool isBuffered, char* dataSetName, uint32_t confRef, uint8_t trgOps, uint8_t options, uint32_t bufTm, uint32_t intgPd); +/** + * \brief create a new log control block (LCB) + * + * Create a new log control block (LCB) and add it to the given logical node (LN). + * + * \param name name of the LCB relative to the parent LN + * \param parent the parent LN. + * \param dataSetName name (object reference) of the default data set or NULL if no data set + * is set by default + * \param logRef name (object reference) of the default log or NULL if no log is set by default. THe LDname doesn't contain the IED name! + * \param trgOps the trigger options supported by this LCB (bit set) + * \param intgPd integrity period in milliseconds + * \param logEna if true the log will be enabled by default, false otherwise + * \param reasonCode if true the reasonCode will be included in the log (this is always true in MMS mapping) + * + * \return the new LCB instance + */ +LogControlBlock* +LogControlBlock_create(const char* name, LogicalNode* parent, char* dataSetName, char* logRef, uint8_t trgOps, + uint32_t intgPd, bool logEna, bool reasonCode); + +/** + * \brief create a log (used by the IEC 61850 log service) + * + * \param name name of the LOG relative to the parent LN + * \param parent the parent LN + * + * \return the new LOG instance + */ +Log* +Log_create(const char* name, LogicalNode* parent); + /** * \brief create a setting group control block (SGCB) * diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 052ba95a..3a99b086 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -1,7 +1,7 @@ /* * model.h * - * Copyright 2013, 2014, 2015 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -84,6 +84,8 @@ typedef struct sSVControlBlock SVControlBlock; typedef struct sLogControlBlock LogControlBlock; +typedef struct sLog Log; + typedef enum { IEC61850_BOOLEAN = 0,/* int */ IEC61850_INT8 = 1, /* int8_t */ @@ -167,6 +169,8 @@ struct sIedModel { GSEControlBlock* gseCBs; SVControlBlock* svCBs; SettingGroupControlBlock* sgcbs; + LogControlBlock* lcbs; + Log* logs; void (*initializer) (void); }; @@ -266,8 +270,20 @@ struct sLogControlBlock { char* logRef; /* object reference to the journal. Defaults to /$GeneralLog */ uint8_t trgOps; /* TrgOps - trigger conditions */ - uint8_t options; /* OptFlds */ uint32_t intPeriod; /* IntgPd - integrity period */ + + bool logEna; /* enable log by default */ + bool reasonCode; /* include reason code in log */ + + LogControlBlock* sibling; /* next control block in list or NULL if this is the last entry */ +}; + +struct sLog { + LogicalNode* parent; + + char* name; + + Log* sibling; /* next log instance in list or NULL if this is the last entry */ }; struct sSettingGroupControlBlock { diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 0c9f7ca1..338f76bf 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -650,6 +650,9 @@ IedServer_updateQuality(IedServer self, DataAttribute* dataAttribute, Quality qu /**@}*/ +void +IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorage); + /** * @defgroup IEC61850_SERVER_SETTING_GROUPS Server side setting group handling * diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index d2ae3ae0..c32e43e8 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -42,6 +42,7 @@ struct sIedServer MmsMapping* mmsMapping; LinkedList clientConnections; uint8_t writeAccessPolicies; + bool running; }; diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h new file mode 100644 index 00000000..ea3793dd --- /dev/null +++ b/src/iec61850/inc_private/logging.h @@ -0,0 +1,123 @@ +/* + * logging.h + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + + +#ifndef LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ +#define LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ + +typedef struct { + char* name; + LogicalNode* parentLN; + + bool locked; + + LogStorage logStorage; + + uint64_t newEntryId; + uint64_t newEntryTime; + + uint64_t oldEntryId; + uint64_t oldEntryTime; + +} LogInstance; + +typedef struct { + char* name; + LogControlBlock* logControlBlock; + MmsMapping* mmsMapping; + + DataSet* dataSet; + char* dataSetRef; + bool isDynamicDataSet; + + LogicalNode* logicalNode; + MmsDomain* domain; + + MmsValue* mmsValue; + MmsVariableSpecification* mmsType; + + MmsValue* oldEntr; + MmsValue* oldEntrTm; + MmsValue* newEntr; + MmsValue* newEntrTm; + + LogInstance* logInstance; + + bool enabled; + + uint64_t nextIntegrityScan; + + int triggerOps; + uint32_t intgPd; + +} LogControl; + + +LogInstance* +LogInstance_create(LogicalNode* parentLN, const char* name); + +void +LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage); + +void +LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag); + +uint64_t +LogInstance_logEntryStart(LogInstance* self); + +void +LogInstance_logEntryData(LogInstance* self, uint64_t entryID, const char* dataRef, MmsValue* value, uint8_t flag); + +void +LogInstance_logEntryFinished(LogInstance* self, uint64_t entryID); + +void +LogInstance_destroy(LogInstance* self); + +LogControl* +LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping); + +void +LogControl_setLog(LogControl* self, LogInstance* logInstance); + +void +LogControl_destroy(LogControl* self); + +void +LogControl_setLogStorage(LogControl* self, LogStorage logStorage); + +MmsVariableSpecification* +Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, + int lcbCount); + +void +Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs); + +MmsValue* +LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig); + +MmsDataAccessError +LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, 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 b969b48c..0a1bb5a2 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -1,7 +1,7 @@ /* * mms_mapping.h * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -25,7 +25,6 @@ #define MMS_MAPPING_H_ #include "iec61850_model.h" -//#include "mms_server_connection.h" #include "mms_device_model.h" #include "control.h" @@ -36,6 +35,13 @@ typedef enum { REPORT_CONTROL_QUALITY_CHANGED } ReportInclusionFlag; +typedef enum { + LOG_CONTROL_NONE, + LOG_CONTROL_VALUE_UPDATE, + LOG_CONTROL_VALUE_CHANGED, + LOG_CONTROL_QUALITY_CHANGED +} LogInclusionFlag; + typedef struct sMmsMapping MmsMapping; MmsMapping* @@ -86,6 +92,9 @@ MmsMapping_createDataSetByNamedVariableList(MmsMapping* self, MmsNamedVariableLi void MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag); +void +MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag); + void MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value); @@ -138,6 +147,9 @@ MmsMapping_setIedServer(MmsMapping* self, IedServer iedServer); void MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicationHandler handler, void* parameter); +void +MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logStorage); + void MmsMapping_installWriteAccessHandler(MmsMapping* self, DataAttribute* dataAttribute, WriteAccessHandler handler, void* parameter); diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index ef09bb24..b5c193e6 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -1,7 +1,7 @@ /* * mms_mapping_internal.h * - * Copyright 2013, 2015 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -35,6 +35,11 @@ struct sMmsMapping { MmsServer mmsServer; LinkedList reportControls; +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + LinkedList logControls; + LinkedList logInstances; +#endif + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) LinkedList gseControls; const char* gooseInterfaceId; diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index f18bebd8..f2dc0b8b 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -198,6 +198,10 @@ createMmsServerCache(IedServer self) && (strcmp(fcName, "MS") != 0) && (strcmp(fcName, "US") != 0) #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + && (strcmp(fcName, "LG") != 0) +#endif + ) { char* variableName = createString(3, lnName, "$", fcName); @@ -400,6 +404,8 @@ IedServer_create(IedModel* iedModel) self->model = iedModel; + // self->running = false; /* not required due to CALLOC */ + self->mmsMapping = MmsMapping_create(iedModel); self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping); @@ -441,8 +447,31 @@ IedServer_create(IedModel* iedModel) void IedServer_destroy(IedServer self) { + + /* Stop server if running */ + if (self->running) { +#if (CONFIG_MMS_THREADLESS_STACK == 1) + IedServer_stopThreadless(self); +#else + IedServer_stop(self); +#endif + } + MmsServer_destroy(self->mmsServer); IsoServer_destroy(self->isoServer); + +#if (CONFIG_MMS_SINGLE_THREADED == 1) + + /* trigger stopping background task thread */ + self->mmsMapping->reportThreadRunning = false; + + /* waiting for thread to finish */ + while (self->mmsMapping->reportThreadFinished == false) { + Thread_sleep(10); + } + +#endif + MmsMapping_destroy(self->mmsMapping); LinkedList_destroyDeep(self->clientConnections, (LinkedListValueDeleteFunction) private_ClientConnection_destroy); @@ -508,17 +537,22 @@ singleThreadedServerThread(void* parameter) void IedServer_start(IedServer self, int tcpPort) { + if (self->running == false) { + #if (CONFIG_MMS_SINGLE_THREADED == 1) - MmsServer_startListeningThreadless(self->mmsServer, tcpPort); + MmsServer_startListeningThreadless(self->mmsServer, tcpPort); - Thread serverThread = Thread_create((ThreadExecutionFunction) singleThreadedServerThread, (void*) self, true); + Thread serverThread = Thread_create((ThreadExecutionFunction) singleThreadedServerThread, (void*) self, true); - Thread_start(serverThread); + Thread_start(serverThread); #else - MmsServer_startListening(self->mmsServer, tcpPort); - MmsMapping_startEventWorkerThread(self->mmsMapping); + MmsServer_startListening(self->mmsServer, tcpPort); + MmsMapping_startEventWorkerThread(self->mmsMapping); #endif + + self->running = true; + } } #endif @@ -541,13 +575,17 @@ IedServer_getDataModel(IedServer self) void IedServer_stop(IedServer self) { - MmsMapping_stopEventWorkerThread(self->mmsMapping); + if (self->running) { + self->running = false; + + MmsMapping_stopEventWorkerThread(self->mmsMapping); #if (CONFIG_MMS_SINGLE_THREADED == 1) - MmsServer_stopListeningThreadless(self->mmsServer); + MmsServer_stopListeningThreadless(self->mmsServer); #else - MmsServer_stopListening(self->mmsServer); + MmsServer_stopListening(self->mmsServer); #endif + } } #endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ @@ -555,7 +593,10 @@ IedServer_stop(IedServer self) void IedServer_startThreadless(IedServer self, int tcpPort) { - MmsServer_startListeningThreadless(self->mmsServer, tcpPort); + if (self->running == false) { + MmsServer_startListeningThreadless(self->mmsServer, tcpPort); + self->running = true; + } } int @@ -573,7 +614,11 @@ IedServer_processIncomingData(IedServer self) void IedServer_stopThreadless(IedServer self) { - MmsServer_stopListeningThreadless(self->mmsServer); + if (self->running) { + self->running = false; + + MmsServer_stopListeningThreadless(self->mmsServer); + } } void @@ -773,12 +818,22 @@ IedServer_getStringAttributeValue(IedServer self, const DataAttribute* dataAttri static inline void checkForUpdateTrigger(IedServer self, DataAttribute* dataAttribute) { -#if (CONFIG_IEC61850_REPORT_SERVICE== 1) +#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_IEC61850_LOG_SERVICE == 1)) if (dataAttribute->triggerOptions & TRG_OPT_DATA_UPDATE) { + +#if (CONFIG_IEC61850_REPORT_SERVICE == 1) MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_VALUE_UPDATE); - } #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_VALUE_UPDATE); +#endif + + + } +#endif /* ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_IEC61850_LOG_SERVICE == 1)) */ } static inline void @@ -795,6 +850,11 @@ checkForChangedTriggers(IedServer self, DataAttribute* dataAttribute) MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_VALUE_CHANGED); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_VALUE_CHANGED); +#endif } else if (dataAttribute->triggerOptions & TRG_OPT_QUALITY_CHANGED) { @@ -807,6 +867,12 @@ checkForChangedTriggers(IedServer self, DataAttribute* dataAttribute) MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_QUALITY_CHANGED); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_QUALITY_CHANGED); +#endif + } #endif /* (CONFIG_IEC61850_REPORT_SERVICE== 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ @@ -1020,6 +1086,13 @@ IedServer_updateQuality(IedServer self, DataAttribute* dataAttribute, Quality qu MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_QUALITY_CHANGED); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + if (dataAttribute->triggerOptions & TRG_OPT_QUALITY_CHANGED) + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_QUALITY_CHANGED); +#endif + } @@ -1212,6 +1285,14 @@ IedServer_setEditSettingGroupConfirmationHandler(IedServer self, SettingGroupCon #endif } +void +IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorage) +{ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsMapping_setLogStorage(self->mmsMapping, logRef, logStorage); +#endif +} + ClientConnection private_IedServer_getClientConnectionByHandle(IedServer self, void* serverConnectionHandle) { diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c new file mode 100644 index 00000000..a7b6ad58 --- /dev/null +++ b/src/iec61850/server/mms_mapping/logging.c @@ -0,0 +1,947 @@ +/* + * logging.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "libiec61850_platform_includes.h" +#include "stack_config.h" +#include "mms_mapping.h" +#include "logging.h" +#include "linked_list.h" +#include "array_list.h" +#include "hal_thread.h" + +#include "simple_allocator.h" +#include "mem_alloc_linked_list.h" + +#include "mms_mapping_internal.h" +#include "mms_value_internal.h" + +#include "logging_api.h" + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + +LogInstance* +LogInstance_create(LogicalNode* parentLN, const char* name) +{ + LogInstance* self = (LogInstance*) GLOBAL_MALLOC(sizeof(LogInstance)); + + self->name = copyString(name); + self->parentLN = parentLN; + self->logStorage = NULL; + self->locked = false; + + self->oldEntryId = 0; + self->oldEntryTime = 0; + self->newEntryId = 0; + self->newEntryTime = 0; + + return self; +} + +void +LogInstance_destroy(LogInstance* self) +{ + GLOBAL_FREEMEM(self->name); + GLOBAL_FREEMEM(self); +} + +void +LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag) +{ + LogStorage logStorage = self->logStorage; + + if (logStorage != NULL) { + + while (self->locked) + Thread_sleep(1); + + self->locked = true; + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Log value - dataRef: %s flag: %i\n", dataRef, flag); + + uint64_t timestamp = Hal_getTimeInMs(); + + uint64_t entryID = LogStorage_addEntry(logStorage, timestamp); + + int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); + + uint8_t* data = (uint8_t*) GLOBAL_MALLOC(dataSize); + + MmsValue_encodeMmsData(value, data, 0, true); + + LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); + + self->locked = false; + + GLOBAL_FREEMEM(data); + + self->newEntryId = entryID; + self->newEntryTime = timestamp; + + } + else + if (DEBUG_IED_SERVER) + printf("IED_SERVER: no log storage available for logging!\n"); +} + +uint64_t +LogInstance_logEntryStart(LogInstance* self) +{ + LogStorage logStorage = self->logStorage; + + if (logStorage != NULL) { + + while (self->locked) + Thread_sleep(1); + + self->locked = true; + + uint64_t timestamp = Hal_getTimeInMs(); + + uint64_t entryID = LogStorage_addEntry(logStorage, timestamp); + + return entryID; + } + else { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: no log storage available for logging!\n"); + + return 0; + } +} + +void +LogInstance_logEntryData(LogInstance* self, uint64_t entryID, const char* dataRef, MmsValue* value, uint8_t flag) +{ + LogStorage logStorage = self->logStorage; + + if (logStorage != NULL) { + + int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); + + uint8_t* data = (uint8_t*) GLOBAL_MALLOC(dataSize); + + MmsValue_encodeMmsData(value, data, 0, true); + + LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); + + self->locked = false; + + GLOBAL_FREEMEM(data); + } +} + +void +LogInstance_logEntryFinished(LogInstance* self, uint64_t entryID) +{ + self->locked = false; +} + +void +LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage) +{ + self->logStorage = logStorage; + + LogStorage_getOldestAndNewestEntries(logStorage, &(self->newEntryId), &(self->newEntryTime), + &(self->oldEntryId), &(self->oldEntryTime)); +} + +LogControl* +LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) +{ + LogControl* self = (LogControl*) GLOBAL_MALLOC(sizeof(LogControl)); + + self->enabled = false; + self->dataSet = NULL; + self->isDynamicDataSet = false; + self->triggerOps = 0; + self->logicalNode = parentLN; + self->mmsMapping = mmsMapping; + self->dataSetRef = NULL; + self->logInstance = NULL; + self->intgPd = 0; + self->nextIntegrityScan = 0; + + return self; +} + +void +LogControl_destroy(LogControl* self) +{ + if (self != NULL) { + + MmsValue_delete(self->mmsValue); + GLOBAL_FREEMEM(self->name); + + if (self->dataSetRef != NULL) + GLOBAL_FREEMEM(self->dataSetRef); + + GLOBAL_FREEMEM(self); + } +} + +void +LogControl_setLog(LogControl* self, LogInstance* logInstance) +{ + self->logInstance = logInstance; +} + +static void +prepareLogControl(LogControl* logControl) +{ + if (logControl->dataSetRef == NULL) { + logControl->enabled = false; + return; + } + + DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, logControl->dataSetRef); + + if (dataSet == NULL) + return; + else + logControl->dataSet = dataSet; +} + +static bool +enableLogging(LogControl* self) +{ + if ((self->dataSet != NULL) && (self->logInstance != NULL)) { + self->enabled = true; + + if ((self->triggerOps & TRG_OPT_INTEGRITY) && (self->intgPd != 0)) + self->nextIntegrityScan = Hal_getTimeInMs(); + else + self->nextIntegrityScan = 0; + + MmsValue* enabled = MmsValue_getSubElement(self->mmsValue, self->mmsType, "LogEna"); + + MmsValue_setBoolean(enabled, true); + + return true; + } + else + return false; +} + +static LogControlBlock* +getLCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index) +{ + int lcbCount = 0; + + LogControlBlock* nextLcb = self->model->lcbs; + + while (nextLcb != NULL ) { + if (nextLcb->parent == logicalNode) { + + if (lcbCount == index) + return nextLcb; + + lcbCount++; + + } + + nextLcb = nextLcb->sibling; + } + + return NULL ; +} + +static LogControl* +lookupLogControl(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName) +{ + LinkedList element = LinkedList_getNext(self->logControls); + + while (element != NULL) { + LogControl* logControl = (LogControl*) element->data; + + if (logControl->domain == domain) { + if (strcmp(logControl->logicalNode->name, lnName) == 0) { + if (strcmp(logControl->logControlBlock->name, objectName) == 0) { + return logControl; + } + } + } + + element = LinkedList_getNext(element); + } + + return NULL; +} + +static LogInstance* +getLogInstanceByLogRef(MmsMapping* self, const char* logRef) +{ + char refStr[130]; + char* domainName; + char* lnName; + char* logName; + + strncpy(refStr, logRef, 129); + + domainName = refStr; + + lnName = strchr(refStr, '/'); + + if (lnName == NULL) + return NULL; + + if ((lnName - domainName) > 64) + return NULL; + + lnName[0] = 0; + lnName++; + + logName = strchr(lnName, '$'); + + if (logName == NULL) + return NULL; + + logName[0] = 0; + logName++; + + LinkedList instance = LinkedList_getNext(self->logInstances); + + while (instance != NULL) { + + LogInstance* logInstance = (LogInstance*) LinkedList_getData(instance); + + if (strcmp(logInstance->name, logName) == 0) { + + if (strcmp(lnName, logInstance->parentLN->name) == 0) { + LogicalDevice* ld = (LogicalDevice*) logInstance->parentLN->parent; + + if (strcmp(ld->name, domainName) == 0) + return logInstance; + } + } + + instance = LinkedList_getNext(instance); + } + + return NULL; +} + +static void +updateLogStatusInLCB(LogControl* self) +{ + LogInstance* logInstance = self->logInstance; + + if (logInstance != NULL) { + MmsValue_setBinaryTime(self->oldEntrTm, logInstance->oldEntryTime); + MmsValue_setBinaryTime(self->newEntrTm, logInstance->newEntryTime); + + MmsValue_setOctetString(self->oldEntr, (uint8_t*) &(logInstance->oldEntryId), 8); + MmsValue_setOctetString(self->newEntr, (uint8_t*) &(logInstance->newEntryId), 8); + } +} + + +static void +freeDynamicDataSet(LogControl* self) +{ + if (self->isDynamicDataSet) { + if (self->dataSet != NULL) { + MmsMapping_freeDynamicallyCreatedDataSet(self->dataSet); + self->isDynamicDataSet = false; + self->dataSet = NULL; + } + } +} + +MmsDataAccessError +LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, + MmsValue* value, MmsServerConnection connection) +{ + bool updateValue = false; + + char variableId[130]; + + strncpy(variableId, variableIdOrig, 129); + + char* separator = strchr(variableId, '$'); + + *separator = 0; + + char* lnName = variableId; + + if (lnName == NULL) + return DATA_ACCESS_ERROR_INVALID_ADDRESS; + + char* objectName = MmsMapping_getNextNameElement(separator + 1); + + if (objectName == NULL) + return DATA_ACCESS_ERROR_INVALID_ADDRESS; + + char* varName = MmsMapping_getNextNameElement(objectName); + + if (varName != NULL) + *(varName - 1) = 0; + + LogControl* logControl = lookupLogControl(self, domain, lnName, objectName); + + if (logControl == NULL) { + return DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; + } + + if (strcmp(varName, "LogEna") == 0) { + bool logEna = MmsValue_getBoolean(value); + + if (logEna == false) { + logControl->enabled = false; + } + else { + + if (enableLogging(logControl)) { + logControl->enabled = true; + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: enabled log control %s\n", logControl->name); + } + else + return DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + } + + updateValue = true; + } + else if (strcmp(varName, "LogRef") == 0) { + + if (logControl->enabled == false) { + /* check if logRef is valid or NULL */ + const char* logRef = MmsValue_toString(value); + + if (logRef == NULL) { + + logControl->logInstance = NULL; + + updateValue = true; + } + else { + if (strcmp(logRef, "") == 0) { + logControl->logInstance = NULL; + updateValue = true; + } + else { + + /* remove IED name from logRef */ + char* iedName = self->mmsDevice->deviceName; + + uint32_t iedNameLen = strlen(iedName); + + if (iedNameLen < strlen(logRef)) { + if (memcmp(iedName, logRef, iedNameLen) == 0) { + logRef = logRef + iedNameLen; + } + } + + LogInstance* logInstance = getLogInstanceByLogRef(self, logRef); + + if (logInstance != NULL) { + + logControl->logInstance = logInstance; + updateValue = true; + } + else + return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + + } + } + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + else if (strcmp(varName, "DatSet") == 0) { + + if (logControl->enabled == false) { + /* check if datSet is valid or NULL/empty */ + const char* dataSetRef = MmsValue_toString(value); + + if (strlen(dataSetRef) == 0) { + logControl->dataSet = NULL; + updateValue = true; + } + else { + DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, dataSetRef); + + if (dataSet != NULL) { + freeDynamicDataSet(logControl); + + logControl->dataSet = dataSet; + updateValue = true; + + } + +#if (MMS_DYNAMIC_DATA_SETS == 1) + + if (dataSet == NULL) { + + dataSet = MmsMapping_getDomainSpecificDataSet(self, dataSetRef); + + if (dataSet == NULL) { + + if (dataSetRef[0] == '/') { /* check for VMD specific data set */ + MmsNamedVariableList mmsVariableList = + MmsDevice_getNamedVariableListWithName(self->mmsDevice, dataSetRef + 1); + + if (mmsVariableList != NULL) + dataSet = MmsMapping_createDataSetByNamedVariableList(self, mmsVariableList); + } + } + + if (dataSet != NULL) { + freeDynamicDataSet(logControl); + logControl->dataSet = dataSet; + logControl->isDynamicDataSet = true; + + updateValue = true; + } + + } + +#endif /*(MMS_DYNAMIC_DATA_SETS == 1) */ + + if (dataSet == NULL) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: data set (%s) not found!\n", logControl->dataSetRef); + return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + } + } + + + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + else if (strcmp(varName, "IntgPd") == 0) { + if (logControl->enabled == false) { + logControl->intgPd = MmsValue_toUint32(value); + updateValue = true; + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + else if (strcmp(varName, "TrgOps") == 0) { + if (logControl->enabled == false) { + logControl->triggerOps = (MmsValue_getBitStringAsInteger(value) / 2); + updateValue = true; + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + + if (updateValue) { + MmsValue* element = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName); + + MmsValue_update(element, value); + + return DATA_ACCESS_ERROR_SUCCESS; + } + + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; +} + +MmsValue* +LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig) +{ + MmsValue* value = NULL; + + char variableId[130]; + + strncpy(variableId, variableIdOrig, 129); + + char* separator = strchr(variableId, '$'); + + *separator = 0; + + char* lnName = variableId; + + if (lnName == NULL) + return NULL; + + char* objectName = MmsMapping_getNextNameElement(separator + 1); + + if (objectName == NULL) + return NULL; + + char* varName = MmsMapping_getNextNameElement(objectName); + + if (varName != NULL) + *(varName - 1) = 0; + + LogControl* logControl = lookupLogControl(self, domain, lnName, objectName); + + if (logControl != NULL) { + + updateLogStatusInLCB(logControl); + + if (varName != NULL) { + value = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName); + } + else { + value = logControl->mmsValue; + } + } + + return value; +} + + +static char* +createDataSetReferenceForDefaultDataSet(LogControlBlock* lcb, LogControl* logControl) +{ + char* dataSetReference; + + char* domainName = MmsDomain_getName(logControl->domain); + char* lnName = lcb->parent->name; + + dataSetReference = createString(5, domainName, "/", lnName, "$", lcb->dataSetName); + + return dataSetReference; +} + +static MmsValue* +createTrgOps(LogControlBlock* logControlBlock) { + MmsValue* trgOps = MmsValue_newBitString(-6); + + uint8_t triggerOps = logControlBlock->trgOps; + + if (triggerOps & TRG_OPT_DATA_CHANGED) + MmsValue_setBitStringBit(trgOps, 1, true); + if (triggerOps & TRG_OPT_QUALITY_CHANGED) + MmsValue_setBitStringBit(trgOps, 2, true); + if (triggerOps & TRG_OPT_DATA_UPDATE) + MmsValue_setBitStringBit(trgOps, 3, true); + if (triggerOps & TRG_OPT_INTEGRITY) + MmsValue_setBitStringBit(trgOps, 4, true); + + return trgOps; +} + +static void +LogControl_updateLogEna(LogControl* self) +{ + MmsValue_setBoolean(MmsValue_getElement(self->mmsValue, 0), self->enabled); +} + +static MmsVariableSpecification* +createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, + LogControl* logControl) +{ + MmsVariableSpecification* lcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + lcb->name = copyString(logControlBlock->name); + lcb->type = MMS_STRUCTURE; + + MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + mmsValue->deleteValue = false; + mmsValue->type = MMS_STRUCTURE; + + int structSize = 9; + + mmsValue->value.structure.size = structSize; + mmsValue->value.structure.components = (MmsValue**) GLOBAL_CALLOC(structSize, sizeof(MmsValue*)); + + lcb->typeSpec.structure.elementCount = structSize; + + lcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(structSize, + sizeof(MmsVariableSpecification*)); + + /* LogEna */ + MmsVariableSpecification* namedVariable = + (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + + namedVariable->name = copyString("LogEna"); + namedVariable->type = MMS_BOOLEAN; + + lcb->typeSpec.structure.elements[0] = namedVariable; + mmsValue->value.structure.components[0] = MmsValue_newBoolean(logControlBlock->logEna); + + /* LogRef */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("LogRef"); + namedVariable->typeSpec.visibleString = -129; + namedVariable->type = MMS_VISIBLE_STRING; + lcb->typeSpec.structure.elements[1] = namedVariable; + + if (logControlBlock->logRef != NULL) { + char logRef[130]; + + int maxLogRefLength = 129 - strlen(self->model->name); + + strcpy(logRef, self->model->name); + strncat(logRef, logControlBlock->logRef, maxLogRefLength); + + mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logRef); + } + else { + char* logRef = createString(4, logControl->domain->domainName, "/", logControlBlock->parent->name, + "$GeneralLog"); + + mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logRef); + + GLOBAL_FREEMEM(logRef); + } + + /* DatSet */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("DatSet"); + namedVariable->typeSpec.visibleString = -129; + namedVariable->type = MMS_VISIBLE_STRING; + lcb->typeSpec.structure.elements[2] = namedVariable; + + if (logControlBlock->dataSetName != NULL) { + char* dataSetReference = createDataSetReferenceForDefaultDataSet(logControlBlock, logControl); + + logControl->dataSetRef = dataSetReference; + + mmsValue->value.structure.components[2] = MmsValue_newVisibleString(dataSetReference); + } + else + mmsValue->value.structure.components[2] = MmsValue_newVisibleString(""); + + /* OldEntrTm */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("OldEntrTm"); + namedVariable->type = MMS_BINARY_TIME; + namedVariable->typeSpec.binaryTime = 6; + lcb->typeSpec.structure.elements[3] = namedVariable; + + mmsValue->value.structure.components[3] = MmsValue_newBinaryTime(false); + + logControl->oldEntrTm = mmsValue->value.structure.components[3]; + + /* NewEntrTm */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("NewEntrTm"); + namedVariable->type = MMS_BINARY_TIME; + namedVariable->typeSpec.binaryTime = 6; + lcb->typeSpec.structure.elements[4] = namedVariable; + + mmsValue->value.structure.components[4] = MmsValue_newBinaryTime(false); + + logControl->newEntrTm = mmsValue->value.structure.components[4]; + + /* OldEntr */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("OldEntr"); + namedVariable->type = MMS_OCTET_STRING; + namedVariable->typeSpec.octetString = 8; + + lcb->typeSpec.structure.elements[5] = namedVariable; + + mmsValue->value.structure.components[5] = MmsValue_newOctetString(8, 8); + + logControl->oldEntr = mmsValue->value.structure.components[5]; + + /* NewEntr */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("NewEntr"); + namedVariable->type = MMS_OCTET_STRING; + namedVariable->typeSpec.octetString = 8; + + lcb->typeSpec.structure.elements[6] = namedVariable; + + mmsValue->value.structure.components[6] = MmsValue_newOctetString(8, 8); + + logControl->newEntr = mmsValue->value.structure.components[6]; + + /* TrgOps */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("TrgOps"); + namedVariable->type = MMS_BIT_STRING; + namedVariable->typeSpec.bitString = -6; + lcb->typeSpec.structure.elements[7] = namedVariable; + mmsValue->value.structure.components[7] = createTrgOps(logControlBlock); + + /* IntgPd */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("IntgPd"); + namedVariable->type = MMS_UNSIGNED; + namedVariable->typeSpec.unsignedInteger = 32; + lcb->typeSpec.structure.elements[8] = namedVariable; + mmsValue->value.structure.components[8] = + MmsValue_newUnsignedFromUint32(logControlBlock->intPeriod); + + logControl->intgPd = logControlBlock->intPeriod; + + logControl->mmsType = lcb; + logControl->mmsValue = mmsValue; + logControl->logControlBlock = logControlBlock; + logControl->triggerOps = logControlBlock->trgOps; + + logControl->enabled = logControlBlock->logEna; + + prepareLogControl(logControl); + + if (logControl->enabled) + enableLogging(logControl); + + LogControl_updateLogEna(logControl); + + return lcb; +} + +MmsVariableSpecification* +Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, + int lcbCount) +{ + MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, + sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("LG"); + namedVariable->type = MMS_STRUCTURE; + + namedVariable->typeSpec.structure.elementCount = lcbCount; + namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(lcbCount, + sizeof(MmsVariableSpecification*)); + + int currentLcb = 0; + + while (currentLcb < lcbCount) { + + LogControl* logControl = LogControl_create(logicalNode, self); + + LogControlBlock* logControlBlock = getLCBForLogicalNodeWithIndex(self, logicalNode, currentLcb); + + logControl->name = createString(3, logicalNode->name, "$LG$", logControlBlock->name); + logControl->domain = domain; + + namedVariable->typeSpec.structure.elements[currentLcb] = + createLogControlBlock(self, logControlBlock, logControl); + + if (logControlBlock->logRef != NULL) + logControl->logInstance = getLogInstanceByLogRef(self, logControlBlock->logRef); + + LinkedList_add(self->logControls, logControl); + + currentLcb++; + } + + return namedVariable; +} + +static void +LogControl_logAllDatasetEntries(LogControl* self, const char* iedName) +{ + if (self->dataSet == NULL) + return; + + if (self->logInstance != NULL) { + + char dataRef[130]; + + LogInstance* log = self->logInstance; + + uint64_t entryID = LogInstance_logEntryStart(log); + + DataSetEntry* dataSetEntry = self->dataSet->fcdas; + + while (dataSetEntry != NULL) { + + sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName); + + LogInstance_logEntryData(log, entryID, dataRef, dataSetEntry->value, TRG_OPT_INTEGRITY * 2); + + dataSetEntry = dataSetEntry->sibling; + } + + LogInstance_logEntryFinished(log, entryID); + + } +} + +void +Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs) +{ + LinkedList logControlElem = LinkedList_getNext(self->logControls); + + while (logControlElem != NULL) { + + LogControl* logControl = (LogControl*) LinkedList_getData(logControlElem); + + if (logControl->enabled) { + + if (logControl->nextIntegrityScan != 0) { + + if (currentTimeInMs >= logControl->nextIntegrityScan) { + + //if (DEBUG_IED_SERVER) + printf("IED_SERVER: INTEGRITY SCAN for log %s\n", logControl->name); + + LogControl_logAllDatasetEntries(logControl, self->mmsDevice->deviceName); + + logControl->nextIntegrityScan += logControl->intgPd; + } + } + } + + logControlElem = LinkedList_getNext(logControlElem); + } +} + +void +MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logStorage) +{ + LogInstance* logInstance = getLogInstanceByLogRef(self, logRef); + + if (logInstance != NULL) { + LogInstance_setLogStorage(logInstance, logStorage); + + char domainName[65]; + + MmsMapping_getMmsDomainFromObjectReference(logRef, domainName); + + char domainNameWithIEDName[65]; + + strcpy(domainNameWithIEDName, self->model->name); + strcat(domainNameWithIEDName, domainName); + + MmsDomain* mmsDomain = MmsDevice_getDomain(self->mmsDevice, domainNameWithIEDName); + + if (mmsDomain == NULL) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: MmsMapping_setLogStorage: domain %s not found!\n", domainNameWithIEDName); + + return; + } + + MmsJournal mmsJournal = NULL; + + const char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + logName += 1; + mmsJournal = MmsDomain_getJournal(mmsDomain, logName); + } + + if (mmsJournal != NULL) + mmsJournal->logStorage = logStorage; + else + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Failed to retrieve MMS journal for log!\n"); + } + + if (DEBUG_IED_SERVER) + if (logInstance == NULL) + printf("IED_SERVER: MmsMapping_setLogStorage no matching log for %s found!\n", logRef); +} + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index fdb430a9..b056b83b 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, 2014, 2015 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -30,6 +30,7 @@ #include "mms_goose.h" #include "mms_sv.h" #include "reporting.h" +#include "logging.h" #include "control.h" #include "ied_server_private.h" @@ -756,6 +757,25 @@ countReportControlBlocksForLogicalNode(MmsMapping* self, LogicalNode* logicalNod } #endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) +static int +countLogControlBlocksForLogicalNode (MmsMapping* self, LogicalNode* logicalNode) +{ + int lcbCount = 0; + + LogControlBlock* lcb = self->model->lcbs; + + while (lcb != NULL) { + if (lcb->parent == logicalNode) + lcbCount++; + + lcb = lcb->sibling; + } + + return lcbCount; +} +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) @@ -870,6 +890,19 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, } #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + + int lcbCount = countLogControlBlocksForLogicalNode(self, logicalNode); + + if (lcbCount > 0) { + if (DEBUG_IED_SERVER) + printf(" and %i LOG control blocks\n", lcbCount); + + componentCount++; + } + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) int gseCount = countGSEControlBlocksForLogicalNode(self, logicalNode); @@ -969,7 +1002,15 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, } #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ - /* TODO create LCBs here */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + if (lcbCount > 0) { + namedVariable->typeSpec.structure.elements[currentComponent] = + Logging_createLCBs(self, domain, logicalNode, lcbCount); + + currentComponent++; + } +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_IEC61850_REPORT_SERVICE == 1) if (brcbCount > 0) { @@ -1069,6 +1110,36 @@ createMmsDomainFromIedDevice(MmsMapping* self, LogicalDevice* logicalDevice) if (domain == NULL) goto exit_function; +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + /* add logs (journals) */ + Log* log = self->model->logs; + + while (log != NULL) { + + char journalName[65]; + + int nameLength = strlen(log->parent->name) + strlen(log->name); + + if (nameLength > 63) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Log name %s invalid! Resulting journal name too long! Skip log\n", log->name); + } + else { + strcpy(journalName, log->parent->name); + strcat(journalName, "$"); + strcat(journalName, log->name); + + MmsDomain_addJournal(domain, journalName); + + LogInstance* logInstance = LogInstance_create(log->parent, log->name); + + LinkedList_add(self->logInstances, (void*) logInstance); + } + + log = log->sibling; + } +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + int nodesCount = LogicalDevice_getLogicalNodeCount(logicalDevice); /* Logical nodes are first level named variables */ @@ -1193,6 +1264,11 @@ MmsMapping_create(IedModel* model) self->reportControls = LinkedList_create(); #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + self->logControls = LinkedList_create(); + self->logInstances = LinkedList_create(); +#endif + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) self->gseControls = LinkedList_create(); self->gooseInterfaceId = NULL; @@ -1254,6 +1330,11 @@ MmsMapping_destroy(MmsMapping* self) LinkedList_destroy(self->settingGroups); #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + LinkedList_destroyDeep(self->logControls, (LinkedListValueDeleteFunction) LogControl_destroy); + LinkedList_destroyDeep(self->logInstances, (LinkedListValueDeleteFunction) LogInstance_destroy); +#endif + LinkedList_destroy(self->observedObjects); LinkedList_destroy(self->attributeAccessHandlers); @@ -1370,6 +1451,19 @@ isSampledValueControlBlock(char* separator) #endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + +static bool +isLogControlBlock(char* separator) +{ + if (strncmp(separator + 1, "LG", 2) == 0) + return true; + + return false; +} + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + char* MmsMapping_getNextNameElement(char* name) { @@ -1705,6 +1799,13 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, #endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + + /* Log control block - LG */ + if (isLogControlBlock(separator)) + return LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(self, domain, variableId, value, connection); + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ #if (CONFIG_IEC61850_REPORT_SERVICE == 1) /* Report control blocks - BR, RP */ @@ -1894,7 +1995,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, SettingGroup* sg = getSettingGroupByMmsDomain(self, domain); if (sg->editingClient != (ClientConnection) connection) - return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; } #endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */ @@ -2145,6 +2246,14 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo } #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + /* LOG control block - LG */ + if (isLogControlBlock(separator)) { + retValue = LIBIEC61850_LOG_SVC_readAccessControlBlock(self, domain, variableId); + goto exit_function; + } +#endif + #if (CONFIG_IEC61850_REPORT_SERVICE == 1) /* Report control blocks - BR, RP */ if (isReportControlBlock(separator)) { @@ -2374,10 +2483,10 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li else { /* Check if data set is referenced in a report */ - LinkedList element = self->reportControls; + LinkedList rcElement = self->reportControls; - while ((element = LinkedList_getNext(element)) != NULL) { - ReportControl* rc = (ReportControl*) element->data; + while ((rcElement = LinkedList_getNext(rcElement)) != NULL) { + ReportControl* rc = (ReportControl*) rcElement->data; if (rc->isDynamicDataSet) { if (rc->dataSet != NULL) { @@ -2386,16 +2495,24 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li if (rc->dataSet->logicalDeviceName != NULL) { if (strcmp(rc->dataSet->name, listName) == 0) { if (strcmp(rc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) { - allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; break; } } } } + else if (listType == MMS_VMD_SPECIFIC) { + if (rc->dataSet->logicalDeviceName == NULL) { + if (strcmp(rc->dataSet->name, listName) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } + } + } else if (listType == MMS_ASSOCIATION_SPECIFIC) { if (rc->dataSet->logicalDeviceName == NULL) { if (strcmp(rc->dataSet->name, listName) == 0) { - allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; break; } } @@ -2404,6 +2521,42 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li } } } + + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + /* check if data set is referenced in a log control block*/ + LinkedList logElement = self->logControls; + + while ((logElement = LinkedList_getNext(logElement)) != NULL) { + LogControl* lc = (LogControl*) logElement->data; + + if (lc->isDynamicDataSet) { + if (lc->dataSet != NULL) { + + if (listType == MMS_DOMAIN_SPECIFIC) { + if (lc->dataSet->logicalDeviceName != NULL) { + if (strcmp(lc->dataSet->name, listName) == 0) { + if (strcmp(lc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } + } + } + } + else if (listType == MMS_VMD_SPECIFIC) { + if (lc->dataSet->logicalDeviceName == NULL) { + if (strcmp(lc->dataSet->name, listName) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } + } + } + + } + } + } + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ } return allow; @@ -2432,7 +2585,7 @@ MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicat self->connectionIndicationHandlerParameter = parameter; } -#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)) + static bool isMemberValueRecursive(MmsValue* container, MmsValue* value) @@ -2460,6 +2613,8 @@ isMemberValueRecursive(MmsValue* container, MmsValue* value) return isMemberValue; } +#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)) + static bool DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index) { @@ -2489,6 +2644,97 @@ DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index) } #endif /* ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT)) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + +static bool +DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef, const char* iedName) +{ + int i = 0; + + DataSetEntry* dataSetEntry = dataSet->fcdas; + + while (dataSetEntry != NULL) { + + MmsValue* dataSetValue = dataSetEntry->value; + + if (dataSetValue != NULL) { /* prevent invalid data set members */ + if (isMemberValueRecursive(dataSetValue, value)) { + if (dataRef != NULL) + sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName); + + return true; + } + } + + i++; + + dataSetEntry = dataSetEntry->sibling; + } + + return false; +} + +void +MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag) +{ + LinkedList element = self->logControls; + + while ((element = LinkedList_getNext(element)) != NULL) { + LogControl* lc = (LogControl*) element->data; + + if ((lc->enabled) && (lc->dataSet != NULL)) { + + uint8_t reasonCode; + + switch (flag) { + + case LOG_CONTROL_VALUE_UPDATE: + if ((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0) + continue; + + reasonCode = TRG_OPT_DATA_UPDATE * 2; + + break; + + case LOG_CONTROL_VALUE_CHANGED: + if (((lc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) && + ((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)) + continue; + + reasonCode = TRG_OPT_DATA_CHANGED * 2; + + break; + + case LOG_CONTROL_QUALITY_CHANGED: + if ((lc->triggerOps & TRG_OPT_QUALITY_CHANGED) == 0) + continue; + + reasonCode = TRG_OPT_QUALITY_CHANGED * 2; + + break; + + default: + continue; + } + + char dataRef[130]; + + if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef, self->model->name)) { + + if (lc->logInstance != NULL) { + LogInstance_logSingleData(lc->logInstance, dataRef, value, reasonCode); + } + else + printf("No log instance available!\n"); + } + + + } + } +} + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_IEC61850_REPORT_SERVICE == 1) void MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag) @@ -2655,10 +2901,27 @@ MmsMapping_createMmsVariableNameFromObjectReference(const char* objectReference, return NULL; if (i == objRefLength) - i = 0; + i = 0; /* for the case when no LD name is present */ else i++; + + if (fc == IEC61850_FC_NONE) { + + int len = objRefLength - i; + + char* mmsVariableName; + + if (buffer == NULL) + mmsVariableName = (char*) GLOBAL_MALLOC(len); + else + mmsVariableName = buffer; + + strcpy(mmsVariableName, objectReference + i); + + return mmsVariableName; + } + char* fcString = FunctionalConstraint_toString(fc); if (fcString == NULL) @@ -2756,6 +3019,10 @@ processPeriodicTasks(MmsMapping* self) #if (CONFIG_IEC61850_SETTING_GROUPS == 1) MmsMapping_checkForSettingGroupReservationTimeouts(self, currentTimeInMs); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + Logging_processIntegrityLogs(self, currentTimeInMs); +#endif } void diff --git a/src/iec61850/server/mms_mapping/mms_sv.c b/src/iec61850/server/mms_mapping/mms_sv.c index b367994f..83e8b74c 100644 --- a/src/iec61850/server/mms_mapping/mms_sv.c +++ b/src/iec61850/server/mms_mapping/mms_sv.c @@ -249,7 +249,6 @@ LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* d return value; } - static SVControlBlock* getSVCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index, bool isUnicast) { diff --git a/src/iec61850/server/model/cdc.c b/src/iec61850/server/model/cdc.c index eb6806e9..acbcaadd 100644 --- a/src/iec61850/server/model/cdc.c +++ b/src/iec61850/server/model/cdc.c @@ -774,11 +774,47 @@ CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options) DataAttribute_create("vendor", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); DataAttribute_create("swRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + if (options & CDC_OPTION_AC_LN0_M) + DataAttribute_create("configRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_AC_LN0_EX) + DataAttribute_create("ldNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0); + + if (options & CDC_OPTION_AC_DLD_M) + DataAttribute_create("lnNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0); + CDC_addStandardOptions(newLPL, options); return newLPL; } +DataObject* +CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options) +{ + DataObject* newDPL = DataObject_create(dataObjectName, parent, 0); + + DataAttribute_create("vendor", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_HWREV) + DataAttribute_create("hwRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_SWREV) + DataAttribute_create("swRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_SERNUM) + DataAttribute_create("serNum", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_MODEL) + DataAttribute_create("model", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_LOCATION) + DataAttribute_create("location", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + CDC_addStandardOptions(newDPL, options); + + return newDPL; +} + /* Directional protection activation information (ACD) */ DataObject* CDC_ACD_create(const char* dataObjectName, ModelNode* parent, uint32_t options) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index e34258dc..1d4b67dc 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -215,6 +215,38 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) ReportControlBlock_create(nameString, currentLN, rptId, (bool) isBuffered, dataSetName, confRef, trgOps, options, bufTm, intgPd); } + else if (StringUtils_startsWith((char*) lineBuffer, "LC")) { + uint32_t trgOps; + uint32_t intgPd; + int logEna; + int withReasonCode; + + int matchedItems = sscanf((char*) lineBuffer, "LC(%s %s %s %u %u %i %i)", + nameString, nameString2, nameString3, &trgOps, &intgPd, &logEna, &withReasonCode); + + if (matchedItems < 7) goto exit_error; + + char* dataSet = NULL; + if (strcmp(nameString2, "-") != 0) + dataSet = nameString2; + + char* logRef = NULL; + if (strcmp(nameString3, "-") != 0) + logRef = nameString3; + + LogControlBlock_create(nameString, currentLN, dataSet, logRef, trgOps, intgPd, logEna, withReasonCode); + } + else if (StringUtils_startsWith((char*) lineBuffer, "LOG")) { + int matchedItems = sscanf((char*) lineBuffer, "LOG(%s)", nameString); + + if (matchedItems < 1) goto exit_error; + + /* remove trailing ')' character */ + int nameLen = strlen(nameString); + nameString[nameLen - 1] = 0; + + Log_create(nameString, currentLN); + } else if (StringUtils_startsWith((char*) lineBuffer, "GC")) { uint32_t confRef; int fixedOffs; diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 206e7b87..6482c461 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -58,6 +58,10 @@ IedModel_create(const char* name/*, MemoryAllocator allocator*/) self->sgcbs = NULL; + self->lcbs = NULL; + + self->logs = NULL; + self->initializer = iedModel_emptyVariableInitializer; return self; @@ -97,6 +101,36 @@ IedModel_addLogicalDevice(IedModel* self, LogicalDevice* lDevice) } } +static void +IedModel_addLog(IedModel* self, Log* log) +{ + if (self->logs == NULL) + self->logs = log; + else { + Log* lastLog = self->logs; + + while (lastLog->sibling != NULL) + lastLog = lastLog->sibling; + + lastLog->sibling = log; + } +} + +static void +IedModel_addLogControlBlock(IedModel* self, LogControlBlock* lcb) +{ + if (self->lcbs == NULL) + self->lcbs = lcb; + else { + LogControlBlock* lastLcb = self->lcbs; + + while (lastLcb->sibling != NULL) + lastLcb = lastLcb->sibling; + + lastLcb->sibling = lcb; + } +} + static void IedModel_addReportControlBlock(IedModel* self, ReportControlBlock* rcb) { @@ -231,6 +265,65 @@ LogicalNode_addDataObject(LogicalNode* self, DataObject* dataObject) } } +static void +LogicalNode_addLog(LogicalNode* self, Log* log) +{ + IedModel* model = (IedModel*) self->parent->parent; + + IedModel_addLog(model, log); +} + +Log* +Log_create(const char* name, LogicalNode* parent) +{ + Log* self = (Log*) GLOBAL_MALLOC(sizeof(Log)); + + self->name = copyString(name); + self->parent = parent; + self->sibling = NULL; + + LogicalNode_addLog(parent, self); + + return self; +} + +static void +LogicalNode_addLogControlBlock(LogicalNode* self, LogControlBlock* lcb) +{ + IedModel* model = (IedModel*) self->parent->parent; + + IedModel_addLogControlBlock(model, lcb); +} + +LogControlBlock* +LogControlBlock_create(const char* name, LogicalNode* parent, char* dataSetName, char* logRef, uint8_t trgOps, + uint32_t intPeriod, bool logEna, bool reasonCode) +{ + LogControlBlock* self = (LogControlBlock*) GLOBAL_MALLOC(sizeof(LogControlBlock)); + + self->name = copyString(name); + self->parent = parent; + + if (dataSetName) + self->dataSetName = copyString(dataSetName); + else + dataSetName = NULL; + + if (logRef) + self->logRef = copyString(logRef); + else + logRef = NULL; + + self->trgOps = trgOps; + self->intPeriod = intPeriod; + self->logEna = logEna; + self->reasonCode = reasonCode; + + LogicalNode_addLogControlBlock(parent, self); + + return self; +} + static void LogicalNode_addReportControlBlock(LogicalNode* self, ReportControlBlock* rcb) { @@ -743,6 +836,28 @@ IedModel_destroy(IedModel* model) sgcb = nextSgcb; } + /* delete all LCBs */ + LogControlBlock* lcb = model->lcbs; + + while (lcb != NULL) { + LogControlBlock* nextLcb = lcb->sibling; + + GLOBAL_FREEMEM(lcb); + + lcb = nextLcb; + } + + /* delete all LOGs */ + Log* log = model->logs; + + while (log != NULL) { + Log* nextLog = log->sibling; + + GLOBAL_FREEMEM(log); + + log = nextLog; + } + /* delete generic model parts */ diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 9b96b704..b2998ec6 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -372,7 +372,7 @@ LogicalNode_hasFCData(LogicalNode* node, FunctionalConstraint fc) DataSet* LogicalNode_getDataSet(LogicalNode* self, const char* dataSetName) { - assert(self->modelType == LogicalNodeModelType); + assert(self->modelType == LogicalNodeModelType); assert(dataSetName != NULL); char dsName[66]; diff --git a/src/logging/drivers/README b/src/logging/drivers/README new file mode 100644 index 00000000..dd1e95dd --- /dev/null +++ b/src/logging/drivers/README @@ -0,0 +1,3 @@ +This directory contains the log driver implementation that implements the logging API. The logging API is a service provider interface that has to be implemented by the log driver. +An application can use different log drivers at the same time. +Each log driver provides a public constructor for the driver specific implementation of the LogStorage class. The constructor has to fill the virtual function table of the LogStorage instance with its own implementation functions. diff --git a/src/logging/drivers/sqlite/log_storage_sqlite.c b/src/logging/drivers/sqlite/log_storage_sqlite.c new file mode 100644 index 00000000..07b0409c --- /dev/null +++ b/src/logging/drivers/sqlite/log_storage_sqlite.c @@ -0,0 +1,486 @@ +/* + * log_storage_sqlite.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + + +#include "logging_api.h" +#include "libiec61850_platform_includes.h" + +#include + +#ifndef DEBUG_LOG_STORAGE_DRIVER +#define DEBUG_LOG_STORAGE_DRIVER 0 +#endif + +static uint64_t +SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp); + +static bool +SqliteLogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); + +static bool +SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + +static bool +SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + +static bool +SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime); + +static void +SqliteLogStorage_destroy(LogStorage self); + + +typedef struct sSqliteLogStorage { + char* filename; + sqlite3* db; + sqlite3_stmt* insertEntryStmt; + sqlite3_stmt* insertEntryDataStmt; + sqlite3_stmt* getEntriesWithRange; + sqlite3_stmt* getEntriesAfter; + sqlite3_stmt* getEntryData; + sqlite3_stmt* getOldEntry; + sqlite3_stmt* getNewEntry; +} SqliteLogStorage; + +static const char* CREATE_TABLE_ENTRYS = "create table if not exists Entries (entryID integer primary key, timeOfEntry integer)"; +static const char* CREATE_TABLE_ENTRY_DATA = "create table if not exists EntryData (entryID integer, dataRef text, value blob, reasonCode integer)"; +static const char* INSERT_ENTRY = "insert into Entries (timeOfEntry) values (?)"; +static const char* INSERT_ENTRY_DATA = "insert into EntryData (entryID, dataRef, value, reasonCode) values (?,?,?,?)"; +static const char* GET_ENTRIES_WITH_RANGE = "select entryID, timeOfEntry from Entries where timeOfEntry >= ? and timeOfEntry <= ?"; +static const char* GET_ENTRIES_AFTER = "select entryID, timeOfEntry from Entries where entryID > ?"; +static const char* GET_ENTRY_DATA = "select dataRef, value, reasonCode from EntryData where entryID = ?"; + +static const char* GET_OLD_ENTRY = "select * from Entries where entryID = (select min(entryID) from Entries where timeOfEntry = (select min(timeOfEntry) from Entries))"; +static const char* GET_NEW_ENTRY = "select * from Entries where entryID = (select max(entryID) from Entries where timeOfEntry = (select max(timeOfEntry) from Entries))"; + +static char* +copyStringInternal(const char* string) +{ + int newStringLength = strlen(string) + 1; + + char* newString = (char*) malloc(newStringLength); + + memcpy(newString, string, newStringLength); + + return newString; +} + +LogStorage +SqliteLogStorage_createInstance(const char* filename) +{ + + sqlite3* db = NULL; + sqlite3_stmt* insertEntryStmt = NULL; + sqlite3_stmt* insertEntryDataStmt = NULL; + sqlite3_stmt* getEntriesWithRange = NULL; + sqlite3_stmt* getEntriesAfter = NULL; + sqlite3_stmt* getEntryData = NULL; + sqlite3_stmt* getOldEntry = NULL; + sqlite3_stmt* getNewEntry = NULL; + char *zErrMsg = 0; + + int rc = sqlite3_open(filename, &db); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_exec(db, CREATE_TABLE_ENTRYS, NULL, 0, &zErrMsg); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_exec(db, CREATE_TABLE_ENTRY_DATA, NULL, 0, &zErrMsg); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare(db, INSERT_ENTRY, -1, &insertEntryStmt, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare(db, INSERT_ENTRY_DATA, -1, &insertEntryDataStmt, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_ENTRIES_WITH_RANGE, -1, &getEntriesWithRange, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_ENTRIES_AFTER, -1, &getEntriesAfter, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_ENTRY_DATA, -1, &getEntryData, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_OLD_ENTRY, -1, &getOldEntry, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_NEW_ENTRY, -1, &getNewEntry, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + LogStorage self = (LogStorage) calloc(1, sizeof(struct sLogStorage)); + + SqliteLogStorage* instanceData = (SqliteLogStorage*) calloc(1, sizeof(struct sSqliteLogStorage)); + + instanceData->filename = copyStringInternal(filename); + instanceData->db = db; + instanceData->insertEntryStmt = insertEntryStmt; + instanceData->insertEntryDataStmt = insertEntryDataStmt; + instanceData->getEntriesWithRange = getEntriesWithRange; + instanceData->getEntriesAfter = getEntriesAfter; + instanceData->getEntryData = getEntryData; + instanceData->getOldEntry = getOldEntry; + instanceData->getNewEntry = getNewEntry; + + self->instanceData = (void*) instanceData; + + self->addEntry = SqliteLogStorage_addEntry; + self->addEntryData = SqliteLogStorage_addEntryData; + self->getEntries = SqliteLogStorage_getEntries; + self->getEntriesAfter = SqliteLogStorage_getEntriesAfter; + self->getOldestAndNewestEntries = SqliteLogStorage_getOldestAndNewestEntries; + self->destroy = SqliteLogStorage_destroy; + + return self; + +exit_with_error: + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - failed to create LogStorage instance!\n"); + + return NULL; +} + + +static uint64_t +SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp) +{ + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - add entry\n"); + + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + int rc; + char *zErrMsg = 0; + + rc = sqlite3_bind_int64(instanceData->insertEntryStmt, 1, (sqlite_int64) timestamp); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_step(instanceData->insertEntryStmt); + + if (rc != SQLITE_DONE) + goto exit_with_error; + + uint64_t id = sqlite3_last_insert_rowid(db); + + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - new entry with ID = %lu\n", id); + + rc = sqlite3_reset(instanceData->insertEntryStmt); + + if (rc != SQLITE_OK) + goto exit_with_error; + + return id; + +exit_with_error: + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - failed to add entry to log!\n"); + + return 0; +} + +static bool +SqliteLogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + char *zErrMsg = 0; + + rc = sqlite3_bind_int64(instanceData->insertEntryDataStmt, 1, (sqlite_int64) entryID); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_bind_text(instanceData->insertEntryDataStmt, 2, dataRef, -1, SQLITE_STATIC); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_bind_blob(instanceData->insertEntryDataStmt, 3, data, dataSize, SQLITE_STATIC); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_bind_int(instanceData->insertEntryDataStmt, 4, reasonCode); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_step(instanceData->insertEntryDataStmt); + + if (rc != SQLITE_DONE) + goto exit_with_error; + + rc = sqlite3_reset(instanceData->insertEntryDataStmt); + + if (rc != SQLITE_OK) + goto exit_with_error; + + return true; + +exit_with_error: + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - failed to add entry data!\n"); + + return false; +} + +static void +getEntryData(LogStorage self, uint64_t entryID, LogEntryDataCallback entryDataCallback, void* parameter) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + int rc; + + rc = sqlite3_bind_int64(instanceData->getEntryData, 1, entryID); + + if (rc != SQLITE_OK) { + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - getEntryData 1 rc:%i\n", rc); + } + + bool sendFinalEvent = true; + + while ((rc = sqlite3_step(instanceData->getEntryData)) == SQLITE_ROW) { + + const char* dataRef = (const char*) sqlite3_column_text(instanceData->getEntryData, 0); + uint8_t* data = (uint8_t*) sqlite3_column_blob(instanceData->getEntryData, 1); + int dataSize = sqlite3_column_bytes(instanceData->getEntryData, 1); + int reasonCode = sqlite3_column_int(instanceData->getEntryData, 2); + + if (entryDataCallback != NULL) { + if (entryDataCallback(parameter, dataRef, data, dataSize, (uint8_t) reasonCode, true) == false) { + sendFinalEvent = false; + break; + } + + } + } + + if (sendFinalEvent) { + if (entryDataCallback != NULL) + entryDataCallback(parameter, NULL, NULL, 0, (uint8_t) 0, false); + } + + rc = sqlite3_reset(instanceData->getEntryData); + + if (rc != SQLITE_OK) + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - getEntryData reset rc:%i\n", rc); +} + +static bool +SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 1, startingTime); + + if (rc != SQLITE_OK) + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries 1 rc:%i\n", rc); + + rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 2, endingTime); + + if (rc != SQLITE_OK) + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries 2 rc:%i\n", rc); + + bool sendFinalEvent = true; + + while((rc = sqlite3_step(instanceData->getEntriesWithRange)) == SQLITE_ROW) { + + uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesWithRange, 0); + uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesWithRange, 1); + + if (entryCallback != NULL) { + if (entryCallback(parameter, timestamp, entryID, true) == false) { + sendFinalEvent = false; + break; + } + + } + + getEntryData(self, entryID, entryDataCallback, parameter); + } + + if (sendFinalEvent) + if (entryCallback != NULL) + entryCallback(parameter, 0, 0, false); + + + rc = sqlite3_reset(instanceData->getEntriesWithRange); + + if (rc != SQLITE_OK) + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries reset rc:%i\n", rc); + + return true; +} + +static bool +SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime) +{ + bool validOldEntry = false; + bool validNewEntry = false; + + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + /* Get oldest entry */ + + rc = sqlite3_step(instanceData->getOldEntry); + + if (rc == SQLITE_ROW) { + *oldEntry = sqlite3_column_int64(instanceData->getOldEntry, 0); + *oldEntryTime = sqlite3_column_int64(instanceData->getOldEntry, 1); + validNewEntry = true; + } + else { + *oldEntry = 0; + *oldEntryTime = 0; + } + + sqlite3_reset(instanceData->getOldEntry); + + /* Get newest entry */ + + rc = sqlite3_step(instanceData->getNewEntry); + + if (rc == SQLITE_ROW) { + *newEntry = sqlite3_column_int64(instanceData->getNewEntry, 0); + *newEntryTime = sqlite3_column_int64(instanceData->getNewEntry, 1); + validOldEntry = true; + } + else { + *newEntry = 0; + *newEntryTime = 0; + } + + sqlite3_reset(instanceData->getNewEntry); + + return (validOldEntry && validNewEntry); +} + +static bool +SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + rc = sqlite3_bind_int64(instanceData->getEntriesAfter, 1, entryID); + + if (rc != SQLITE_OK) + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntriesAfter 1 rc:%i\n", rc); + + bool sendFinalEvent = true; + + while ((rc = sqlite3_step(instanceData->getEntriesAfter)) == SQLITE_ROW) { + + uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesAfter, 0); + uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesAfter, 1); + + if (entryCallback != NULL) { + if (entryCallback(parameter, timestamp, entryID, true) == false) { + sendFinalEvent = false; + break; + } + } + + getEntryData(self, entryID, entryDataCallback, parameter); + } + + if (sendFinalEvent) + if (entryCallback != NULL) + entryCallback(parameter, 0, 0, false); + + rc = sqlite3_reset(instanceData->getEntriesAfter); + + if (rc != SQLITE_OK) + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntriesAfter reset rc:%i\n", rc); + + return true; +} + +static void +SqliteLogStorage_destroy(LogStorage self) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) self->instanceData; + + sqlite3_finalize(instanceData->insertEntryStmt); + sqlite3_finalize(instanceData->insertEntryDataStmt); + sqlite3_finalize(instanceData->getEntriesWithRange); + sqlite3_finalize(instanceData->getEntriesAfter); + sqlite3_finalize(instanceData->getEntryData); + sqlite3_finalize(instanceData->getOldEntry); + sqlite3_finalize(instanceData->getNewEntry); + + if (sqlite3_close(instanceData->db) != SQLITE_OK) + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage: failed to close database %s!\n", instanceData->filename); + + free(instanceData->filename); + free(instanceData); + free(self); +} + + + + diff --git a/src/logging/log_storage.c b/src/logging/log_storage.c new file mode 100644 index 00000000..e7847e4a --- /dev/null +++ b/src/logging/log_storage.c @@ -0,0 +1,65 @@ +/* + * log_storage.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "logging_api.h" + +uint64_t +LogStorage_addEntry(LogStorage self, uint64_t timestamp) +{ + return self->addEntry(self, timestamp); +} + +bool +LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode) +{ + return self->addEntryData(self, entryID, dataRef, data, dataSize, reasonCode); +} + +bool +LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + return self->getEntries(self, startingTime, endingTime, entryCallback, entryDataCallback, parameter); +} + +bool +LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + return self->getEntriesAfter(self, startingTime, entryID, entryCallback, entryDataCallback, parameter); +} + +void +LogStorage_destroy(LogStorage self) +{ + self->destroy(self); +} + + +bool +LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime) +{ + return self->getOldestAndNewestEntries(self, newEntry, newEntryTime, oldEntry, oldEntryTime); +} + diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h new file mode 100644 index 00000000..403c3fb3 --- /dev/null +++ b/src/logging/logging_api.h @@ -0,0 +1,193 @@ +/* + * logging_api.h + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#ifndef LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ +#define LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + + +/** \addtogroup server_api_group + * @{ + */ + +/** + * @defgroup LOGGING_SPI Service provider interface (SPI) for log storage implementations + * + * This interface has to be implemented by the log storage provider. The Log storage provider + * has to provide a specific constructor that creates an instance of LogStorage by allocating + * the required memory fot the struct sLogStorage data structure and populate the function + * pointers with provider specific implementation functions. + * + * @{ + */ + +/** The LogStorage object handle */ +typedef struct sLogStorage* LogStorage; + +/** + * \brief Will be called for each new LogEntry by the getEntries and getEntriesAfter functions + * + * \param parameter - a user provided parameter that is passed to the callback handler + * \param timestamp - the entry timestamp of the LogEntry + * \param entryID - the entryID of the LogEntry + * \param moreFollow - more data will follow - if false, the data is not valid and no more data will follow + * + * \return true ready to receive new data, false abort operation + */ +typedef bool (*LogEntryCallback) (void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow); + +/** + * \brief Will be called for each new LogEntryData by the getEntries and getEntriesAfter functions + * + * \param parameter - a user provided parameter that is passed to the callback handler + * \param dataRef - the data reference of the LogEntryData + * \param data - the data content as an unstructured binary data block + * \param dataSize - the size of the binary data block + * \param reasonCode - the reasonCode of the LogEntryData + * \param moreFollow - more data will follow - if false, the data is not valid and no more data will follow + * + * \return true ready to receive new data, false abort operation + */ +typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow); + +struct sLogStorage { + + void* instanceData; + + uint64_t (*addEntry) (LogStorage self, uint64_t timestamp); + + bool (*addEntryData) (LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); + + bool (*getEntries) (LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + + bool (*getEntriesAfter) (LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + + bool (*getOldestAndNewestEntries) (LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime); + + void (*destroy) (LogStorage self); +}; + + +/** + * \brief Add an entry to the log + * + * \param self the pointer of the LogStorage instance + * \param timestamp the entry time of the new entry + * + * \return the entryID of the new entry + */ +uint64_t +LogStorage_addEntry(LogStorage self, uint64_t timestamp); + +/** + * \brief Add new entry data to an existing log entry + * + * \param self the pointer of the LogStorage instance + * \param entryID the ID of the log entry where the data will be added + * \param dataRef the data reference of the log entry data + * \param data the data content as an unstructured binary data block + * \param dataSize - the size of the binary data block + * \param reasonCode - the reasonCode of the LogEntryData + * + * \return true if the entry data was successfully added, false otherwise + */ +bool +LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); + +/** + * \brief Get log entries specified by a time range + * + * \param self the pointer of the LogStorage instance + * \param startingTime start time of the time range + * \param endingTime end time of the time range + * \param entryCallback callback function to be called for each new log entry + * \param entryDataCallback callback function to be called for each new log entry data + * \param parameter - a user provided parameter that is passed to the callback handler + * + * \return true if the request has been successful, false otherwise + */ +bool +LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + +/** + * \brief Get log entries specified by a start log entry + * + * The request will return all log entries that where entered into the log after the + * log entry specified by startingTime and entryID. + * + * \param self the pointer of the LogStorage instance + * \param startingTime timestamp of the start log entry + * \param entryID entryID of the start log entry + * \param entryCallback callback function to be called for each new log entry + * \param entryDataCallback callback function to be called for each new log entry data + * \param parameter - a user provided parameter that is passed to the callback handler + * + * \return true if the request has been successful, false otherwise + */ +bool +LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + +/** + * \brief Get the entry time and entryID of the oldest and the newest log entries + * + * This function is used to update the log status information in the LCB + * + * \param self the pointer of the LogStorage instance + * \param newEntry pointer to store the entryID of the newest entry + * \param newEntryTime pointer to store the entry time of the newest entry + * \param oldEntry pointer to store the entryID of the oldest entry + * \param oldEntryTime pointer to store the entry time of the oldest entry + * + */ +bool +LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime); + +/** + * \brief Destroy the LogStorage instance and free all related resources + * + * \param self the pointer of the LogStorage instance + */ +void +LogStorage_destroy(LogStorage self); + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ */ diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 29195485..593f1dea 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -279,6 +279,20 @@ MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, con LinkedList /* */ MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, const char* domainId); +/** + * \brief Get the names of all journals present in a MMS domain of the server + * + * This will result in a domain specific GetNameList request. + * + * \param self MmsConnection instance to operate on + * \param mmsError user provided variable to store error code + * \param domainId the domain name for the domain specific request + * + * \return the domain specific journal names or NULL if the request failed. + */ +LinkedList /* */ +MmsConnection_getDomainJournals(MmsConnection self, MmsError* mmsError, const char* domainId); + /** * \brief Get the names of all named variable lists associated with this client connection. * @@ -706,6 +720,60 @@ bool MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const char* fileSpecification, const char* continueAfter, MmsFileDirectoryHandler handler, void* handlerParameter); +typedef struct sMmsJournalEntry* MmsJournalEntry; + +typedef struct sMmsJournalVariable* MmsJournalVariable; + +struct sMmsJournalEntry { + MmsValue* entryID; /* type MMS_OCTET_STRING */ + MmsValue* occurenceTime; /* type MMS_BINARY_TIME */ + LinkedList journalVariables; +}; + +struct sMmsJournalVariable { + char* tag; + MmsValue* value; +}; + +/** + * \brief Destroy a single MmsJournalEntry instance. + * + * This function will destroy the whole MmsJournalEntry object including the attached list + * of MmsJournalVariable objects. It is intended to be used in conjunction with the + * LinkedList_destroyDeep function in order to free the result of MmsConnection_readJournalTimeRange + * or MmsConnection_readJournalStartAfter + * + * LinkedList_destroyDeep(journalEntries, (LinkedListValueDeleteFunction) + * MmsJournalEntry_destroy); + * + * \param self the MmsJournalEntry instance to destroy + */ +void +MmsJournalEntry_destroy(MmsJournalEntry self); + +const MmsValue* +MmsJournalEntry_getEntryID(MmsJournalEntry self); + +const MmsValue* +MmsJournalEntry_getOccurenceTime(MmsJournalEntry self); + +const LinkedList /* */ +MmsJournalEntry_getJournalVariables(MmsJournalEntry self); + +const char* +MmsJournalVariable_getTag(MmsJournalVariable self); + +const MmsValue* +MmsJournalVariable_getValue(MmsJournalVariable self); + + +LinkedList +MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime, bool* moreFollows); + +LinkedList +MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification, bool* moreFollows); /** * \brief Destroy (free) an MmsServerIdentity object diff --git a/src/mms/inc/mms_common.h b/src/mms/inc/mms_common.h index 50d6b121..c1a8169e 100644 --- a/src/mms/inc/mms_common.h +++ b/src/mms/inc/mms_common.h @@ -45,6 +45,8 @@ typedef enum MMS_ERROR_PARSING_RESPONSE = 4, MMS_ERROR_HARDWARE_FAULT = 5, MMS_ERROR_CONCLUDE_REJECTED = 6, + MMS_ERROR_INVALID_ARGUMENTS = 7, + MMS_ERROR_OTHER = 9, /* confirmed error PDU codes */ @@ -64,6 +66,7 @@ typedef enum MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE = 41, MMS_ERROR_SERVICE_OTHER = 50, + MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT = 55, MMS_ERROR_SERVICE_PREEMPT_OTHER = 60, diff --git a/src/mms/inc/mms_device_model.h b/src/mms/inc/mms_device_model.h index 218bdeff..f4c9be1d 100644 --- a/src/mms/inc/mms_device_model.h +++ b/src/mms/inc/mms_device_model.h @@ -29,6 +29,7 @@ #include "mms_type_spec.h" #include "mms_common.h" #include "mms_named_variable_list.h" +#include "logging_api.h" #ifdef __cplusplus extern "C" { @@ -49,14 +50,23 @@ typedef struct { MmsDomain** domains; } MmsDevice; + +struct sMmsJournal { + char* name; + LogStorage logStorage; +}; + +typedef struct sMmsJournal* MmsJournal; + /* - * Server side data structure to hold informations for a MMS domain (Logical Device) + * Server side data structure to hold information of an MMS domain (Logical Device) */ struct sMmsDomain { char* domainName; int namedVariablesCount; MmsVariableSpecification** namedVariables; LinkedList /**/ namedVariableLists; + LinkedList /* */ journals; }; /** @@ -80,6 +90,12 @@ MmsDomain_create(char* domainName); char* MmsDomain_getName(MmsDomain* self); +void +MmsDomain_addJournal(MmsDomain* self, const char* name); + +MmsJournal +MmsDomain_getJournal(MmsDomain* self, const char* name); + /** * Delete a MmsDomain instance * @@ -185,6 +201,12 @@ MmsDevice_getNamedVariableLists(MmsDevice* self); MmsNamedVariableList MmsDevice_getNamedVariableListWithName(MmsDevice* self, const char* variableListName); +MmsJournal +MmsJournal_create(const char* name); + +void +MmsJournal_destroy(MmsJournal self); + /**@}*/ #ifdef __cplusplus diff --git a/src/mms/inc/mms_value.h b/src/mms/inc/mms_value.h index f9177768..61253fb6 100644 --- a/src/mms/inc/mms_value.h +++ b/src/mms/inc/mms_value.h @@ -101,7 +101,7 @@ MmsValue_getArraySize(const MmsValue* self); * \return the element object */ MmsValue* -MmsValue_getElement(MmsValue* array, int index); +MmsValue_getElement(const MmsValue* array, int index); /** * \brief Create an emtpy array. @@ -911,7 +911,7 @@ MmsValue_isDeletable(MmsValue* self); * \param self the MmsValue instance */ MmsType -MmsValue_getType(MmsValue* self); +MmsValue_getType(const MmsValue* self); /** * \brief Get a sub-element of a MMS_STRUCTURE value specified by a path name. @@ -947,8 +947,8 @@ MmsValue_getTypeString(MmsValue* self); * * \return a pointer to the start of the buffer */ -char* -MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize); +const char* +MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize); /** * \brief create a new MmsValue instance from a BER encoded MMS Data element (deserialize) diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 8406f9c6..fad704a3 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -94,9 +94,10 @@ struct sMmsConnection { * MMS Object class enumeration type */ typedef enum { - MMS_NAMED_VARIABLE, - MMS_NAMED_VARIABLE_LIST, - MMS_DOMAIN_NAMES + MMS_OBJECT_CLASS_NAMED_VARIABLE = 0, + MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST = 2, + MMS_OBJECT_CLASS_JOURNAL = 8, + MMS_OBJECT_CLASS_DOMAIN = 9 } MmsObjectClass; MmsValue* @@ -259,4 +260,15 @@ int mmsClient_createMmsGetNameListRequestAssociationSpecific(long invokeId, ByteBuffer* writeBuffer, const char* continueAfter); +void +mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime); + +void +mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification); + +bool +mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, LinkedList* result); + #endif /* MMS_MSG_INTERNAL_H_ */ diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 69913375..0ddbbbd9 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -168,7 +168,11 @@ MmsPdu_t* mmsServer_createConfirmedResponse(uint32_t invokeId); void -mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType); +mmsServer_createServiceErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType); + +void +mmsServer_createServiceErrorPduWithServiceSpecificInfo(uint32_t invokeId, ByteBuffer* response, + MmsError errorType, uint8_t* serviceSpecificInfo, int serviceSpecficInfoLength); void mmsServer_writeConcludeResponsePdu(ByteBuffer* response); @@ -221,6 +225,14 @@ mmsServer_handleStatusRequest( int invokeId, ByteBuffer* response); +void +mmsServer_handleReadJournalRequest( + MmsServerConnection connection, + uint8_t* requestBuffer, + int bufPos, int maxBufPos, + uint32_t invokeId, + ByteBuffer* response); + void mmsServer_handleFileDirectoryRequest( MmsServerConnection connection, diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 3e444449..b3a99d24 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1054,7 +1054,7 @@ mmsClient_getNameListSingleRequest( payload, continueAfter); else { - if (objectClass == MMS_DOMAIN_NAMES) + if (objectClass == MMS_OBJECT_CLASS_DOMAIN) mmsClient_createMmsGetNameListRequestVMDspecific(invokeId, payload, continueAfter); else @@ -1110,31 +1110,37 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, LinkedList /* */ MmsConnection_getVMDVariableNames(MmsConnection self, MmsError* mmsError) { - return mmsClient_getNameList(self, mmsError, NULL, MMS_NAMED_VARIABLE, false); + return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_NAMED_VARIABLE, false); } LinkedList /* */ MmsConnection_getDomainNames(MmsConnection self, MmsError* mmsError) { - return mmsClient_getNameList(self, mmsError, NULL, MMS_DOMAIN_NAMES, false); + return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_DOMAIN, false); } LinkedList /* */ MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, const char* domainId) { - return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE, false); + return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_NAMED_VARIABLE, false); } LinkedList /* */ MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, const char* domainId) { - return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE_LIST, false); + return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST, false); +} + +LinkedList /* */ +MmsConnection_getDomainJournals(MmsConnection self, MmsError* mmsError, const char* domainId) +{ + return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_JOURNAL, false); } LinkedList /* */ MmsConnection_getVariableListNamesAssociationSpecific(MmsConnection self, MmsError* mmsError) { - return mmsClient_getNameList(self, mmsError, NULL, MMS_NAMED_VARIABLE_LIST, true); + return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST, true); } MmsValue* @@ -1636,6 +1642,124 @@ MmsConnection_getServerStatus(MmsConnection self, MmsError* mmsError, int* vmdLo } +static LinkedList +readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuffer* payload, bool* moreFollows) +{ + *mmsError = MMS_ERROR_NONE; + + ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload); + + LinkedList response = NULL; + + if (self->lastResponseError != MMS_ERROR_NONE) + *mmsError = self->lastResponseError; + else if (responseMessage != NULL) { + + if (mmsClient_parseReadJournalResponse(self, moreFollows, &response) == false) + *mmsError = MMS_ERROR_PARSING_RESPONSE; + } + + releaseResponse(self); + + if (self->associationState == MMS_STATE_CLOSED) + *mmsError = MMS_ERROR_CONNECTION_LOST; + + return response; +} + +static void +MmsJournalVariable_destroy(MmsJournalVariable self) +{ + if (self != NULL) { + GLOBAL_FREEMEM(self->tag); + MmsValue_delete(self->value); + GLOBAL_FREEMEM(self); + } +} + +void +MmsJournalEntry_destroy(MmsJournalEntry self) +{ + if (self != NULL) { + MmsValue_delete(self->entryID); + MmsValue_delete(self->occurenceTime); + LinkedList_destroyDeep(self->journalVariables, + (LinkedListValueDeleteFunction) MmsJournalVariable_destroy); + GLOBAL_FREEMEM(self); + } +} + + +const MmsValue* +MmsJournalEntry_getEntryID(MmsJournalEntry self) +{ + return self->entryID; +} + +const MmsValue* +MmsJournalEntry_getOccurenceTime(MmsJournalEntry self) +{ + return self->occurenceTime; +} + +const LinkedList /* */ +MmsJournalEntry_getJournalVariables(MmsJournalEntry self) +{ + return self->journalVariables; +} + +const char* +MmsJournalVariable_getTag(MmsJournalVariable self) +{ + return self->tag; +} + +const MmsValue* +MmsJournalVariable_getValue(MmsJournalVariable self) +{ + return self->value; +} + +LinkedList +MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime, bool* moreFollows) +{ + if ((MmsValue_getType(startingTime) != MMS_BINARY_TIME) || + (MmsValue_getType(endingTime) != MMS_BINARY_TIME)) { + + *mmsError = MMS_ERROR_INVALID_ARGUMENTS; + return NULL; + } + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + mmsClient_createReadJournalRequestWithTimeRange(invokeId, payload, domainId, itemId, startingTime, endingTime); + + return readJournal(self, mmsError, invokeId, payload, moreFollows); +} + +LinkedList +MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification, bool* moreFollows) +{ + + if ((MmsValue_getType(timeSpecification) != MMS_BINARY_TIME) || + (MmsValue_getType(entrySpecification) != MMS_OCTET_STRING)) { + + *mmsError = MMS_ERROR_INVALID_ARGUMENTS; + return NULL; + } + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + mmsClient_createReadJournalRequestStartAfter(invokeId, payload, domainId, itemId, timeSpecification, entrySpecification); + + return readJournal(self, mmsError, invokeId, payload, moreFollows); +} int32_t MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filename, uint32_t initialPosition, diff --git a/src/mms/iso_mms/client/mms_client_get_namelist.c b/src/mms/iso_mms/client/mms_client_get_namelist.c index b0b03b7c..8ca64b6d 100644 --- a/src/mms/iso_mms/client/mms_client_get_namelist.c +++ b/src/mms/iso_mms/client/mms_client_get_namelist.c @@ -229,12 +229,7 @@ mmsClient_createGetNameListRequestDomainOrVMDSpecific(long invokeId, const char* request->objectClass.present = ObjectClass_PR_basicObjectClass; - if (objectClass == MMS_NAMED_VARIABLE) - asn_long2INTEGER(&request->objectClass.choice.basicObjectClass, - ObjectClass__basicObjectClass_namedVariable); - else if (objectClass == MMS_NAMED_VARIABLE_LIST) - asn_long2INTEGER(&request->objectClass.choice.basicObjectClass, - ObjectClass__basicObjectClass_namedVariableList); + asn_long2INTEGER(&request->objectClass.choice.basicObjectClass, objectClass); asn_enc_rval_t rval; diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c new file mode 100644 index 00000000..919e2c57 --- /dev/null +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -0,0 +1,511 @@ +/* + * mms_client_journals.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "libiec61850_platform_includes.h" +#include "stack_config.h" +#include "mms_common.h" +#include "mms_client_connection.h" +#include "byte_buffer.h" + +#include "mms_client_internal.h" +#include "ber_encoder.h" +#include "ber_decode.h" +#include "conversions.h" +#include "mms_value_internal.h" + +//TODO add event-based API to parse journal entries + +static bool +parseJournalVariable(uint8_t* buffer, int bufPos, int maxLength, MmsJournalVariable journalVariable) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0x80: /* variableTag */ + + if (journalVariable->tag == NULL) { + journalVariable->tag = (char*) GLOBAL_MALLOC(length + 1); + memcpy(journalVariable->tag, buffer + bufPos, length); + journalVariable->tag[length] = 0; + } + + break; + + case 0xa1: /* valueSpec */ + + if (journalVariable->value == NULL) { + journalVariable->value = MmsValue_decodeMmsData(buffer, bufPos, length); + } + + break; + + default: + break; + + } + + bufPos += length; + } + + return true; +} + +static bool +parseJournalVariables(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + MmsJournalVariable journalVariable; + + switch (tag) { + case 0x30: /* journalVariable */ + + journalVariable = (MmsJournalVariable) + GLOBAL_CALLOC(1, sizeof(struct sMmsJournalVariable)); + + parseJournalVariable(buffer, bufPos, length, journalVariable); + + LinkedList_add(journalEntry->journalVariables, (void*) journalVariable); + + break; + + default: + break; + + } + + bufPos += length; + } + + return true; +} + +static bool +parseData(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0xa1: /* journalVariables */ + + journalEntry->journalVariables = LinkedList_create(); + + parseJournalVariables(buffer, bufPos, length, journalEntry); + + break; + + default: + break; + + } + + bufPos += length; + } + + return true; +} + +static bool +parseEntryContent(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0x80: /* occurenceTime */ + if (length == 6) + journalEntry->occurenceTime = MmsValue_newBinaryTime(false); + else if (length == 4) + journalEntry->occurenceTime = MmsValue_newBinaryTime(true); + else + break; + + memcpy(journalEntry->occurenceTime->value.binaryTime.buf, buffer + bufPos, length); + + break; + + case 0xa2: /* data */ + + parseData(buffer, bufPos, length, journalEntry); + + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: ignore unknown tag %02x\n", tag); + break; + } + + bufPos += length; + } + + return true; +} + +static bool +parseJournalEntry(uint8_t* buffer, int bufPos, int maxLength, LinkedList journalEntries) +{ + int maxBufPos = bufPos + maxLength; + + MmsJournalEntry journalEntry = (MmsJournalEntry) GLOBAL_CALLOC(1, sizeof(struct sMmsJournalEntry)); + LinkedList_add(journalEntries, (void*) journalEntry); + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + + case 0x80: /* entryID */ + journalEntry->entryID = MmsValue_newOctetString(length, length); + MmsValue_setOctetString(journalEntry->entryID, buffer + bufPos, length); + break; + + case 0xa1: /* originatingApplication */ + /* ignore */ + break; + + case 0xa2: /* entryContent */ + if (parseEntryContent(buffer, bufPos, length, journalEntry) == false) + return false; + + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: unknown tag %02x\n", tag); + + return false; + } + + bufPos += length; + } + + return true; +} + +static bool +parseListOfJournalEntries(uint8_t* buffer, int bufPos, int maxLength, LinkedList journalEntries) +{ + int maxBufPos = bufPos + maxLength; + + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0x30: + if (parseJournalEntry(buffer, bufPos, length, journalEntries) == false) + return false; + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: unknown tag %02x\n", tag); + + return false; + } + + bufPos += length; + } + + return true; +} + +bool +mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, LinkedList* result) +{ + uint8_t* buffer = self->lastResponse->buffer; + int maxBufPos = self->lastResponse->size; + int bufPos = self->lastResponseBufPos; + int length; + + uint8_t tag = buffer[bufPos++]; + + if (tag != 0xbf) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: unknown tag %02x\n", tag); + return false; + } + + tag = buffer[bufPos++]; + + if (moreFollows) + *moreFollows = false; + + if (tag != 0x41) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: unknown tag %02x\n", tag); + return false; + } + + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) + return false; + + int endPos = bufPos + length; + + if (endPos > maxBufPos) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: message to short (length:%i maxBufPos:%i)!\n", length, maxBufPos); + return false; + } + + LinkedList journalEntries = NULL; + + while (bufPos < endPos) { + tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + switch (tag) { + case 0xa0: /* listOfJournalEntry */ + journalEntries = LinkedList_create(); + + if (!parseListOfJournalEntries(buffer, bufPos, length, journalEntries)) + return false; + break; + + case 0x81: /* moreFollows */ + if (moreFollows) + *moreFollows = BerDecoder_decodeBoolean(buffer, bufPos); + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: message contains unknown tag %02x!\n", tag); + + return false; + } + + bufPos += length; + } + + *result = journalEntries; + + return true; +} + +void +mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime) +{ + /* calculate sizes */ + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); + + uint32_t domainIdStringSize = strlen(domainId); + uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; + + uint32_t itemIdStringSize = strlen(itemId); + uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; + + uint32_t objectIdSize = domainIdSize + itemIdSize; + + uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); + + uint32_t startingTimeSize = 2 + startingTime->value.binaryTime.size; + + uint32_t rangeStartSpecSize = 2 + startingTimeSize; + + uint32_t endingTimeSize = 2 + endingTime->value.binaryTime.size; + + uint32_t rangeEndSpecSize = 2 + endingTimeSize; + + uint32_t journalReadContentSize = journalNameSize + rangeStartSpecSize + rangeEndSpecSize; + + uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalReadContentSize) + (journalReadContentSize); + + uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; + + /* encode to buffer */ + int bufPos = 0; + uint8_t* buffer = request->buffer; + + bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + + /* encode read journal tag (context | structured ) [65 = 41h] */ + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); + + /* encode journalName */ + bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); + + /* encode rangeStartSpecification|startingTime */ + bufPos = BerEncoder_encodeTL(0xa1, startingTimeSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x80, startingTime->value.binaryTime.buf, + startingTime->value.binaryTime.size, buffer, bufPos); + + /* encode rangeStopSpecification|endingTime */ + bufPos = BerEncoder_encodeTL(0xa2, endingTimeSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x80, endingTime->value.binaryTime.buf, + endingTime->value.binaryTime.size, buffer, bufPos); + + request->size = bufPos; +} + +void +mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification) +{ + /* calculate sizes */ + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); + + uint32_t domainIdStringSize = strlen(domainId); + uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; + + uint32_t itemIdStringSize = strlen(itemId); + uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; + + uint32_t objectIdSize = domainIdSize + itemIdSize; + + uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); + + uint32_t timeSpecificationSize = 2 + timeSpecification->value.binaryTime.size; + + uint32_t entrySpecificationSize = 2 + entrySpecification->value.octetString.size; + + uint32_t entryToStartAfterContentSize = timeSpecificationSize + entrySpecificationSize; + + uint32_t entryToStartAfterSize = 1 + BerEncoder_determineLengthSize(entryToStartAfterContentSize) + + entryToStartAfterContentSize; + + uint32_t journalReadContentSize = journalNameSize + entryToStartAfterSize; + + uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalReadContentSize) + (journalReadContentSize); + + uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; + + /* encode to buffer */ + int bufPos = 0; + uint8_t* buffer = request->buffer; + + bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + + /* encode read journal tag (context | structured ) [65 = 41h] */ + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); + + /* encode journalName */ + bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); + + /* encode entryToStartAfter */ + bufPos = BerEncoder_encodeTL(0xa5, entryToStartAfterContentSize, buffer, bufPos); + + /* encode entryToStartAfter|timeSpecification */ + bufPos = BerEncoder_encodeOctetString(0x80, timeSpecification->value.binaryTime.buf, + timeSpecification->value.binaryTime.size, buffer, bufPos); + + /* encode entryToStartAfter|entrySpecification */ + bufPos = BerEncoder_encodeOctetString(0x81, entrySpecification->value.octetString.buf, + entrySpecification->value.octetString.size, buffer, bufPos); + + request->size = bufPos; +} + diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index b096c04b..d2eca37f 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -1600,7 +1600,12 @@ MmsValue_newBinaryTime(bool timeOfDay) void MmsValue_setBinaryTime(MmsValue* self, uint64_t timestamp) { - uint64_t mmsTime = timestamp - (441763200000LL); + uint64_t mmsTime; + + if (timestamp > 441763200000LL) + mmsTime = timestamp - (441763200000LL); + else + mmsTime = 0; uint8_t* binaryTimeBuf = self->value.binaryTime.buf; @@ -1877,7 +1882,7 @@ MmsValue_setElement(MmsValue* complexValue, int index, MmsValue* elementValue) } MmsValue* -MmsValue_getElement(MmsValue* complexValue, int index) +MmsValue_getElement(const MmsValue* complexValue, int index) { if ((complexValue->type != MMS_ARRAY) && (complexValue->type != MMS_STRUCTURE)) return NULL; @@ -1918,7 +1923,7 @@ MmsValue_isDeletable(MmsValue* self) } MmsType -MmsValue_getType(MmsValue* self) +MmsValue_getType(const MmsValue* self) { return self->type; } @@ -1970,8 +1975,8 @@ MmsValue_getTypeString(MmsValue* self) } } -char* -MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) +const char* +MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) { switch (MmsValue_getType(self)) { case MMS_STRUCTURE: @@ -1985,7 +1990,7 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) int i; for (i = 0; i < arraySize; i++) { - char* currentStr = MmsValue_printToBuffer(MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos); + const char* currentStr = MmsValue_printToBuffer((const MmsValue*) MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos); bufPos += strlen(currentStr); @@ -2100,7 +2105,7 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) case MMS_STRING: case MMS_VISIBLE_STRING: - strncpy(buffer, MmsValue_toString(self), bufferSize); + strncpy(buffer, MmsValue_toString((MmsValue*) self), bufferSize); /* Ensure buffer is always 0 terminated */ if (bufferSize > 0) diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index 5e22fe29..57bc35ac 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -41,6 +41,7 @@ MmsDomain_create(char* domainName) self->domainName = copyString(domainName); self->namedVariableLists = LinkedList_create(); + self->journals = NULL; return self; } @@ -57,6 +58,10 @@ MmsDomain_destroy(MmsDomain* self) GLOBAL_FREEMEM(self->namedVariables); } + if (self->journals != NULL) { + LinkedList_destroyDeep(self->journals, (LinkedListValueDeleteFunction) MmsJournal_destroy); + } + LinkedList_destroyDeep(self->namedVariableLists, (LinkedListValueDeleteFunction) MmsNamedVariableList_destroy); GLOBAL_FREEMEM(self); @@ -68,6 +73,39 @@ MmsDomain_getName(MmsDomain* self) return self->domainName; } +void +MmsDomain_addJournal(MmsDomain* self, const char* name) +{ + if (self->journals == NULL) + self->journals = LinkedList_create(); + + MmsJournal journal = MmsJournal_create(name); + + LinkedList_add(self->journals, (void*) journal); +} + + +MmsJournal +MmsDomain_getJournal(MmsDomain* self, const char* name) +{ + if (self->journals != NULL) { + + LinkedList journal = LinkedList_getNext(self->journals); + + while (journal != NULL) { + + MmsJournal mmsJournal = (MmsJournal) LinkedList_getData(journal); + + if (strcmp(mmsJournal->name, name) == 0) + return mmsJournal; + + journal = LinkedList_getNext(journal); + } + } + + return NULL; +} + bool MmsDomain_addNamedVariableList(MmsDomain* self, MmsNamedVariableList variableList) { diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index 582fb7cf..1f25ba73 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -123,13 +123,69 @@ encodeFileAttributes(uint8_t tag, uint32_t fileSize, char* gtString, uint8_t* bu } } +static void +createExtendedFilename(char* extendedFileName, char* fileName) +{ + strcpy(extendedFileName, CONFIG_VIRTUAL_FILESTORE_BASEPATH); + strncat(extendedFileName, fileName, sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256); +} + +static bool +getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModificationTimestamp) +{ + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, filename); + + return FileSystem_getFileInfo(extendedFileName, fileSize, lastModificationTimestamp); +} + +static FileHandle +openFile(char* fileName, bool readWrite) +{ + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, fileName); + + return FileSystem_openFile(extendedFileName, readWrite); +} + +static DirectoryHandle +openDirectory(char* directoryName) +{ + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, directoryName); + + return FileSystem_openDirectory(extendedFileName); +} + +static bool +renameFile(char* oldFilename, char* newFilename) { + char extendedOldFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + char extendedNewFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedOldFileName, oldFilename); + createExtendedFilename(extendedNewFileName, newFilename); + + return FileSystem_renameFile(extendedOldFileName, extendedNewFileName); +} + +static bool +deleteFile(char* fileName) { + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, fileName); + + return FileSystem_deleteFile(extendedFileName); +} static void createFileOpenResponse(uint32_t invokeId, ByteBuffer* response, char* fullPath, MmsFileReadStateMachine* frsm) { uint64_t msTime; - FileSystem_getFileInfo(fullPath, &(frsm->fileSize), &msTime); + getFileInfo(fullPath, &(frsm->fileSize), &msTime); char gtString[30]; @@ -216,19 +272,19 @@ mmsServer_handleFileDeleteRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: Delete file (%s)\n", filename); - if (!FileSystem_getFileInfo(filename, NULL, NULL)) { + if (!getFileInfo(filename, NULL, NULL)) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: File (%s) not found\n", filename); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); return; } - if (!FileSystem_deleteFile(filename)) { + if (!deleteFile(filename)) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: Delete file (%s) failed\n", filename); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_ACCESS_DENIED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_ACCESS_DENIED); return; } @@ -284,7 +340,7 @@ mmsServer_handleFileOpenRequest( MmsFileReadStateMachine* frsm = getFreeFrsm(connection); if (frsm != NULL) { - FileHandle fileHandle = FileSystem_openFile(filename, false); + FileHandle fileHandle = openFile(filename, false); if (fileHandle != NULL) { frsm->fileHandle = fileHandle; @@ -294,12 +350,12 @@ mmsServer_handleFileOpenRequest( createFileOpenResponse(invokeId, response, filename, frsm); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); } else goto exit_invalid_parameter; @@ -388,7 +444,7 @@ mmsServer_handleFileReadRequest( if (frsm != NULL) createFileReadResponse(connection, invokeId, response, frsm); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); } static void @@ -441,9 +497,10 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire { int directoryNameLength = strlen(directoryName); - DirectoryHandle directory = FileSystem_openDirectory(directoryName); + DirectoryHandle directory = openDirectory(directoryName); if (directory != NULL) { + bool isDirectory; char* fileName = FileSystem_readDirectory(directory, &isDirectory); @@ -457,13 +514,7 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire strcat(directoryName, fileName); - if (isDirectory) { - bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows); - } - else { - bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows); - - } + bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows); if (*moreFollows == true) break; @@ -485,8 +536,7 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire uint32_t fileSize; - if (FileSystem_getFileInfo(directoryName, &fileSize, &msTime)) { - + if (getFileInfo(directoryName, &fileSize, &msTime)) { char gtString[30]; Conversions_msTimeToGeneralizedTime(msTime, (uint8_t*) gtString); @@ -547,7 +597,7 @@ createFileDirectoryResponse(uint32_t invokeId, ByteBuffer* response, int maxPduS if (DEBUG_MMS_SERVER) printf("MMS_SERVER: Error opening directory!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); return; } @@ -642,7 +692,7 @@ mmsServer_handleFileRenameRequest( } if ((strlen(currentFileName) != 0) && (strlen(newFileName) != 0)) { - if (FileSystem_renameFile(currentFileName, newFileName)){ + if (renameFile(currentFileName, newFileName)){ /* send positive response */ createNullResponseExtendedTag(invokeId, response, 0x4b); } @@ -651,7 +701,7 @@ mmsServer_handleFileRenameRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: rename file failed!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); } } else diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index a36dbedb..6204e247 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -173,6 +173,38 @@ addSubNamedVaribleNamesToList(LinkedList nameList, char* prefix, MmsVariableSpec #endif /* (CONFIG_MMS_SUPPORT_FLATTED_NAME_SPACE == 1) */ + + + +static LinkedList +getJournalListDomainSpecific(MmsServerConnection connection, char* domainName) +{ + MmsDevice* device = MmsServer_getDevice(connection->server); + + LinkedList nameList = NULL; + + MmsDomain* domain = MmsDevice_getDomain(device, domainName); + + if (domain != NULL) { + nameList = LinkedList_create(); + + if (domain->journals != NULL) { + + LinkedList journalList = domain->journals; + + while ((journalList = LinkedList_getNext(journalList)) != NULL) { + + MmsJournal journal = (MmsJournal) LinkedList_getData(journalList); + + LinkedList_add(nameList, (void*) journal->name); + } + + } + } + + return nameList; +} + static LinkedList getNameListDomainSpecific(MmsServerConnection connection, char* domainName) { @@ -316,7 +348,7 @@ createNameListResponse( } if (startElement == NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); return; } } @@ -497,18 +529,32 @@ mmsServer_handleGetNameListRequest( LinkedList nameList = getNameListDomainSpecific(connection, domainSpecificName); if (nameList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { createNameListResponse(connection, invokeId, nameList, response, continueAfterId); LinkedList_destroy(nameList); } } + else if (objectClass == OBJECT_CLASS_JOURNAL) { + LinkedList nameList = getJournalListDomainSpecific(connection, domainSpecificName); + + if (nameList == NULL) + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + else { +#if (CONFIG_MMS_SORT_NAME_LIST == 1) + StringUtils_sortList(nameList); +#endif + + createNameListResponse(connection, invokeId, nameList, response, continueAfterId); + LinkedList_destroyStatic(nameList); + } + } #if (MMS_DATA_SET_SERVICE == 1) else if (objectClass == OBJECT_CLASS_NAMED_VARIABLE_LIST) { LinkedList nameList = getNamedVariableListsDomainSpecific(connection, domainSpecificName); if (nameList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { #if (CONFIG_MMS_SORT_NAME_LIST == 1) @@ -524,7 +570,7 @@ mmsServer_handleGetNameListRequest( else { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList domain specific objectClass %i not supported!\n", objectClass); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } } @@ -582,7 +628,7 @@ mmsServer_handleGetNameListRequest( else { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList VMD specific objectClass %i not supported!\n", objectClass); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } } @@ -602,14 +648,14 @@ mmsServer_handleGetNameListRequest( LinkedList_destroy(nameList); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ #endif /* (MMS_DATA_SET_SERVICE == 1) */ else { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList(%i) not supported!\n", objectScope); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } } 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 d47ecb0e..b1c813ea 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 @@ -221,7 +221,7 @@ createVariableAccessAttributesResponse( if (domain == NULL) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: domain %s not known\n", domainId); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_function; } @@ -237,7 +237,7 @@ createVariableAccessAttributesResponse( if (namedVariable == NULL) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: named variable %s not known\n", nameId); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_function; @@ -266,7 +266,7 @@ createVariableAccessAttributesResponse( if (DEBUG_MMS_SERVER) printf("MMS getVariableAccessAttributes: message to large! send error PDU!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_SERVICE_OTHER); goto exit_function; diff --git a/src/mms/iso_mms/server/mms_journal.c b/src/mms/iso_mms/server/mms_journal.c new file mode 100644 index 00000000..ecb26d19 --- /dev/null +++ b/src/mms/iso_mms/server/mms_journal.c @@ -0,0 +1,46 @@ +/* + * mms_journal.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "libiec61850_platform_includes.h" +#include "mms_device_model.h" +#include "mms_server_internal.h" + +MmsJournal +MmsJournal_create(const char* name) +{ + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: create new journal %s\n", name); + + MmsJournal self = (MmsJournal) GLOBAL_MALLOC(sizeof(struct sMmsJournal)); + + self->name = copyString(name); + + return self; +} + +void +MmsJournal_destroy(MmsJournal self) +{ + GLOBAL_FREEMEM(self->name); + GLOBAL_FREEMEM(self); +} diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c new file mode 100644 index 00000000..74d69a77 --- /dev/null +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -0,0 +1,533 @@ +/* + * mms_journal_service.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "libiec61850_platform_includes.h" +#include "mms_server_internal.h" +#include "mms_value_internal.h" + +#if (MMS_JOURNAL_SERVICE == 1) + + +typedef struct sJournalVariable* JournalVariable; + +struct sJournalVariable { + char* tag; /* UTF8(1..255) */ + int tagSize; + + uint8_t* data; + int dataSize; + + JournalVariable next; +}; + +typedef struct { + uint8_t* entryID; + int entryIDSize; + + uint64_t timestamp; + + JournalVariable listOfVariables; +} JournalEntry; + + +typedef struct sJournalEncoder* JournalEncoder; + +struct sJournalEncoder { + uint8_t* buffer; + int maxSize; + int bufPos; + int currentEntryBufPos; /* store start buffer position of current entry in case the whole JournalEntry will become too long */ + uint64_t currentEntryId; //TODO use a byte array for the generic MMS case! + uint64_t currentTimestamp; + bool moreFollows; +}; + +static bool +entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow) +{ + JournalEncoder encoder = (JournalEncoder) parameter; + + if (moreFollow) { + + if (encoder->moreFollows) + return false; + + encoder->currentEntryBufPos = encoder->bufPos; + + encoder->bufPos += 48; /* reserve space for common entry parts */ + + encoder->currentEntryId = entryID; + encoder->currentTimestamp = timestamp; + } + + return true; +} + +static const char* REASON_CODE_STR = "ReasonCode"; + +static bool +entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow) +{ + JournalEncoder encoder = (JournalEncoder) parameter; + + uint8_t* buffer = encoder->buffer; + + //TODO check if entry is too long for buffer! + + if (moreFollow) { + int bufPos = encoder->bufPos; + + uint32_t dataRefStrLen = strlen(dataRef); + uint32_t dataRefLen = 1 + BerEncoder_determineLengthSize(dataRefStrLen) + dataRefStrLen; + + uint32_t valueSpecLen = 1 + BerEncoder_determineLengthSize(dataSize) + dataSize; + + uint32_t firstVariableLen = 1 + BerEncoder_determineLengthSize(valueSpecLen + dataRefLen) + + valueSpecLen + dataRefLen; + + uint8_t reasonCodeNBuf[2]; + MmsValue reasonCodeValue; + reasonCodeValue.type = MMS_BIT_STRING; + reasonCodeValue.value.bitString.size = 7; + reasonCodeValue.value.bitString.buf = reasonCodeNBuf; + + MmsValue_setBitStringFromInteger(&reasonCodeValue, reasonCode); + + uint32_t reasonCodeValueLen = MmsValue_encodeMmsData(&reasonCodeValue, NULL, 0, false); + + uint32_t reasonCodeContentLen = reasonCodeValueLen + 2 + 12; + + uint32_t secondVariableLen = 1 + BerEncoder_determineLengthSize(reasonCodeContentLen) + + reasonCodeContentLen; + + uint32_t totalLen = firstVariableLen + secondVariableLen; + + if ((int) (bufPos + totalLen) > encoder->maxSize) { + encoder->moreFollows = true; + encoder->bufPos = encoder->currentEntryBufPos; /* remove last entry */ + return false; + } + + bufPos = BerEncoder_encodeTL(0x30, valueSpecLen + dataRefLen, buffer, bufPos); + + /* encode dataRef */ + bufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) dataRef, dataRefStrLen, buffer, bufPos); + + /* encode valueSpec */ + bufPos = BerEncoder_encodeOctetString(0xa1, data, dataSize, buffer, bufPos); + + /* encode reasonCode */ + + bufPos = BerEncoder_encodeTL(0x30, reasonCodeContentLen , buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) REASON_CODE_STR, 10, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, reasonCodeValueLen, buffer, bufPos); + bufPos = MmsValue_encodeMmsData(&reasonCodeValue, buffer, bufPos, true); + + encoder->bufPos = bufPos; + } + else { + int dataContentLen = encoder->bufPos - (encoder->currentEntryBufPos + 48); + + int journalVariablesLen = 1 + BerEncoder_determineLengthSize(dataContentLen) + dataContentLen; + + int dataLen = 1 + BerEncoder_determineLengthSize(journalVariablesLen) + journalVariablesLen; + + int dataAndTimeLen = dataLen + 8; + + int entryContentLen = 1 + BerEncoder_determineLengthSize(dataAndTimeLen) + dataAndTimeLen; + + int journalEntryContentLen = 10 /* entryIdentifier */ + + 4 /* originatingApplication */ + + entryContentLen; + + int headerBufPos = encoder->currentEntryBufPos; + + headerBufPos = BerEncoder_encodeTL(0x30, journalEntryContentLen, buffer, headerBufPos); + + headerBufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) &(encoder->currentEntryId), 8, buffer, headerBufPos); + + headerBufPos = BerEncoder_encodeTL(0xa1, 2, buffer, headerBufPos); + headerBufPos = BerEncoder_encodeTL(0x30, 0, buffer, headerBufPos); + + headerBufPos = BerEncoder_encodeTL(0xa2, dataAndTimeLen, buffer, headerBufPos); + + MmsValue occurenceTime; + occurenceTime.type = MMS_BINARY_TIME; + occurenceTime.value.binaryTime.size = 6; + MmsValue_setBinaryTime(&occurenceTime, encoder->currentTimestamp); + headerBufPos = BerEncoder_encodeOctetString(0x80, occurenceTime.value.binaryTime.buf, 6, buffer, headerBufPos); + + headerBufPos = BerEncoder_encodeTL(0xa2, journalVariablesLen, buffer, headerBufPos); + headerBufPos = BerEncoder_encodeTL(0xa1, dataContentLen, buffer, headerBufPos); + + int entryHeaderLength = headerBufPos - encoder->currentEntryBufPos; + + /* move data to entry header */ + memmove(buffer + (encoder->currentEntryBufPos + entryHeaderLength), buffer + (encoder->currentEntryBufPos + 48), + dataContentLen); + + /* prepare buffer for next entry. */ + encoder->bufPos = encoder->currentEntryBufPos + entryHeaderLength + dataContentLen; + } + + return true; +} + +static bool +parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bufPos, int maxBufPos , uint32_t invokeId, ByteBuffer* response) +{ + uint8_t tag = buffer[(*bufPos)++]; + int length; + + if (tag != 0x1a) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return false; + } + + *bufPos = BerDecoder_decodeLength(buffer, &length, *bufPos, maxBufPos); + + if (*bufPos < 0) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return false; + } + + if (length > maxLength) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return false; + } + + memcpy(filename, buffer + *bufPos, length); + filename[length] = 0; + *bufPos += length; + + return true; +} + +#define RESERVED_SPACE_FOR_HEADER 22 + +void +mmsServer_handleReadJournalRequest( + MmsServerConnection connection, + uint8_t* requestBuffer, + int bufPos, int maxBufPos, + uint32_t invokeId, + ByteBuffer* response) +{ + char domainId[65]; + char logName[65]; + uint8_t entryIdBuf[64]; /* maximum size of entry id is 64 bytes! */ + + MmsValue entrySpec; + entrySpec.type = MMS_OCTET_STRING; + entrySpec.value.octetString.buf = entryIdBuf; + entrySpec.value.octetString.maxSize = 64; + + MmsValue rangeStart; + MmsValue rangeStop; + + bool hasNames = false; + bool hasRangeStartSpec = false; + bool hasRangeStopSpec = false; + bool hasTimeSpec = false; + bool hasEntrySpec = false; + + while (bufPos < maxBufPos) { + uint8_t tag = requestBuffer[bufPos++]; + int length; + + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (bufPos < 0) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + + switch (tag) { + + case 0xa0: /* journalName */ + { + uint8_t objectIdTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + switch (objectIdTag) { + case 0xa1: /* domain-specific */ + + if (!parseStringWithMaxLength(domainId, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { + return; + } + + if (!parseStringWithMaxLength(logName, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { + return; + } + + hasNames = true; + + break; + + default: + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + + } + } + + break; + + case 0xa1: /* rangeStartSpecification */ + { + uint8_t subTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (subTag != 0x80) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + } + + + if ((length == 4) || (length == 6)) { + + rangeStart.type = MMS_BINARY_TIME; + rangeStart.value.binaryTime.size = length; + + memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); + + hasRangeStartSpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; // forward request to implementation class + } + + bufPos += length; + } + + break; + + case 0xa2: /* rangeStopSpecification */ + { + uint8_t subTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (subTag != 0x80) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + } + + if ((length == 4) || (length == 6)) { + rangeStop.type = MMS_BINARY_TIME; + rangeStop.value.binaryTime.size = length; + + memcpy(rangeStop.value.binaryTime.buf, requestBuffer + bufPos, length); + + hasRangeStopSpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; // forward request to implementation class + } + + bufPos += length; + } + + break; + + case 0xa5: /* entryToStartAfter */ + { + int maxSubBufPos = bufPos + length; + + while (bufPos < maxSubBufPos) { + uint8_t subTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + switch (subTag) { + case 0x80: /* timeSpecification */ + + if ((length == 4) || (length == 6)) { + rangeStart.type = MMS_BINARY_TIME; + rangeStart.value.binaryTime.size = length; + + memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); + + hasTimeSpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + break; + + case 0x81: /* entrySpecification */ + + if (length <= entrySpec.value.octetString.maxSize) { + memcpy(entrySpec.value.octetString.buf, requestBuffer + bufPos, length); + entrySpec.value.octetString.size = length; + + hasEntrySpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + break; + + default: + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + } + + bufPos += length; + } + + } + break; + + default: + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + } + + //TODO check if required fields are present + if (hasNames == false) { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal missing journal name\n"); + + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + //TODO check valid field combinations + + /* lookup journal */ + MmsDevice* mmsDevice = MmsServer_getDevice(connection->server); + + MmsDomain* mmsDomain = MmsDevice_getDomain(mmsDevice, domainId); + + if (mmsDomain == NULL) { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal domain %s not found\n", domainId); + + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + MmsJournal mmsJournal = MmsDomain_getJournal(mmsDomain, logName); + + if (mmsJournal == NULL) { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal journal %s not found\n", logName); + + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal - read journal %s ...\n", mmsJournal->name); + + struct sJournalEncoder encoder; + + encoder.buffer = response->buffer; + encoder.moreFollows = false; + encoder.maxSize = connection->maxPduSize - 3; /* reserve three bytes for moreFollows */ + encoder.bufPos = RESERVED_SPACE_FOR_HEADER; /* reserve space for header */ + + LogStorage logStorage = mmsJournal->logStorage; + + if (logStorage != NULL) { + + if (hasRangeStartSpec && hasRangeStopSpec) { + LogStorage_getEntries(logStorage, MmsValue_getBinaryTimeAsUtcMs(&rangeStart), MmsValue_getBinaryTimeAsUtcMs(&rangeStop), + entryCallback, entryDataCallback, &encoder); + } + else if (hasEntrySpec && hasTimeSpec) { + LogStorage_getEntriesAfter(logStorage, MmsValue_getBinaryTimeAsUtcMs(&rangeStart), *((uint64_t*) entryIdBuf), + entryCallback, entryDataCallback, &encoder); + } + else { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal missing valid argument combination\n"); + + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + } + /* actual encoding will happen in callback handler. When getEntries returns the data is + * already encoded in the buffer. + */ + + /* encode message header in response buffer */ + + uint8_t* buffer = encoder.buffer; + bufPos = 0; + + int dataSize = encoder.bufPos - RESERVED_SPACE_FOR_HEADER; + + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize((uint32_t) invokeId) + 2; + uint32_t listOfEntriesLen = 1 + BerEncoder_determineLengthSize(dataSize) + dataSize; + + uint32_t moreFollowsLen; + + if (encoder.moreFollows) + moreFollowsLen = 3; + else + moreFollowsLen = 0; + + uint32_t readJournalLen = 2 + BerEncoder_determineLengthSize(listOfEntriesLen + moreFollowsLen) + + (listOfEntriesLen + moreFollowsLen); + + uint32_t confirmedResponsePDUSize = readJournalLen + invokeIdSize; + + bufPos = BerEncoder_encodeTL(0xa1, confirmedResponsePDUSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize - 2, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32((uint32_t) invokeId, buffer, bufPos); + + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(listOfEntriesLen + moreFollowsLen, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa0, dataSize, buffer, bufPos); + + /* move encoded JournalEntry data to continue the buffer header */ + + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal: Encoded message header with %i bytes\n", bufPos); + + memmove(buffer + bufPos, buffer + RESERVED_SPACE_FOR_HEADER, dataSize); + + bufPos = bufPos + dataSize; + + /* encode morefollows */ + if (encoder.moreFollows) + bufPos = BerEncoder_encodeBoolean(0x81, true, buffer, bufPos); + + response->size = bufPos; +} + +#endif /* (MMS_JOURNAL_SERVICE == 1) */ 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 71ce3a5e..1c730454 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 @@ -99,6 +99,18 @@ createDeleteNamedVariableListResponse(uint32_t invokeId, ByteBuffer* response, response->size = bufPos; } +static void /* Confirmed service error (ServiceError) */ +createServiceErrorDeleteVariableLists(uint32_t invokeId, ByteBuffer* response, + MmsError errorType, uint32_t numberDeleted) +{ + uint8_t buffer[8]; + + int size = BerEncoder_encodeUInt32WithTL(0x86, numberDeleted, buffer, 0); + + mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, + buffer, size); +} + void mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, uint8_t* buffer, int bufPos, int maxBufPos, @@ -125,6 +137,8 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, MmsDevice* device = MmsServer_getDevice(connection->server); if (scopeOfDelete == DeleteNamedVariableListRequest__scopeOfDelete_specific) { + MmsError serviceError = MMS_ERROR_NONE; + int numberMatched = 0; int numberDeleted = 0; @@ -154,10 +168,14 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, if (MmsNamedVariableList_isDeletable(variableList)) { - if (mmsServer_callVariableListChangedHandler(false, MMS_DOMAIN_SPECIFIC, domain, listName, connection) == MMS_ERROR_NONE) { + MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_DOMAIN_SPECIFIC, domain, listName, connection); + + if (deleteError == MMS_ERROR_NONE) { MmsDomain_deleteNamedVariableList(domain, listName); numberDeleted++; } + else + serviceError = deleteError; } } } @@ -173,10 +191,14 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, if (variableList != NULL) { numberMatched++; - if (mmsServer_callVariableListChangedHandler(false, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection) == MMS_ERROR_NONE) { + MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection); + + if (deleteError == MMS_ERROR_NONE) { numberDeleted++; MmsServerConnection_deleteNamedVariableList(connection, listName); } + else + serviceError = deleteError; } } else if (request->listOfVariableListName->list.array[i]->present == ObjectName_PR_vmdspecific) { @@ -190,19 +212,25 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, if (variableList != NULL) { numberMatched++; - if (mmsServer_callVariableListChangedHandler(false, MMS_VMD_SPECIFIC, NULL, listName, connection) - == MMS_ERROR_NONE) { + MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_VMD_SPECIFIC, NULL, listName, connection); + + if (deleteError == MMS_ERROR_NONE) { numberDeleted++; mmsServer_deleteVariableList(device->namedVariableLists, listName); } + else + serviceError = deleteError; } } } - createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted); + if (serviceError == MMS_ERROR_NONE) + createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted); + else + createServiceErrorDeleteVariableLists(invokeId, response, serviceError, numberDeleted); } else { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); @@ -404,7 +432,7 @@ mmsServer_handleDefineNamedVariableListRequest( char domainName[65]; if (request->variableListName.choice.domainspecific.domainId.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -415,7 +443,7 @@ mmsServer_handleDefineNamedVariableListRequest( MmsDomain* domain = MmsDevice_getDomain(device, domainName); if (domain == NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -423,7 +451,7 @@ mmsServer_handleDefineNamedVariableListRequest( char variableListName[65]; if (request->variableListName.choice.domainspecific.itemId.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -432,7 +460,7 @@ mmsServer_handleDefineNamedVariableListRequest( request->variableListName.choice.domainspecific.itemId.size); if (MmsDomain_getNamedVariableList(domain, variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); } else { MmsError mmsError; @@ -450,15 +478,15 @@ mmsServer_handleDefineNamedVariableListRequest( } else { MmsNamedVariableList_destroy(namedVariableList); - mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); + mmsServer_createServiceErrorPdu(invokeId, response, mmsError); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); + mmsServer_createServiceErrorPdu(invokeId, response, mmsError); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); } @@ -470,7 +498,7 @@ mmsServer_handleDefineNamedVariableListRequest( if (request->variableListName.choice.aaspecific.size > 64) { //TODO send reject PDU instead? - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -479,7 +507,7 @@ mmsServer_handleDefineNamedVariableListRequest( request->variableListName.choice.aaspecific.size); if (MmsServerConnection_getNamedVariableList(connection, variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); } else { MmsError mmsError; @@ -495,16 +523,16 @@ mmsServer_handleDefineNamedVariableListRequest( } else { MmsNamedVariableList_destroy(namedVariableList); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); + mmsServer_createServiceErrorPdu(invokeId, response, mmsError); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); } else if (request->variableListName.present == ObjectName_PR_vmdspecific) { LinkedList vmdScopeNVLs = MmsDevice_getNamedVariableLists(connection->server->device); @@ -515,7 +543,7 @@ mmsServer_handleDefineNamedVariableListRequest( if (request->variableListName.choice.vmdspecific.size > 64) { //TODO send reject PDU instead? - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -524,7 +552,7 @@ mmsServer_handleDefineNamedVariableListRequest( request->variableListName.choice.vmdspecific.size); if (mmsServer_getNamedVariableListWithName(MmsDevice_getNamedVariableLists(connection->server->device), variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); } else { MmsError mmsError; @@ -541,7 +569,7 @@ mmsServer_handleDefineNamedVariableListRequest( } else { MmsNamedVariableList_destroy(namedVariableList); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); } } @@ -549,7 +577,7 @@ mmsServer_handleDefineNamedVariableListRequest( } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED); exit_free_struct: asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); @@ -648,7 +676,7 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if ((request->choice.domainspecific.domainId.size > 64) || (request->choice.domainspecific.itemId.size > 64)) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); goto exit_function; } @@ -669,10 +697,10 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if (variableList != NULL) createGetNamedVariableListAttributesResponse(invokeId, response, variableList); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } #if (MMS_DYNAMIC_DATA_SETS == 1) @@ -681,7 +709,7 @@ mmsServer_handleGetNamedVariableListAttributesRequest( char listName[65]; if (request->choice.aaspecific.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); goto exit_function; } @@ -693,14 +721,14 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if (varList != NULL) createGetNamedVariableListAttributesResponse(invokeId, response, varList); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ else if (request->present == ObjectName_PR_vmdspecific) { char listName[65]; if (request->choice.vmdspecific.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); goto exit_function; } @@ -714,10 +742,10 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if (varList != NULL) createGetNamedVariableListAttributesResponse(invokeId, response, varList); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } else { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } exit_function: diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index cfe2af9c..3ad46abb 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -431,7 +431,7 @@ encodeReadResponse(MmsServerConnection connection, if (DEBUG_MMS_SERVER) printf("MMS read: message to large! send error PDU!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_SERVICE_OTHER); goto exit_function; @@ -647,7 +647,7 @@ handleReadNamedVariableListRequest( if (domain == NULL) { if (DEBUG_MMS_SERVER) printf("MMS read: domain %s not found!\n", domainIdStr); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } else { MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr); @@ -658,7 +658,7 @@ handleReadNamedVariableListRequest( } else { if (DEBUG_MMS_SERVER) printf("MMS read: named variable list %s not found!\n", nameIdStr); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } } } @@ -672,7 +672,7 @@ handleReadNamedVariableListRequest( MmsNamedVariableList namedList = mmsServer_getNamedVariableListWithName(connection->server->device->namedVariableLists, listName); if (namedList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { VarAccessSpec accessSpec; @@ -697,7 +697,7 @@ handleReadNamedVariableListRequest( MmsNamedVariableList namedList = MmsServerConnection_getNamedVariableList(connection, listName); if (namedList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { VarAccessSpec accessSpec; @@ -711,7 +711,7 @@ handleReadNamedVariableListRequest( } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } #endif /* MMS_DATA_SET_SERVICE == 1 */ @@ -745,7 +745,7 @@ mmsServer_handleReadRequest( } #endif else { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); diff --git a/src/mms/iso_mms/server/mms_server_common.c b/src/mms/iso_mms/server/mms_server_common.c index 46d87bed..ba277499 100644 --- a/src/mms/iso_mms/server/mms_server_common.c +++ b/src/mms/iso_mms/server/mms_server_common.c @@ -1,7 +1,7 @@ /* * mms_server_common.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -45,96 +45,142 @@ mmsServer_createConfirmedResponse(uint32_t invokeId) return mmsPdu; } +static void +mapErrorTypeToErrorClass(MmsError errorType, uint8_t* tag, uint8_t* value) +{ + switch (errorType) { + + case MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED: + *tag = 0x87; /* access */ + *value = 1; + break; + + case MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT: + *tag = 0x87; /* access */ + *value = 2; + break; + + case MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED: + *tag = 0x87; /* access */ + *value = 3; + break; + + case MMS_ERROR_SERVICE_OTHER: + *tag = 0x84; /* service */ + *value = 0; + break; + + case MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT: + *tag = 0x84; /* service */ + *value = 5; + break; + + case MMS_ERROR_DEFINITION_OTHER: + *tag = 0x82; /* definition */ + *value = 0; + break; + + case MMS_ERROR_DEFINITION_OBJECT_UNDEFINED: + *tag = 0x82; /* definition */ + *value = 1; + break; + + case MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED: + *tag = 0x82; /* definition */ + *value = 3; + break; + + case MMS_ERROR_DEFINITION_OBJECT_EXISTS: + *tag = 0x82; /* definition */ + *value = 5; + break; + + case MMS_ERROR_FILE_OTHER: + *tag = 0x8b; /* file */ + *value = 0; + break; + + case MMS_ERROR_FILE_FILE_NON_EXISTENT: + *tag = 0x8b; /* file */ + *value = 7; + break; + + case MMS_ERROR_RESOURCE_OTHER: + *tag = 0x83; /* resource */ + *value = 0; + break; + + case MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE: + *tag = 0x83; /* resource */ + *value = 4; + break; + + default: + + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: unknown errorType!\n"); + + *tag = 0x8c; /* others */ + *value = 0; + break; + + } + +} void -mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType) +mmsServer_createServiceErrorPduWithServiceSpecificInfo(uint32_t invokeId, ByteBuffer* response, + MmsError errorType, uint8_t* serviceSpecificInfo, int serviceSpecficInfoLength) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); - mmsPdu->present = MmsPdu_PR_confirmedErrorPDU; + /* determine encoded size */ - asn_long2INTEGER(&(mmsPdu->choice.confirmedErrorPDU.invokeID), - invokeId); + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId) + 2; - if (errorType == MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_access; + uint32_t specificInfoSize = 0; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__access_objectnonexistent); - } - else if (errorType == MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_access; + if (serviceSpecificInfo != NULL) + specificInfoSize = 1 + BerEncoder_determineLengthSize(serviceSpecficInfoLength) + + serviceSpecficInfoLength; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__access_objectaccessdenied); - } - else if (errorType == MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_access; + uint32_t serviceErrorContentSize = 5 /* errorClass */ + specificInfoSize; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__access_objectaccessunsupported); - } - else if (errorType == MMS_ERROR_SERVICE_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_service; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__service_other); - } - else if (errorType == MMS_ERROR_DEFINITION_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_other); - } - else if (errorType == MMS_ERROR_DEFINITION_OBJECT_EXISTS) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_objectexists); - } - else if (errorType == MMS_ERROR_DEFINITION_OBJECT_UNDEFINED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_objectundefined); - } - else if (errorType == MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_typeunsupported); - } - else if (errorType == MMS_ERROR_FILE_FILE_NON_EXISTENT) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_file; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__file_filenonexistent); - } - else if (errorType == MMS_ERROR_FILE_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_file; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__file_other); - } - else if (errorType == MMS_ERROR_RESOURCE_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_resource; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__resource_other); - } - else if (errorType == MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_resource; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__resource_capabilityunavailable); - } + uint32_t serviceErrorSize = 1 + BerEncoder_determineLengthSize(serviceErrorContentSize) + + serviceErrorContentSize; + + uint32_t confirmedErrorContentSize = serviceErrorSize + invokeIdSize; + + /* encode */ + uint8_t* buffer = response->buffer; + int bufPos = response->size; + + bufPos = BerEncoder_encodeTL(0xa2, confirmedErrorContentSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0x80, invokeIdSize - 2, buffer, bufPos); /* invokeID */ + bufPos = BerEncoder_encodeUInt32((uint32_t) invokeId, buffer, bufPos); - der_encode(&asn_DEF_MmsPdu, mmsPdu, - mmsServer_write_out, (void*) response); + bufPos = BerEncoder_encodeTL(0xa2, serviceErrorContentSize, buffer, bufPos); /* serviceError */ + bufPos = BerEncoder_encodeTL(0xa0, 3, buffer, bufPos); /* serviceError */ - asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + uint8_t errorCodeTag; + uint8_t errorCodeValue; + + mapErrorTypeToErrorClass(errorType, &errorCodeTag, &errorCodeValue); + + buffer[bufPos++] = errorCodeTag; + buffer[bufPos++] = 1; + buffer[bufPos++] = errorCodeValue; + + if (serviceSpecificInfo != NULL) + bufPos = BerEncoder_encodeOctetString(0xa3, serviceSpecificInfo, serviceSpecficInfoLength, + buffer, bufPos); + + response->size = bufPos; +} + +void /* Confirmed service error (ServiceError) */ +mmsServer_createServiceErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType) +{ + mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, NULL, 0); } int diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index c0cf1c20..eced91b7 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -1,7 +1,7 @@ /* * mms_server_connection.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -64,8 +64,8 @@ mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response } else if (reason == MMS_ERROR_REJECT_INVALID_PDU) { mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_pduError; - mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU = - RejectPDU__rejectReason__pduError_invalidPdu; + asn_long2INTEGER(&mmsPdu->choice.rejectPDU.rejectReason.choice.pduError, + RejectPDU__rejectReason__pduError_invalidPdu); } else { mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU; @@ -108,11 +108,15 @@ handleConfirmedRequestPdu( return; } - if (DEBUG_MMS_SERVER) printf("tag %02x extended tag: %i size: %i\n", tag, extendedTag, length); - if (extendedTag) { switch(tag) { +#if (MMS_JOURNAL_SERVICE == 1) + case 0x41: /* read-journal */ + mmsServer_handleReadJournalRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + break; +#endif /* (MMS_JOURNAL_SERVICE == 1) */ + #if (MMS_FILE_SERVICE == 1) case 0x48: /* file-open-request */ mmsServer_handleFileOpenRequest(self, buffer, bufPos, bufPos + length, invokeId, response); @@ -149,7 +153,8 @@ handleConfirmedRequestPdu( switch(tag) { case 0x02: /* invoke Id */ invokeId = BerDecoder_decodeUint32(buffer, length, bufPos); - if (DEBUG_MMS_SERVER) printf("invokeId: %i\n", invokeId); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: received request with invokeId: %i\n", invokeId); self->lastInvokeId = invokeId; break; @@ -250,7 +255,7 @@ MmsServerConnection_parseMessage(MmsServerConnection self, ByteBuffer* message, return MMS_ERROR; if (DEBUG_MMS_SERVER) - printf("mms_server: recvd MMS-PDU type: %02x size: %i\n", pduType, pduLength); + printf("MMS_SERVER: recvd MMS-PDU type: %02x size: %i\n", pduType, pduLength); switch (pduType) { case 0xa8: /* Initiate request PDU */ @@ -266,7 +271,8 @@ MmsServerConnection_parseMessage(MmsServerConnection self, ByteBuffer* message, retVal = MMS_CONCLUDE; break; case 0xa4: /* Reject PDU - silently ignore */ - if (DEBUG) printf("received reject PDU!\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: received reject PDU!\n"); retVal = MMS_OK; break; default: diff --git a/src/mms/iso_mms/server/mms_status_service.c b/src/mms/iso_mms/server/mms_status_service.c index d0d8f14b..a547b9f0 100644 --- a/src/mms/iso_mms/server/mms_status_service.c +++ b/src/mms/iso_mms/server/mms_status_service.c @@ -24,7 +24,7 @@ #include "libiec61850_platform_includes.h" #include "mms_server_internal.h" -#if MMS_STATUS_SERVICE == 1 +#if (MMS_STATUS_SERVICE == 1) void mmsServer_handleStatusRequest( diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index 577a95ee..f32bc31d 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -67,24 +67,23 @@ struct sIsoServer { #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) LinkedList openClientConnections; - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore openClientConnectionsMutex; /* mutex for openClientConnections list */ -#endif - #else IsoConnection* openClientConnections; #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore userLock; +#endif +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + Semaphore openClientConnectionsMutex; /* mutex for openClientConnections list */ Semaphore connectionCounterMutex; #endif + int connectionCounter; }; -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) static inline void lockClientConnections(IsoServer self) { @@ -102,6 +101,18 @@ static void addClientConnection(IsoServer self, IsoConnection connection) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + Semaphore_wait(self->connectionCounterMutex); +#endif + + self->connectionCounter++; + if (DEBUG_ISO_SERVER) + printf("IsoServer: increase connection counter to %i!\n", self->connectionCounter); + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + lockClientConnections(self); +#endif + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) LinkedList_add(self->openClientConnections, connection); #else @@ -118,30 +129,37 @@ addClientConnection(IsoServer self, IsoConnection connection) } } #endif + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + Semaphore_post(self->connectionCounterMutex); +#endif + } static void removeClientConnection(IsoServer self, IsoConnection connection) { + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + lockClientConnections(self); +#endif + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) #if (CONFIG_MMS_SINGLE_THREADED == 0) -#if (CONFIG_MMS_THREADLESS_STACK != 1) - lockClientConnections(self); -#endif - LinkedList_remove(self->openClientConnections, connection); -#if (CONFIG_MMS_THREADLESS_STACK != 1) - unlockClientConnections(self); -#endif - #endif /* (CONFIG_MMS_SINGLE_THREADED == 0) */ #else + int i; for (i = 0; i < CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS; i++) { @@ -155,18 +173,22 @@ removeClientConnection(IsoServer self, IsoConnection connection) } } #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif } static void closeAllOpenClientConnections(IsoServer self) { -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) - -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); #endif +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) + LinkedList openConnection = LinkedList_getNext(self->openClientConnections); while (openConnection != NULL) { IsoConnection isoConnection = (IsoConnection) openConnection->data; @@ -186,11 +208,6 @@ closeAllOpenClientConnections(IsoServer self) self->openClientConnections = NULL; #endif - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - unlockClientConnections(self); -#endif - #else int i; @@ -206,7 +223,12 @@ closeAllOpenClientConnections(IsoServer self) } } +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); #endif + } static void @@ -214,7 +236,7 @@ handleClientConnections(IsoServer self) { #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); #endif @@ -237,12 +259,15 @@ handleClientConnections(IsoServer self) openConnection = LinkedList_getNext(openConnection); } -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) unlockClientConnections(self); #endif #else +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + lockClientConnections(self); +#endif int i; @@ -260,6 +285,11 @@ handleClientConnections(IsoServer self) } } + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif + #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ } @@ -319,8 +349,6 @@ handleIsoConnections(IsoServer self) IsoConnection isoConnection = IsoConnection_create(connectionSocket, self); - private_IsoServer_increaseConnectionCounter(self); - addClientConnection(self, isoConnection); self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter, @@ -357,8 +385,6 @@ handleIsoConnectionsThreadless(IsoServer self) IsoConnection isoConnection = IsoConnection_create(connectionSocket, self); - private_IsoServer_increaseConnectionCounter(self); - addClientConnection(self, isoConnection); self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter, @@ -419,12 +445,9 @@ IsoServer_create() GLOBAL_CALLOC(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS, sizeof(IsoConnection)); #endif -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) self->connectionCounterMutex = Semaphore_create(1); -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) self->openClientConnectionsMutex = Semaphore_create(1); -#endif - #endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ self->connectionCounter = 0; @@ -525,12 +548,13 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) handles = Handleset_new(); if (handles != NULL) { -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); #endif +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) + LinkedList openConnection = LinkedList_getNext(self->openClientConnections); LinkedList lastConnection = self->openClientConnections; @@ -550,10 +574,6 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) lastConnection = lastConnection->next; } -#if (CONFIG_MMS_THREADLESS_STACK != 1) - unlockClientConnections(self); -#endif - #else int i; @@ -561,12 +581,20 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) if (self->openClientConnections[i] != NULL) { if (IsoConnection_isRunning(self->openClientConnections[i])) { IsoConnection_addHandleSet(self->openClientConnections[i], handles); - } else { + } + else { +#if ((CONFIG_MMS_SINGLE_THREADED == 1) || (CONFIG_MMS_THREADLESS_STACK == 1)) IsoConnection_destroy(self->openClientConnections[i]); +#endif self->openClientConnections[i] = NULL; } } } + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif + #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ Handleset_addSocket(handles, self->serverSocket); result = Handleset_waitReady(handles, timeoutMs); @@ -668,42 +696,26 @@ IsoServer_destroy(IsoServer self) LinkedList_destroyStatic(self->openClientConnections); #endif /* (CONFIG_MMS_SINGLE_THREADED == 1) */ -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); - Semaphore_destroy(self->openClientConnectionsMutex); #endif #else GLOBAL_FREEMEM(self->openClientConnections); #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_destroy(self->connectionCounterMutex); + Semaphore_destroy(self->openClientConnectionsMutex); #endif GLOBAL_FREEMEM(self); } -void -private_IsoServer_increaseConnectionCounter(IsoServer self) -{ -#if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_wait(self->connectionCounterMutex); -#endif - - self->connectionCounter++; - if (DEBUG_ISO_SERVER) - printf("IsoServer: increase connection counter to %i!\n", self->connectionCounter); - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_post(self->connectionCounterMutex); -#endif -} - void private_IsoServer_decreaseConnectionCounter(IsoServer self) { -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_wait(self->connectionCounterMutex); #endif @@ -712,7 +724,7 @@ private_IsoServer_decreaseConnectionCounter(IsoServer self) if (DEBUG_ISO_SERVER) printf("IsoServer: decrease connection counter to %i!\n", self->connectionCounter); -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_post(self->connectionCounterMutex); #endif } @@ -722,13 +734,13 @@ private_IsoServer_getConnectionCounter(IsoServer self) { int connectionCounter; -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_wait(self->connectionCounterMutex); #endif connectionCounter = self->connectionCounter; -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_post(self->connectionCounterMutex); #endif diff --git a/src/version.rc.in b/src/version.rc.in new file mode 100644 index 00000000..377bdc7e --- /dev/null +++ b/src/version.rc.in @@ -0,0 +1,20 @@ +1 VERSIONINFO +FILEVERSION @LIB_VERSION_MAJOR@,@LIB_VERSION_MINOR@,@LIB_VERSION_PATCH@,0 +PRODUCTVERSION @LIB_VERSION_MAJOR@,@LIB_VERSION_MINOR@,@LIB_VERSION_PATCH@,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "FileVersion", "@LIB_VERSION_MAJOR@.@LIB_VERSION_MINOR@.@LIB_VERSION_PATCH@.0" + VALUE "ProductVersion", "@LIB_VERSION_MAJOR@.@LIB_VERSION_MINOR@.@LIB_VERSION_PATCH@.0" + VALUE "ProductName", "libIEC61850" + VALUE "FileDescription", "libIEC61850 - open source library for IEC 61850" + VALUE "LegalCopyright", "Dual license : Commercial or GPLv3" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x400, 1252 + END +END diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index acec3203..1113aa2e 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -113,7 +113,6 @@ EXPORTS FileSystem_readDirectory @302 FileSystem_readFile @303 FileSystem_renameFile @304 - FileSystem_setBasePath @305 FunctionalConstraint_fromString @313 FunctionalConstraint_toString @314 GSEControlBlock_create @316 @@ -528,4 +527,23 @@ EXPORTS MmsValue_encodeMmsData ControlObjectClient_setInterlockCheck ControlObjectClient_setSynchroCheck - + LogStorage_addEntry + LogStorage_addEntryData + LogStorage_getEntries + LogStorage_getEntriesAfter + LogStorage_getOldestAndNewestEntries + LogStorage_destroy + IedServer_setLogStorage + MmsJournalEntry_destroy + MmsJournalEntry_getEntryID + MmsJournalEntry_getOccurenceTime + MmsJournalEntry_getJournalVariables + MmsJournalVariable_getTag + MmsJournalVariable_getValue + MmsConnection_readJournalTimeRange + MmsConnection_readJournalStartAfter + LogControlBlock_create + Log_create + IedConnection_queryLogByTime + IedConnection_queryLogAfter + CDC_DPL_create diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index d3322ec0..7d0c71c2 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -113,7 +113,6 @@ EXPORTS FileSystem_readDirectory @302 FileSystem_readFile @303 FileSystem_renameFile @304 - FileSystem_setBasePath @305 FunctionalConstraint_fromString @313 FunctionalConstraint_toString @314 GSEControlBlock_create @316 @@ -604,3 +603,23 @@ EXPORTS IedServer_updateVisibleStringAttributeValue ControlObjectClient_setInterlockCheck ControlObjectClient_setSynchroCheck + LogStorage_addEntry + LogStorage_addEntryData + LogStorage_getEntries + LogStorage_getEntriesAfter + LogStorage_getOldestAndNewestEntries + LogStorage_destroy + IedServer_setLogStorage + MmsJournalEntry_destroy + MmsJournalEntry_getEntryID + MmsJournalEntry_getOccurenceTime + MmsJournalEntry_getJournalVariables + MmsJournalVariable_getTag + MmsJournalVariable_getValue + MmsConnection_readJournalTimeRange + MmsConnection_readJournalStartAfter + LogControlBlock_create + Log_create + IedConnection_queryLogByTime + IedConnection_queryLogAfter + CDC_DPL_create diff --git a/third_party/cmake/modules/Findsqlite.cmake b/third_party/cmake/modules/Findsqlite.cmake new file mode 100644 index 00000000..cfdd566a --- /dev/null +++ b/third_party/cmake/modules/Findsqlite.cmake @@ -0,0 +1,48 @@ +# - Try to find the sqlite library +# Once done this will define +# +# SQLITE_FOUND - system has sqlite +# SQLITE_INCLUDE_DIRS - the sqlite include directory +# SQLITE_LIBRARIES - Link these to use sqlite +# +# Define SQLITE_MIN_VERSION for which version desired. +# + +INCLUDE(FindPkgConfig) + +IF(Sqlite_FIND_REQUIRED) + SET(_pkgconfig_REQUIRED "REQUIRED") +ELSE(Sqlite_FIND_REQUIRED) + SET(_pkgconfig_REQUIRED "") +ENDIF(Sqlite_FIND_REQUIRED) + +IF(SQLITE_MIN_VERSION) + PKG_SEARCH_MODULE(SQLITE ${_pkgconfig_REQUIRED} sqlite>=${SQLITE_MIN_VERSION} sqlite${SQLITE_MIN_VERSION}) +ELSE(SQLITE_MIN_VERSION) + PKG_SEARCH_MODULE(SQLITE ${_pkgconfig_REQUIRED} sqlite) +ENDIF(SQLITE_MIN_VERSION) + +IF(NOT SQLITE_FOUND AND NOT PKG_CONFIG_FOUND) + FIND_PATH(SQLITE_INCLUDE_DIRS sqlite${SQLITE_MIN_VERSION}.h) + FIND_LIBRARY(SQLITE_LIBRARIES sqlite${SQLITE_MIN_VERSION}) + + # Report results + IF(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS) + SET(SQLITE_FOUND 1) + IF(NOT Sqlite_FIND_QUIETLY) + MESSAGE(STATUS "Found Sqlite: ${SQLITE_LIBRARIES}") + ENDIF(NOT Sqlite_FIND_QUIETLY) + ELSE(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS) + IF(Sqlite_FIND_REQUIRED) + MESSAGE(SEND_ERROR "Could not find Sqlite") + ELSE(Sqlite_FIND_REQUIRED) + IF(NOT Sqlite_FIND_QUIETLY) + MESSAGE(STATUS "Could not find Sqlite") + ENDIF(NOT Sqlite_FIND_QUIETLY) + ENDIF(Sqlite_FIND_REQUIRED) + ENDIF(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS) +ENDIF(NOT SQLITE_FOUND AND NOT PKG_CONFIG_FOUND) + +# Hide advanced variables from CMake GUIs +MARK_AS_ADVANCED(SQLITE_LIBRARIES SQLITE_INCLUDE_DIRS) + diff --git a/third_party/sqlite/README b/third_party/sqlite/README new file mode 100644 index 00000000..2e1781bf --- /dev/null +++ b/third_party/sqlite/README @@ -0,0 +1 @@ +Copy the sqlite amalagamation source code files (sqlite3.c sqlite3ext.h sqlite3.h) in this directory. You can download an archive withan archive with the files here: https://www.sqlite.org/download.html diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 9e3e43d5..eaf73e39 100644 Binary files a/tools/model_generator/genconfig.jar and b/tools/model_generator/genconfig.jar differ diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index 784ac0ac..d9a0b3fb 100644 Binary files a/tools/model_generator/genmodel.jar and b/tools/model_generator/genmodel.jar differ diff --git a/tools/model_generator/modelviewer.jar b/tools/model_generator/modelviewer.jar index 27a0c016..9beb17fe 100644 Binary files a/tools/model_generator/modelviewer.jar and b/tools/model_generator/modelviewer.jar differ diff --git a/tools/model_generator/src/com/libiec61850/scl/model/Log.java b/tools/model_generator/src/com/libiec61850/scl/model/Log.java index 3b39343d..da7f595e 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/Log.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/Log.java @@ -33,8 +33,10 @@ public class Log { public Log(Node logNode) throws SclParserException { name = ParserUtils.parseAttribute(logNode, "name"); - if (name == null) - throw new SclParserException(logNode, "Log is missing required attribute name!"); + if (name == null) { + /* Use default log name */ + name = "GeneralLog"; + } } public String getName() { diff --git a/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java b/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java index b781aca2..9960ee53 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java @@ -6,7 +6,7 @@ import com.libiec61850.scl.ParserUtils; import com.libiec61850.scl.SclParserException; /* - * Copyright 2014 Michael Zillgith + * Copyright 2014-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -34,6 +34,7 @@ public class LogControl { private String ldInst = null; private String prefix = ""; private String lnClass = "LLN0"; + private String lnInst = ""; private String logName; private boolean logEna = true; private boolean reasonCode = true; @@ -49,6 +50,10 @@ public class LogControl { desc = ParserUtils.parseAttribute(logControlNode, "desc"); dataSet = ParserUtils.parseAttribute(logControlNode, "datSet"); + if (dataSet != null) + if (dataSet.equals("")) + dataSet = null; + ldInst = ParserUtils.parseAttribute(logControlNode, "ldInst"); prefix = ParserUtils.parseAttribute(logControlNode, "prefix"); @@ -57,11 +62,19 @@ public class LogControl { if (lnClassString != null) lnClass = lnClassString; + String lnInstString = ParserUtils.parseAttribute(logControlNode, "lnInst"); + + if (lnInstString != null) + lnInst = lnInstString; + logName = ParserUtils.parseAttribute(logControlNode, "logName"); if (logName == null) throw new SclParserException(logControlNode, "LogControl is missing required attribute \"logName\""); + if (logName.equals("")) + logName = null; + String intgPdString = ParserUtils.parseAttribute(logControlNode, "intgPd"); if (intgPdString != null) diff --git a/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java b/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java index 021dfc80..c43c5860 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java @@ -280,6 +280,14 @@ public class LogicalNode implements DataModelNode { return settingGroupControlBlocks; } + public List getLogControlBlocks() { + return logControlBlocks; + } + + public List getLogs() { + return logs; + } + @Override public DataModelNode getChildByName(String childName) { for (DataObject dataObject : dataObjects) { diff --git a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java index a7fd6606..f936fab2 100644 --- a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java @@ -44,6 +44,8 @@ import com.libiec61850.scl.model.DataSet; import com.libiec61850.scl.model.FunctionalConstraintData; import com.libiec61850.scl.model.GSEControl; import com.libiec61850.scl.model.IED; +import com.libiec61850.scl.model.Log; +import com.libiec61850.scl.model.LogControl; import com.libiec61850.scl.model.LogicalDevice; import com.libiec61850.scl.model.LogicalNode; import com.libiec61850.scl.model.ReportControlBlock; @@ -97,7 +99,7 @@ public class DynamicModelGenerator { for (LogicalNode logicalNode : logicalDevice.getLogicalNodes()) { output.print("LN(" + logicalNode.getName() + "){\n"); - exportLogicalNode(output, logicalNode); + exportLogicalNode(output, logicalNode, logicalDevice); output.println("}"); } @@ -107,7 +109,7 @@ public class DynamicModelGenerator { return iecString.replace('.', '$'); } - private void exportLogicalNode(PrintStream output, LogicalNode logicalNode) { + private void exportLogicalNode(PrintStream output, LogicalNode logicalNode, LogicalDevice logicalDevice) { for (SettingControl sgcb : logicalNode.getSettingGroupControlBlocks()) { output.print("SG(" + sgcb.getActSG() + " " + sgcb.getNumOfSGs() + ")\n"); @@ -142,6 +144,12 @@ public class DynamicModelGenerator { printRCBInstance(output, rcb, ""); } + for (LogControl lcb : logicalNode.getLogControlBlocks()) + printLCB(output, lcb, logicalNode, logicalDevice); + + for (Log log : logicalNode.getLogs()) + output.println("LOG(" + log.getName() + ")"); + for (GSEControl gcb : logicalNode.getGSEControlBlocks()) { LogicalDevice ld = logicalNode.getParentLogicalDevice(); @@ -187,6 +195,36 @@ public class DynamicModelGenerator { } } } + + private void printLCB(PrintStream output, LogControl lcb, LogicalNode ln, LogicalDevice logicalDevice) { + output.print("LC("); + output.print(lcb.getName() + " "); + + if (lcb.getDataSet() != null) + output.print(lcb.getDataSet() + " "); + else + output.print("- "); + + if (lcb.getLogName() != null) { + String logRef = logicalDevice.getInst() + "/" + ln.getName() + "$" + lcb.getLogName(); + output.print(logRef + " "); + } + else + output.print("- "); + + output.print(lcb.getTriggerOptions().getIntValue() + " "); + output.print(lcb.getIntgPd() + " "); + + if (lcb.isLogEna()) + output.print("1 "); + else + output.print("0 "); + + if (lcb.isReasonCode()) + output.println("1)"); + else + output.println("0)"); + } private void printRCBInstance(PrintStream output, ReportControlBlock rcb, String index) { output.print("RC("); diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index a0b1ea6d..020339f7 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -3,7 +3,7 @@ package com.libiec61850.tools; /* * StaticModelGenerator.java * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -48,6 +48,8 @@ import com.libiec61850.scl.model.FunctionalConstraint; import com.libiec61850.scl.model.FunctionalConstraintData; import com.libiec61850.scl.model.GSEControl; import com.libiec61850.scl.model.IED; +import com.libiec61850.scl.model.Log; +import com.libiec61850.scl.model.LogControl; import com.libiec61850.scl.model.LogicalDevice; import com.libiec61850.scl.model.LogicalNode; import com.libiec61850.scl.model.ReportControlBlock; @@ -68,6 +70,14 @@ public class StaticModelGenerator { private List rcbVariableNames; private int currentRcbVariableNumber = 0; + private StringBuffer logControlBlocks; + private List lcbVariableNames; + private int currentLcbVariableNumber = 0; + + private StringBuffer logs; + private List logVariableNames; + private int currentLogVariableNumber = 0; + private StringBuffer gseControlBlocks; private List gseVariableNames; private int currentGseVariableNumber = 0; @@ -100,9 +110,12 @@ public class StaticModelGenerator { { this.cOut = cOut; this.hOut = hOut; + this.initializerBuffer = new StringBuffer(); + this.reportControlBlocks = new StringBuffer(); this.rcbVariableNames = new LinkedList(); + this.gseControlBlocks = new StringBuffer(); this.gseVariableNames = new LinkedList(); @@ -112,6 +125,12 @@ public class StaticModelGenerator { this.settingGroupControlBlocks = new StringBuffer(); this.sgcbVariableNames = new LinkedList(); + this.logControlBlocks = new StringBuffer(); + this.lcbVariableNames = new LinkedList(); + + this.logs = new StringBuffer(); + this.logVariableNames = new LinkedList(); + SclParser sclParser = new SclParser(stream); this.outputFileName = outputFileName; @@ -252,15 +271,6 @@ public class StaticModelGenerator { } } -// private String getLogicalDeviceName(LogicalDevice logicalDevice) { -// String logicalDeviceName = logicalDevice.getLdName(); -// -// if (logicalDeviceName == null) -// logicalDeviceName = ied.getName() + logicalDevice.getInst(); -// -// return logicalDeviceName; -// } - private String getLogicalDeviceInst(LogicalDevice logicalDevice) { return logicalDevice.getInst(); } @@ -273,6 +283,10 @@ public class StaticModelGenerator { createReportVariableList(logicalDevices); + createLogControlVariableList(logicalDevices); + + createLogVariableList(logicalDevices); + createGooseVariableList(logicalDevices); createSmvVariableList(logicalDevices); @@ -307,7 +321,7 @@ public class StaticModelGenerator { cOut.println(" (ModelNode*) &" + firstChildName); cOut.println("};\n"); - printLogicalNodeDefinitions(ldName, logicalDevice.getLogicalNodes()); + printLogicalNodeDefinitions(ldName, logicalDevice, logicalDevice.getLogicalNodes()); } @@ -318,6 +332,7 @@ public class StaticModelGenerator { cOut.println(reportControlBlocks); + for (String smv : smvVariableNames) cOut.println("extern SVControlBlock " + smv + ";"); @@ -332,7 +347,17 @@ public class StaticModelGenerator { cOut.println("extern SettingGroupControlBlock " + sgcb + ";"); cOut.println(settingGroupControlBlocks); - + + for (String lcb : lcbVariableNames) + cOut.println("extern LogControlBlock " + lcb + ";"); + + cOut.println(logControlBlocks); + + for (String log : logVariableNames) + cOut.println("extern Log " + log + ";"); + + cOut.println(logs); + String firstLogicalDeviceName = logicalDevices.get(0).getInst(); cOut.println("\nIedModel " + modelPrefix + " = {"); cOut.println(" \"" + ied.getName() + "\","); @@ -363,6 +388,16 @@ public class StaticModelGenerator { else cOut.println(" NULL,"); + if (lcbVariableNames.size() > 0) + cOut.println(" &" + lcbVariableNames.get(0) + ","); + else + cOut.println(" NULL,"); + + if (logVariableNames.size() > 0) + cOut.println(" &" + logVariableNames.get(0) + ","); + else + cOut.println(" NULL,"); + cOut.println(" initializeValues\n};"); } @@ -438,6 +473,54 @@ public class StaticModelGenerator { } } + private void createLogControlVariableList(List logicalDevices) + { + for (LogicalDevice ld : logicalDevices) { + List lnodes = ld.getLogicalNodes(); + + String ldName = ld.getInst(); + + for (LogicalNode ln : lnodes) { + List lcbs = ln.getLogControlBlocks(); + + int lcbCount = 0; + + for (LogControl logControl : lcbs) { + + String lcbVariableName = modelPrefix + "_" + ldName + "_" + ln.getName() + "_lcb" + lcbCount; + + lcbVariableNames.add(lcbVariableName); + + lcbCount++; + } + } + } + } + + private void createLogVariableList(List logicalDevices) + { + for (LogicalDevice ld : logicalDevices) { + List lnodes = ld.getLogicalNodes(); + + String ldName = ld.getInst(); + + for (LogicalNode ln : lnodes) { + List logs = ln.getLogs(); + + int logCount = 0; + + for (Log log : logs) { + + String logVariableName = modelPrefix + "_" + ldName + "_" + ln.getName() + "_log" + logCount; + + logVariableNames.add(logVariableName); + + logCount++; + } + } + } + } + private void createSettingControlsVariableList(List logicalDevices) { for (LogicalDevice ld : logicalDevices) { List lnodes = ld.getLogicalNodes(); @@ -458,7 +541,7 @@ public class StaticModelGenerator { } } - private void printLogicalNodeDefinitions(String ldName, List logicalNodes) { + private void printLogicalNodeDefinitions(String ldName, LogicalDevice logicalDevice, List logicalNodes) { for (int i = 0; i < logicalNodes.size(); i++) { LogicalNode logicalNode = logicalNodes.get(i); @@ -484,6 +567,10 @@ public class StaticModelGenerator { printDataObjectDefinitions(lnName, logicalNode.getDataObjects(), null); printReportControlBlocks(lnName, logicalNode); + + printLogControlBlocks(ldName, lnName, logicalNode, logicalDevice); + + printLogs(lnName, logicalNode); printGSEControlBlocks(ldName, lnName, logicalNode); @@ -990,6 +1077,34 @@ public class StaticModelGenerator { } } + private void printLogControlBlocks(String ldName, String lnPrefix, LogicalNode logicalNode, LogicalDevice logicalDevice) + { + List logControlBlocks = logicalNode.getLogControlBlocks(); + + int lcbCount = logControlBlocks.size(); + + int lcbNumber = 0; + + for (LogControl lcb : logControlBlocks) { + printLogControlBlock(logicalDevice, lnPrefix, lcb, lcbNumber, lcbCount); + lcbNumber++; + } + } + + private void printLogs(String lnPrefix, LogicalNode logicalNode) + { + List logs = logicalNode.getLogs(); + + int logCount = logs.size(); + + int logNumber = 0; + + for (Log log : logs) { + printLog(lnPrefix, log, logNumber, logCount); + logNumber++; + } + } + private void printSettingControlBlock(String lnPrefix, LogicalNode logicalNode) { List settingControls = logicalNode.getSettingGroupControlBlocks(); @@ -1019,8 +1134,100 @@ public class StaticModelGenerator { currentSGCBVariableNumber++; } } + + private void printLog(String lnPrefix, Log log, int logNumber, int logCount) + { + String logVariableName = lnPrefix + "_log" + logNumber; + + String logString = "Log " + logVariableName + " = {"; + + logString += "&" + lnPrefix + ", "; + + logString += "\"" + log.getName() + "\", "; + + currentLogVariableNumber++; + + if (currentLogVariableNumber < logVariableNames.size()) + logString += "&" + logVariableNames.get(currentLogVariableNumber); + else + logString += "NULL"; - private void printReportControlBlockInstance(String lnPrefix, ReportControlBlock rcb, String index, int reportNumber, int reportsCount) { + logString += "};\n"; + + this.logs.append(logString); + } + + private void printLogControlBlock(LogicalDevice logicalDevice, String lnPrefix, LogControl lcb, int lcbNumber, int lcbCount) + { + String lcbVariableName = lnPrefix + "_lcb" + lcbNumber; + + String lcbString = "LogControlBlock " + lcbVariableName + " = {"; + + lcbString += "&" + lnPrefix + ", "; + + lcbString += "\"" + lcb.getName() + "\", "; + + if (lcb.getDataSet() != null) + lcbString += "\"" + lcb.getDataSet() + "\", "; + else + lcbString += "NULL, "; + + String logRef; + + if (lcb.getLdInst() == null) + logRef = logicalDevice.getInst() + "/"; + else + logRef = lcb.getLdInst() + "/"; + + if (lcb.getLnClass().equals("LLN0")) + logRef += "LLN0$"; + else + logRef += lcb.getLnClass() + "$"; + + if (lcb.getLogName() != null) + lcbString += "\"" + logRef + lcb.getLogName() + "\", "; + else + lcbString += "NULL, "; + + int triggerOps = 0; + + if (lcb.getTriggerOptions() != null) + triggerOps = lcb.getTriggerOptions().getIntValue(); + + if (triggerOps >= 16) + triggerOps = triggerOps - 16; + + lcbString += triggerOps + ", "; + + if (lcb.getIntgPd() != 0) + lcbString += lcb.getIntgPd() + ", "; + else + lcbString += "0, "; + + if (lcb.isLogEna()) + lcbString += "true, "; + else + lcbString += "false, "; + + if (lcb.isReasonCode()) + lcbString += "true, "; + else + lcbString += "false, "; + + currentLcbVariableNumber++; + + if (currentLcbVariableNumber < lcbVariableNames.size()) + lcbString += "&" + lcbVariableNames.get(currentLcbVariableNumber); + else + lcbString += "NULL"; + + lcbString += "};\n"; + + this.logControlBlocks.append(lcbString); + } + + private void printReportControlBlockInstance(String lnPrefix, ReportControlBlock rcb, String index, int reportNumber, int reportsCount) + { String rcbVariableName = lnPrefix + "_report" + reportNumber; String rcbString = "ReportControlBlock " + rcbVariableName + " = {";