From a0f67f9a5dbda5c3deed38fb881dbe0da4736b14 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 1 Dec 2014 10:47:22 +0100 Subject: [PATCH] - update to current development version --- CHANGELOG | 36 + CMakeLists.txt | 122 +-- Makefile | 103 +-- config/stack_config.h | 38 +- config/stack_config.h.cmake | 28 +- demos/beaglebone/static_model.c | 43 +- dotnet/IEC61850forCSharp/Control.cs | 289 ++++++- dotnet/IEC61850forCSharp/DataSet.cs | 28 + dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs | 505 +++++++++++- .../IEC61850forCSharp.csproj | 1 + .../IsoConnectionParameters.cs | 96 ++- dotnet/IEC61850forCSharp/MmsValue.cs | 370 ++++++++- .../MmsVariableSpecification.cs | 201 +++++ .../IEC61850forCSharp/ReportControlBlock.cs | 297 ++++++- dotnet/IEC61850forCSharp/Reporting.cs | 122 ++- dotnet/control/ControlExample.cs | 28 +- dotnet/datasets/DataSetExample.cs | 5 +- dotnet/dotnet.sln | 12 + dotnet/example3/AssemblyInfo.cs | 27 + dotnet/example3/Main.cs | 48 ++ dotnet/example3/example3.csproj | 45 + dotnet/files/AssemblyInfo.cs | 27 + dotnet/files/FileServicesExample.cs | 89 ++ dotnet/files/files.csproj | 45 + dotnet/model_browsing/ModelBrowsing.cs | 21 + dotnet/reporting/ReportingExample.cs | 11 +- dotnet/tests/Test.cs | 59 ++ dotnet/tests/tests.csproj | 6 +- examples/CMakeLists.txt | 2 + examples/Makefile | 2 + .../goose_publisher/goose_publisher_example.c | 18 +- .../goose_subscriber_example.c | 26 +- .../client_example1.c | 2 +- .../client_example2.c | 8 +- .../client_example3.c | 113 +++ .../client_example4.c | 2 +- .../client_example5.c | 11 +- .../client_example_files.c | 2 +- .../client_example_reporting.c | 6 +- .../mms_client_example1/mms_client_example1.c | 2 +- examples/server_example1/server_example1.c | 2 +- examples/server_example1/static_model.c | 37 +- examples/server_example1/static_model.h | 2 +- examples/server_example2/server_example2.c | 2 +- examples/server_example2/static_model.c | 63 +- examples/server_example2/static_model.h | 4 +- examples/server_example3/server_example3.c | 15 +- examples/server_example3/static_model.c | 37 +- examples/server_example3/static_model.h | 2 +- examples/server_example4/server_example4.c | 2 +- examples/server_example4/static_model.c | 27 +- examples/server_example4/static_model.h | 2 +- examples/server_example5/server_example5.c | 2 +- examples/server_example5/static_model.c | 27 +- examples/server_example5/static_model.h | 2 +- .../server_example_61400_25.c | 2 +- .../server_example_61400_25/static_model.c | 15 +- .../server_example_61400_25/static_model.h | 2 +- .../server_example_ca.c | 2 +- .../static_model.c | 6 +- .../static_model.h | 2 +- .../server_example_config_file.c | 6 +- .../vmd-filestore/model.cfg | 9 +- .../server_example_control.c | 29 +- .../simpleIO_control_tests.icd | 6 + .../server_example_control/static_model.c | 323 +++++++- .../server_example_control/static_model.h | 46 +- .../server_example_dynamic.c | 4 +- .../server_example_goose.c | 2 +- .../simpleIO_direct_control_goose.icd | 4 +- examples/server_example_goose/static_model.c | 30 +- examples/server_example_goose/static_model.h | 2 +- make/stack_includes.mk | 27 +- make/target_system.mk | 4 +- src/CMakeLists.txt | 57 +- src/common/buffer_chain.c | 2 +- src/common/byte_buffer.c | 10 +- src/common/lib_memory.c | 83 ++ src/common/linked_list.c | 14 +- src/common/map.c | 24 +- src/common/simple_allocator.c | 13 +- src/common/string_utilities.c | 10 +- src/doxygen.config | 48 +- src/goose/goose_publisher.c | 35 +- src/goose/goose_receiver.c | 778 ++++++++++++++++++ src/goose/goose_receiver.h | 70 ++ src/goose/goose_receiver_internal.h | 62 ++ src/goose/goose_subscriber.c | 651 +-------------- src/goose/goose_subscriber.h | 47 +- src/hal/ethernet/bsd/ethernet_bsd.c | 54 +- src/hal/ethernet/linux/ethernet_linux.c | 15 +- src/hal/ethernet/win32/ethernet_win32.c | 66 +- .../filesystem/linux/file_provider_linux.c | 8 +- .../filesystem/win32/file_provider_win32.c | 30 +- src/hal/socket/bsd/socket_bsd.c | 121 ++- src/hal/socket/linux/socket_linux.c | 138 +++- src/hal/socket/win32/socket_win32.c | 176 +++- src/hal/thread/linux/thread_linux.c | 26 +- src/hal/thread/win32/thread_win32.c | 7 +- src/mms/asn1/asn1_ber_primitive_value.c | 16 +- src/mms/asn1/ber_decode.c | 2 +- src/mms/asn1/ber_encoder.c | 8 +- src/mms/iso_client/iso_client_connection.c | 589 +++++++++++++ .../iso_common/iso_connection_parameters.c | 18 +- src/mms/iso_cotp/cotp.c | 518 ++++++------ src/mms/iso_mms/asn1c/GeneralizedTime.c | 17 + src/mms/iso_mms/asn1c/INTEGER.c | 17 +- src/mms/iso_mms/asn1c/NativeEnumerated.c | 5 +- src/mms/iso_mms/asn1c/asn_internal.h | 16 + src/mms/iso_mms/asn1c/asn_system.h | 229 +++--- src/mms/iso_mms/asn1c/der_encoder.c | 32 +- src/mms/iso_mms/asn1c/xer_encoder.h | 118 +-- src/mms/iso_mms/client/mms_client_common.c | 2 +- .../iso_mms/client/mms_client_connection.c | 179 ++-- src/mms/iso_mms/client/mms_client_files.c | 12 +- .../iso_mms/client/mms_client_get_namelist.c | 14 +- .../client/mms_client_get_var_access.c | 6 +- src/mms/iso_mms/client/mms_client_identify.c | 2 +- src/mms/iso_mms/client/mms_client_initiate.c | 16 +- .../client/mms_client_named_variable_list.c | 48 +- src/mms/iso_mms/client/mms_client_read.c | 72 +- src/mms/iso_mms/client/mms_client_write.c | 42 +- src/mms/iso_mms/common/mms_common_msg.c | 96 +-- src/mms/iso_mms/common/mms_type_spec.c | 16 +- src/mms/iso_mms/common/mms_value.c | 151 ++-- src/mms/iso_mms/server/mms_device.c | 7 +- src/mms/iso_mms/server/mms_domain.c | 34 +- src/mms/iso_mms/server/mms_file_service.c | 2 +- .../iso_mms/server/mms_get_namelist_service.c | 2 +- .../server/mms_get_var_access_service.c | 44 +- .../iso_mms/server/mms_information_report.c | 17 +- .../iso_mms/server/mms_named_variable_list.c | 12 +- .../server/mms_named_variable_list_service.c | 172 ++-- src/mms/iso_mms/server/mms_read_service.c | 2 +- src/mms/iso_mms/server/mms_server.c | 64 +- src/mms/iso_mms/server/mms_server_common.c | 24 +- .../iso_mms/server/mms_server_connection.c | 8 +- src/mms/iso_mms/server/mms_value_cache.c | 10 +- src/mms/iso_mms/server/mms_write_service.c | 28 +- src/mms/iso_server/iso_connection.c | 542 +++++++----- src/mms/iso_server/iso_server.c | 424 +++++++++- src/mms/iso_session/iso_session.c | 3 +- src/sampled_values/sv_publisher.c | 2 +- src/vs/libiec61850-wo-goose.def | 33 + src/vs/libiec61850.def | 40 +- tools/model_generator/genconfig.jar | Bin 71235 -> 73079 bytes tools/model_generator/genmodel.jar | Bin 71552 -> 73078 bytes .../scl/DataAttributeDefinition.java | 38 +- .../libiec61850/scl/model/DataAttribute.java | 7 + .../libiec61850/scl/model/LogicalNode.java | 9 +- .../scl/types/DataAttributeType.java | 1 + .../libiec61850/scl/types/DataObjectType.java | 14 +- .../tools/DynamicModelGenerator.java | 17 +- .../tools/StaticModelGenerator.java | 143 +++- 154 files changed, 7561 insertions(+), 2567 deletions(-) create mode 100644 dotnet/IEC61850forCSharp/MmsVariableSpecification.cs create mode 100644 dotnet/example3/AssemblyInfo.cs create mode 100644 dotnet/example3/Main.cs create mode 100644 dotnet/example3/example3.csproj create mode 100644 dotnet/files/AssemblyInfo.cs create mode 100644 dotnet/files/FileServicesExample.cs create mode 100644 dotnet/files/files.csproj create mode 100644 src/common/lib_memory.c create mode 100644 src/goose/goose_receiver.c create mode 100644 src/goose/goose_receiver.h create mode 100644 src/goose/goose_receiver_internal.h create mode 100644 src/mms/iso_client/iso_client_connection.c diff --git a/CHANGELOG b/CHANGELOG index 7ea34044..2019cf3b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,39 @@ +Changes to version 0.8.2 +------------------------ +- Client: Added adjustable timeout to connect functions +- Client: Support for T selectors with different size than two bytes +- GOOSE publisher: added support for explicit GoID +- GOOSE subscriber: refactored subscriber +- Server: Added support for setting groups +- Server: Added support for data references in reports +- SCL parser: support for "Val" attributes in DA type definitions +- Server: Added function to disable all GOOSE publishing +- Client/Server: added helper functions for Dbpos +- C# API: more features and improved stability + +Changes to version 0.8.1 +------------------------ +- IEC 61850/MMS client: IedConnection and MmsConnection objects can now be reused +- reorganization of library header files +- C# API provides support for bit string and octet string data type +- IEC 61850 client/server: Added support for CommandTermination- +- C# API added handler for CommandTermination messages +- IEC 61850 server: IED name for static device model can now be configured at runtime +- Client/server: Fixed problem with association specific data sets +- GOOSE: fixed triggering GOOSE reports for data set members without trigger condition set +- Linux/Windows/BSD: added support for select based socket read (reduces CPU load) +- C# API added support for file services +- a lot of small bug fixes + +Changes to version 0.8 +----------------------- +- HAL: socket layer. Some changes. read and accept are now non-blocking. Changes the standard implementations (Linux/POSIX, WIN32, BSD) accordingly +- server stack: added single-threaded and threadless operation modes to better fit to resource constraint devices (added new configuration option CONFIG_MMS_SINGLE_THREADED) +- some small changes in server side control model handling (ControlHandler return values and behaviour) to enable threadless and single-threaded operation. +- C# client API extended and XML documentation completed +- some smaller bug fixes and extensions + + Changes to version 0.7.8 ------------------------ - IED client: added client side support for Ed.1 compliant control (client side automatically detects if a control is ed1 or ed2 compliant) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bcab8ee..4c597cc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ endif() project(libiec61850) set(LIB_VERSION_MAJOR "0") -set(LIB_VERSION_MINOR "7") -set(LIB_VERSION_PATCH "8") +set(LIB_VERSION_MINOR "8") +set(LIB_VERSION_PATCH "2") # feature checks include(CheckLibraryExists) @@ -26,6 +26,9 @@ set(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS 5 CACHE STRING "Configure the maximum option(BUILD_EXAMPLES "Build the examples" ON) +option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" OFF) +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 option(CONFIG_INCLUDE_GOOSE_SUPPORT "Build with GOOSE support" ON) @@ -33,12 +36,15 @@ 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_SETTING_GROUPS "Build with support for IEC 61850 setting group services" ON) + option(CONFIG_ACTIVATE_TCP_KEEPALIVE "Activate TCP keepalive" ON) set(CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE "8000" CACHE STRING "Default buffer size for buffered reports in byte" ) # advanced options option(DEBUG "Enable debugging mode (include assertions)" OFF) +option(DEBUG_SOCKET "Enable printf debugging for socket layer" OFF) option(DEBUG_COTP "Enable COTP printf debugging" OFF) option(DEBUG_ISO_SERVER "Enable ISO SERVER printf debugging" OFF) option(DEBUG_ISO_CLIENT "Enable ISO CLIENT printf debugging" OFF) @@ -51,64 +57,45 @@ option(DEBUG_MMS_CLIENT "Enable MMS CLIENT printf debugging" OFF) include_directories( ${CMAKE_CURRENT_BINARY_DIR}/config - src/common + src/common/inc src/goose - src/hal - src/hal/ethernet - src/hal/socket - src/hal/thread - src/hal/filesystem - src/hal/time - src/iedclient - src/iedcommon - src/iedserver - src/iedserver/impl - src/iedserver/mms_mapping - src/iedserver/model - src/mms/asn1 - src/mms/iso_acse - src/mms/iso_client - src/mms/iso_cotp + src/hal/inc + src/iec61850/inc + src/iec61850/inc_private + src/mms/inc + src/mms/inc_private src/mms/iso_mms/asn1c - src/mms/iso_mms/client - src/mms/iso_mms/common - src/mms/iso_mms/server - src/mms/iso_presentation - src/mms/iso_server - src/mms/iso_common - src/mms/iso_session ) set(API_HEADERS - src/hal/time/time_hal.h - src/hal/ethernet/ethernet.h - src/hal/thread/thread.h - src/hal/filesystem/filesystem.h - src/common/libiec61850_common_api.h - src/common/linked_list.h - src/common/byte_buffer.h - src/iedclient/iec61850_client.h - src/iedcommon/iec61850_common.h - src/iedserver/iec61850_server.h - src/iedserver/model/model.h - src/iedserver/model/cdc.h - src/iedserver/model/dynamic_model.h - src/iedserver/model/config_file_parser.h - src/mms/iso_mms/common/mms_value.h - src/mms/iso_mms/common/mms_common.h - src/mms/iso_mms/common/mms_types.h - src/mms/iso_mms/server/mms_device_model.h - src/mms/iso_mms/server/mms_server.h - src/mms/iso_mms/server/mms_named_variable_list.h - src/mms/iso_mms/common/mms_type_spec.h - src/mms/asn1/ber_integer.h - src/mms/asn1/asn1_ber_primitive_value.h - src/mms/iso_server/iso_server.h - src/mms/iso_common/iso_connection_parameters.h + src/hal/inc/hal_time.h + src/hal/inc/hal_thread.h + src/hal/inc/hal_filesystem.h + src/common/inc/libiec61850_common_api.h + src/common/inc/linked_list.h + src/common/inc/byte_buffer.h + src/common/inc/lib_memory.h + src/iec61850/inc/iec61850_client.h + src/iec61850/inc/iec61850_common.h + src/iec61850/inc/iec61850_server.h + src/iec61850/inc/iec61850_model.h + src/iec61850/inc/iec61850_cdc.h + src/iec61850/inc/iec61850_dynamic_model.h + src/iec61850/inc/iec61850_config_file_parser.h + src/mms/inc/mms_value.h + src/mms/inc/mms_common.h + src/mms/inc/mms_types.h + src/mms/inc/mms_device_model.h + src/mms/inc/mms_server.h + src/mms/inc/mms_named_variable_list.h + src/mms/inc/mms_type_spec.h + src/mms/inc/mms_client_connection.h + src/mms/inc/iso_connection_parameters.h + src/mms/inc/iso_server.h + src/mms/inc/ber_integer.h + src/mms/inc/asn1_ber_primitive_value.h src/goose/goose_subscriber.h - src/mms/iso_mms/client/mms_client_connection.h - src/mms/iso_client/iso_client_connection.h - src/hal/socket/socket.h + src/goose/goose_receiver.h ) IF(WIN32) @@ -129,3 +116,30 @@ add_subdirectory(src) INSTALL(FILES ${API_HEADERS} DESTINATION include/libiec61850) +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") +SET(CPACK_PACKAGE_CONTACT "info@libiec61850.com") +SET(CPACK_PACKAGE_VERSION_MAJOR "${LIB_VERSION_MAJOR}") +SET(CPACK_PACKAGE_VERSION_MINOR "${LIB_VERSION_MINOR}") +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) +INCLUDE(CPack) + +ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") + + + diff --git a/Makefile b/Makefile index 0369ec80..3fd46ac3 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ LIB_SOURCE_DIRS += src/mms/asn1 LIB_SOURCE_DIRS += src/mms/iso_cotp LIB_SOURCE_DIRS += src/mms/iso_mms/server LIB_SOURCE_DIRS += src/mms/iso_mms/client -LIB_SOURCE_DIRS += src/mms/iso_client/impl +LIB_SOURCE_DIRS += src/mms/iso_client LIB_SOURCE_DIRS += src/mms/iso_common LIB_SOURCE_DIRS += src/mms/iso_mms/common LIB_SOURCE_DIRS += src/mms/iso_mms/asn1c @@ -20,13 +20,12 @@ LIB_SOURCE_DIRS += src/mms/iso_server ifndef EXCLUDE_ETHERNET_WINDOWS LIB_SOURCE_DIRS += src/goose endif -LIB_SOURCE_DIRS += src/iedclient/impl -LIB_SOURCE_DIRS += src/iedcommon -LIB_SOURCE_DIRS += src/iedserver -LIB_SOURCE_DIRS += src/iedserver/model -LIB_SOURCE_DIRS += src/iedserver/mms_mapping -LIB_SOURCE_DIRS += src/iedserver/impl -LIB_SOURCE_DIRS += src/hal +LIB_SOURCE_DIRS += src/iec61850/client +LIB_SOURCE_DIRS += src/iec61850/common +LIB_SOURCE_DIRS += src/iec61850/server +LIB_SOURCE_DIRS += src/iec61850/server/model +LIB_SOURCE_DIRS += src/iec61850/server/mms_mapping +LIB_SOURCE_DIRS += src/iec61850/server/impl ifeq ($(HAL_IMPL), WIN32) LIB_SOURCE_DIRS += src/hal/socket/win32 LIB_SOURCE_DIRS += src/hal/thread/win32 @@ -46,34 +45,15 @@ LIB_SOURCE_DIRS += src/hal/ethernet/bsd LIB_SOURCE_DIRS += src/hal/filesystem/linux LIB_SOURCE_DIRS += src/hal/time/unix endif - -LIB_INCLUDE_DIRS += src/mms/iso_presentation -LIB_INCLUDE_DIRS += src/mms/iso_session -LIB_INCLUDE_DIRS += src/mms/iso_cotp -LIB_INCLUDE_DIRS += src/mms/iso_acse LIB_INCLUDE_DIRS += config -LIB_INCLUDE_DIRS += src/mms/asn1 -LIB_INCLUDE_DIRS += src/mms/iso_client -LIB_INCLUDE_DIRS += src/mms/iso_mms/server -LIB_INCLUDE_DIRS += src/mms/iso_mms/common -LIB_INCLUDE_DIRS += src/mms/iso_mms/client -LIB_INCLUDE_DIRS += src/mms/iso_mms/asn1c -LIB_INCLUDE_DIRS += src/common -LIB_INCLUDE_DIRS += src/hal/socket -LIB_INCLUDE_DIRS += src/hal/thread -LIB_INCLUDE_DIRS += src/hal/ethernet -LIB_INCLUDE_DIRS += src/hal/filesystem -LIB_INCLUDE_DIRS += src/hal/time -LIB_INCLUDE_DIRS += src/hal +LIB_INCLUDE_DIRS += src/common/inc +LIB_INCLUDE_DIRS += src/mms/iso_mms/asn1c +LIB_INCLUDE_DIRS += src/mms/inc +LIB_INCLUDE_DIRS += src/mms/inc_private +LIB_INCLUDE_DIRS += src/hal/inc LIB_INCLUDE_DIRS += src/goose -LIB_INCLUDE_DIRS += src/mms/iso_server -LIB_INCLUDE_DIRS += src/mms/iso_common -LIB_INCLUDE_DIRS += src/iedclient -LIB_INCLUDE_DIRS += src/iedcommon -LIB_INCLUDE_DIRS += src/iedserver -LIB_INCLUDE_DIRS += src/iedserver/model -LIB_INCLUDE_DIRS += src/iedserver/mms_mapping -LIB_INCLUDE_DIRS += src/iedserver/impl +LIB_INCLUDE_DIRS += src/iec61850/inc +LIB_INCLUDE_DIRS += src/iec61850/inc_private ifeq ($(HAL_IMPL), WIN32) LIB_INCLUDE_DIRS += third_party/winpcap/Include endif @@ -84,35 +64,34 @@ ifndef INSTALL_PREFIX INSTALL_PREFIX = ./.install endif -LIB_API_HEADER_FILES = src/hal/time/time_hal.h -LIB_API_HEADER_FILES += src/hal/ethernet/ethernet.h -LIB_API_HEADER_FILES += src/hal/thread/thread.h -LIB_API_HEADER_FILES += src/hal/filesystem/filesystem.h -LIB_API_HEADER_FILES += src/common/libiec61850_common_api.h -LIB_API_HEADER_FILES += src/common/linked_list.h -LIB_API_HEADER_FILES += src/common/byte_buffer.h -LIB_API_HEADER_FILES += src/iedclient/iec61850_client.h -LIB_API_HEADER_FILES += src/iedcommon/iec61850_common.h -LIB_API_HEADER_FILES += src/iedserver/iec61850_server.h -LIB_API_HEADER_FILES += src/iedserver/model/model.h -LIB_API_HEADER_FILES += src/iedserver/model/cdc.h -LIB_API_HEADER_FILES += src/iedserver/model/dynamic_model.h -LIB_API_HEADER_FILES += src/iedserver/model/config_file_parser.h -LIB_API_HEADER_FILES += src/mms/iso_mms/common/mms_value.h -LIB_API_HEADER_FILES += src/mms/iso_mms/common/mms_common.h -LIB_API_HEADER_FILES += src/mms/iso_mms/common/mms_types.h -LIB_API_HEADER_FILES += src/mms/iso_mms/server/mms_device_model.h -LIB_API_HEADER_FILES += src/mms/iso_mms/server/mms_server.h -LIB_API_HEADER_FILES += src/mms/iso_mms/server/mms_named_variable_list.h -LIB_API_HEADER_FILES += src/mms/iso_mms/common/mms_type_spec.h -LIB_API_HEADER_FILES += src/mms/asn1/ber_integer.h -LIB_API_HEADER_FILES += src/mms/asn1/asn1_ber_primitive_value.h -LIB_API_HEADER_FILES += src/mms/iso_server/iso_server.h -LIB_API_HEADER_FILES += src/mms/iso_common/iso_connection_parameters.h +LIB_API_HEADER_FILES = src/hal/inc/hal_time.h +LIB_API_HEADER_FILES += src/hal/inc/hal_thread.h +LIB_API_HEADER_FILES += src/hal/inc/hal_filesystem.h +LIB_API_HEADER_FILES += src/common/inc/libiec61850_common_api.h +LIB_API_HEADER_FILES += src/common/inc/linked_list.h +LIB_API_HEADER_FILES += src/common/inc/byte_buffer.h +LIB_API_HEADER_FILES += src/common/inc/lib_memory.h +LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_client.h +LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_common.h +LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_server.h +LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_model.h +LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_cdc.h +LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_dynamic_model.h +LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_config_file_parser.h +LIB_API_HEADER_FILES += src/mms/inc/mms_value.h +LIB_API_HEADER_FILES += src/mms/inc/mms_common.h +LIB_API_HEADER_FILES += src/mms/inc/mms_types.h +LIB_API_HEADER_FILES += src/mms/inc/mms_device_model.h +LIB_API_HEADER_FILES += src/mms/inc/mms_server.h +LIB_API_HEADER_FILES += src/mms/inc/mms_named_variable_list.h +LIB_API_HEADER_FILES += src/mms/inc/mms_type_spec.h +LIB_API_HEADER_FILES += src/mms/inc/mms_client_connection.h +LIB_API_HEADER_FILES += src/mms/inc/iso_connection_parameters.h +LIB_API_HEADER_FILES += src/mms/inc/iso_server.h +LIB_API_HEADER_FILES += src/mms/inc/ber_integer.h +LIB_API_HEADER_FILES += src/mms/inc/asn1_ber_primitive_value.h LIB_API_HEADER_FILES += src/goose/goose_subscriber.h -LIB_API_HEADER_FILES += src/mms/iso_mms/client/mms_client_connection.h -LIB_API_HEADER_FILES += src/mms/iso_client/iso_client_connection.h -LIB_API_HEADER_FILES += src/hal/socket/socket.h +LIB_API_HEADER_FILES += src/goose/goose_receiver.h get_sources_from_directory = $(wildcard $1/*.c) get_sources = $(foreach dir, $1, $(call get_sources_from_directory,$(dir))) diff --git a/config/stack_config.h b/config/stack_config.h index 1c38acbb..e995ab0d 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -10,9 +10,10 @@ #define STACK_CONFIG_H_ /* include asserts if set to 1 */ -#define DEBUG 0 +#define DEBUG 1 /* print debugging information with printf if set to 1 */ +#define DEBUG_SOCKET 0 #define DEBUG_COTP 0 #define DEBUG_ISO_SERVER 0 #define DEBUG_ISO_CLIENT 0 @@ -20,10 +21,28 @@ #define DEBUG_IED_CLIENT 0 #define DEBUG_MMS_CLIENT 0 #define DEBUG_MMS_SERVER 0 +#define DEBUG_GOOSE_SUBSCRIBER 0 +#define DEBUG_GOOSE_PUBLISHER 0 /* Maximum MMS PDU SIZE - default is 65000 */ #define CONFIG_MMS_MAXIMUM_PDU_SIZE 65000 +/* + * Enable single threaded mode + * + * 1 ==> server runs in single threaded mode (a single thread for the server and all client connections) + * 0 ==> server runs in multi-threaded mode (one thread for each connection and + * one server background thread ) + */ +#define CONFIG_MMS_SINGLE_THREADED 1 + +/* + * Optimize stack for threadless operation - don't use semaphores + * + * WARNING: If set to 1 normal single- and multi-threaded server are no longer working! + */ +#define CONFIG_MMS_THREADLESS_STACK 0 + /* number of concurrent MMS client connections the server accepts, -1 for no limit */ #define CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS 5 @@ -48,6 +67,7 @@ /* Ethernet interface ID for GOOSE and SV */ #define CONFIG_ETHERNET_INTERFACE_ID "eth0" //#define CONFIG_ETHERNET_INTERFACE_ID "vboxnet0" +//#define CONFIG_ETHERNET_INTERFACE_ID "eth-f" //#define CONFIG_ETHERNET_INTERFACE_ID "en0" // OS X uses enX in place of ethX as ethernet NIC names. /* Set to 1 to include GOOSE support in the build. Otherwise set to 0 */ @@ -102,10 +122,16 @@ /* The default buffer size of buffered RCBs in bytes */ #define CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE 65536 +/* include support for setting groups */ +#define CONFIG_IEC61850_SETTING_GROUPS 1 + +/* default reservation time of a setting group control block in s */ +#define CONFIG_IEC61850_SG_RESVTMS 10 + /* 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.7.8" +#define CONFIG_DEFAULT_MMS_REVISION "0.8.2" /* MMS virtual file store base path - where file services are looking for files */ #define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/" @@ -113,6 +139,12 @@ /* Maximum number of open file per MMS connection (for MMS file read service) */ #define CONFIG_MMS_MAX_NUMBER_OF_OPEN_FILES_PER_CONNECTION 5 +#define CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS 10 + +#define CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS 10 + +#define CONFIG_MMS_MAX_NUMBER_OF_VMD_SPECIFIC_DATA_SETS 10 + /* Definition of supported services */ #define MMS_DEFAULT_PROFILE 1 @@ -140,4 +172,6 @@ /* VMD scope named variables are not used by IEC 61850 */ #define CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES 0 +#define CONFIG_INCLUDE_PLATFORM_SPECIFIC_HEADERS 0 + #endif /* STACK_CONFIG_H_ */ diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index 249d8f24..3f46e272 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -19,6 +19,7 @@ #cmakedefine01 DEBUG /* print debugging information with printf if set to 1 */ +#cmakedefine01 DEBUG_SOCKET #cmakedefine01 DEBUG_COTP #cmakedefine01 DEBUG_ISO_SERVER #cmakedefine01 DEBUG_ISO_CLIENT @@ -26,6 +27,17 @@ #cmakedefine01 DEBUG_IED_CLIENT #cmakedefine01 DEBUG_MMS_CLIENT #cmakedefine01 DEBUG_MMS_SERVER +#cmakedefine01 DEBUG_GOOSE_SUBSCRIBER +#cmakedefine01 DEBUG_GOOSE_PUBLISHER + +/* 1 ==> server runs in single threaded mode (one dedicated thread for the server) + * 0 ==> server runs in multi threaded mode (one thread for each connection and + * one server background thread ) + */ +#cmakedefine01 CONFIG_MMS_SINGLE_THREADED + +/* Optimize stack for threadless operation - don't use semaphores */ +#cmakedefine01 CONFIG_MMS_THREADLESS_STACK /* Maximum MMS PDU SIZE - default is 65000 */ #cmakedefine CONFIG_MMS_MAXIMUM_PDU_SIZE @CONFIG_MMS_MAXIMUM_PDU_SIZE@ @@ -52,9 +64,9 @@ #define CONFIG_TCP_READ_TIMEOUT_MS 1000 /* Ethernet interface ID for GOOSE and SV */ -//#define CONFIG_ETHERNET_INTERFACE_ID "eth0" +#define CONFIG_ETHERNET_INTERFACE_ID "eth0" //#define CONFIG_ETHERNET_INTERFACE_ID "vboxnet0" -#define CONFIG_ETHERNET_INTERFACE_ID "en0" // OS X uses enX in place of ethX as ethernet NIC names. +//#define CONFIG_ETHERNET_INTERFACE_ID "en0" // OS X uses enX in place of ethX as ethernet NIC names. /* Set to 1 to include GOOSE support in the build. Otherwise set to 0 */ #cmakedefine01 CONFIG_INCLUDE_GOOSE_SUPPORT @@ -108,6 +120,12 @@ /* The default buffer size of buffered RCBs in bytes */ #cmakedefine CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE @CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE@ +/* include support for setting groups */ +#cmakedefine01 CONFIG_IEC61850_SETTING_GROUPS + +/* default reservation time of a setting group control block in s */ +#define CONFIG_IEC61850_SG_RESVTMS 100 + /* default results for MMS identify service */ #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" #define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" @@ -125,6 +143,12 @@ /* Maximum number of open file per MMS connection (for MMS file read service) */ #define CONFIG_MMS_MAX_NUMBER_OF_OPEN_FILES_PER_CONNECTION 5 +#define CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS 10 + +#define CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS 10 + +#define CONFIG_MMS_MAX_NUMBER_OF_VMD_SPECIFIC_DATA_SETS 10 + /* Definition of supported services */ #define MMS_DEFAULT_PROFILE 1 diff --git a/demos/beaglebone/static_model.c b/demos/beaglebone/static_model.c index 0150f871..5c8824c5 100644 --- a/demos/beaglebone/static_model.c +++ b/demos/beaglebone/static_model.c @@ -150,15 +150,12 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_q; extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; -extern DataSet ds_GenericIO_LLN0_Events; +static DataSetEntry ds_GenericIO_LLN0_Events_fcda0; +static DataSetEntry ds_GenericIO_LLN0_Events_fcda1; +static DataSetEntry ds_GenericIO_LLN0_Events_fcda2; +static DataSetEntry ds_GenericIO_LLN0_Events_fcda3; - -extern DataSetEntry ds_GenericIO_LLN0_Events_fcda0; -extern DataSetEntry ds_GenericIO_LLN0_Events_fcda1; -extern DataSetEntry ds_GenericIO_LLN0_Events_fcda2; -extern DataSetEntry ds_GenericIO_LLN0_Events_fcda3; - -DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { +static DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { "beagleGenericIO", "GGIO1$ST$SPCSO1$stVal", -1, @@ -167,7 +164,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { &ds_GenericIO_LLN0_Events_fcda1 }; -DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { +static DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { "beagleGenericIO", "GGIO1$ST$SPCSO2$stVal", -1, @@ -176,7 +173,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { &ds_GenericIO_LLN0_Events_fcda2 }; -DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { +static DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { "beagleGenericIO", "GGIO1$ST$SPCSO3$stVal", -1, @@ -185,7 +182,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { &ds_GenericIO_LLN0_Events_fcda3 }; -DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { +static DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { "beagleGenericIO", "GGIO1$ST$DPCSO1$stVal", -1, @@ -194,12 +191,11 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { NULL }; -DataSet ds_GenericIO_LLN0_Events = { +static DataSet ds_GenericIO_LLN0_Events = { "beagleGenericIO", "LLN0$Events", 4, - &ds_GenericIO_LLN0_Events_fcda0, - NULL + &ds_GenericIO_LLN0_Events_fcda0 }; LogicalDevice iedModel_GenericIO = { @@ -1923,11 +1919,22 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { NULL, 0}; -extern ReportControlBlock iedModel_GenericIO_LLN0_report0; -extern ReportControlBlock iedModel_GenericIO_LLN0_report1; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 1, 8, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsRCB201", "Events2", false, "Events", 1, 8, 111, 50, 1000, NULL}; +static ReportControlBlock iedModel_GenericIO_LLN0_report0; +static ReportControlBlock iedModel_GenericIO_LLN0_report1; + +static ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB", "Events1", false, "Events", 1, 16, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; +static ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsRCB2", "Events2", false, "Events", 1, 16, 111, 50, 1000, NULL}; + + + + + + + + + + diff --git a/dotnet/IEC61850forCSharp/Control.cs b/dotnet/IEC61850forCSharp/Control.cs index 0b3294bc..d064334b 100644 --- a/dotnet/IEC61850forCSharp/Control.cs +++ b/dotnet/IEC61850forCSharp/Control.cs @@ -29,8 +29,14 @@ using IEC61850.Common; namespace IEC61850 { + /// + /// IEC 61850 common API parts (used by client and server API) + /// namespace Common { + /// + /// Control model + /// public enum ControlModel { CONTROL_MODEL_STATUS_ONLY = 0, CONTROL_MODEL_DIRECT_NORMAL = 1, @@ -38,39 +44,142 @@ namespace IEC61850 CONTROL_MODEL_DIRECT_ENHANCED = 3, CONTROL_MODEL_SBO_ENHANCED = 4 } + + /// + /// Originator category + /// + public enum OrCat { + /** Not supported - should not be used */ + NOT_SUPPORTED = 0, + /** Control operation issued from an operator using a client located at bay level */ + BAY_CONTROL = 1, + /** Control operation issued from an operator using a client located at station level */ + STATION_CONTROL = 2, + /** Control operation from a remote operator outside the substation (for example network control center) */ + REMOTE_CONTROL = 3, + /** Control operation issued from an automatic function at bay level */ + AUTOMATIC_BAY = 4, + /** Control operation issued from an automatic function at station level */ + AUTOMATIC_STATION = 5, + /** Control operation issued from a automatic function outside of the substation */ + AUTOMATIC_REMOTE = 6, + /** Control operation issued from a maintenance/service tool */ + MAINTENANCE = 7, + /** Status change occurred without control action (for example external trip of a circuit breaker or failure inside the breaker) */ + PROCESS = 8 + } } namespace Client { + [StructLayout(LayoutKind.Sequential)] + internal struct LastApplErrorInternal + { + public int ctlNum; + public int error; + public int addCause; + } + + public class LastApplError + { + public int ctlNum; + public int error; + public ControlAddCause addCause; + + + internal LastApplError (LastApplErrorInternal lastApplError) + { + this.addCause = (ControlAddCause) lastApplError.addCause; + this.error = lastApplError.error; + this.ctlNum = lastApplError.ctlNum; + } + } + + /// + /// Control object. + /// public class ControlObject { + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ControlObjectClient_create(string objectReference, IntPtr connection); + private static extern LastApplErrorInternal ControlObjectClient_getLastApplError(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void ControlObjectClient_destroy(IntPtr self); + private static extern IntPtr ControlObjectClient_create(string objectReference, IntPtr connection); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern int ControlObjectClient_getControlModel(IntPtr self); + private static extern void ControlObjectClient_destroy(IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern bool ControlObjectClient_operate(IntPtr self, IntPtr ctlVal, UInt64 operTime); + private static extern int ControlObjectClient_getControlModel(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern bool ControlObjectClient_operate(IntPtr self, IntPtr ctlVal, UInt64 operTime); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern bool ControlObjectClient_select(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern bool ControlObjectClient_selectWithValue(IntPtr self, IntPtr ctlVal); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern bool ControlObjectClient_cancel(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void ControlObjectClient_setOrigin(IntPtr self, string orIdent, int orCat); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void ControlObjectClient_enableInterlockCheck(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void ControlObjectClient_enableSynchroCheck(IntPtr self); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void InternalCommandTerminationHandler(IntPtr parameter,IntPtr controlClient); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern void ControlObjectClient_setCommandTerminationHandler(IntPtr self, + InternalCommandTerminationHandler handler, IntPtr handlerParameter); + + public delegate void CommandTerminationHandler (Object parameter, ControlObject controlObject); private IntPtr controlObject; + private CommandTerminationHandler commandTerminationHandler = null; + private Object commandTerminationHandlerParameter = null; + + private void MyCommandTerminationHandler (IntPtr paramter, IntPtr controlClient) + { + if (commandTerminationHandler != null) + commandTerminationHandler(commandTerminationHandlerParameter, this); + } + + internal ControlObject (string objectReference, IntPtr connection) { this.controlObject = ControlObjectClient_create(objectReference, connection); if (this.controlObject == System.IntPtr.Zero) throw new IedConnectionException("Control object not found", 0); + + InternalCommandTerminationHandler intCommandTerminationHandler = new InternalCommandTerminationHandler (MyCommandTerminationHandler); + + ControlObjectClient_setCommandTerminationHandler(controlObject, intCommandTerminationHandler, controlObject); } ~ControlObject () { - ControlObjectClient_destroy(controlObject); + if (this.controlObject != System.IntPtr.Zero) + ControlObjectClient_destroy(controlObject); } + /// + /// Gets the control model. + /// + /// + /// The control model. + /// public ControlModel GetControlModel () { ControlModel controlModel = (ControlModel) ControlObjectClient_getControlModel(controlObject); @@ -78,11 +187,36 @@ namespace IEC61850 return controlModel; } + /// + /// Sets the origin parameter used by control commands. + /// + /// + /// Originator. An arbitrary string identifying the controlling client. + /// + /// + /// Originator category. + /// + public void SetOrigin (string originator, OrCat originatorCategory) + { + ControlObjectClient_setOrigin(controlObject, originator, (int) originatorCategory); + } + + /// + /// Operate the control with the specified control value. + /// + /// the new value of the control + /// true when the operation has been successful, false otherwise public bool Operate (bool ctlVal) { return Operate (ctlVal, 0); } + /// + /// Operate the control with the specified control value (time activated control). + /// + /// the new value of the control + /// the time when the operation will be executed + /// true when the operation has been successful, false otherwise public bool Operate (bool ctlVal, UInt64 operTime) { MmsValue value = new MmsValue(ctlVal); @@ -90,11 +224,22 @@ namespace IEC61850 return Operate (value, operTime); } + /// + /// Operate the control with the specified control value. + /// + /// the new value of the control + /// true when the operation has been successful, false otherwise public bool Operate (float ctlVal) { return Operate (ctlVal, 0); } + /// + /// Operate the control with the specified control value (time activated control). + /// + /// the new value of the control + /// the time when the operation will be executed + /// true when the operation has been successful, false otherwise public bool Operate (float ctlVal, UInt64 operTime) { MmsValue value = new MmsValue(ctlVal); @@ -102,11 +247,22 @@ namespace IEC61850 return Operate (value, operTime); } + /// + /// Operate the control with the specified control value. + /// + /// the new value of the control + /// true when the operation has been successful, false otherwise public bool Operate (int ctlVal) { return Operate (ctlVal, 0); } + /// + /// Operate the control with the specified control value (time activated control). + /// + /// the new value of the control + /// the time when the operation will be executed + /// true when the operation has been successful, false otherwise public bool Operate (int ctlVal, UInt64 operTime) { MmsValue value = new MmsValue(ctlVal); @@ -114,15 +270,138 @@ namespace IEC61850 return Operate (value, operTime); } + /// + /// Operate the control with the specified control value. + /// + /// the new value of the control + /// true when the operation has been successful, false otherwise public bool Operate (MmsValue ctlVal) { return Operate (ctlVal, 0); } + /// + /// Operate the control with the specified control value (time activated control). + /// + /// the new value of the control + /// the time when the operation will be executed + /// true when the operation has been successful, false otherwise public bool Operate (MmsValue ctlVal, UInt64 operTime) { return ControlObjectClient_operate(controlObject, ctlVal.valueReference, operTime); } + + /// + /// Select the control object. + /// + /// true when the selection has been successful, false otherwise + public bool Select () + { + return ControlObjectClient_select(controlObject); + } + + + /// + /// Send a select with value command for generic MmsValue instances + /// + /// + /// the value to be checked. + /// + /// true when the selection has been successful, false otherwise + public bool SelectWithValue (MmsValue ctlVal) + { + return ControlObjectClient_selectWithValue(controlObject, ctlVal.valueReference); + } + + /// + /// Send a select with value command for boolean controls + /// + /// + /// the value to be checked. + /// + /// true when the selection has been successful, false otherwise + public bool SelectWithValue (bool ctlVal) + { + return SelectWithValue(new MmsValue(ctlVal)); + } + + /// + /// Send a select with value command for integer controls + /// + /// + /// the value to be checked. + /// + /// true when the selection has been successful, false otherwise + public bool SelectWithValue (int ctlVal) + { + return SelectWithValue(new MmsValue(ctlVal)); + } + + /// + /// Send a select with value command for float controls + /// + /// + /// the value to be checked. + /// + /// true when the selection has been successful, false otherwise + public bool SelectWithValue (float ctlVal) + { + return SelectWithValue(new MmsValue(ctlVal)); + } + + /// + /// Cancel a selection or time activated operation + /// + /// true when the cancelation has been successful, false otherwise + public bool Cancel () + { + return ControlObjectClient_cancel(controlObject); + } + + /// + /// Enables the synchro check for operate commands + /// + public void EnableSynchroCheck () + { + ControlObjectClient_enableSynchroCheck(controlObject); + } + + /// + /// Enables the interlock check for operate and select commands + /// + public void EnableInterlockCheck () + { + ControlObjectClient_enableInterlockCheck(controlObject); + } + + /// + /// Gets the last received LastApplError (Additional Cause Diagnostics) value. + /// + /// + /// The last appl error. + /// + public LastApplError GetLastApplError () + { + LastApplErrorInternal lastApplError = ControlObjectClient_getLastApplError(controlObject); + + return new LastApplError(lastApplError); + } + + /// + /// Sets the command termination handler. + /// + /// + /// the handler (delegate) that is invoked when a CommandTerminationMessage is received. + /// + /// + /// Parameter. + /// + public void SetCommandTerminationHandler (CommandTerminationHandler handler, Object parameter) + { + this.commandTerminationHandler = handler; + this.commandTerminationHandlerParameter = parameter; + } + } } diff --git a/dotnet/IEC61850forCSharp/DataSet.cs b/dotnet/IEC61850forCSharp/DataSet.cs index 12ec8d60..c0c887e9 100644 --- a/dotnet/IEC61850forCSharp/DataSet.cs +++ b/dotnet/IEC61850forCSharp/DataSet.cs @@ -24,10 +24,16 @@ using System; using System.Runtime.InteropServices; +using IEC61850.Common; + namespace IEC61850 { namespace Client { + /// + /// This class is used to represent a data set. It is used to store the values + /// of a data set. + /// public class DataSet { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -51,6 +57,12 @@ namespace IEC61850 this.nativeObject = nativeObject; } + /// + /// Gets the object reference of the data set + /// + /// + /// object reference. + /// public string GetReference () { if (reference == null) { @@ -62,6 +74,15 @@ namespace IEC61850 return reference; } + /// + /// Gets the values associated with the data set object + /// + /// This function will return the locally stored values associated with the data set. + /// These are the values received by the last request to the server. A call to this method doesn't + /// invoke a request to the server! + /// + /// The locally stored values of the data set (as MmsValue instance of type MMS_ARRAY) + /// public MmsValue GetValues () { if (values == null) { @@ -73,6 +94,13 @@ namespace IEC61850 return values; } + + /// + /// Gets the number of elements of the data set + /// + /// + /// the number of elementes (data set members) + /// public int GetSize () { return ClientDataSet_getDataSetSize (nativeObject); diff --git a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs index 60bd7952..82f133a2 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ClientAPI.cs @@ -28,8 +28,14 @@ using System.Collections; using IEC61850.Common; +/// +/// IEC 61850 API for the libiec61850 .NET wrapper library +/// namespace IEC61850 { + /// + /// IEC 61850 client API. + /// namespace Client { @@ -67,6 +73,12 @@ namespace IEC61850 [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr IedConnection_create (); + [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] + static extern void IedConnection_destroy (IntPtr self); + + [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] + static extern void IedConnection_setConnectTimeout(IntPtr self, UInt32 timeoutInMs); + [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] static extern void IedConnection_connect (IntPtr self, out int error, string hostname, int tcpPort); @@ -88,6 +100,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getDataDirectory (IntPtr self, out int error, string dataReference); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedConnection_getDataDirectoryByFC (IntPtr self, out int error, string dataReference, FunctionalConstraint fc); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedConnection_getDataDirectoryFC (IntPtr self, out int error, string dataReference); @@ -100,7 +115,10 @@ namespace IEC61850 [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr IedConnection_getLogicalDeviceDirectory (IntPtr self, out int error, string logicalDeviceName); - + [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] + static extern IntPtr IedConnection_getVariableSpecification(IntPtr self, out int error, string objectReference, int fc); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void InternalConnectionClosedHandler (IntPtr parameter,IntPtr Iedconnection); public delegate void ConnectionClosedHandler (IedConnection connection); @@ -124,7 +142,19 @@ namespace IEC61850 static extern IntPtr IedConnection_getMmsConnection (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr MmsConnection_getIsoConnectionParameters(IntPtr mmsConnection); + static extern IntPtr MmsConnection_getIsoConnectionParameters(IntPtr mmsConnection); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedConnection_getFileDirectory(IntPtr self, out int error, string directoryName); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void IedConnection_deleteFile(IntPtr self, out int error, string fileName); + + /******************** + * FileDirectoryEntry + *********************/ + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void FileDirectoryEntry_destroy(IntPtr self); /**************** * LinkedList @@ -136,7 +166,16 @@ namespace IEC61850 static extern IntPtr LinkedList_getData (IntPtr self); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - static extern void LinkedList_destroy (IntPtr self); + static extern void LinkedList_destroy (IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void LinkedList_destroyStatic(IntPtr self); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void LinkedListValueDeleteFunction(IntPtr pointer); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void LinkedList_destroyDeep(IntPtr list, LinkedListValueDeleteFunction valueDeleteFunction); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LinkedList_create (); @@ -144,7 +183,7 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void LinkedList_add (IntPtr self, IntPtr data); - private IntPtr connection; + private IntPtr connection = IntPtr.Zero; private InternalConnectionClosedHandler connectionClosedHandler; private ConnectionClosedHandler userProvidedHandler = null; @@ -153,6 +192,12 @@ namespace IEC61850 connection = IedConnection_create (); } + ~IedConnection () + { + if (connection != IntPtr.Zero) + IedConnection_destroy(connection); + } + public IsoConnectionParameters GetConnectionParameters () { IntPtr mmsConnection = IedConnection_getMmsConnection(connection); @@ -162,12 +207,32 @@ namespace IEC61850 return new IsoConnectionParameters(parameters); } + private void FreeHGlobaleDeleteFunction (IntPtr pointer) + { + Marshal.FreeHGlobal(pointer); + } + + + private UInt32 connectTimeout = 10000; + + public UInt32 ConnectTimeout { + get { + return connectTimeout; + } + set { + connectTimeout = value; + } + } + + /// Establish an MMS connection to a server /// This exception is thrown if there is a connection or service error public void Connect (string hostname, int tcpPort) { int error; + IedConnection_setConnectTimeout(connection, connectTimeout); + IedConnection_connect (connection, out error, hostname, tcpPort); if (error != 0) @@ -199,19 +264,41 @@ namespace IEC61850 if (error != 0) throw new IedConnectionException ("GetDeviceDirectory failed", error); - IntPtr element = LinkedList_getNext (linkedList); - List newList = new List (); - while (element != IntPtr.Zero) { - string ld = Marshal.PtrToStringAnsi (LinkedList_getData (element)); + if (fileDirectory == false) { - newList.Add (ld); + IntPtr element = LinkedList_getNext (linkedList); - element = LinkedList_getNext (element); - } + while (element != IntPtr.Zero) { + string ld = Marshal.PtrToStringAnsi (LinkedList_getData (element)); - LinkedList_destroy (linkedList); + newList.Add (ld); + + element = LinkedList_getNext (element); + } + + LinkedList_destroy (linkedList); + } + else { + + IntPtr element = LinkedList_getNext(linkedList); + + while (element != IntPtr.Zero) + { + IntPtr elementData = LinkedList_getData(element); + + FileDirectoryEntry entry = new FileDirectoryEntry(elementData); + + newList.Add(entry.GetFileName()); + + FileDirectoryEntry_destroy(elementData); + + element = LinkedList_getNext(element); + } + + LinkedList_destroyStatic(linkedList); + } return newList; } @@ -243,6 +330,12 @@ namespace IEC61850 return newList; } + /// Get the directory of a logical node (LN) + /// This function returns the directory contents of a LN. Depending on the provided ACSI class + /// The function returns either data object references, or references of other objects like data sets, + /// report control blocks, ... + /// The object reference of a DO, SDO, or DA. + /// the ACSI class of the requested directory elements. /// This exception is thrown if there is a connection or service error public List GetLogicalNodeDirectory (string logicalNodeName, ACSIClass acsiClass) { @@ -270,6 +363,8 @@ namespace IEC61850 return newList; } + /// Get a list of attributes (with functional constraints) of a DO, SDO, or DA + /// The object reference of a DO, SDO, or DA. /// This exception is thrown if there is a connection or service error public List GetDataDirectory (string dataReference) { @@ -297,6 +392,40 @@ namespace IEC61850 return newList; } + /// Get the list of attributes with the specified FC of a DO, SDO, or DA + /// The object reference of a DO, SDO, or DA. + /// Functional constraint + /// This exception is thrown if there is a connection or service error + public List GetDataDirectory (string dataReference, FunctionalConstraint fc) + { + int error; + + IntPtr linkedList = IedConnection_getDataDirectoryByFC (connection, out error, dataReference, fc); + + if (error != 0) + throw new IedConnectionException ("GetDataDirectory failed", error); + + IntPtr element = LinkedList_getNext (linkedList); + + List newList = new List (); + + while (element != IntPtr.Zero) { + string dataObject = Marshal.PtrToStringAnsi (LinkedList_getData (element)); + + newList.Add (dataObject); + + element = LinkedList_getNext (element); + } + + LinkedList_destroy (linkedList); + + return newList; + } + + /// Get a list of attributes (with functional constraints) of a DO, SDO, or DA + /// This function is similar to the GetDataDirectory except that the returned element names + /// have the functional contraint (FC) appended. + /// The object reference of a DO, SDO, or DA. /// This exception is thrown if there is a connection or service error public List GetDataDirectoryFC (string dataReference) { @@ -324,6 +453,23 @@ namespace IEC61850 return newList; } + + /// Read the variable specification (type description of a DA or FDCO + /// The object reference of a DA or FCDO. + /// The functional constraint (FC) of the object + /// This exception is thrown if there is a connection or service error + public MmsVariableSpecification GetVariableSpecification (string objectReference, FunctionalConstraint fc) + { + int error; + + IntPtr varSpecPtr = IedConnection_getVariableSpecification(connection, out error, objectReference, (int) fc); + + if (error != 0) + throw new IedConnectionException ("GetVariableSpecification failed", error); + + return new MmsVariableSpecification(varSpecPtr, true); + } + private IntPtr readObjectInternal (string objectReference, FunctionalConstraint fc) { int error; @@ -342,6 +488,7 @@ namespace IEC61850 /// Read the value of a data attribute (DA) or functional constraint data object (FCDO). /// The object reference of a DA or FCDO. /// The functional constraint (FC) of the object + /// the received value as an MmsValue instance /// This exception is thrown if there is a connection or service error public MmsValue ReadValue (String objectReference, FunctionalConstraint fc) { @@ -353,6 +500,7 @@ namespace IEC61850 /// Read the value of a basic data attribute (BDA) of type boolean. /// The object reference of a BDA. /// The functional constraint (FC) of the object + /// the received boolean value /// This exception is thrown if there is a connection or service error public bool ReadBooleanValue (string objectReference, FunctionalConstraint fc) { @@ -423,6 +571,25 @@ namespace IEC61850 } } + /// Read the value of a basic data attribute (BDA) of type bit string. + /// The object reference of a BDA. + /// The functional constraint (FC) of the object + /// This exception is thrown if there is a connection or service error + public int ReadBitStringValue (string objectReference, FunctionalConstraint fc) + { + IntPtr mmsValue = readObjectInternal (objectReference, fc); + + if (MmsValue_getType (mmsValue) == (int)MmsType.MMS_BIT_STRING) { + int bitStringValue = (int)MmsValue_getBitStringAsInteger (mmsValue); + + MmsValue_delete (mmsValue); + return bitStringValue; + } else { + MmsValue_delete (mmsValue); + throw new IedConnectionException ("Result is not of type bit string", 0); + } + } + /// Read the value of a basic data attribute (BDA) of type timestamp (MMS_UTC_TIME). /// The object reference of a BDA. /// The functional constraint (FC) of the object @@ -457,6 +624,12 @@ namespace IEC61850 throw new IedConnectionException ("Result is not of type integer (MMS_INTEGER)", 0); } + /// Write the value of a data attribute (DA) or functional constraint data object (FCDO). + /// This function can be used to write simple or complex variables (setpoints, parameters, descriptive values...) + /// of the server. + /// The object reference of a BDA. + /// The functional constraint (FC) of the object + /// MmsValue object representing asimple or complex variable data /// This exception is thrown if there is a connection or service error public void WriteValue (string objectReference, FunctionalConstraint fc, MmsValue value) { @@ -466,8 +639,111 @@ namespace IEC61850 if (error != 0) throw new IedConnectionException ("Write value failed", error); - } - + } + + /// Read the content of a file directory. + /// The name of the directory. + /// This exception is thrown if there is a connection or service error + public List GetFileDirectory(string directoryName) + { + int error; + + IntPtr fileEntryList = IedConnection_getFileDirectory(connection, out error, directoryName); + + if (error != 0) + throw new IedConnectionException("Reading file directory failed", error); + + List fileDirectory = new List(); + + IntPtr element = LinkedList_getNext(fileEntryList); + + while (element != IntPtr.Zero) + { + IntPtr elementData = LinkedList_getData(element); + + FileDirectoryEntry entry = new FileDirectoryEntry(elementData); + + fileDirectory.Add(entry); + + FileDirectoryEntry_destroy(elementData); + + element = LinkedList_getNext(element); + } + + LinkedList_destroyStatic(fileEntryList); + + return fileDirectory; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool InternalIedClientGetFileHandler(IntPtr parameter, IntPtr buffer, UInt32 bytesRead); + + private bool iedClientGetFileHandler(IntPtr parameter, IntPtr buffer, UInt32 bytesRead) + { + GCHandle handle = GCHandle.FromIntPtr(parameter); + + GetFileCallback getFileCallback = (GetFileCallback) handle.Target; + + byte[] bytes = new byte[bytesRead]; + + Marshal.Copy(buffer, bytes, 0, (int) bytesRead); + + return getFileCallback.handler(getFileCallback.parameter, bytes); + } + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 IedConnection_getFile(IntPtr self, out int error, string fileName, InternalIedClientGetFileHandler handler, + IntPtr handlerParameter); + + + public delegate bool GetFileHandler(object parameter, byte[] data); + + private class GetFileCallback + { + public GetFileCallback(GetFileHandler handler, object parameter) + { + this.handler = handler; + this.parameter = parameter; + } + + public GetFileHandler handler; + public object parameter; + } + + /// + /// Download a file from the server. + /// + /// + /// File name of the file (full path) + /// + /// + /// Callback handler that is invoked for each chunk of the file received + /// + /// + /// User provided parameter that is passed to the callback handler + /// + public void GetFile(string fileName, GetFileHandler handler, object parameter) + { + int error; + + GetFileCallback getFileCallback = new GetFileCallback(handler, parameter); + + GCHandle handle = GCHandle.Alloc(getFileCallback); + + IedConnection_getFile(connection, out error, fileName, new InternalIedClientGetFileHandler(iedClientGetFileHandler), + GCHandle.ToIntPtr(handle)); + + if (error != 0) + throw new IedConnectionException("Error reading file", error); + + handle.Free(); + } + + /// + /// Abort (close) the connection. + /// + /// This function will send an abort request to the server. This will immediately interrupt the + /// connection. /// This exception is thrown if there is a connection or service error public void Abort () { @@ -479,6 +755,11 @@ namespace IEC61850 throw new IedConnectionException ("Abort failed", error); } + /// + /// Release (close) the connection. + /// + /// This function will send an release request to the server. The function will block until the + /// connection is released or an error occured. /// This exception is thrown if there is a connection or service error public void Release () { @@ -490,6 +771,12 @@ namespace IEC61850 throw new IedConnectionException ("Release failed", error); } + /// + /// Immediately close the connection. + /// + /// This function will close the connnection to the server by closing the TCP connection. + /// The client will not send an abort or release request as required by the specification! + /// This exception is thrown if there is a connection or service error public void Close () { IedConnection_close(connection); @@ -501,6 +788,14 @@ namespace IEC61850 userProvidedHandler (this); } + /// + /// Install a callback handler that will be invoked if the connection is closed. + /// + /// The handler is called when the connection is closed no matter if the connection was closed + /// by the client or by the server. Any new call to this function will replace the callback handler installed + /// by a prior function call. + /// The user provided callback handler + /// This exception is thrown if there is a connection or service error public void InstallConnectionClosedHandler (ConnectionClosedHandler handler) { connectionClosedHandler = new InternalConnectionClosedHandler (MyConnectionClosedHandler); @@ -510,6 +805,28 @@ namespace IEC61850 IedConnection_installConnectionClosedHandler (connection, connectionClosedHandler, connection); } + /// + /// Read the values of a data set (GetDataSetValues service). + /// + /// This function will invoke a readDataSetValues service and return a new DataSet value containing the + /// received values. + /// The object reference of the data set + + /// This exception is thrown if there is a connection or service error + public DataSet GetDataSetValues (string dataSetReference) + { + return ReadDataSetValues(dataSetReference, null); + } + + /// + /// Read the values of a data set (GetDataSetValues service). + /// + /// This function will invoke a readDataSetValues service and return a new DataSet value containing the + /// received values. If an existing instance of DataSet is provided to the function the existing instance will be + /// updated by the new values. + /// The object reference of the data set + /// The object reference of an existing data set instance or null + /// a DataSet instance containing the received values /// This exception is thrown if there is a connection or service error public DataSet ReadDataSetValues (string dataSetReference, DataSet dataSet) { @@ -531,6 +848,14 @@ namespace IEC61850 return dataSet; } + /// + /// Create a new data set. + /// + /// This function creates a new data set at the server. The data set consists of the members defined + /// by the list of object references provided. + /// The object reference of the data set + /// A list of object references of the data set elements + /// This exception is thrown if there is a connection or service error public void CreateDataSet (string dataSetReference, List dataSetElements) { IntPtr linkedList = LinkedList_create (); @@ -545,13 +870,20 @@ namespace IEC61850 IedConnection_createDataSet (connection, out error, dataSetReference, linkedList); - LinkedList_destroy (linkedList); + LinkedList_destroyDeep(linkedList, new LinkedListValueDeleteFunction(FreeHGlobaleDeleteFunction)); if (error != 0) throw new IedConnectionException ("Failed to create data set", error); } + /// + /// Delete a data set. + /// + /// This function will delete a data set at the server. This function may fail if the data set is not + /// deletable. + /// The object reference of the data set + /// This exception is thrown if there is a connection or service error public void DeleteDataSet (string dataSetReference) { int error; @@ -615,7 +947,45 @@ namespace IEC61850 { return (IedClientError)this.errorCode; } - } + } + + public class FileDirectoryEntry + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr FileDirectoryEntry_getFileName(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt32 FileDirectoryEntry_getFileSize(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + private static extern UInt64 FileDirectoryEntry_getLastModified(IntPtr self); + + private string fileName; + private UInt32 fileSize; + private UInt64 lastModified; + + internal FileDirectoryEntry(IntPtr nativeFileDirectoryEntry) + { + fileName = Marshal.PtrToStringAnsi(FileDirectoryEntry_getFileName(nativeFileDirectoryEntry)); + fileSize = FileDirectoryEntry_getFileSize(nativeFileDirectoryEntry); + lastModified = FileDirectoryEntry_getLastModified(nativeFileDirectoryEntry); + } + + public string GetFileName() + { + return fileName; + } + + public UInt32 GetFileSize() + { + return fileSize; + } + + public UInt64 GetLastModified() + { + return lastModified; + } + } public enum IedClientError { @@ -680,10 +1050,15 @@ namespace IEC61850 [Flags] public enum TriggerOptions { NONE = 0, + /** send report when value of data changed */ DATA_CHANGED = 1, + /** send report when quality of data changed */ QUALITY_CHANGED = 2, + /** send report when data or quality is updated */ DATA_UPDATE = 4, + /** periodic transmission of all data set values */ INTEGRITY = 8, + /** general interrogation (on client request) */ GI = 16 } @@ -708,6 +1083,9 @@ namespace IEC61850 QUESTIONABLE = 3 } + /// + /// The quality of a data object. + /// public class Quality { @@ -740,16 +1118,27 @@ namespace IEC61850 public enum ACSIClass { + /** data objects */ ACSI_CLASS_DATA_OBJECT, + /** data sets (container for multiple data objects) */ ACSI_CLASS_DATA_SET, + /** buffered report control blocks */ ACSI_CLASS_BRCB, + /** unbuffered report control blocks */ ACSI_CLASS_URCB, + /** log control blocks */ ACSI_CLASS_LCB, + /** logs (journals) */ ACSI_CLASS_LOG, + /** setting group control blocks */ ACSI_CLASS_SGCB, + /** GOOSE control blocks */ ACSI_CLASS_GoCB, + /** GSE control blocks */ ACSI_CLASS_GsCB, + /** multicast sampled values control blocks */ ACSI_CLASS_MSVCB, + /** unicast sampled values control blocks */ ACSI_CLASS_USVCB } @@ -785,5 +1174,89 @@ namespace IEC61850 NONE = -1 } + public enum ControlAddCause { + ADD_CAUSE_UNKNOWN = 0, + ADD_CAUSE_NOT_SUPPORTED = 1, + ADD_CAUSE_BLOCKED_BY_SWITCHING_HIERARCHY = 2, + ADD_CAUSE_SELECT_FAILED = 3, + ADD_CAUSE_INVALID_POSITION = 4, + ADD_CAUSE_POSITION_REACHED = 5, + ADD_CAUSE_PARAMETER_CHANGE_IN_EXECUTION = 6, + ADD_CAUSE_STEP_LIMIT = 7, + ADD_CAUSE_BLOCKED_BY_MODE = 8, + ADD_CAUSE_BLOCKED_BY_PROCESS = 9, + ADD_CAUSE_BLOCKED_BY_INTERLOCKING = 10, + ADD_CAUSE_BLOCKED_BY_SYNCHROCHECK = 11, + ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION = 12, + ADD_CAUSE_BLOCKED_BY_HEALTH = 13, + ADD_CAUSE_1_OF_N_CONTROL = 14, + ADD_CAUSE_ABORTION_BY_CANCEL = 15, + ADD_CAUSE_TIME_LIMIT_OVER = 16, + ADD_CAUSE_ABORTION_BY_TRIP = 17, + ADD_CAUSE_OBJECT_NOT_SELECTED = 18, + ADD_CAUSE_OBJECT_ALREADY_SELECTED = 19, + ADD_CAUSE_NO_ACCESS_AUTHORITY = 20, + ADD_CAUSE_ENDED_WITH_OVERSHOOT = 21, + ADD_CAUSE_ABORTION_DUE_TO_DEVIATION = 22, + ADD_CAUSE_ABORTION_BY_COMMUNICATION_LOSS = 23, + ADD_CAUSE_ABORTION_BY_COMMAND = 24, + ADD_CAUSE_NONE = 25, + ADD_CAUSE_INCONSISTENT_PARAMETERS = 26, + ADD_CAUSE_LOCKED_BY_OTHER_CLIENT = 27 + } + + /// + /// Object reference. Helper function to handle object reference strings. + /// + public static class ObjectReference { + + /// + /// Get the name part of an object reference with appended FC + /// + /// + /// The element name. + /// + /// + /// Object reference with appended fc. + /// + public static string getElementName (string objectReferenceWithFc) + { + int fcPartStartIndex = objectReferenceWithFc.IndexOf('['); + + if (fcPartStartIndex == -1) + return objectReferenceWithFc; + + return objectReferenceWithFc.Substring(0, fcPartStartIndex); + } + + /// + /// Get the FC of an object reference with appended FC. + /// + /// + /// The FC + /// + /// + /// Object reference with FC. + /// + public static FunctionalConstraint getFC (string objectReferenceWithFc) + { + int fcPartStartIndex = objectReferenceWithFc.IndexOf('['); + + if (fcPartStartIndex == -1) + return FunctionalConstraint.NONE; + + string fcString = objectReferenceWithFc.Substring(fcPartStartIndex + 1 , 2); + + try + { + return (FunctionalConstraint) Enum.Parse(typeof(FunctionalConstraint), fcString); + } + catch(ArgumentException) + { + return FunctionalConstraint.NONE; + } + } + } + } } diff --git a/dotnet/IEC61850forCSharp/IEC61850forCSharp.csproj b/dotnet/IEC61850forCSharp/IEC61850forCSharp.csproj index 5c0e30c3..535e22ab 100644 --- a/dotnet/IEC61850forCSharp/IEC61850forCSharp.csproj +++ b/dotnet/IEC61850forCSharp/IEC61850forCSharp.csproj @@ -40,6 +40,7 @@ + \ No newline at end of file diff --git a/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs b/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs index 22aa246a..342dbc30 100644 --- a/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs +++ b/dotnet/IEC61850forCSharp/IsoConnectionParameters.cs @@ -30,12 +30,26 @@ namespace IEC61850 { public enum AcseAuthenticationMechanism { + /** don't use authentication */ ACSE_AUTH_NONE = 0, + /** use password authentication */ ACSE_AUTH_PASSWORD = 1 } + /// + /// Connection parameters associated with the ISO protocol layers (transport, session, presentation, ACSE) + /// public class IsoConnectionParameters { + + [StructLayout(LayoutKind.Sequential)] + private struct NativeTSelector + { + public byte size; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)] public byte[] value; + } + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void IsoConnectionParameters_destroy(IntPtr self); @@ -43,13 +57,13 @@ namespace IEC61850 private static extern void IsoConnectionParameters_setRemoteApTitle(IntPtr self, string apTitle, int aeQualifier); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - private static extern void IsoConnectionParameters_setRemoteAddresses(IntPtr self, UInt32 pSelector, UInt16 sSelector, UInt16 tSelector); + private static extern void IsoConnectionParameters_setRemoteAddresses(IntPtr self, UInt32 pSelector, UInt16 sSelector, NativeTSelector tSelector); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void IsoConnectionParameters_setLocalApTitle(IntPtr self, string apTitle, int aeQualifier); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - private static extern void IsoConnectionParameters_setLocalAddresses(IntPtr self, UInt32 pSelector, UInt16 sSelector, UInt16 tSelector); + private static extern void IsoConnectionParameters_setLocalAddresses(IntPtr self, UInt32 pSelector, UInt16 sSelector, NativeTSelector tSelector); [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] private static extern void IsoConnectionParameters_setAcseAuthenticationParameter(IntPtr self, IntPtr acseAuthParameter); @@ -80,29 +94,97 @@ namespace IEC61850 if (authParameter != IntPtr.Zero) AcseAuthenticationParameter_destroy(authParameter); - IsoConnectionParameters_destroy(self); + //IsoConnectionParameters_destroy(self); } + /// + /// Sets the remote ap title related parameters + /// + /// + /// remote AP title. + /// + /// + /// remote AE qualifier. + /// public void SetRemoteApTitle(string apTitle, int aeQualifier) { IsoConnectionParameters_setRemoteApTitle(self, apTitle, aeQualifier); } - public void SetRemoteAddresses (UInt32 pSelector, UInt16 sSelector, UInt16 tSelector) + /// + /// Sets the remote addresses for ISO layers (transport, session, presentation) + /// + /// + /// presentation layer address + /// + /// + /// session layer address + /// + /// + /// ISO COTP transport layer address + /// + public void SetRemoteAddresses (UInt32 pSelector, UInt16 sSelector, byte[] tSelector) { - IsoConnectionParameters_setRemoteAddresses(self, pSelector, sSelector, tSelector); + if (tSelector.Length > 4) + throw new ArgumentOutOfRangeException("tSelector", "maximum size (4) exceeded"); + + NativeTSelector nativeTSelector; + nativeTSelector.size = (byte) tSelector.Length; + nativeTSelector.value = new byte[4]; + + for (int i = 0; i < tSelector.Length; i++) + nativeTSelector.value[i] = tSelector[i]; + + IsoConnectionParameters_setRemoteAddresses(self, pSelector, sSelector, nativeTSelector); } + /// + /// Sets the local ap title related parameters + /// + /// + /// local AP title. + /// + /// + /// local AE qualifier. + /// public void SetLocalApTitle (string apTitle, int aeQualifier) { IsoConnectionParameters_setLocalApTitle(self, apTitle, aeQualifier); } - public void SetLocalAddresses (UInt32 pSelector, UInt16 sSelector, UInt16 tSelector) + /// + /// Sets the local addresses for ISO layers (transport, session, presentation) + /// + /// + /// presentation layer address + /// + /// + /// session layer address + /// + /// + /// ISO COTP transport layer address + /// + public void SetLocalAddresses (UInt32 pSelector, UInt16 sSelector, byte[] tSelector) { - IsoConnectionParameters_setLocalAddresses(self, pSelector, sSelector, tSelector); + if (tSelector.Length > 4) + throw new ArgumentOutOfRangeException("tSelector", "maximum size (4) exceeded"); + + NativeTSelector nativeTSelector; + nativeTSelector.size = (byte) tSelector.Length; + nativeTSelector.value = new byte[4]; + + for (int i = 0; i < tSelector.Length; i++) + nativeTSelector.value[i] = tSelector[i]; + + IsoConnectionParameters_setLocalAddresses(self, pSelector, sSelector, nativeTSelector); } + /// + /// Instruct ACSE layer to use password authentication. + /// + /// + /// Password that will be used to authenticate the client + /// public void UsePasswordAuthentication (string password) { if (authParameter == IntPtr.Zero) { diff --git a/dotnet/IEC61850forCSharp/MmsValue.cs b/dotnet/IEC61850forCSharp/MmsValue.cs index 7d54894a..0ab8c06b 100644 --- a/dotnet/IEC61850forCSharp/MmsValue.cs +++ b/dotnet/IEC61850forCSharp/MmsValue.cs @@ -25,12 +25,16 @@ using System; using System.Runtime.InteropServices; using System.Collections.Generic; -using System.Collections; +using System.Collections; +using System.Text; namespace IEC61850 { - namespace Client + namespace Common { + /// + /// This class is used to hold MMS data values of different types. + /// public class MmsValue : IEnumerable { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -48,6 +52,18 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern UInt32 MmsValue_getBitStringAsInteger (IntPtr self); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void MmsValue_setBitStringFromInteger(IntPtr self, UInt32 intValue); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int MmsValue_getBitStringSize(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void MmsValue_setBitStringBit(IntPtr self, int bitPos, bool value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool MmsValue_getBitStringBit(IntPtr self, int bitPos); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern Int32 MmsValue_toInt32 (IntPtr self); @@ -87,9 +103,37 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_newIntegerFromInt32 (Int32 integer); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_newUnsignedFromUint32(UInt32 integer); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr MmsValue_newIntegerFromInt64 (Int64 integer); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_newBitString(int bitSize); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_newVisibleString(string value); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_newOctetString(int size, int maxSize); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void MmsValue_setOctetString(IntPtr self, [Out] byte[] buf, int size); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt16 MmsValue_getOctetStringSize(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt16 MmsValue_getOctetStringMaxSize(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsValue_getOctetStringBuffer(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool MmsValue_equals(IntPtr self, IntPtr otherValue); + + internal MmsValue (IntPtr value) { valueReference = value; @@ -122,33 +166,165 @@ namespace IEC61850 valueReference = MmsValue_newIntegerFromInt32 (value); } + public MmsValue (UInt32 value) + { + valueReference = MmsValue_newUnsignedFromUint32(value); + } + public MmsValue (long value) { valueReference = MmsValue_newIntegerFromInt64 (value); } + public MmsValue (string value) + { + valueReference = MmsValue_newVisibleString(value); + } + ~MmsValue () { if (responsableForDeletion) MmsValue_delete (valueReference); } + /// + /// Create a new MmsValue instance of type MMS_BIT_STRING. + /// + /// + /// the new MmsValue instance + /// + /// + /// the size of the bit string in bits. + /// + public static MmsValue NewBitString(int bitSize) + { + IntPtr newValue = MmsValue_newBitString(bitSize); + + return new MmsValue(newValue, true); + } + + /// + /// Create a new MmsValue instance of type MMS_OCTET_STRING. + /// + /// + /// the new MmsValue instance + /// + /// + /// the maximum size of the octet string in bytes + /// + /// + /// the current size of the octet string in bytes (defaults to 0) + /// + public static MmsValue NewOctetString (int maxSize, int size = 0) + { + IntPtr newValue = MmsValue_newOctetString(size, maxSize); + + return new MmsValue(newValue, true); + } + internal IntPtr valueReference; private bool responsableForDeletion; + /// + /// Gets the type of the value + /// + /// + /// The type. + /// public new MmsType GetType () { return (MmsType)MmsValue_getType (valueReference); } + /// + /// Gets the size of an array, structure, or bit string + /// + /// + /// + /// Return the size of an array of structure (number of elements) + /// The value has to be of type MMS_ARRAY, MMS_STRUCTURE, MMS_BIT_STRING ... + /// + /// the number of elements (array/structure elements, octets, bits depending on type) + /// + /// This exception is thrown if the value has the wrong type. public int Size () { if ((GetType () == MmsType.MMS_ARRAY) || (GetType () == MmsType.MMS_STRUCTURE)) { return MmsValue_getArraySize (valueReference); - } else - throw new MmsValueException ("Value is not a complex type"); + } else if (GetType () == MmsType.MMS_BIT_STRING) { + return MmsValue_getBitStringSize(valueReference); + } + else if (GetType () == MmsType.MMS_OCTET_STRING) { + return MmsValue_getOctetStringSize(valueReference); + } + else + throw new MmsValueException ("Operation not supported for this type"); + } + + /// + /// Gets the maximum size of an octet string + /// + /// + /// The maximum size (in bytes) of the octet string + /// + public int MaxSize () + { + if (GetType () == MmsType.MMS_OCTET_STRING) { + return MmsValue_getOctetStringMaxSize(valueReference); + } + else + throw new MmsValueException ("Operation not supported for this type"); } + /// + /// Gets the octet string as byte array + /// + /// Instance has to be of type MMS_OCTET_STRING. + /// + /// + /// Byte array containing the bytes of the octet string. + /// + /// This exception is thrown if the value has the wrong type. + public byte[] getOctetString () + { + if (GetType () == MmsType.MMS_OCTET_STRING) { + IntPtr buffer = MmsValue_getOctetStringBuffer(valueReference); + int bufferSize = this.Size(); + + byte[] octetString = new byte[bufferSize]; + + Marshal.Copy(buffer, octetString, 0, bufferSize); + + return octetString; + } + else + throw new MmsValueException ("Operation not supported for this type"); + } + + public void setOctetString (byte[] octetString) + { + if (GetType () == MmsType.MMS_OCTET_STRING) { + + if (this.MaxSize() < octetString.Length) + throw new MmsValueException("octet string is to large"); + + MmsValue_setOctetString(valueReference, octetString, octetString.Length); + } + else + throw new MmsValueException ("Operation not supported for this type"); + } + + /// + /// Get an element of an array or structure + /// + /// + /// the MmsValue element. + /// + /// + /// index of the element starting with 0 + /// + /// This exception is thrown if the value has the wrong type. + /// This exception is thrown if the index is out of range. public MmsValue GetElement (int index) { MmsType type = GetType (); @@ -163,6 +339,17 @@ namespace IEC61850 throw new MmsValueException ("Value is of wrong type"); } + /// + /// Gets the timestamp value as UTC time in s (UNIX time stamp). + /// + /// + /// Return the value as seconds since epoch (1.1.1970 UTC). + /// The value has to be of type MMS_UTC_TIME. + /// + /// + /// The UTC time in seconds (UNIX time stamp). + /// + /// This exception is thrown if the value has the wrong type. public UInt32 ToUnixTimestamp () { if (GetType () == MmsType.MMS_UTC_TIME) @@ -171,6 +358,17 @@ namespace IEC61850 throw new MmsValueException ("Value is not a time type"); } + /// + /// Gets the timestamp value as UTC time in ms. + /// + /// + /// Return the value as milliseconds since epoch (1.1.1970 UTC). + /// The value has to be of type MMS_UTC_TIME. + /// + /// + /// The UTC time in ms. + /// + /// This exception is thrown if the value has the wrong type. public ulong GetUtcTimeInMs () { if (GetType () == MmsType.MMS_UTC_TIME) { @@ -179,13 +377,33 @@ namespace IEC61850 throw new MmsValueException ("Value is not a time type"); } + /// + /// Convert a millisecond time (milliseconds since epoch) to DataTimeOffset + /// + /// + /// The time as DataTimeOffset + /// + /// + /// the millisecond time + /// public static DateTimeOffset MsTimeToDateTimeOffset (UInt64 msTime) { DateTimeOffset retVal = new DateTimeOffset (1970, 1, 1, 0, 0, 0, TimeSpan.Zero); - return retVal.AddMilliseconds (msTime); + return retVal.AddMilliseconds ((double) msTime); } + /// + /// Convert MMS_UTC_TIME to DateTimeOffset instance + /// + /// + /// Return the value as DateTimeOffset instance. + /// The value has to be of type MMS_UTC_TIME. + /// + /// + /// the value as DataTimeOffset instance + /// + /// This exception is thrown if the value has the wrong type. public DateTimeOffset GetUtcTimeAsDateTimeOffset () { if (GetType () == MmsType.MMS_UTC_TIME) @@ -194,36 +412,123 @@ namespace IEC61850 throw new MmsValueException ("Value is not a time type"); } + + /// + /// Return the value as 32 bit signed integer. + /// + /// + /// Return the value as 32 bit signed integer (Int32). + /// The value has to be of type MMS_INTEGER. + /// + /// + /// the value if the object as 32 bit signed integer + /// + /// This exception is thrown if the value has the wrong type. public Int32 ToInt32 () { if (GetType () != MmsType.MMS_INTEGER) throw new MmsValueException ("Value type is not integer"); - Int32 retVal = MmsValue_toInt32 (valueReference); - - return retVal; + return MmsValue_toInt32 (valueReference); } + /// + /// Return the value as 64 bit signed integer. + /// + /// + /// Return the value as 64 bit signed integer (Int64). + /// The value has to be of type MMS_INTEGER. + /// + /// + /// the value if the object as 64 bit signed integer + /// + /// This exception is thrown if the value has the wrong type. public Int64 ToInt64 () { if (GetType () != MmsType.MMS_INTEGER) throw new MmsValueException ("Value type is not integer"); - Int64 retVal = MmsValue_toInt64 (valueReference); - - return retVal; + return MmsValue_toInt64 (valueReference); } + /// + /// Return the value as 32 bit unsigned integer. + /// + /// + /// Return the value as 32 bit unsigned integer (Int32). + /// The value has to be of type MMS_INTEGER. + /// + /// + /// the value if the object as 32 bit unsigned integer + /// + /// This exception is thrown if the value has the wrong type. public UInt32 ToUint32 () { if (GetType () != MmsType.MMS_UNSIGNED) throw new MmsValueException ("Value type is not unsigned"); - UInt32 retVal = MmsValue_toUint32 (valueReference); + return MmsValue_toUint32 (valueReference); + } + + public UInt32 BitStringToUInt32 () + { + if (GetType () != MmsType.MMS_BIT_STRING) + throw new MmsValueException("Value type is not bit string"); + + return MmsValue_getBitStringAsInteger(valueReference); + } - return retVal; + public void BitStringFromUInt32 (UInt32 intValue) + { + if (GetType () != MmsType.MMS_BIT_STRING) + throw new MmsValueException("Value type is not bit string"); + + MmsValue_setBitStringFromInteger(valueReference, intValue); } + public void SetBit (int bitPos, bool bitValue) + { + if (GetType () != MmsType.MMS_BIT_STRING) + throw new MmsValueException("Value type is not bit string"); + + MmsValue_setBitStringBit(valueReference, bitPos, bitValue); + } + + public bool GetBit (int bitPos) + { + if (GetType () != MmsType.MMS_BIT_STRING) + throw new MmsValueException("Value type is not bit string"); + + return MmsValue_getBitStringBit(valueReference, bitPos); + } + + private string GetBitStringAsString() + { + if (GetType() != MmsType.MMS_BIT_STRING) + throw new MmsValueException("Value type is not bit string"); + + int size = Size(); + + StringBuilder builder = new StringBuilder(size); + + for (int i = 0; i < size; i++) + { + if (MmsValue_getBitStringBit(valueReference, i)) + builder.Append('1'); + else + builder.Append('0'); + } + + return builder.ToString(); + } + + /// + /// Gets the boolean value + /// + /// + /// The boolean value + /// + /// This exception is thrown if the value has the wrong type. public bool GetBoolean () { if (GetType () == MmsType.MMS_BOOLEAN) @@ -232,6 +537,13 @@ namespace IEC61850 throw new MmsValueException ("Value type is not boolean"); } + /// + /// Gets the float value of an MMS_FLOAT instance + /// + /// + /// The float value + /// + /// This exception is thrown if the value has the wrong type. public float ToFloat () { if (GetType () == MmsType.MMS_FLOAT) @@ -240,6 +552,13 @@ namespace IEC61850 throw new MmsValueException ("Value type is not float"); } + /// + /// Gets the double value of an MMS_FLOAT instance + /// + /// + /// The float value + /// + /// This exception is thrown if the value has the wrong type. public double ToDouble () { if (GetType () == MmsType.MMS_FLOAT) @@ -248,6 +567,13 @@ namespace IEC61850 throw new MmsValueException ("Value type is not float"); } + public override bool Equals (object obj) + { + MmsValue otherValue = (MmsValue) obj; + + return MmsValue_equals(this.valueReference, otherValue.valueReference); + } + // override standard ToString() method public override string ToString () { @@ -265,6 +591,8 @@ namespace IEC61850 return ToDouble ().ToString (); case MmsType.MMS_UTC_TIME: return GetUtcTimeAsDateTimeOffset ().ToString (); + case MmsType.MMS_BIT_STRING: + return GetBitStringAsString(); default: return "unknown"; } @@ -321,21 +649,37 @@ namespace IEC61850 public enum MmsType { + /** array type (multiple elements of the same type) */ MMS_ARRAY = 0, + /** structure type (multiple elements of different types) */ MMS_STRUCTURE = 1, + /** boolean */ MMS_BOOLEAN = 2, + /** bit string */ MMS_BIT_STRING = 3, + /** signed integer */ MMS_INTEGER = 4, + /** unsigned integer */ MMS_UNSIGNED = 5, + /** floating point value (32 or 64 bit) */ MMS_FLOAT = 6, + /** octet string */ MMS_OCTET_STRING = 7, + /** visible string - ANSI string */ MMS_VISIBLE_STRING = 8, + /** Generalized time */ MMS_GENERALIZED_TIME = 9, + /** Binary time */ MMS_BINARY_TIME = 10, + /** Binary coded decimal (BCD) - not used */ MMS_BCD = 11, + /** object ID - not used */ MMS_OBJ_ID = 12, + /** Unicode string */ MMS_STRING = 13, + /** UTC time */ MMS_UTC_TIME = 14, + /** will be returned in case of an error (contains error code) */ MMS_DATA_ACCESS_ERROR = 15 } diff --git a/dotnet/IEC61850forCSharp/MmsVariableSpecification.cs b/dotnet/IEC61850forCSharp/MmsVariableSpecification.cs new file mode 100644 index 00000000..159f10e6 --- /dev/null +++ b/dotnet/IEC61850forCSharp/MmsVariableSpecification.cs @@ -0,0 +1,201 @@ +/* + * MmsVariableSpecification.cs + * + * Copyright 2014 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. + */ +using System; + +using System.Runtime.InteropServices; +using System.Collections.Generic; + +using System.Collections; + +namespace IEC61850 +{ + namespace Common + { + /// + /// MMS variable specification. This class is used to represent an MMS variable type definition. + /// + public class MmsVariableSpecification : IEnumerable + { + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern void MmsVariableSpecification_destroy(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsVariableSpecification_getChildValue(IntPtr self, IntPtr value, string childId); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsVariableSpecification_getNamedVariableRecursive(IntPtr variable, string nameId); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int MmsVariableSpecification_getType(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsVariableSpecification_getName(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int MmsVariableSpecification_getSize(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsVariableSpecification_getChildSpecificationByIndex(IntPtr self, int index); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr MmsVariableSpecification_getArrayElementSpecification(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern int MmsVariableSpecification_getExponentWidth(IntPtr self); + + private IntPtr self; + private bool responsableForDeletion; + + internal MmsVariableSpecification (IntPtr self) + { + this.self = self; + this.responsableForDeletion = false; + } + + internal MmsVariableSpecification (IntPtr self, bool responsableForDeletion) + { + this.self = self; + this.responsableForDeletion = responsableForDeletion; + } + + ~MmsVariableSpecification () + { + if (responsableForDeletion) + MmsVariableSpecification_destroy(self); + } + + /// + /// Gets the MmsValue type of the variable + /// + /// + /// The MmsType of the variable + /// + public new MmsType GetType () + { + return (MmsType) MmsVariableSpecification_getType(self); + } + + /// + /// Gets the type of the array elements. + /// + /// + /// The array element type. + /// + /// This exception is thrown if the value is not of type MMS_ARRAY + public MmsVariableSpecification getArrayElementType () + { + if (GetType() == MmsType.MMS_ARRAY) { + IntPtr varSpecPtr = MmsVariableSpecification.MmsVariableSpecification_getArrayElementSpecification(self); + return new MmsVariableSpecification(varSpecPtr); + } + else + throw new MmsValueException ("specification is of wrong type"); + } + + /// + /// Gets the element specification of a structure element + /// + /// + /// The element of the structure at given index + /// + /// + /// Index. + /// + public MmsVariableSpecification GetElement (int index) + { + if (GetType () == MmsType.MMS_STRUCTURE) { + + if ((index >= 0) && (index < Size ())) { + IntPtr varSpecPtr = MmsVariableSpecification_getChildSpecificationByIndex(self, index); + return new MmsVariableSpecification(varSpecPtr); + } + else + throw new MmsValueException ("Index out of bounds"); + } + else + throw new MmsValueException ("specification is of wrong type"); + } + + /// + /// Gets the name of the variable + /// + /// + /// The name. + /// + public string GetName () + { + IntPtr namePtr = MmsVariableSpecification_getName(self); + + return Marshal.PtrToStringAnsi (namePtr); + } + + /// + /// Get the "size" of the variable (array size, number of structure elements ...) + /// + public int Size () + { + return MmsVariableSpecification_getSize(self); + } + + IEnumerator IEnumerable.GetEnumerator () + { + return new MmsVariableSpecificationEnumerator (this); + } + + private class MmsVariableSpecificationEnumerator : IEnumerator + { + private MmsVariableSpecification value; + private int index = -1; + + public MmsVariableSpecificationEnumerator (MmsVariableSpecification value) + { + this.value = value; + } + + #region IEnumerator Members + public void Reset () + { + index = -1; + } + + public object Current { + + get { return value.GetElement (index);} + } + + public bool MoveNext () + { + index++; + + if (index >= value.Size ()) + return false; + else + return true; + } + + #endregion + } + + } + } +} diff --git a/dotnet/IEC61850forCSharp/ReportControlBlock.cs b/dotnet/IEC61850forCSharp/ReportControlBlock.cs index e86ffae1..de66949d 100644 --- a/dotnet/IEC61850forCSharp/ReportControlBlock.cs +++ b/dotnet/IEC61850forCSharp/ReportControlBlock.cs @@ -31,8 +31,19 @@ namespace IEC61850 namespace Client { + /// + /// Report handler. + /// public delegate void ReportHandler (Report report, object parameter); + /// + /// Report control block (RCB) representation. + /// + /// + /// This class is used as a client side representation (copy) of a report control block (RCB). + /// Values from the server will only be read when the GetRCBValues method is called. + /// Values at the server are only affected when the SetRCBValues method is called. + /// public class ReportControlBlock { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] @@ -133,12 +144,11 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern void IedConnection_installReportHandler (IntPtr connection, string rcbReference, string rptId, InternalReportHandler handler, - IntPtr handlerParameter); - + IntPtr handlerParameter); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void InternalReportHandler (IntPtr parameter, IntPtr report); - - private IntPtr self; private IntPtr connection; private string objectReference; @@ -183,8 +193,6 @@ namespace IEC61850 private void internalReportHandler (IntPtr parameter, IntPtr report) { - Console.WriteLine ("internalReportHandler called " + this); - try { if (this.report == null) @@ -207,20 +215,42 @@ namespace IEC61850 this.objectReference = objectReference; } + /// + /// Installs the report handler. + /// + /// + /// This will install a callback handler (delegate) that is invoked whenever a report + /// related this RCB is received. Any call of this method will replace an previously registered + /// handler! + /// + /// + /// report handler + /// + /// + /// parameter is passed to the handler when the handler is invoked. + /// public void InstallReportHandler (ReportHandler reportHandler, object parameter) { this.reportHandler = new ReportHandler(reportHandler); - Console.WriteLine("Installed report handler " + this.reportHandler); - this.reportHandlerParameter = parameter; if (reportHandlerInstalled == false) { - IedConnection_installReportHandler (this.connection, objectReference, this.GetRptId (), new InternalReportHandler(internalReportHandler), IntPtr.Zero); + + string reportId = this.GetRptId (); + +// if ((GetRptId() == null) || (GetRptId().Length == 0)) +// reportId = + + IedConnection_installReportHandler (this.connection, objectReference, reportId, new InternalReportHandler(internalReportHandler), IntPtr.Zero); reportHandlerInstalled = true; } } + /// + /// Read all RCB values from the server + /// + /// This exception is thrown if there is a connection or service error public void GetRCBValues () { int error; @@ -231,84 +261,142 @@ namespace IEC61850 throw new IedConnectionException ("getRCBValues service failed", error); } + /// + /// Write changed RCB values to the server. + /// + /// + /// This function will only write the RCB values that were set by one of the setter methods. + /// The RCB values are sent by a single MMS write request. + /// + /// This exception is thrown if there is a connection or service error public void SetRCBValues () { SetRCBValues (true); } + /// + /// Write changed RCB values to the server. + /// + /// + /// This function will only write the RCB values that were set by one of the setter methods. + /// + /// This exception is thrown if there is a connection or service error + /// + /// If true the values are sent by single MMS write request. Otherwise the values are all sent by their own MMS write requests. + /// public void SetRCBValues (bool singleRequest) - { - UInt32 parametersMask = 0; + { + UInt32 parametersMask = 0; - if (flagRptId) - parametersMask += 1; + if (flagRptId) + parametersMask += 1; - if (flagRptEna) - parametersMask += 2; + if (flagRptEna) + parametersMask += 2; - if (flagResv) - parametersMask += 4; + if (flagResv) + parametersMask += 4; - if (flagDataSetReference) - parametersMask += 8; + if (flagDataSetReference) + parametersMask += 8; - if (flagConfRev) - parametersMask += 16; + if (flagConfRev) + parametersMask += 16; - if (flagOptFlds) - parametersMask += 32; + if (flagOptFlds) + parametersMask += 32; - if (flagBufTm) - parametersMask += 64; + if (flagBufTm) + parametersMask += 64; - if (flagSqNum) - parametersMask += 128; + if (flagSqNum) + parametersMask += 128; - if (flagTrgOps) - parametersMask += 256; + if (flagTrgOps) + parametersMask += 256; - if (flagIntgPd) - parametersMask += 512; + if (flagIntgPd) + parametersMask += 512; - if (flagGI) - parametersMask += 1024; + if (flagGI) + parametersMask += 1024; - if (flagPurgeBuf) - parametersMask += 2048; + if (flagPurgeBuf) + parametersMask += 2048; - if (flagEntryId) - parametersMask += 4096; + if (flagEntryId) + parametersMask += 4096; - if (flagResvTms) - parametersMask += 16384; + if (flagResvTms) + parametersMask += 16384; - int error; + int error; - IedConnection_setRCBValues (connection, out error, self, parametersMask, singleRequest); + IedConnection_setRCBValues (connection, out error, self, parametersMask, singleRequest); - if (error != 0) - throw new IedConnectionException ("setRCBValues service failed", error); + if (error != 0) + throw new IedConnectionException ("setRCBValues service failed", error); + + if (flagRptId) { + + if (reportHandlerInstalled) { + reportHandlerInstalled = false; + InstallReportHandler(this.reportHandler, this.reportHandlerParameter); + } + } + + resetSendFlags(); } + /// + /// Determines whether this instance is a buffered or unbuffered RCB. + /// + /// + /// true if this instance is a buffered RCB; otherwise, false. + /// public bool IsBuffered () { return ClientReportControlBlock_isBuffered (self); } - public UInt64 getEntryTime () + /// + /// Gets the entry time of the RCB as ms time + /// + /// + /// The entry time is the timestamp of the last report sent. + /// + /// + /// The entry time as ms timestamp + /// + public UInt64 GetEntryTime () { return ClientReportControlBlock_getEntryTime (self); } + /// + /// Gets the entry time of the RCB as DateTimeOffset + /// + /// + /// The entry time is the timestamp of the last report sent. + /// + /// + /// The entry time as DataTimeOffset + /// public DateTimeOffset GetEntryTimeAsDateTimeOffset () { - UInt64 entryTime = getEntryTime (); + UInt64 entryTime = GetEntryTime (); DateTimeOffset retVal = new DateTimeOffset (1970, 1, 1, 0, 0, 0, TimeSpan.Zero); return retVal.AddMilliseconds (entryTime); } + /// + /// Gets the data set reference of the associated data set + /// + /// + /// The data set reference. + /// public string GetDataSetReference () { IntPtr dataSetRefPtr = ClientReportControlBlock_getDataSetReference (self); @@ -316,6 +404,12 @@ namespace IEC61850 return Marshal.PtrToStringAnsi (dataSetRefPtr); } + /// + /// Sets the data set reference. Use this method to select the associated data set for the RCB + /// + /// + /// The data set reference. + /// public void SetDataSetReference (string dataSetReference) { ClientReportControlBlock_setDataSetReference (self, dataSetReference); @@ -323,6 +417,12 @@ namespace IEC61850 flagDataSetReference = true; } + /// + /// Gets the report identifier. + /// + /// + /// The report identifier. + /// public string GetRptId () { IntPtr rptIdPtr = ClientReportControlBlock_getRptId (self); @@ -330,22 +430,58 @@ namespace IEC61850 return Marshal.PtrToStringAnsi (rptIdPtr); } + /// + /// Sets the RptId (report ID) of the RCB + /// + /// + /// The new RptId + /// + public void SetRptId (string rptId) + { + ClientReportControlBlock_setRptId(self, rptId); + flagRptId = true; + } + + /// + /// Check if reporting is currently enabled + /// + /// + /// true, if reporting is enabled, false otherwise + /// public bool GetRptEna () { return ClientReportControlBlock_getRptEna (self); } + /// + /// Sets report enable flag. Use this to enable reporting + /// + /// + /// true to enable reporting, false to disable + /// public void SetRptEna (bool rptEna) { ClientReportControlBlock_setRptEna (self, rptEna); flagRptEna = true; } + /// + /// Gets the buffer time. + /// + /// + /// The buffer time in ms. + /// public UInt32 GetBufTm() { return ClientReportControlBlock_getBufTm (self); } + /// + /// Sets the buffer time. + /// + /// + /// Buffer time is ms. + /// public void SetBufTm (UInt32 bufTm) { ClientReportControlBlock_setBufTm (self, bufTm); @@ -353,33 +489,80 @@ namespace IEC61850 flagBufTm = true; } + /// + /// Gets the GI flag + /// + /// + /// true, if GI flag is set + /// public bool GetGI () { return ClientReportControlBlock_getGI (self); } + /// + /// Sets the GI flag. Use this to trigger a GI (general interrogation) command. + /// + /// + /// request general interrogation of true + /// public void SetGI (bool GI) { ClientReportControlBlock_setGI (self, GI); flagGI = true; } + /// + /// Check if RCB is reserved by a client + /// + /// + /// true, the RCB is reserver by a client + /// public bool GetResv () { return ClientReportControlBlock_getResv (self); } + /// + /// Gets the configuration revision of the RCB + /// + /// + /// The conf rev. + /// + public UInt32 GetConfRev () + { + return ClientReportControlBlock_getConfRev (self); + } + + /// + /// Sets RESV flag. Use this to reserve (allocate) this RCB. + /// + /// + /// true: reserver this RCB for exclusive use + /// public void SetResv (bool resv) { ClientReportControlBlock_setResv (self, resv); flagResv = true; } + /// + /// Gets the trigger options of the RCB + /// + /// + /// trigger options + /// public TriggerOptions GetTrgOps() { return (TriggerOptions) ClientReportControlBlock_getTrgOps (self); } + /// + /// Sets the trigger options of the RCB. + /// + /// + /// trigger options + /// public void SetTrgOps(TriggerOptions trgOps) { ClientReportControlBlock_setTrgOps (self, (int) trgOps); @@ -387,22 +570,46 @@ namespace IEC61850 flagTrgOps = true; } + /// + /// Gets the integrity period + /// + /// + /// integrity period in ms + /// public UInt32 GetIntgPd () { return ClientReportControlBlock_getIntgPd (self); } + /// + /// Sets the integrity period + /// + /// + /// integrity period in ms + /// public void SetIntgPd (UInt32 intgPd) { ClientReportControlBlock_setIntgPd (self, intgPd); flagIntgPd = true; } + /// + /// Gets the option fields. + /// + /// + /// The option fields + /// public ReportOptions GetOptFlds() { return (ReportOptions) ClientReportControlBlock_getOptFlds (self); } + /// + /// Sets the option field. Used to enable or disable optional report elements + /// + /// + /// Option field. + /// public void SetOptFlds(ReportOptions optFlds) { ClientReportControlBlock_setOptFlds (self, (int)optFlds); diff --git a/dotnet/IEC61850forCSharp/Reporting.cs b/dotnet/IEC61850forCSharp/Reporting.cs index 3cdd9d1e..4bade8e5 100644 --- a/dotnet/IEC61850forCSharp/Reporting.cs +++ b/dotnet/IEC61850forCSharp/Reporting.cs @@ -23,13 +23,15 @@ using System; using System.Runtime.InteropServices; +using IEC61850.Common; + namespace IEC61850 { namespace Client { public partial class IedConnection { - public ReportControlBlock getReportControlBlock (string rcbObjectReference) + public ReportControlBlock GetReportControlBlock (string rcbObjectReference) { return new ReportControlBlock (rcbObjectReference, connection); } @@ -59,6 +61,9 @@ namespace IEC61850 REASON_UNKNOWN = 6 } + /// + /// A class to hold the contents of a received report + /// public class Report { @@ -74,6 +79,37 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern int ClientReport_getReasonForInclusion(IntPtr self, int elementIndex); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool ClientReport_hasSeqNum(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt16 ClientReport_getSeqNum(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool ClientReport_hasDataSetName(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool ClientReport_hasReasonForInclusion(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool ClientReport_hasConfRev(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern UInt32 ClientReport_getConfRev(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool ClientReport_hasBufOvfl(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern bool ClientReport_hasDataReference(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientReport_getRcbReference(IntPtr self); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ClientReport_getRptId(IntPtr self); + + private IntPtr self; private IntPtr dataSetValues = IntPtr.Zero; @@ -85,11 +121,23 @@ namespace IEC61850 this.self = self; } + /// + /// Determines whether the report has a timestamp. + /// + /// + /// true if this report has a timestamp; otherwise, false. + /// public bool HasTimestamp () { return ClientReport_hasTimestamp (self); } + /// + /// Gets the timestamp. + /// + /// + /// The timestamp as milliseconds since 1.1.1970 UTC 00:00 or 0 if no timestamp is present. + /// public UInt64 GetTimestamp () { if (HasTimestamp ()) @@ -98,6 +146,52 @@ namespace IEC61850 return 0; } + public bool HasDataSetName () + { + return ClientReport_hasDataSetName(self); + } + + public bool HasDataReference () + { + return ClientReport_hasDataReference(self); + } + + public bool HasConfRev () + { + return ClientReport_hasConfRev(self); + } + + public UInt32 GetConfRev () + { + return ClientReport_getConfRev(self); + } + + public bool HasBufOvfl () + { + return ClientReport_hasBufOvfl(self); + } + + public bool HasSeqNum () + { + return ClientReport_hasSeqNum(self); + } + + public UInt16 GetSeqNum () + { + return ClientReport_getSeqNum(self); + } + + public bool HasReasonForInclusion () + { + return ClientReport_hasReasonForInclusion(self); + } + + /// + /// Gets the data set values as MMS_ARRAY instance. + /// + /// + /// The data set values. + /// public MmsValue GetDataSetValues () { if (dataSetValues == IntPtr.Zero) { @@ -112,6 +206,15 @@ namespace IEC61850 return values; } + /// + /// Gets the reason for inclusion of data set member with the given index + /// + /// + /// The reason for inclusion. + /// + /// + /// index of the data set member in the data set + /// public ReasonForInclusion GetReasonForInclusion (int index) { if (values == null) { @@ -129,6 +232,23 @@ namespace IEC61850 return (ReasonForInclusion) ClientReport_getReasonForInclusion(self, index); } + public string GetRcbReference () + { + IntPtr rcbRef = ClientReport_getRcbReference(self); + + return Marshal.PtrToStringAnsi (rcbRef); + } + + public string GetRptId () + { + IntPtr rptId = ClientReport_getRptId(self); + + if (rptId == IntPtr.Zero) + return GetRcbReference(); + else + return Marshal.PtrToStringAnsi (rptId); + } + } } diff --git a/dotnet/control/ControlExample.cs b/dotnet/control/ControlExample.cs index 8da9f4cd..1ca2b515 100644 --- a/dotnet/control/ControlExample.cs +++ b/dotnet/control/ControlExample.cs @@ -2,12 +2,19 @@ using System; using System.Collections.Generic; using IEC61850.Common; using IEC61850.Client; +using System.Threading; namespace control { class ControlExample { + private static void commandTerminationHandler (Object parameter, ControlObject control) + { + LastApplError lastApplError = control.GetLastApplError(); + Console.WriteLine("HANDLER CALLED! " + lastApplError.addCause); + } + public static void Main (string[] args) { IedConnection con = new IedConnection (); @@ -25,7 +32,8 @@ namespace control { con.Connect(hostname, 102); - string objectReference = "IEDM1CPUBHKW/DRCC1.DERStr"; + /* direct control with normal security */ + string objectReference = "simpleIOGenericIO/GGIO1.SPCSO1"; ControlObject control = con.CreateControlObject(objectReference); @@ -33,9 +41,27 @@ namespace control Console.WriteLine(objectReference + " has control model " + controlModel.ToString()); + if (controlModel != ControlModel.CONTROL_MODEL_STATUS_ONLY) + control.Operate(true); + if (!control.Operate(true)) Console.WriteLine("operate failed!"); + /* direct control with enhanced security */ + objectReference = "simpleIOGenericIO/GGIO1.SPCSO3"; + control = con.CreateControlObject(objectReference); + + controlModel = control.GetControlModel(); + Console.WriteLine(objectReference + " has control model " + controlModel.ToString()); + + if (controlModel == ControlModel.CONTROL_MODEL_DIRECT_ENHANCED) { + control.SetCommandTerminationHandler(commandTerminationHandler, null); + + control.Operate(true); + + Thread.Sleep(1000); + } + con.Abort(); diff --git a/dotnet/datasets/DataSetExample.cs b/dotnet/datasets/DataSetExample.cs index f6a11493..bd73cd54 100644 --- a/dotnet/datasets/DataSetExample.cs +++ b/dotnet/datasets/DataSetExample.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using IEC61850.Client; +using IEC61850.Common; namespace datasets { @@ -16,7 +17,7 @@ namespace datasets if (args.Length > 0) hostname = args[0]; else - hostname = "10.0.2.2"; + hostname = "localhost"; Console.WriteLine("Connect to " + hostname); @@ -31,7 +32,6 @@ namespace datasets Console.WriteLine("LD: " + entry); } - // create a new data set List dataSetElements = new List(); @@ -49,7 +49,6 @@ namespace datasets Console.WriteLine("DS element: " + entry); } - // read the values of the newly created data set DataSet dataSet = con.ReadDataSetValues("IEDM1CPUBHKW/LLN0.ds1", null); diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln index 8265db95..e781ee8a 100644 --- a/dotnet/dotnet.sln +++ b/dotnet/dotnet.sln @@ -19,6 +19,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "authenticate", "authenticat EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\tests.csproj", "{FBDFE530-DBEB-474B-BA54-9AB287DD57B3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "files", "files\files.csproj", "{77127456-19B9-4D1A-AEF9-40F8D1C5695E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example3", "example3\example3.csproj", "{5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,6 +41,14 @@ Global {59B85486-F48D-4978-BD35-8F5C3A8288D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {59B85486-F48D-4978-BD35-8F5C3A8288D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {59B85486-F48D-4978-BD35-8F5C3A8288D4}.Release|Any CPU.Build.0 = Release|Any CPU + {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}.Release|Any CPU.Build.0 = Release|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77127456-19B9-4D1A-AEF9-40F8D1C5695E}.Release|Any CPU.Build.0 = Release|Any CPU {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/dotnet/example3/AssemblyInfo.cs b/dotnet/example3/AssemblyInfo.cs new file mode 100644 index 00000000..8fa9151c --- /dev/null +++ b/dotnet/example3/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("example3")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("mzillgit")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/dotnet/example3/Main.cs b/dotnet/example3/Main.cs new file mode 100644 index 00000000..5f04ce98 --- /dev/null +++ b/dotnet/example3/Main.cs @@ -0,0 +1,48 @@ +using System; +using IEC61850.Client; +using System.Collections.Generic; + +namespace example3 +{ + class MainClass + { + public static void Main (string[] args) + { + IedConnection con = new IedConnection (); + + string hostname; + + if (args.Length > 0) + hostname = args[0]; + else + hostname = "localhost"; + + Console.WriteLine("Connect to " + hostname); + + + try + { + IsoConnectionParameters parameters = con.GetConnectionParameters(); + + parameters.SetRemoteAddresses(1,1, new byte[] {0x00, 0x01, 0x02, 0x03}); + + con.ConnectTimeout = 10000; + + con.Connect(hostname, 102); + + List serverDirectory = con.GetServerDirectory(false); + + foreach (string entry in serverDirectory) + { + Console.WriteLine("LD: " + entry); + } + + con.Release(); + } + catch (IedConnectionException e) + { + Console.WriteLine(e.Message); + } + } + } +} diff --git a/dotnet/example3/example3.csproj b/dotnet/example3/example3.csproj new file mode 100644 index 00000000..7a1f9d03 --- /dev/null +++ b/dotnet/example3/example3.csproj @@ -0,0 +1,45 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2} + Exe + example3 + example3 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + none + true + bin\Release + prompt + 4 + true + + + + + + + + + + + + {C35D624E-5506-4560-8074-1728F1FA1A4D} + IEC61850forCSharp + + + \ No newline at end of file diff --git a/dotnet/files/AssemblyInfo.cs b/dotnet/files/AssemblyInfo.cs new file mode 100644 index 00000000..1133bf62 --- /dev/null +++ b/dotnet/files/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("files")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("mzillgit")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/dotnet/files/FileServicesExample.cs b/dotnet/files/FileServicesExample.cs new file mode 100644 index 00000000..ccc94a0a --- /dev/null +++ b/dotnet/files/FileServicesExample.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; + +using IEC61850.Client; +using IEC61850.Common; +using System.IO; + +namespace files +{ + class MainClass + { + public static void printFiles (IedConnection con, string prefix, string parent) + { + List files = con.GetFileDirectory (parent); + + foreach (FileDirectoryEntry file in files) { + Console.WriteLine(prefix + file.GetFileName() + "\t" + file.GetFileSize() + "\t" + + MmsValue.MsTimeToDateTimeOffset(file.GetLastModified())); + + if (file.GetFileName().EndsWith("/")) { + printFiles (con, prefix + " ", parent + file.GetFileName()); + } + } + + } + + static bool getFileHandler (object parameter, byte[] data) + { + Console.WriteLine("received " + data.Length + " bytes"); + + BinaryWriter binWriter = (BinaryWriter) parameter; + + binWriter.Write(data); + + return true; + } + + + public static void Main (string[] args) + { + IedConnection con = new IedConnection (); + + string hostname; + + if (args.Length > 0) + hostname = args[0]; + else + hostname = "10.0.2.2"; + + Console.WriteLine("Connect to " + hostname); + + try + { + con.Connect(hostname, 102); + + Console.WriteLine ("Files in server root directory:"); + List serverDirectory = con.GetServerDirectory(true); + + foreach (string entry in serverDirectory) { + Console.WriteLine(entry); + } + + Console.WriteLine(); + + Console.WriteLine ("File directory tree at server:"); + printFiles(con, "", ""); + Console.WriteLine(); + + string filename = "IEDSERVER.BIN"; + + Console.WriteLine("Download file " + filename); + + /* Download file from server and write it to a new local file */ + FileStream fs = new FileStream(filename, FileMode.Create); + BinaryWriter w = new BinaryWriter(fs); + + con.GetFile(filename, new IedConnection.GetFileHandler(getFileHandler), w); + + fs.Close(); + + con.Abort(); + } + catch (IedConnectionException e) + { + Console.WriteLine(e.Message); + } + } + } +} diff --git a/dotnet/files/files.csproj b/dotnet/files/files.csproj new file mode 100644 index 00000000..348f8045 --- /dev/null +++ b/dotnet/files/files.csproj @@ -0,0 +1,45 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {77127456-19B9-4D1A-AEF9-40F8D1C5695E} + Exe + files + files + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + none + true + bin\Release + prompt + 4 + true + + + + + + + + + + + + {C35D624E-5506-4560-8074-1728F1FA1A4D} + IEC61850forCSharp + + + \ No newline at end of file diff --git a/dotnet/model_browsing/ModelBrowsing.cs b/dotnet/model_browsing/ModelBrowsing.cs index 71bc1b39..ce4f7fec 100644 --- a/dotnet/model_browsing/ModelBrowsing.cs +++ b/dotnet/model_browsing/ModelBrowsing.cs @@ -51,6 +51,27 @@ namespace model_browsing foreach (string dataObject in dataObjects) { Console.WriteLine(" DO: " + dataObject); + + List dataDirectory = con.GetDataDirectoryFC(logicalNodeReference + "." + dataObject); + + foreach (string dataDirectoryElement in dataDirectory) { + + string daReference = logicalNodeReference + "." + dataObject + "." + ObjectReference.getElementName(dataDirectoryElement); + + // get the type specification of a variable + MmsVariableSpecification specification = con.GetVariableSpecification(daReference, ObjectReference.getFC(dataDirectoryElement)); + + Console.WriteLine (" DA/SDO: [" + ObjectReference.getFC(dataDirectoryElement) + "] " + + ObjectReference.getElementName(dataDirectoryElement) + " : " + specification.GetType() + + "(" + specification.Size() + ")"); + + if (specification.GetType() == MmsType.MMS_STRUCTURE) { + foreach (MmsVariableSpecification elementSpec in specification) { + Console.WriteLine(" " + elementSpec.GetName() + " : " + elementSpec.GetType()); + } + } + } + } // discover data sets diff --git a/dotnet/reporting/ReportingExample.cs b/dotnet/reporting/ReportingExample.cs index 1f93ad49..dceb88bd 100644 --- a/dotnet/reporting/ReportingExample.cs +++ b/dotnet/reporting/ReportingExample.cs @@ -27,6 +27,10 @@ namespace reporting } } + ReportControlBlock rcb = (ReportControlBlock) parameter; + + Console.WriteLine("Buffered: " + rcb.IsBuffered()); + } @@ -48,13 +52,14 @@ namespace reporting try { con.Connect (hostname, 102); - string rcbReference = "simpleIOGenericIO/LLN0.RP.EventsRCB"; + string rcbReference = "simpleIOGenericIO/LLN0.RP.EventsRCB01"; - ReportControlBlock rcb = con.getReportControlBlock(rcbReference); + ReportControlBlock rcb = con.GetReportControlBlock(rcbReference); rcb.GetRCBValues(); - rcb.InstallReportHandler(reportHandler, null); + // note: the second parameter is not required! + rcb.InstallReportHandler(reportHandler, rcb); if (rcb.IsBuffered()) Console.WriteLine ("RCB: " + rcbReference + " is buffered"); diff --git a/dotnet/tests/Test.cs b/dotnet/tests/Test.cs index 35a78b9f..c5b03c02 100644 --- a/dotnet/tests/Test.cs +++ b/dotnet/tests/Test.cs @@ -15,6 +15,65 @@ namespace tests Assert.AreEqual (10.0f, val.ToFloat ()); } + + [Test ()] + public void MmsValueBitString () + { + var val = MmsValue.NewBitString(10); + + Assert.AreEqual (MmsType.MMS_BIT_STRING, val.GetType()); + Assert.AreEqual (10, val.Size()); + + val.BitStringFromUInt32(7); + + Assert.AreEqual(7, val.BitStringToUInt32()); + + Assert.AreEqual(true, val.GetBit(0)); + Assert.AreEqual(true, val.GetBit(1)); + Assert.AreEqual(true, val.GetBit(2)); + Assert.AreEqual(false, val.GetBit(3)); + + Assert.AreEqual(false, val.GetBit(9)); + + Assert.AreEqual(false, val.GetBit(10)); + + val.SetBit(3, true); + Assert.AreEqual(true, val.GetBit(3)); + + Assert.AreEqual(15, val.BitStringToUInt32()); + + val.SetBit(3, false); + Assert.AreEqual(7, val.BitStringToUInt32()); + } + + [Test()] + public void MmsValueOctetString () + { + var val = MmsValue.NewOctetString(20); + + Assert.AreEqual (0, val.Size()); + Assert.AreEqual (20, val.MaxSize()); + + byte[] octetString = val.getOctetString(); + + Assert.AreEqual (0, octetString.Length); + + octetString = new byte[5]; + octetString[0] = 0x11; + octetString[1] = 0x12; + octetString[2] = 0x13; + octetString[3] = 0x14; + octetString[4] = 0x15; + + val.setOctetString(octetString); + + Assert.AreEqual(5, val.Size()); + + byte[] secondOctetString = val.getOctetString(); + + Assert.AreEqual(octetString, secondOctetString); + } + } } diff --git a/dotnet/tests/tests.csproj b/dotnet/tests/tests.csproj index fffb7f87..76e97b72 100644 --- a/dotnet/tests/tests.csproj +++ b/dotnet/tests/tests.csproj @@ -21,7 +21,7 @@ false - full + none true bin\Release prompt @@ -30,7 +30,9 @@ - + + nunit + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3c52875b..5bc51cf5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,7 +8,9 @@ add_subdirectory(server_example_control) add_subdirectory(server_example_dynamic) add_subdirectory(server_example_config_file) 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(iec61850_client_example1) add_subdirectory(iec61850_client_example2) add_subdirectory(iec61850_client_example3) diff --git a/examples/Makefile b/examples/Makefile index e2168aef..1b83d6ac 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -21,6 +21,8 @@ EXAMPLE_DIRS += server_example_config_file EXAMPLE_DIRS += server_example_dynamic 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 += goose_subscriber EXAMPLE_DIRS += goose_publisher EXAMPLE_DIRS += mms_utility diff --git a/examples/goose_publisher/goose_publisher_example.c b/examples/goose_publisher/goose_publisher_example.c index 494fb497..47b0390a 100644 --- a/examples/goose_publisher/goose_publisher_example.c +++ b/examples/goose_publisher/goose_publisher_example.c @@ -23,11 +23,23 @@ main(int argc, char** argv) LinkedList_add(dataSetValues, MmsValue_newBinaryTime(false)); LinkedList_add(dataSetValues, MmsValue_newIntegerFromInt32(5678)); - GoosePublisher publisher = GoosePublisher_create(NULL, "eth0"); + CommParameters gooseCommParameters; - GoosePublisher_setGoCbRef(publisher, "Test1/LLN0$GO$gocb1"); + gooseCommParameters.appId = 1000; + gooseCommParameters.dstAddress[0] = 0x01; + gooseCommParameters.dstAddress[1] = 0x0c; + gooseCommParameters.dstAddress[2] = 0xcd; + gooseCommParameters.dstAddress[3] = 0x01; + gooseCommParameters.dstAddress[4] = 0x00; + gooseCommParameters.dstAddress[5] = 0x01; + gooseCommParameters.vlanId = 0; + gooseCommParameters.vlanPriority = 4; + + GoosePublisher publisher = GoosePublisher_create(&gooseCommParameters, "eth0"); + + GoosePublisher_setGoCbRef(publisher, "simpleIOGenericIO/LLN0$GO$gcbAnalogValues"); GoosePublisher_setConfRev(publisher, 1); - GoosePublisher_setDataSetRef(publisher, "Test1/LLN0$dataset1"); + GoosePublisher_setDataSetRef(publisher, "simpleIOGenericIO/LLN0$AnalogValues"); int i = 0; diff --git a/examples/goose_subscriber/goose_subscriber_example.c b/examples/goose_subscriber/goose_subscriber_example.c index 09fe5688..00fd888c 100644 --- a/examples/goose_subscriber/goose_subscriber_example.c +++ b/examples/goose_subscriber/goose_subscriber_example.c @@ -6,8 +6,8 @@ * Has to be started as root in Linux. */ -#include "goose_subscriber.h" -#include "thread.h" +#include "goose_receiver.h" +#include "hal_thread.h" #include #include @@ -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: %"PRIu64"\n", GooseSubscriber_getTimestamp(subscriber)); + printf(" timestamp: %llu\n", GooseSubscriber_getTimestamp(subscriber)); MmsValue* values = GooseSubscriber_getDataSetValues(subscriber); @@ -50,20 +50,26 @@ main(int argc, char** argv) MmsValue_setElement(dataSetValues, i, dataSetEntry); } - // GooseSubscriber subscriber = GooseSubscriber_create("simpleIOGenericIO/LLN0$GO$gcbEvents", dataSetValues); + GooseReceiver receiver = GooseReceiver_create(); - GooseSubscriber subscriber = GooseSubscriber_create("simpleIOGenericIO/LLN0$GO$gcbAnalogValues", NULL); + if (argc > 1) { + printf("Set interface id: %s\n", argv[1]); + GooseReceiver_setInterfaceId(receiver, argv[1]); + } + else { + printf("Using interface eth0\n"); + GooseReceiver_setInterfaceId(receiver, "eth0"); + } - if (argc > 1) { - printf("Set interface id: %s\n", argv[1]); - GooseSubscriber_setInterfaceId(subscriber, argv[1]); - } + GooseSubscriber subscriber = GooseSubscriber_create("simpleIOGenericIO/LLN0$GO$gcbAnalogValues", NULL); GooseSubscriber_setAppId(subscriber, 1000); GooseSubscriber_setListener(subscriber, gooseListener, NULL); - GooseSubscriber_subscribe(subscriber); + GooseReceiver_addSubscriber(receiver, subscriber); + + GooseReceiver_start(receiver); signal(SIGINT, sigint_handler); diff --git a/examples/iec61850_client_example1/client_example1.c b/examples/iec61850_client_example1/client_example1.c index fcd56d7f..b8429a09 100644 --- a/examples/iec61850_client_example1/client_example1.c +++ b/examples/iec61850_client_example1/client_example1.c @@ -9,7 +9,7 @@ #include #include -#include "thread.h" +#include "hal_thread.h" void reportCallbackFunction(void* parameter, ClientReport report) diff --git a/examples/iec61850_client_example2/client_example2.c b/examples/iec61850_client_example2/client_example2.c index c6d6cf26..d199d0ab 100644 --- a/examples/iec61850_client_example2/client_example2.c +++ b/examples/iec61850_client_example2/client_example2.c @@ -25,6 +25,8 @@ printDataDirectory(char* doRef, IedConnection con, int spaces) LinkedList dataAttributes = IedConnection_getDataDirectory(con, &error, doRef); + //LinkedList dataAttributes = IedConnection_getDataDirectoryByFC(con, &error, doRef, MX); + if (dataAttributes != NULL) { LinkedList dataAttribute = LinkedList_getNext(dataAttributes); @@ -70,7 +72,10 @@ main(int argc, char** argv) printf("Get logical device list...\n"); LinkedList deviceList = IedConnection_getLogicalDeviceList(con, &error); - printf("error: %i\n", error); + if (error != IED_ERROR_OK) { + printf("Failed to read device list (error code: %i)\n", error); + goto cleanup_and_exit; + } LinkedList device = LinkedList_getNext(deviceList); @@ -191,6 +196,7 @@ main(int argc, char** argv) printf("Connection failed!\n"); } +cleanup_and_exit: IedConnection_destroy(con); } diff --git a/examples/iec61850_client_example3/client_example3.c b/examples/iec61850_client_example3/client_example3.c index 93ab88df..6e8e0533 100644 --- a/examples/iec61850_client_example3/client_example3.c +++ b/examples/iec61850_client_example3/client_example3.c @@ -5,10 +5,27 @@ */ #include "iec61850_client.h" +#include "hal_thread.h" #include #include +static void commandTerminationHandler(void *parameter, ControlObjectClient connection) +{ + + + LastApplError lastApplError = ControlObjectClient_getLastApplError(connection); + + // if lastApplError.error != 0 this indicates a CommandTermination- + if (lastApplError.error != 0) { + printf("Received CommandTermination-.\n"); + printf(" LastApplError: %i\n", lastApplError.error); + printf(" addCause: %i\n", lastApplError.addCause); + } + else + printf("Received CommandTermination+.\n"); +} + int main(int argc, char** argv) { char* hostname; @@ -57,6 +74,7 @@ int main(int argc, char** argv) { if (error == IED_ERROR_OK) { bool state = MmsValue_getBoolean(stVal); + MmsValue_delete(stVal); printf("New status of simpleIOGenericIO/GGIO1.SPCSO1.stVal: %i\n", state); } @@ -91,6 +109,101 @@ int main(int argc, char** argv) { ControlObjectClient_destroy(control); + /**************************************** + * Direct control with enhanced security + ****************************************/ + + control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO3", con); + + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); + + ctlVal = MmsValue_newBoolean(true); + + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO3 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO3\n"); + } + + MmsValue_delete(ctlVal); + + /* Wait for command termination message */ + Thread_sleep(1000); + + ControlObjectClient_destroy(control); + + /* Check if status value has changed */ + + stVal = IedConnection_readObject(con, &error, "simpleIOGenericIO/GGIO1.SPCSO3.stVal", ST); + + if (error == IED_ERROR_OK) { + bool state = MmsValue_getBoolean(stVal); + + printf("New status of simpleIOGenericIO/GGIO1.SPCSO3.stVal: %i\n", state); + + MmsValue_delete(stVal); + } + else { + printf("Reading status for simpleIOGenericIO/GGIO1.SPCSO3 failed!\n"); + } + + /*********************************************** + * Select before operate with enhanced security + ***********************************************/ + + control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO4", con); + + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); + + ctlVal = MmsValue_newBoolean(true); + + if (ControlObjectClient_selectWithValue(control, ctlVal)) { + + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO4 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO4!\n"); + } + + } + else { + printf("failed to select simpleIOGenericIO/GGIO1.SPCSO4!\n"); + } + + MmsValue_delete(ctlVal); + + /* Wait for command termination message */ + Thread_sleep(1000); + + ControlObjectClient_destroy(control); + + + /********************************************************************* + * Direct control with enhanced security (expect CommandTermination-) + *********************************************************************/ + + control = ControlObjectClient_create("simpleIOGenericIO/GGIO1.SPCSO9", con); + + ControlObjectClient_setCommandTerminationHandler(control, commandTerminationHandler, NULL); + + ctlVal = MmsValue_newBoolean(true); + + if (ControlObjectClient_operate(control, ctlVal, 0 /* operate now */)) { + printf("simpleIOGenericIO/GGIO1.SPCSO9 operated successfully\n"); + } + else { + printf("failed to operate simpleIOGenericIO/GGIO1.SPCSO9\n"); + } + + MmsValue_delete(ctlVal); + + /* Wait for command termination message */ + Thread_sleep(1000); + + ControlObjectClient_destroy(control); + IedConnection_close(con); } diff --git a/examples/iec61850_client_example4/client_example4.c b/examples/iec61850_client_example4/client_example4.c index 12cec16d..1b1d4619 100644 --- a/examples/iec61850_client_example4/client_example4.c +++ b/examples/iec61850_client_example4/client_example4.c @@ -11,7 +11,7 @@ #include #include -#include "thread.h" +#include "hal_thread.h" static void printDataSetValues(MmsValue* dataSet) diff --git a/examples/iec61850_client_example5/client_example5.c b/examples/iec61850_client_example5/client_example5.c index 4f43805a..f49c0aba 100644 --- a/examples/iec61850_client_example5/client_example5.c +++ b/examples/iec61850_client_example5/client_example5.c @@ -11,7 +11,7 @@ #include #include -#include "thread.h" +#include "hal_thread.h" int main(int argc, char** argv) { @@ -47,9 +47,12 @@ int main(int argc, char** argv) { // IsoConnectionParameters_setRemoteApTitle(parameters, NULL, 0); // IsoConnectionParameters_setLocalApTitle(parameters, NULL, 0); + TSelector localTSelector = { 3, { 0x00, 0x01, 0x02 } }; + TSelector remoteTSelector = { 2, { 0x00, 0x01 } }; + /* change parameters for presentation, session and transport layers */ - IsoConnectionParameters_setRemoteAddresses(parameters, 0x12345678, 12 , 13); - IsoConnectionParameters_setLocalAddresses(parameters, 0x87654321, 1234 , 2345); + IsoConnectionParameters_setRemoteAddresses(parameters, 0x12345678, 12, localTSelector); + IsoConnectionParameters_setLocalAddresses(parameters, 0x87654321, 1234 , remoteTSelector); char* password = "top secret"; @@ -61,6 +64,8 @@ int main(int argc, char** argv) { IsoConnectionParameters_setAcseAuthenticationParameter(parameters, auth); + IedConnection_setConnectTimeout(con, 10000); + /* call connect when all parameters are set */ IedConnection_connect(con, &error, hostname, tcpPort); diff --git a/examples/iec61850_client_example_files/client_example_files.c b/examples/iec61850_client_example_files/client_example_files.c index 36efa3fb..0b3f5f38 100644 --- a/examples/iec61850_client_example_files/client_example_files.c +++ b/examples/iec61850_client_example_files/client_example_files.c @@ -15,7 +15,7 @@ #include #include -#include "thread.h" +#include "hal_thread.h" #define MAX_BUFFER_SIZE 2000000 diff --git a/examples/iec61850_client_example_reporting/client_example_reporting.c b/examples/iec61850_client_example_reporting/client_example_reporting.c index e234b3c2..1c6f7a86 100644 --- a/examples/iec61850_client_example_reporting/client_example_reporting.c +++ b/examples/iec61850_client_example_reporting/client_example_reporting.c @@ -11,7 +11,7 @@ #include #include -#include "thread.h" +#include "hal_thread.h" static int running = 0; @@ -105,7 +105,7 @@ int main(int argc, char** argv) { /* Read RCB values */ ClientReportControlBlock rcb = - IedConnection_getRCBValues(con, &error, "simpleIOGenericIO/LLN0.RP.EventsRCB", NULL); + IedConnection_getRCBValues(con, &error, "simpleIOGenericIO/LLN0.RP.EventsRCB01", NULL); if (error != IED_ERROR_OK) { printf("getRCBValues service error!\n"); @@ -131,7 +131,7 @@ int main(int argc, char** argv) { Thread_sleep(1000); - IedConnection_triggerGIReport(con, &error, "simpleIOGenericIO/LLN0.RP.EventsRCB"); + IedConnection_triggerGIReport(con, &error, "simpleIOGenericIO/LLN0.RP.EventsRCB01"); if (error != IED_ERROR_OK) { printf("Error triggering a GI report (code: %i)\n", error); diff --git a/examples/mms_client_example1/mms_client_example1.c b/examples/mms_client_example1/mms_client_example1.c index ece6535b..d8ad47c6 100644 --- a/examples/mms_client_example1/mms_client_example1.c +++ b/examples/mms_client_example1/mms_client_example1.c @@ -27,7 +27,7 @@ #include #include #include "mms_client_connection.h" -#include "thread.h" +#include "hal_thread.h" int main(int argc, char** argv) { diff --git a/examples/server_example1/server_example1.c b/examples/server_example1/server_example1.c index 7d3e14fb..14a0b2a1 100644 --- a/examples/server_example1/server_example1.c +++ b/examples/server_example1/server_example1.c @@ -22,7 +22,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include diff --git a/examples/server_example1/static_model.c b/examples/server_example1/static_model.c index c3ac50db..3abfe08a 100644 --- a/examples/server_example1/static_model.c +++ b/examples/server_example1/static_model.c @@ -4,7 +4,7 @@ * automatically generated from sampleModel_with_dataset.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -148,7 +148,7 @@ extern DataSetEntry ds_Device1_LLN0_dataset1_fcda1; extern DataSetEntry ds_Device1_LLN0_dataset1_fcda2; DataSetEntry ds_Device1_LLN0_dataset1_fcda0 = { - "SampleIEDDevice1", + "Device1", "LLN0$ST$Mod$q", -1, NULL, @@ -157,7 +157,7 @@ DataSetEntry ds_Device1_LLN0_dataset1_fcda0 = { }; DataSetEntry ds_Device1_LLN0_dataset1_fcda1 = { - "SampleIEDDevice1", + "Device1", "MMXU1$ST$Mod$q", -1, NULL, @@ -166,7 +166,7 @@ DataSetEntry ds_Device1_LLN0_dataset1_fcda1 = { }; DataSetEntry ds_Device1_LLN0_dataset1_fcda2 = { - "SampleIEDDevice1", + "Device1", "MMXU1$CF$Mod$ctlModel", -1, NULL, @@ -175,7 +175,7 @@ DataSetEntry ds_Device1_LLN0_dataset1_fcda2 = { }; DataSet ds_Device1_LLN0_dataset1 = { - "SampleIEDDevice1", + "Device1", "LLN0$dataset1", 3, &ds_Device1_LLN0_dataset1_fcda0, @@ -184,7 +184,7 @@ DataSet ds_Device1_LLN0_dataset1 = { LogicalDevice iedModel_Device1 = { LogicalDeviceModelType, - "SampleIEDDevice1", + "Device1", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_Device1_LLN0 @@ -1718,31 +1718,9 @@ DataAttribute iedModel_Device1_MMXU2_TotW_t = { NULL, 0}; - extern ReportControlBlock iedModel_Device1_LLN0_report0; -ReportControlBlock iedModel_Device1_LLN0_report0 = {&iedModel_Device1_LLN0, "LLN0_Events_BuffRep", "LLN0$RP$brcbEV1", true, "dataset1", 1, 9, 239, 50, 900000, NULL}; - - - - - - - - - - - - - - - - - - - - - +ReportControlBlock iedModel_Device1_LLN0_report0 = {&iedModel_Device1_LLN0, "LLN0_Events_BuffRep01", "LLN0$RP$brcbEV1", true, "dataset1", 1, 9, 239, 50, 900000, NULL}; @@ -1753,6 +1731,7 @@ IedModel iedModel = { &ds_Device1_LLN0_dataset1, &iedModel_Device1_LLN0_report0, NULL, + NULL, initializeValues }; diff --git a/examples/server_example1/static_model.h b/examples/server_example1/static_model.h index 94afedd8..208787e3 100644 --- a/examples/server_example1/static_model.h +++ b/examples/server_example1/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_Device1; diff --git a/examples/server_example2/server_example2.c b/examples/server_example2/server_example2.c index 20fe7a6e..e4d95e90 100644 --- a/examples/server_example2/server_example2.c +++ b/examples/server_example2/server_example2.c @@ -22,7 +22,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include diff --git a/examples/server_example2/static_model.c b/examples/server_example2/static_model.c index 6eeed2f0..fbc3cfa7 100644 --- a/examples/server_example2/static_model.c +++ b/examples/server_example2/static_model.c @@ -1,10 +1,10 @@ /* * static_model.c * - * automatically generated from complexModel.scd + * automatically generated from complexModel.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -314,7 +314,7 @@ extern DataSetEntry ds_Inverter_LLN0_dataset1_fcda3; extern DataSetEntry ds_Inverter_LLN0_dataset1_fcda4; DataSetEntry ds_Inverter_LLN0_dataset1_fcda0 = { - "ied1Inverter", + "Inverter", "LLN0$ST$Mod$q", -1, NULL, @@ -323,7 +323,7 @@ DataSetEntry ds_Inverter_LLN0_dataset1_fcda0 = { }; DataSetEntry ds_Inverter_LLN0_dataset1_fcda1 = { - "ied1Battery", + "Battery", "LLN0$ST$Mod$q", -1, NULL, @@ -332,7 +332,7 @@ DataSetEntry ds_Inverter_LLN0_dataset1_fcda1 = { }; DataSetEntry ds_Inverter_LLN0_dataset1_fcda2 = { - "ied1Inverter", + "Inverter", "MMXU1$ST$Mod$q", -1, NULL, @@ -341,7 +341,7 @@ DataSetEntry ds_Inverter_LLN0_dataset1_fcda2 = { }; DataSetEntry ds_Inverter_LLN0_dataset1_fcda3 = { - "ied1Inverter", + "Inverter", "MMXU1$CF$Mod$ctlModel", -1, NULL, @@ -350,7 +350,7 @@ DataSetEntry ds_Inverter_LLN0_dataset1_fcda3 = { }; DataSetEntry ds_Inverter_LLN0_dataset1_fcda4 = { - "ied1Inverter", + "Inverter", "MMXU1$MX$TotW$mag", -1, NULL, @@ -359,7 +359,7 @@ DataSetEntry ds_Inverter_LLN0_dataset1_fcda4 = { }; DataSet ds_Inverter_LLN0_dataset1 = { - "ied1Inverter", + "Inverter", "LLN0$dataset1", 5, &ds_Inverter_LLN0_dataset1_fcda0, @@ -368,7 +368,7 @@ DataSet ds_Inverter_LLN0_dataset1 = { LogicalDevice iedModel_Inverter = { LogicalDeviceModelType, - "ied1Inverter", + "Inverter", (ModelNode*) &iedModel, (ModelNode*) &iedModel_Battery, (ModelNode*) &iedModel_Inverter_LLN0 @@ -2480,7 +2480,7 @@ DataAttribute iedModel_Inverter_MMXU1_W_phsC_t = { LogicalDevice iedModel_Battery = { LogicalDeviceModelType, - "ied1Battery", + "Battery", (ModelNode*) &iedModel, (ModelNode*) &iedModel_Physical_Measurements, (ModelNode*) &iedModel_Battery_LLN0 @@ -3512,7 +3512,7 @@ DataAttribute iedModel_Battery_ZBTC1_ChaA_t = { LogicalDevice iedModel_Physical_Measurements = { LogicalDeviceModelType, - "ied1Physical_Measurements", + "Physical_Measurements", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_Physical_Measurements_LLN0 @@ -3870,47 +3870,9 @@ DataAttribute iedModel_Physical_Measurements_LPHD1_Proxy_t = { NULL, 0}; - extern ReportControlBlock iedModel_Inverter_LLN0_report0; -ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "rcb1", "ID", false, "dataset1", 0, 3, 32, 0, 0, NULL}; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "rcb101", "ID", false, "dataset1", 0, 3, 32, 0, 0, NULL}; @@ -3921,6 +3883,7 @@ IedModel iedModel = { &ds_Inverter_LLN0_dataset1, &iedModel_Inverter_LLN0_report0, NULL, + NULL, initializeValues }; diff --git a/examples/server_example2/static_model.h b/examples/server_example2/static_model.h index 609fc245..34623f80 100644 --- a/examples/server_example2/static_model.h +++ b/examples/server_example2/static_model.h @@ -1,14 +1,14 @@ /* * static_model.h * - * automatically generated from complexModel.scd + * automatically generated from complexModel.icd */ #ifndef STATIC_MODEL_H_ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_Inverter; diff --git a/examples/server_example3/server_example3.c b/examples/server_example3/server_example3.c index e7659369..0953e409 100644 --- a/examples/server_example3/server_example3.c +++ b/examples/server_example3/server_example3.c @@ -6,7 +6,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include @@ -66,6 +66,16 @@ controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test) return true; } + +static void +connectionHandler (IedServer self, ClientConnection connection, bool connected, void* parameter) +{ + if (connected) + printf("Connection opened\n"); + else + printf("Connection closed\n"); +} + int main(int argc, char** argv) { @@ -89,6 +99,8 @@ main(int argc, char** argv) (ControlHandler) controlHandlerForBinaryOutput, IEDMODEL_GenericIO_GGIO1_SPCSO4); + IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); + /* MMS server will be instructed to start listening to client connections. */ IedServer_start(iedServer, 102); @@ -135,4 +147,5 @@ main(int argc, char** argv) /* Cleanup - free all resources */ IedServer_destroy(iedServer); + } /* main() */ diff --git a/examples/server_example3/static_model.c b/examples/server_example3/static_model.c index ced6aaeb..90017480 100644 --- a/examples/server_example3/static_model.c +++ b/examples/server_example3/static_model.c @@ -4,7 +4,7 @@ * automatically generated from simpleIO_direct_control.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -160,7 +160,7 @@ extern DataSetEntry ds_GenericIO_LLN0_Events_fcda2; extern DataSetEntry ds_GenericIO_LLN0_Events_fcda3; DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO1$stVal", -1, NULL, @@ -169,7 +169,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO2$stVal", -1, NULL, @@ -178,7 +178,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO3$stVal", -1, NULL, @@ -187,7 +187,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO4$stVal", -1, NULL, @@ -196,7 +196,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { }; DataSet ds_GenericIO_LLN0_Events = { - "simpleIOGenericIO", + "GenericIO", "LLN0$Events", 4, &ds_GenericIO_LLN0_Events_fcda0, @@ -209,7 +209,7 @@ extern DataSetEntry ds_GenericIO_LLN0_Events2_fcda2; extern DataSetEntry ds_GenericIO_LLN0_Events2_fcda3; DataSetEntry ds_GenericIO_LLN0_Events2_fcda0 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO1", -1, NULL, @@ -218,7 +218,7 @@ DataSetEntry ds_GenericIO_LLN0_Events2_fcda0 = { }; DataSetEntry ds_GenericIO_LLN0_Events2_fcda1 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO2", -1, NULL, @@ -227,7 +227,7 @@ DataSetEntry ds_GenericIO_LLN0_Events2_fcda1 = { }; DataSetEntry ds_GenericIO_LLN0_Events2_fcda2 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO3", -1, NULL, @@ -236,7 +236,7 @@ DataSetEntry ds_GenericIO_LLN0_Events2_fcda2 = { }; DataSetEntry ds_GenericIO_LLN0_Events2_fcda3 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO4", -1, NULL, @@ -245,7 +245,7 @@ DataSetEntry ds_GenericIO_LLN0_Events2_fcda3 = { }; DataSet ds_GenericIO_LLN0_Events2 = { - "simpleIOGenericIO", + "GenericIO", "LLN0$Events2", 4, &ds_GenericIO_LLN0_Events2_fcda0, @@ -254,7 +254,7 @@ DataSet ds_GenericIO_LLN0_Events2 = { LogicalDevice iedModel_GenericIO = { LogicalDeviceModelType, - "simpleIOGenericIO", + "GenericIO", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_GenericIO_LLN0 @@ -1973,13 +1973,12 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { 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; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB", "Events1", false, "Events", 1, 8, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 1, 8, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 8, 111, 50, 1000, &iedModel_GenericIO_LLN0_report2}; ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 8, 111, 50, 1000, &iedModel_GenericIO_LLN0_report3}; ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 8, 111, 50, 1000, NULL}; @@ -1987,21 +1986,13 @@ ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, - - - - - - - - - IedModel iedModel = { "simpleIO", &iedModel_GenericIO, &ds_GenericIO_LLN0_Events, &iedModel_GenericIO_LLN0_report0, NULL, + NULL, initializeValues }; diff --git a/examples/server_example3/static_model.h b/examples/server_example3/static_model.h index 6883b430..b5670e9f 100644 --- a/examples/server_example3/static_model.h +++ b/examples/server_example3/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_GenericIO; diff --git a/examples/server_example4/server_example4.c b/examples/server_example4/server_example4.c index e09ffee9..166515e1 100644 --- a/examples/server_example4/server_example4.c +++ b/examples/server_example4/server_example4.c @@ -16,7 +16,7 @@ #include "iec61850_server.h" #include "static_model.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include diff --git a/examples/server_example4/static_model.c b/examples/server_example4/static_model.c index 4cb637a6..08974481 100644 --- a/examples/server_example4/static_model.c +++ b/examples/server_example4/static_model.c @@ -4,7 +4,7 @@ * automatically generated from simpleIO_direct_control.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -158,7 +158,7 @@ extern DataSetEntry ds_GenericIO_LLN0_Events_fcda2; extern DataSetEntry ds_GenericIO_LLN0_Events_fcda3; DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO1$stVal", -1, NULL, @@ -167,7 +167,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO2$stVal", -1, NULL, @@ -176,7 +176,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO3$stVal", -1, NULL, @@ -185,7 +185,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO4$stVal", -1, NULL, @@ -194,7 +194,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { }; DataSet ds_GenericIO_LLN0_Events = { - "simpleIOGenericIO", + "GenericIO", "LLN0$Events", 4, &ds_GenericIO_LLN0_Events_fcda0, @@ -203,7 +203,7 @@ DataSet ds_GenericIO_LLN0_Events = { LogicalDevice iedModel_GenericIO = { LogicalDeviceModelType, - "simpleIOGenericIO", + "GenericIO", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_GenericIO_LLN0 @@ -1909,19 +1909,9 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { NULL, 0}; - extern ReportControlBlock iedModel_GenericIO_LLN0_report0; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB", "Events", false, "Events", 1, 8, 111, 50, 1000, NULL}; - - - - - - - - - +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 8, 111, 50, 1000, NULL}; @@ -1932,6 +1922,7 @@ IedModel iedModel = { &ds_GenericIO_LLN0_Events, &iedModel_GenericIO_LLN0_report0, NULL, + NULL, initializeValues }; diff --git a/examples/server_example4/static_model.h b/examples/server_example4/static_model.h index 4f352309..58dd28b5 100644 --- a/examples/server_example4/static_model.h +++ b/examples/server_example4/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_GenericIO; diff --git a/examples/server_example5/server_example5.c b/examples/server_example5/server_example5.c index 2f31f96b..58931db2 100644 --- a/examples/server_example5/server_example5.c +++ b/examples/server_example5/server_example5.c @@ -22,7 +22,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include diff --git a/examples/server_example5/static_model.c b/examples/server_example5/static_model.c index 4cb637a6..08974481 100644 --- a/examples/server_example5/static_model.c +++ b/examples/server_example5/static_model.c @@ -4,7 +4,7 @@ * automatically generated from simpleIO_direct_control.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -158,7 +158,7 @@ extern DataSetEntry ds_GenericIO_LLN0_Events_fcda2; extern DataSetEntry ds_GenericIO_LLN0_Events_fcda3; DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO1$stVal", -1, NULL, @@ -167,7 +167,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO2$stVal", -1, NULL, @@ -176,7 +176,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO3$stVal", -1, NULL, @@ -185,7 +185,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO4$stVal", -1, NULL, @@ -194,7 +194,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { }; DataSet ds_GenericIO_LLN0_Events = { - "simpleIOGenericIO", + "GenericIO", "LLN0$Events", 4, &ds_GenericIO_LLN0_Events_fcda0, @@ -203,7 +203,7 @@ DataSet ds_GenericIO_LLN0_Events = { LogicalDevice iedModel_GenericIO = { LogicalDeviceModelType, - "simpleIOGenericIO", + "GenericIO", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_GenericIO_LLN0 @@ -1909,19 +1909,9 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { NULL, 0}; - extern ReportControlBlock iedModel_GenericIO_LLN0_report0; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB", "Events", false, "Events", 1, 8, 111, 50, 1000, NULL}; - - - - - - - - - +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 8, 111, 50, 1000, NULL}; @@ -1932,6 +1922,7 @@ IedModel iedModel = { &ds_GenericIO_LLN0_Events, &iedModel_GenericIO_LLN0_report0, NULL, + NULL, initializeValues }; diff --git a/examples/server_example5/static_model.h b/examples/server_example5/static_model.h index 4f352309..58dd28b5 100644 --- a/examples/server_example5/static_model.h +++ b/examples/server_example5/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_GenericIO; diff --git a/examples/server_example_61400_25/server_example_61400_25.c b/examples/server_example_61400_25/server_example_61400_25.c index c86e153b..890b5a3d 100644 --- a/examples/server_example_61400_25/server_example_61400_25.c +++ b/examples/server_example_61400_25/server_example_61400_25.c @@ -22,7 +22,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include diff --git a/examples/server_example_61400_25/static_model.c b/examples/server_example_61400_25/static_model.c index 1a8e22de..d40302fc 100644 --- a/examples/server_example_61400_25/static_model.c +++ b/examples/server_example_61400_25/static_model.c @@ -4,7 +4,7 @@ * automatically generated from wtur.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -344,7 +344,7 @@ extern DataAttribute iedModel_WTG_WTUR1_SetTurOp_cmAcs; LogicalDevice iedModel_WTG = { LogicalDeviceModelType, - "WINDWTG", + "WTG", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_WTG_LLN0 @@ -4466,22 +4466,13 @@ DataAttribute iedModel_WTG_WTUR1_SetTurOp_cmAcs = { - - - - - - - - - - IedModel iedModel = { "WIND", &iedModel_WTG, NULL, NULL, NULL, + NULL, initializeValues }; diff --git a/examples/server_example_61400_25/static_model.h b/examples/server_example_61400_25/static_model.h index 171dba6f..c19af1c4 100644 --- a/examples/server_example_61400_25/static_model.h +++ b/examples/server_example_61400_25/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_WTG; diff --git a/examples/server_example_complex_array/server_example_ca.c b/examples/server_example_complex_array/server_example_ca.c index bad2b47e..5db5a755 100644 --- a/examples/server_example_complex_array/server_example_ca.c +++ b/examples/server_example_complex_array/server_example_ca.c @@ -5,7 +5,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include diff --git a/examples/server_example_complex_array/static_model.c b/examples/server_example_complex_array/static_model.c index 07b6e111..979e8d35 100644 --- a/examples/server_example_complex_array/static_model.c +++ b/examples/server_example_complex_array/static_model.c @@ -4,7 +4,7 @@ * automatically generated from mhai_array.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -58,7 +58,7 @@ extern DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency; LogicalDevice iedModel_ComplexArray = { LogicalDeviceModelType, - "testComplexArray", + "ComplexArray", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_ComplexArray_LLN0 @@ -589,12 +589,14 @@ DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency = { + IedModel iedModel = { "test", &iedModel_ComplexArray, NULL, NULL, NULL, + NULL, initializeValues }; diff --git a/examples/server_example_complex_array/static_model.h b/examples/server_example_complex_array/static_model.h index bad70b71..0999d581 100644 --- a/examples/server_example_complex_array/static_model.h +++ b/examples/server_example_complex_array/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_ComplexArray; diff --git a/examples/server_example_config_file/server_example_config_file.c b/examples/server_example_config_file/server_example_config_file.c index 11320ccc..924707bc 100644 --- a/examples/server_example_config_file/server_example_config_file.c +++ b/examples/server_example_config_file/server_example_config_file.c @@ -15,13 +15,13 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include -#include "filesystem.h" -#include "config_file_parser.h" +#include "hal_filesystem.h" +#include "iec61850_config_file_parser.h" static int running = 0; diff --git a/examples/server_example_config_file/vmd-filestore/model.cfg b/examples/server_example_config_file/vmd-filestore/model.cfg index 4b7698a3..d0507a1f 100644 --- a/examples/server_example_config_file/vmd-filestore/model.cfg +++ b/examples/server_example_config_file/vmd-filestore/model.cfg @@ -1,6 +1,7 @@ -MODEL{ -LD(simpleIOGenericIO){ +MODEL(simpleIO){ +LD(GenericIO){ LN(LLN0){ +SG(1 2) DO(Mod 0){ DA(q 0 23 0 2 0); DA(t 0 22 0 0 0); @@ -35,8 +36,8 @@ DE(GGIO1$MX$AnIn2); DE(GGIO1$MX$AnIn3); DE(GGIO1$MX$AnIn4); } -RC(EventsRCB Events 0 Events 1 8 111 50 1000); -RC(AnalogValuesRCB AnalogValues 0 AnalogValues 1 8 111 50 1000); +RC(EventsRCB01 - 0 Events 1 8 111 50 1000); +RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 8 111 50 1000); GC(gcbEvents events Events 2 0){ PA(4 111 1000 010ccd010001); } diff --git a/examples/server_example_control/server_example_control.c b/examples/server_example_control/server_example_control.c index 202606ac..72fc6bc2 100644 --- a/examples/server_example_control/server_example_control.c +++ b/examples/server_example_control/server_example_control.c @@ -5,7 +5,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include @@ -44,23 +44,31 @@ checkHandler(void* parameter, MmsValue* ctlVal, bool test, bool interlockCheck, if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO4) return CONTROL_ACCEPTED; + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO9) + return CONTROL_ACCEPTED; + return CONTROL_OBJECT_UNDEFINED; } -void +static ControlHandlerResult controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test) { if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO1) IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal, value); - if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO2) + else if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO2) IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal, value); - if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO3) + else if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO3) IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal, value); - if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO4) + else if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO4) IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal, value); + + else + return CONTROL_RESULT_FAILED; + + return CONTROL_RESULT_OK; } int @@ -91,7 +99,16 @@ main(int argc, char** argv) /* this is optional - performs operative checks */ IedServer_setPerformCheckHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4, checkHandler, - IEDMODEL_GenericIO_GGIO1_SPCSO4); + IEDMODEL_GenericIO_GGIO1_SPCSO4); + + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO9, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO9); + + /* this is optional - performs operative checks */ + IedServer_setPerformCheckHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO9, checkHandler, + IEDMODEL_GenericIO_GGIO1_SPCSO9); /* MMS server will be instructed to start listening to client connections. */ IedServer_start(iedServer, 102); diff --git a/examples/server_example_control/simpleIO_control_tests.icd b/examples/server_example_control/simpleIO_control_tests.icd index 63c13eae..6bfd88cd 100644 --- a/examples/server_example_control/simpleIO_control_tests.icd +++ b/examples/server_example_control/simpleIO_control_tests.icd @@ -93,6 +93,11 @@ sbo-with-enhanced-security + + + direct-with-enhanced-security + + @@ -127,6 +132,7 @@ + diff --git a/examples/server_example_control/static_model.c b/examples/server_example_control/static_model.c index f9040c3e..50e3df5d 100644 --- a/examples/server_example_control/static_model.c +++ b/examples/server_example_control/static_model.c @@ -4,7 +4,7 @@ * automatically generated from simpleIO_control_tests.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -277,6 +277,28 @@ extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_q; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_t; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_ctlModel; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO9; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_t; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_ctlModel; extern DataObject iedModel_GenericIO_GGIO1_Ind1; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_q; @@ -298,7 +320,7 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; LogicalDevice iedModel_GenericIO = { LogicalDeviceModelType, - "simpleIOGenericIO", + "GenericIO", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_GenericIO_LLN0 @@ -3195,7 +3217,7 @@ DataObject iedModel_GenericIO_GGIO1_SPCSO8 = { DataObjectModelType, "SPCSO8", (ModelNode*) &iedModel_GenericIO_GGIO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO8_SBOw, 0 }; @@ -3681,6 +3703,288 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_ctlModel = { NULL, 0}; +DataObject iedModel_GenericIO_GGIO1_SPCSO9 = { + DataObjectModelType, + "SPCSO9", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlVal, + 0, + CO, + CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin, + NULL, + 0, + CO, + BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orCat, + 0, + CO, + CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orIdent, + NULL, + 0, + CO, + ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin, + NULL, + NULL, + 0, + CO, + OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_T, + NULL, + 0, + CO, + INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_Test, + NULL, + 0, + CO, + TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper_Check, + NULL, + 0, + CO, + BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Oper, + NULL, + NULL, + 0, + CO, + CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel = { + DataAttributeModelType, + "Cancel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_stVal, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlVal, + 0, + CO, + CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin, + NULL, + 0, + CO, + BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orCat, + 0, + CO, + CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orIdent, + NULL, + 0, + CO, + ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin, + NULL, + NULL, + 0, + CO, + OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_T, + NULL, + 0, + CO, + INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel_Test, + NULL, + 0, + CO, + TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_Cancel, + NULL, + NULL, + 0, + CO, + BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_q, + NULL, + 0, + ST, + BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_t, + NULL, + 0, + ST, + QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9_ctlModel, + NULL, + 0, + ST, + TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO9, + NULL, + NULL, + 0, + CF, + ENUMERATED, + 0, + NULL, + 0}; + DataObject iedModel_GenericIO_GGIO1_Ind1 = { DataObjectModelType, "Ind1", @@ -3878,22 +4182,13 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { - - - - - - - - - - IedModel iedModel = { "simpleIO", &iedModel_GenericIO, NULL, NULL, NULL, + NULL, initializeValues }; @@ -3920,4 +4215,6 @@ iedModel_GenericIO_GGIO1_SPCSO6_ctlModel.mmsValue = MmsValue_newIntegerFromInt32 iedModel_GenericIO_GGIO1_SPCSO7_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(3); iedModel_GenericIO_GGIO1_SPCSO8_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(4); + +iedModel_GenericIO_GGIO1_SPCSO9_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(3); } diff --git a/examples/server_example_control/static_model.h b/examples/server_example_control/static_model.h index e93a2bc3..67c3967f 100644 --- a/examples/server_example_control/static_model.h +++ b/examples/server_example_control/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_GenericIO; @@ -280,6 +280,28 @@ extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_q; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_t; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO8_ctlModel; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO9; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_Cancel_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_t; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO9_ctlModel; extern DataObject iedModel_GenericIO_GGIO1_Ind1; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_q; @@ -568,6 +590,28 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #define IEDMODEL_GenericIO_GGIO1_SPCSO8_q (&iedModel_GenericIO_GGIO1_SPCSO8_q) #define IEDMODEL_GenericIO_GGIO1_SPCSO8_t (&iedModel_GenericIO_GGIO1_SPCSO8_t) #define IEDMODEL_GenericIO_GGIO1_SPCSO8_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO8_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9 (&iedModel_GenericIO_GGIO1_SPCSO9) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper (&iedModel_GenericIO_GGIO1_SPCSO9_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO9_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel_origin (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel_T (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_Cancel_Test (&iedModel_GenericIO_GGIO1_SPCSO9_Cancel_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_stVal (&iedModel_GenericIO_GGIO1_SPCSO9_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_q (&iedModel_GenericIO_GGIO1_SPCSO9_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_t (&iedModel_GenericIO_GGIO1_SPCSO9_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO9_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO9_ctlModel) #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) diff --git a/examples/server_example_dynamic/server_example_dynamic.c b/examples/server_example_dynamic/server_example_dynamic.c index 361acd0f..49dccbc1 100644 --- a/examples/server_example_dynamic/server_example_dynamic.c +++ b/examples/server_example_dynamic/server_example_dynamic.c @@ -6,7 +6,7 @@ */ #include "iec61850_server.h" -#include "thread.h" +#include "hal_thread.h" #include #include #include @@ -40,6 +40,8 @@ int main(int argc, char** argv) { DataObject* lln0_mod = CDC_ENS_create("Mod", (ModelNode*) lln0, 0); DataObject* lln0_health = CDC_ENS_create("Health", (ModelNode*) lln0, 0); + SettingGroupControlBlock_create(lln0, 1, 1); + /* Add a temperature sensor LN */ LogicalNode* ttmp1 = LogicalNode_create("TTMP1", lDevice1); DataObject* ttmp1_tmpsv = CDC_SAV_create("TmpSv", (ModelNode*) ttmp1, 0, false); diff --git a/examples/server_example_goose/server_example_goose.c b/examples/server_example_goose/server_example_goose.c index 7df535ce..f014c42b 100644 --- a/examples/server_example_goose/server_example_goose.c +++ b/examples/server_example_goose/server_example_goose.c @@ -7,7 +7,7 @@ */ #include "iec61850_server.h" -#include "thread.h" /* for Thread_sleep() */ +#include "hal_thread.h" /* for Thread_sleep() */ #include #include #include diff --git a/examples/server_example_goose/simpleIO_direct_control_goose.icd b/examples/server_example_goose/simpleIO_direct_control_goose.icd index 81357b14..5f68fa01 100644 --- a/examples/server_example_goose/simpleIO_direct_control_goose.icd +++ b/examples/server_example_goose/simpleIO_direct_control_goose.icd @@ -17,7 +17,7 @@
-

111

+

1

4

01-0c-cd-01-00-01

1000

@@ -25,7 +25,7 @@
-

111

+

1

4

01-0c-cd-01-00-01

1000

diff --git a/examples/server_example_goose/static_model.c b/examples/server_example_goose/static_model.c index 1295dfe1..2beaeac0 100644 --- a/examples/server_example_goose/static_model.c +++ b/examples/server_example_goose/static_model.c @@ -4,7 +4,7 @@ * automatically generated from simpleIO_direct_control_goose.icd */ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; static void initializeValues(); @@ -159,7 +159,7 @@ extern DataSetEntry ds_GenericIO_LLN0_Events_fcda2; extern DataSetEntry ds_GenericIO_LLN0_Events_fcda3; DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO1$stVal", -1, NULL, @@ -168,7 +168,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda0 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO2$stVal", -1, NULL, @@ -177,7 +177,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda1 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO3$stVal", -1, NULL, @@ -186,7 +186,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda2 = { }; DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$ST$SPCSO4$stVal", -1, NULL, @@ -195,7 +195,7 @@ DataSetEntry ds_GenericIO_LLN0_Events_fcda3 = { }; DataSet ds_GenericIO_LLN0_Events = { - "simpleIOGenericIO", + "GenericIO", "LLN0$Events", 4, &ds_GenericIO_LLN0_Events_fcda0, @@ -208,7 +208,7 @@ extern DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda2; extern DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda3; DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda0 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$MX$AnIn1", -1, NULL, @@ -217,7 +217,7 @@ DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda0 = { }; DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda1 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$MX$AnIn2", -1, NULL, @@ -226,7 +226,7 @@ DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda1 = { }; DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda2 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$MX$AnIn3", -1, NULL, @@ -235,7 +235,7 @@ DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda2 = { }; DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda3 = { - "simpleIOGenericIO", + "GenericIO", "GGIO1$MX$AnIn4", -1, NULL, @@ -244,7 +244,7 @@ DataSetEntry ds_GenericIO_LLN0_AnalogValues_fcda3 = { }; DataSet ds_GenericIO_LLN0_AnalogValues = { - "simpleIOGenericIO", + "GenericIO", "LLN0$AnalogValues", 4, &ds_GenericIO_LLN0_AnalogValues_fcda0, @@ -253,7 +253,7 @@ DataSet ds_GenericIO_LLN0_AnalogValues = { LogicalDevice iedModel_GenericIO = { LogicalDeviceModelType, - "simpleIOGenericIO", + "GenericIO", (ModelNode*) &iedModel, NULL, (ModelNode*) &iedModel_GenericIO_LLN0 @@ -1970,7 +1970,7 @@ extern GSEControlBlock iedModel_GenericIO_LLN0_gse1; static PhyComAddress iedModel_GenericIO_LLN0_gse0_address = { 4, - 111, + 1, 1000, {0x1, 0xc, 0xcd, 0x1, 0x0, 0x1} }; @@ -1979,7 +1979,7 @@ GSEControlBlock iedModel_GenericIO_LLN0_gse0 = {&iedModel_GenericIO_LLN0, "gcbEv static PhyComAddress iedModel_GenericIO_LLN0_gse1_address = { 4, - 111, + 1, 1000, {0x1, 0xc, 0xcd, 0x1, 0x0, 0x1} }; @@ -1987,12 +1987,14 @@ static PhyComAddress iedModel_GenericIO_LLN0_gse1_address = { GSEControlBlock iedModel_GenericIO_LLN0_gse1 = {&iedModel_GenericIO_LLN0, "gcbAnalogValues", "analog", "AnalogValues", 2, false,&iedModel_GenericIO_LLN0_gse1_address, NULL}; + IedModel iedModel = { "simpleIO", &iedModel_GenericIO, &ds_GenericIO_LLN0_Events, &iedModel_GenericIO_LLN0_report0, &iedModel_GenericIO_LLN0_gse0, + NULL, initializeValues }; diff --git a/examples/server_example_goose/static_model.h b/examples/server_example_goose/static_model.h index 5a4bb9b2..5e827db7 100644 --- a/examples/server_example_goose/static_model.h +++ b/examples/server_example_goose/static_model.h @@ -8,7 +8,7 @@ #define STATIC_MODEL_H_ #include -#include "model.h" +#include "iec61850_model.h" extern IedModel iedModel; extern LogicalDevice iedModel_GenericIO; diff --git a/make/stack_includes.mk b/make/stack_includes.mk index 6d89ad10..28e1291c 100644 --- a/make/stack_includes.mk +++ b/make/stack_includes.mk @@ -1,24 +1,9 @@ INCLUDES = -I$(LIBIEC_HOME)/config -INCLUDES += -I$(LIBIEC_HOME)/src/common -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_presentation -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_session -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_cotp -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_acse -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_mms/common -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_mms/client -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_mms/server -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_client -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_common -INCLUDES += -I$(LIBIEC_HOME)/src/mms/iso_server +INCLUDES += -I$(LIBIEC_HOME)/src/common/inc +INCLUDES += -I$(LIBIEC_HOME)/src/mms/inc +INCLUDES += -I$(LIBIEC_HOME)/src/mms/inc_private INCLUDES += -I$(LIBIEC_HOME)/src/mms/asn1 -INCLUDES += -I$(LIBIEC_HOME)/src/iedcommon -INCLUDES += -I$(LIBIEC_HOME)/src/iedserver/mms_mapping -INCLUDES += -I$(LIBIEC_HOME)/src/iedserver/model -INCLUDES += -I$(LIBIEC_HOME)/src/iedserver -INCLUDES += -I$(LIBIEC_HOME)/src/iedclient -INCLUDES += -I$(LIBIEC_HOME)/src/hal -INCLUDES += -I$(LIBIEC_HOME)/src/hal/thread -INCLUDES += -I$(LIBIEC_HOME)/src/hal/socket -INCLUDES += -I$(LIBIEC_HOME)/src/hal/filesystem -INCLUDES += -I$(LIBIEC_HOME)/src/hal/time +INCLUDES += -I$(LIBIEC_HOME)/src/iec61850/inc +INCLUDES += -I$(LIBIEC_HOME)/src/iec61850/inc_private +INCLUDES += -I$(LIBIEC_HOME)/src/hal/inc INCLUDES += -I$(LIBIEC_HOME)/src/goose diff --git a/make/target_system.mk b/make/target_system.mk index 161acd90..c7edf351 100644 --- a/make/target_system.mk +++ b/make/target_system.mk @@ -4,7 +4,7 @@ MIPSEL_TOOLCHAIN_PREFIX=mipsel-openwrt-linux- # ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabihf- #ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi- #ARM_TOOLCHAIN_PREFIX=arm-poky-linux-gnueabi- -ARM_TOOLCHAIN_PREFIX=arm-linux- +ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi- UCLINUX_ARM_TOOLCHAIN_PREFIX=arm-uclinux-elf- MINGW_TOOLCHAIN_PREFIX=i586-mingw32msvc- #MINGW_TOOLCHAIN_PREFIX=x86_64-w64-mingw32- @@ -126,7 +126,7 @@ LIB_OBJS_DIR = $(LIBIEC_HOME)/build endif CFLAGS += -g -CFLAGS += -Os +#CFLAGS += -Os DYNLIB_LDFLAGS=-lpthread endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 19d94212..46dbfae8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,12 +5,12 @@ set (lib_common_SRCS ./common/map.c ./common/linked_list.c ./common/byte_buffer.c +./common/lib_memory.c ./common/string_utilities.c ./common/buffer_chain.c ./common/conversions.c ./common/mem_alloc_linked_list.c ./common/simple_allocator.c -./common/byte_stream.c ./mms/iso_server/iso_connection.c ./mms/iso_server/iso_server.c ./mms/iso_acse/acse.c @@ -52,24 +52,25 @@ set (lib_common_SRCS ./mms/asn1/asn1_ber_primitive_value.c ./mms/asn1/ber_encoder.c ./mms/asn1/ber_integer.c -./mms/iso_client/impl/iso_client_connection.c +./mms/iso_client/iso_client_connection.c ./mms/iso_common/iso_connection_parameters.c ./mms/iso_session/iso_session.c -./iedclient/impl/client_control.c -./iedclient/impl/client_report_control.c -./iedclient/impl/client_report.c -./iedclient/impl/ied_connection.c -./iedcommon/iec61850_common.c -./iedserver/impl/ied_server.c -./iedserver/impl/client_connection.c -./iedserver/model/model.c -./iedserver/model/dynamic_model.c -./iedserver/model/cdc.c -./iedserver/model/config_file_parser.c -./iedserver/mms_mapping/control.c -./iedserver/mms_mapping/mms_mapping.c -./iedserver/mms_mapping/reporting.c -./iedserver/mms_mapping/mms_goose.c +./iec61850/client/client_control.c +./iec61850/client/client_report_control.c +./iec61850/client/client_goose_control.c +./iec61850/client/client_report.c +./iec61850/client/ied_connection.c +./iec61850/common/iec61850_common.c +./iec61850/server/impl/ied_server.c +./iec61850/server/impl/client_connection.c +./iec61850/server/model/model.c +./iec61850/server/model/dynamic_model.c +./iec61850/server/model/cdc.c +./iec61850/server/model/config_file_parser.c +./iec61850/server/mms_mapping/control.c +./iec61850/server/mms_mapping/mms_mapping.c +./iec61850/server/mms_mapping/reporting.c +./iec61850/server/mms_mapping/mms_goose.c ) @@ -168,6 +169,7 @@ set (lib_asn1c_SRCS set (lib_goose_SRCS ./goose/goose_subscriber.c +./goose/goose_receiver.c ./goose/goose_publisher.c ) @@ -274,8 +276,6 @@ set_target_properties(iec61850-shared PROPERTIES ) - - GENERATE_EXPORT_HEADER(iec61850-shared BASE_NAME iec61850-shared EXPORT_MACRO_NAME iec61850-shared_EXPORT @@ -286,12 +286,21 @@ GENERATE_EXPORT_HEADER(iec61850-shared add_library (iec61850 STATIC ${library_SRCS}) IF(UNIX) -target_link_libraries (iec61850 - -lpthread - -lm -) + IF (CONFIG_SYSTEM_HAS_CLOCK_GETTIME) + target_link_libraries (iec61850 + -lpthread + -lm + -lrt + ) + ELSE () + target_link_libraries (iec61850 + -lpthread + -lm + ) + ENDIF (CONFIG_SYSTEM_HAS_CLOCK_GETTIME) ENDIF(UNIX) + iF(WITH_WPCAP) target_link_libraries(iec61850 ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/lib/wpcap.lib @@ -318,6 +327,8 @@ if(MSVC) endif() ENDIF(WITH_WPCAP) + + install (TARGETS iec61850 iec61850-shared RUNTIME DESTINATION bin ARCHIVE DESTINATION lib diff --git a/src/common/buffer_chain.c b/src/common/buffer_chain.c index 39253be2..96043140 100644 --- a/src/common/buffer_chain.c +++ b/src/common/buffer_chain.c @@ -42,7 +42,7 @@ BufferChain_destroy(BufferChain self) while (currentChainElement != NULL) { BufferChain nextChainElement = currentChainElement->nextPart; - free(currentChainElement); + GLOBAL_FREEMEM(currentChainElement); currentChainElement = nextChainElement; } } diff --git a/src/common/byte_buffer.c b/src/common/byte_buffer.c index 96949611..9c1a4f9d 100644 --- a/src/common/byte_buffer.c +++ b/src/common/byte_buffer.c @@ -28,10 +28,10 @@ ByteBuffer* ByteBuffer_create(ByteBuffer* self, int maxSize) { if (self == NULL) { - self = (ByteBuffer*) calloc(1, sizeof(ByteBuffer)); + self = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer)); } - self->buffer = (uint8_t*) calloc(maxSize, sizeof(uint8_t)); + self->buffer = (uint8_t*) GLOBAL_CALLOC(maxSize, sizeof(uint8_t)); self->maxSize = maxSize; self->size = 0; @@ -41,8 +41,8 @@ ByteBuffer_create(ByteBuffer* self, int maxSize) void ByteBuffer_destroy(ByteBuffer* self) { - free(self->buffer); - free(self); + GLOBAL_FREEMEM(self->buffer); + GLOBAL_FREEMEM(self); } void @@ -105,6 +105,7 @@ ByteBuffer_setSize(ByteBuffer* self, int size) return self->size; } +#if 0 void ByteBuffer_print(ByteBuffer* self, char* message) { @@ -119,3 +120,4 @@ ByteBuffer_print(ByteBuffer* self, char* message) } printf("\n"); } +#endif diff --git a/src/common/lib_memory.c b/src/common/lib_memory.c new file mode 100644 index 00000000..d279b6c0 --- /dev/null +++ b/src/common/lib_memory.c @@ -0,0 +1,83 @@ +/* + * lib_memory.c + * + * Copyright 2014 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 "lib_memory.h" + +static MemoryExceptionHandler exceptionHandler = NULL; +static void* exceptionHandlerParameter = NULL; + +static void +noMemoryAvailableHandler(void) +{ + if (exceptionHandler != NULL) + exceptionHandler(exceptionHandlerParameter); +} + +void +Memory_installExceptionHandler(MemoryExceptionHandler handler, void* parameter) +{ + exceptionHandler = handler; + exceptionHandlerParameter = parameter; +} + +void* +Memory_malloc(size_t size) +{ + void* memory = malloc(size); + + if (memory == NULL) + noMemoryAvailableHandler(); + + return memory; +} + + +void* +Memory_calloc(size_t nmemb, size_t size) +{ + void* memory = calloc(nmemb, size); + + if (memory == NULL) + noMemoryAvailableHandler(); + + return memory; +} + + +void * +Memory_realloc(void *ptr, size_t size) +{ + void* memory = realloc(ptr, size); + + if (memory == NULL) + noMemoryAvailableHandler(); + + return memory; +} + +void +Memory_free(void* memb) +{ + free(memb); +} + diff --git a/src/common/linked_list.c b/src/common/linked_list.c index 93dd0436..a487b097 100644 --- a/src/common/linked_list.c +++ b/src/common/linked_list.c @@ -1,7 +1,7 @@ /* * linked_list.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -38,7 +38,7 @@ LinkedList_create() { LinkedList newList; - newList = (LinkedList) malloc(sizeof(struct sLinkedList)); + newList = (LinkedList) GLOBAL_MALLOC(sizeof(struct sLinkedList)); newList->data = NULL; newList->next = NULL; @@ -57,9 +57,11 @@ LinkedList_destroyDeep(LinkedList list, LinkedListValueDeleteFunction valueDelet do { currentElement = nextElement; nextElement = currentElement->next; + if (currentElement->data != NULL) valueDeleteFunction(currentElement->data); - free(currentElement); + + GLOBAL_FREEMEM(currentElement); } while (nextElement != NULL); } @@ -67,7 +69,7 @@ LinkedList_destroyDeep(LinkedList list, LinkedListValueDeleteFunction valueDelet void LinkedList_destroy(LinkedList list) { - LinkedList_destroyDeep(list, free); + LinkedList_destroyDeep(list, Memory_free); } /** @@ -82,7 +84,7 @@ LinkedList_destroyStatic(LinkedList list) do { currentElement = nextElement; nextElement = currentElement->next; - free(currentElement); + GLOBAL_FREEMEM(currentElement); } while (nextElement != NULL); } @@ -123,7 +125,7 @@ LinkedList_remove(LinkedList list, void* data) while (currentElement != NULL) { if (currentElement->data == data) { lastElement->next = currentElement->next; - free(currentElement); + GLOBAL_FREEMEM(currentElement); return true; } diff --git a/src/common/map.c b/src/common/map.c index c1fe97b2..d0ab9e33 100644 --- a/src/common/map.c +++ b/src/common/map.c @@ -42,7 +42,7 @@ comparePointerKeys(void* key1, void* key2) Map Map_create() { - Map map = (Map) calloc(1, sizeof(struct sMap)); + Map map = (Map) GLOBAL_CALLOC(1, sizeof(struct sMap)); map->entries = LinkedList_create(); map->compareKeys = comparePointerKeys; return map; @@ -57,7 +57,7 @@ Map_size(Map map) void* Map_addEntry(Map map, void* key, void* value) { - MapEntry* entry = (MapEntry*) malloc(sizeof(MapEntry)); + MapEntry* entry = (MapEntry*) GLOBAL_MALLOC(sizeof(MapEntry)); entry->key = key; entry->value = value; LinkedList_add(map->entries, entry); @@ -81,9 +81,9 @@ Map_removeEntry(Map map, void* key, bool deleteKey) value = entry->value; if (deleteKey == true) - free(entry->key); - free(entry); - free(element); + GLOBAL_FREEMEM(entry->key); + GLOBAL_FREEMEM(entry); + GLOBAL_FREEMEM(element); break; } @@ -117,12 +117,12 @@ Map_delete(Map map, bool deleteKey) while ((element = LinkedList_getNext(element)) != NULL) { MapEntry* entry = (MapEntry*) element->data; if (deleteKey == true) - free(entry->key); - free(entry->value); + GLOBAL_FREEMEM(entry->key); + GLOBAL_FREEMEM(entry->value); } LinkedList_destroy(map->entries); - free(map); + GLOBAL_FREEMEM(map); } void @@ -133,12 +133,12 @@ Map_deleteStatic(Map map, bool deleteKey) if (deleteKey == true) { while ((element = LinkedList_getNext(element)) != NULL) { MapEntry* entry = (MapEntry*) element->data; - free(entry->key); + GLOBAL_FREEMEM(entry->key); } } LinkedList_destroy(map->entries); - free(map); + GLOBAL_FREEMEM(map); } void @@ -150,10 +150,10 @@ Map_deleteDeep(Map map, bool deleteKey, void while ((element = LinkedList_getNext(element)) != NULL) { MapEntry* entry = (MapEntry*) element->data; if (deleteKey == true) - free(entry->key); + GLOBAL_FREEMEM(entry->key); valueDeleteFunction(entry->value); } LinkedList_destroy(map->entries); - free(map); + GLOBAL_FREEMEM(map); } diff --git a/src/common/simple_allocator.c b/src/common/simple_allocator.c index 6e0fac08..6592f746 100644 --- a/src/common/simple_allocator.c +++ b/src/common/simple_allocator.c @@ -1,7 +1,7 @@ /* * simple_allocator.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -33,9 +33,20 @@ MemoryAllocator_init(MemoryAllocator* self, char* memoryBlock, int size) self->size = size; } +static int +getAlignedSize(int size) +{ + if ((size % sizeof(void*)) > 0) + return sizeof(void*) * ((size + sizeof(void*)) / sizeof(void*)); + else + return size; +} + char* MemoryAllocator_allocate(MemoryAllocator* self, int size) { + size = getAlignedSize(size); + if (((self->currentPtr - self->memoryBlock) + size) <= self->size) { char* ptr = self->currentPtr; self->currentPtr += size; diff --git a/src/common/string_utilities.c b/src/common/string_utilities.c index 98dfd29b..702f9453 100644 --- a/src/common/string_utilities.c +++ b/src/common/string_utilities.c @@ -28,7 +28,7 @@ copySubString(char* startPos, char* endPos) { int newStringLength = endPos - startPos; - char* newString = (char*) malloc(newStringLength) + 1; + char* newString = (char*) GLOBAL_MALLOC(newStringLength) + 1; memcpy(newString, startPos, newStringLength); @@ -42,7 +42,7 @@ copyString(const char* string) { int newStringLength = strlen(string) + 1; - char* newString = (char*) malloc(newStringLength); + char* newString = (char*) GLOBAL_MALLOC(newStringLength); memcpy(newString, string, newStringLength); @@ -50,7 +50,7 @@ copyString(const char* string) } char* -copyStringToBuffer(char* string, char* buffer) +copyStringToBuffer(const char* string, char* buffer) { int newStringLength = strlen(string) + 1; @@ -63,7 +63,7 @@ copyStringToBuffer(char* string, char* buffer) char* createStringFromBuffer(uint8_t* buf, int size) { - char* newStr = (char*) malloc(size + 1); + char* newStr = (char*) GLOBAL_MALLOC(size + 1); memcpy(newStr, buf, size); newStr[size] = 0; @@ -110,7 +110,7 @@ createString(int count, ...) } va_end(ap); - newStr = (char*) malloc(newStringLength + 1); + newStr = (char*) GLOBAL_MALLOC(newStringLength + 1); currentPos = newStr; diff --git a/src/doxygen.config b/src/doxygen.config index 81704c69..84558522 100644 --- a/src/doxygen.config +++ b/src/doxygen.config @@ -16,9 +16,9 @@ DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "libIEC61850 0.7.8" +PROJECT_NAME = "libIEC61850" -PROJECT_NUMBER = +PROJECT_NUMBER = 0.8.2 PROJECT_BRIEF = "Open-source IEC 61850 MMS/GOOSE server and client library" @@ -207,29 +207,31 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = "iedclient/iec61850_client.h" -INPUT += "mms/iso_mms/common/mms_value.h" -INPUT += "common/linked_list.h" +INPUT = "iec61850/inc/iec61850_client.h" +INPUT += "mms/inc/mms_value.h" +INPUT += "common/inc/linked_list.h" INPUT += "doxygen/mainpage.doxygen" -INPUT += "iedserver/iec61850_server.h" -INPUT += "iedcommon/iec61850_common.h" -INPUT += "iedserver/model/model.h" -INPUT += "iedserver/model/dynamic_model.h" -INPUT += "iedserver/model/config_file_parser.h" -INPUT += "iedserver/model/cdc.h" +INPUT += "iec61850/inc/iec61850_server.h" +INPUT += "iec61850/inc/iec61850_common.h" +INPUT += "iec61850/inc/iec61850_model.h" +INPUT += "iec61850/inc/iec61850_dynamic_model.h" +INPUT += "iec61850/inc/iec61850_config_file_parser.h" +INPUT += "iec61850/inc/iec61850_cdc.h" INPUT += "goose/goose_subscriber.h" -INPUT += "mms/iso_mms/server/mms_device_model.h" -INPUT += "mms/iso_mms/common/mms_types.h" -INPUT += "mms/iso_mms/server/mms_server.h" -INPUT += "mms/iso_server/iso_server.h" -INPUT += "mms/iso_mms/server/mms_named_variable_list.h" -INPUT += "mms/iso_mms/client/mms_client_connection.h" -INPUT += "mms/iso_client/iso_connection_parameters.h" -INPUT += "hal/socket/socket.h" -INPUT += "hal/thread/thread.h" -INPUT += "hal/ethernet/ethernet.h" -INPUT += "hal/filesystem/filesystem.h" -INPUT += "hal/hal.h" +INPUT += "mms/inc/mms_device_model.h" +INPUT += "mms/inc/mms_types.h" +INPUT += "mms/inc/mms_server.h" +INPUT += "mms/inc/iso_server.h" +INPUT += "mms/inc/mms_named_variable_list.h" +INPUT += "mms/inc/mms_type_spec.h" +INPUT += "mms/inc/mms_types.h" +INPUT += "mms/inc/mms_client_connection.h" +INPUT += "mms/inc/iso_connection_parameters.h" +INPUT += "hal/inc/hal_socket.h" +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_ENCODING = UTF-8 diff --git a/src/goose/goose_publisher.c b/src/goose/goose_publisher.c index a4090c9c..371fde2e 100644 --- a/src/goose/goose_publisher.c +++ b/src/goose/goose_publisher.c @@ -24,11 +24,15 @@ #include "libiec61850_platform_includes.h" #include "stack_config.h" #include "goose_publisher.h" -#include "ethernet.h" +#include "hal_ethernet.h" #include "ber_encoder.h" #include "mms_server_internal.h" #include "mms_value_internal.h" +#ifndef DEBUG_GOOSE_PUBLISHER +#define DEBUG_GOOSE_PUBLISHER 0 +#endif + #define GOOSE_MAX_MESSAGE_SIZE 1518 static void @@ -36,7 +40,7 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, char* interf struct sGoosePublisher { uint8_t* buffer; - uint16_t appId; + //uint16_t appId; EthernetSocket ethernetSocket; int lengthField; int payloadStart; @@ -45,9 +49,9 @@ struct sGoosePublisher { char* goCBRef; char* dataSetRef; - uint16_t minTime; - uint16_t maxTime; - bool fixedOffs; + //uint16_t minTime; + //uint16_t maxTime; + //bool fixedOffs; uint32_t confRev; uint32_t stNum; @@ -63,7 +67,7 @@ struct sGoosePublisher { GoosePublisher GoosePublisher_create(CommParameters* parameters, char* interfaceID) { - GoosePublisher self = (GoosePublisher) calloc(1, sizeof(struct sGoosePublisher)); + GoosePublisher self = (GoosePublisher) GLOBAL_CALLOC(1, sizeof(struct sGoosePublisher)); prepareGooseBuffer(self, parameters, interfaceID); @@ -82,16 +86,16 @@ GoosePublisher_destroy(GoosePublisher self) MmsValue_delete(self->timestamp); if (self->goID != NULL) - free(self->goID); + GLOBAL_FREEMEM(self->goID); if (self->goCBRef != NULL) - free(self->goCBRef); + GLOBAL_FREEMEM(self->goCBRef); if (self->dataSetRef != NULL) - free(self->dataSetRef); + GLOBAL_FREEMEM(self->dataSetRef); - free(self->buffer); - free(self); + GLOBAL_FREEMEM(self->buffer); + GLOBAL_FREEMEM(self); } void @@ -190,7 +194,7 @@ prepareGooseBuffer(GoosePublisher self, CommParameters* parameters, char* interf else self->ethernetSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, dstAddr); - self->buffer = (uint8_t*) malloc(GOOSE_MAX_MESSAGE_SIZE); + self->buffer = (uint8_t*) GLOBAL_MALLOC(GOOSE_MAX_MESSAGE_SIZE); memcpy(self->buffer, dstAddr, 6); memcpy(self->buffer + 6, srcAddr, 6); @@ -246,6 +250,8 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe if (self->goID != NULL) goosePduLength += BerEncoder_determineEncodedStringSize(self->goID); + else + goosePduLength += BerEncoder_determineEncodedStringSize(self->goCBRef); uint32_t timeAllowedToLive = self->timeAllowedToLive; @@ -303,6 +309,8 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe /* Encode goID */ if (self->goID != NULL) bufPos = BerEncoder_encodeStringWithTag(0x83, self->goID, buffer, bufPos); + else + bufPos = BerEncoder_encodeStringWithTag(0x83, self->goCBRef, buffer, bufPos); /* Encode t */ bufPos = BerEncoder_encodeOctetString(0x84, self->timestamp->value.utcTime, 8, buffer, bufPos); @@ -363,6 +371,9 @@ GoosePublisher_publish(GoosePublisher self, LinkedList dataSet) self->buffer[lengthIndex] = gooseLength / 256; self->buffer[lengthIndex + 1] = gooseLength & 0xff; + if (DEBUG_GOOSE_PUBLISHER) + printf("GOOSE_PUBLISHER: send GOOSE message\n"); + Ethernet_sendPacket(self->ethernetSocket, self->buffer, self->payloadStart + payloadLength); return 0; diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c new file mode 100644 index 00000000..0b5e39d6 --- /dev/null +++ b/src/goose/goose_receiver.c @@ -0,0 +1,778 @@ +/* + * goose_receiver.c + * + * Copyright 2014 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 "goose_subscriber.h" +#include "hal_ethernet.h" +#include "hal_thread.h" + +#include "ber_decode.h" + +#include "mms_value.h" +#include "mms_value_internal.h" +#include "linked_list.h" + +#include "goose_receiver.h" +#include "goose_receiver_internal.h" + +#ifndef DEBUG_GOOSE_SUBSCRIBER +#define DEBUG_GOOSE_SUBSCRIBER 0 +#endif + +#define ETH_BUFFER_LENGTH 1518 + +#define ETH_P_GOOSE 0x88b8 + +struct sGooseReceiver { + bool running; + char* interfaceId; + uint8_t* buffer; + EthernetSocket ethSocket; + LinkedList subscriberList; +}; + + +GooseReceiver +GooseReceiver_create() +{ + GooseReceiver self = (GooseReceiver) GLOBAL_MALLOC(sizeof(struct sGooseReceiver)); + + if (self != NULL) { + self->running = false; + self->interfaceId = NULL; + self->buffer = (uint8_t*) GLOBAL_MALLOC(ETH_BUFFER_LENGTH); + self->ethSocket = NULL; + self->subscriberList = LinkedList_create(); + } + + return self; +} + +void +GooseReceiver_addSubscriber(GooseReceiver self, GooseSubscriber subscriber) +{ + LinkedList_add(self->subscriberList, (void*) subscriber); +} + +void +GooseReceiver_removeSubscriber(GooseReceiver self, GooseSubscriber subscriber) +{ + LinkedList_remove(self->subscriberList, (void*) subscriber); +} + + +void +GooseReceiver_setInterfaceId(GooseReceiver self, const char* interfaceId) +{ + if (self->interfaceId != NULL) + GLOBAL_FREEMEM(self->interfaceId); + + self->interfaceId = copyString(interfaceId); +} + + +void +GooseReceiver_setBackupListener(GooseReceiver self) +{ +} + +static void +createNewStringFromBufferElement(MmsValue* value, uint8_t* bufferSrc, int elementLength) +{ + value->value.visibleString.buf = (char*) GLOBAL_MALLOC(elementLength + 1); + memcpy(value->value.visibleString.buf, bufferSrc, elementLength); + value->value.visibleString.buf[elementLength] = 0; + value->value.visibleString.size = elementLength; +} + +static int +parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) +{ + int bufPos = 0; + int elementLength = 0; + + int elementIndex = 0; + + int maxIndex = MmsValue_getArraySize(dataSetValues) - 1; + + while (bufPos < allDataLength) { + uint8_t tag = buffer[bufPos++]; + + if (elementIndex > maxIndex) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: too much elements!\n"); + return 0; + } + + MmsValue* value = MmsValue_getElement(dataSetValues, elementIndex); + + bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); + + if (bufPos + elementLength > allDataLength) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: sub element is too large!\n"); + return 0; + } + + switch (tag) { + case 0x80: /* reserved for access result */ + printf("GOOSE_SUBSCRIBER: found reserved value (tag 0x80)!\n"); + break; + case 0xa1: /* array */ + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found array\n"); + if (MmsValue_getType(value) == MMS_ARRAY) { + if (!parseAllData(buffer + bufPos, elementLength, value)) + return -1; + } + break; + case 0xa2: /* structure */ + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found structure\n"); + if (MmsValue_getType(value) == MMS_STRUCTURE) { + if (!parseAllData(buffer + bufPos, elementLength, value)) + return -1; + } + break; + case 0x83: /* boolean */ + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found boolean\n"); + + if (MmsValue_getType(value) == MMS_BOOLEAN) { + MmsValue_setBoolean(value, BerDecoder_decodeBoolean(buffer, bufPos)); + } + else + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: message contains value of wrong type!\n"); + + break; + + case 0x84: /* BIT STRING */ + if (MmsValue_getType(value) == MMS_BIT_STRING) { + int padding = buffer[bufPos]; + int bitStringLength = (8 * (elementLength - 1)) - padding; + if (bitStringLength == value->value.bitString.size) { + memcpy(value->value.bitString.buf, buffer + bufPos + 1, + elementLength - 1); + } + else + if (DEBUG_GOOSE_SUBSCRIBER) + printf("bit-string is of wrong size"); + } + break; + case 0x85: /* integer */ + if (MmsValue_getType(value) == MMS_INTEGER) { + if (elementLength <= value->value.integer->maxSize) { + value->value.integer->size = elementLength; + memcpy(value->value.integer->octets, buffer + bufPos, elementLength); + } + } + break; + case 0x86: /* unsigned integer */ + if (MmsValue_getType(value) == MMS_UNSIGNED) { + if (elementLength <= value->value.integer->maxSize) { + value->value.integer->size = elementLength; + memcpy(value->value.integer->octets, buffer + bufPos, elementLength); + } + } + break; + case 0x87: /* Float */ + if (MmsValue_getType(value) == MMS_FLOAT) { + if (elementLength == 9) { + MmsValue_setDouble(value, BerDecoder_decodeDouble(buffer, bufPos)); + } + else if (elementLength == 5) { + MmsValue_setFloat(value, BerDecoder_decodeFloat(buffer, bufPos)); + } + } + break; + + case 0x89: /* octet string */ + if (MmsValue_getType(value) == MMS_OCTET_STRING) { + if (elementLength <= value->value.octetString.maxSize) { + value->value.octetString.size = elementLength; + memcpy(value->value.octetString.buf, buffer + bufPos, elementLength); + } + } + break; + case 0x8a: /* visible string */ + if (MmsValue_getType(value) == MMS_VISIBLE_STRING) { + + if (value->value.visibleString.buf != NULL) { + if ((int32_t) value->value.visibleString.size >= elementLength) { + memcpy(value->value.visibleString.buf, buffer + bufPos, elementLength); + value->value.visibleString.buf[elementLength] = 0; + } + else { + GLOBAL_FREEMEM(value->value.visibleString.buf); + + createNewStringFromBufferElement(value, buffer + bufPos, elementLength); + } + } + else + createNewStringFromBufferElement(value, buffer + bufPos, elementLength); + + } + break; + case 0x8c: /* binary time */ + if (MmsValue_getType(value) == MMS_BINARY_TIME) { + if ((elementLength == 4) || (elementLength == 6)) { + memcpy(value->value.binaryTime.buf, buffer + bufPos, elementLength); + } + } + break; + case 0x91: /* Utctime */ + if (elementLength == 8) { + if (MmsValue_getType(value) == MMS_UTC_TIME) { + MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); + } + else + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: message contains value of wrong type!\n"); + } + else + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: UTCTime element is of wrong size!\n"); + break; + default: + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: found unkown tag %02x\n", tag); + break; + } + + bufPos += elementLength; + + elementIndex++; + } + + return 1; +} + +static MmsValue* +parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLength, bool isStructure) +{ + int bufPos = 0; + int elementLength = 0; + + int elementIndex = 0; + + MmsValue* dataSetValues = NULL; + + while (bufPos < allDataLength) { + uint8_t tag = buffer[bufPos++]; + + bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); + + if (bufPos + elementLength > allDataLength) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: sub element is too large!\n"); + goto exit_with_error; + } + + switch (tag) { + case 0x80: /* reserved for access result */ + break; + case 0xa1: /* array */ + break; + case 0xa2: /* structure */ + break; + case 0x83: /* boolean */ + break; + case 0x84: /* BIT STRING */ + break; + case 0x85: /* integer */ + break; + case 0x86: /* unsigned integer */ + break; + case 0x87: /* Float */ + break; + case 0x89: /* octet string */ + break; + case 0x8a: /* visible string */ + break; + case 0x8c: /* binary time */ + break; + case 0x91: /* Utctime */ + break; + default: + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: found unkown tag %02x\n", tag); + goto exit_with_error; + } + + bufPos += elementLength; + + elementIndex++; + } + + if (isStructure) + dataSetValues = MmsValue_createEmptyStructure(elementIndex); + else + dataSetValues = MmsValue_createEmtpyArray(elementIndex); + + elementIndex = 0; + bufPos = 0; + + while (bufPos < allDataLength) { + uint8_t tag = buffer[bufPos++]; + + bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); + + if (bufPos + elementLength > allDataLength) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: sub element is too large!\n"); + goto exit_with_error; + } + + MmsValue* value = NULL; + + switch (tag) { + case 0xa1: /* array */ + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found array\n"); + + value = parseAllDataUnknownValue(self, buffer + bufPos, elementLength, false); + + if (value == NULL) + goto exit_with_error; + + break; + case 0xa2: /* structure */ + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found structure\n"); + + value = parseAllDataUnknownValue(self, buffer + bufPos, elementLength, true); + + if (value == NULL) + goto exit_with_error; + + break; + case 0x83: /* boolean */ + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found boolean\n"); + value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); + + break; + + case 0x84: /* BIT STRING */ + { + int padding = buffer[bufPos]; + int bitStringLength = (8 * (elementLength - 1)) - padding; + value = MmsValue_newBitString(bitStringLength); + memcpy(value->value.bitString.buf, buffer + bufPos + 1, elementLength - 1); + + } + break; + case 0x85: /* integer */ + value = MmsValue_newInteger(elementLength * 8); + memcpy(value->value.integer->octets, buffer + bufPos, elementLength); + break; + case 0x86: /* unsigned integer */ + value = MmsValue_newUnsigned(elementLength * 8); + memcpy(value->value.integer->octets, buffer + bufPos, elementLength); + break; + case 0x87: /* Float */ + if (elementLength == 9) + value = MmsValue_newDouble(BerDecoder_decodeDouble(buffer, bufPos)); + else if (elementLength == 5) + value = MmsValue_newFloat(BerDecoder_decodeFloat(buffer, bufPos)); + break; + + case 0x89: /* octet string */ + value = MmsValue_newOctetString(elementLength, elementLength); + memcpy(value->value.octetString.buf, buffer + bufPos, elementLength); + break; + case 0x8a: /* visible string */ + value = MmsValue_newVisibleStringFromByteArray(buffer + bufPos, elementLength); + break; + case 0x8c: /* binary time */ + if (elementLength == 4) + value = MmsValue_newBinaryTime(true); + else if (elementLength == 6) + value = MmsValue_newBinaryTime(false); + + if ((elementLength == 4) || (elementLength == 6)) + memcpy(value->value.binaryTime.buf, buffer + bufPos, elementLength); + + break; + case 0x91: /* Utctime */ + if (elementLength == 8) { + value = MmsValue_newUtcTime(0); + MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); + } + else + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: UTCTime element is of wrong size!\n"); + break; + default: + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: found unkown tag %02x\n", tag); + goto exit_with_error; + } + + bufPos += elementLength; + + if (value != NULL) { + MmsValue_setElement(dataSetValues, elementIndex, value); + elementIndex++; + } + } + + self->dataSetValuesSelfAllocated = true; + + return dataSetValues; + +exit_with_error: + + if (dataSetValues != NULL) + MmsValue_delete(dataSetValues); + + return NULL; +} + + +static int +parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) +{ + int bufPos = 0; + uint32_t timeAllowedToLive = 0; + uint32_t stNum = 0; + uint32_t sqNum = 0; + uint32_t confRev; + bool simulation = false; + bool ndsCom = false; + GooseSubscriber matchingSubscriber = NULL; + uint8_t* timestampBufPos = NULL; + uint8_t* dataSetBufferAddress = NULL; + int dataSetBufferLength = 0; + + uint32_t numberOfDatSetEntries = 0; + + if (buffer[bufPos++] == 0x61) { + int gooseLength; + bufPos = BerDecoder_decodeLength(buffer, &gooseLength, bufPos, apduLength); + + int gooseEnd = bufPos + gooseLength; + + while (bufPos < gooseEnd) { + int elementLength; + + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, apduLength); + + if (bufPos + elementLength > apduLength) { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: Malformed message: sub element is too large!\n"); + + goto exit_with_fault; + } + + if (bufPos == -1) + goto exit_with_fault; + + switch(tag) { + case 0x80: /* gocbRef */ + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found gocbRef\n"); + + { + LinkedList element = LinkedList_getNext(self->subscriberList); + + while (element != NULL) { + GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element); + + if (subscriber->goCBRefLen == elementLength) { + if (memcmp(subscriber->goCBRef, buffer + bufPos, elementLength) == 0) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: gocbRef is matching!\n"); + matchingSubscriber = subscriber; + break; + } + } + + element = LinkedList_getNext(element); + } + + if (matchingSubscriber == NULL) + return 0; + } + + break; + + case 0x81: /* timeAllowedToLive */ + + timeAllowedToLive = BerDecoder_decodeUint32(buffer, elementLength, bufPos); + + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found timeAllowedToLive %u\n", timeAllowedToLive); + + break; + + case 0x82: + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found dataSet\n"); + break; + + case 0x83: + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found goId\n"); + break; + + case 0x84: + timestampBufPos = buffer + bufPos; + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found timestamp\n"); + break; + + case 0x85: + stNum = BerDecoder_decodeUint32(buffer, elementLength, bufPos); + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found stNum: %u\n", stNum); + break; + + case 0x86: + sqNum = BerDecoder_decodeUint32(buffer, elementLength, bufPos); + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found sqNum: %u\n", sqNum); + break; + + case 0x87: + simulation = BerDecoder_decodeBoolean(buffer, bufPos); + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found simulation: %i\n", simulation); + break; + + case 0x88: + confRev = BerDecoder_decodeUint32(buffer, elementLength, bufPos); + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found confRev: %u\n", confRev); + break; + + case 0x89: + ndsCom = BerDecoder_decodeBoolean(buffer, bufPos); + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found ndsCom: %i\n", ndsCom); + break; + + case 0x8a: + numberOfDatSetEntries = BerDecoder_decodeUint32(buffer, elementLength, bufPos); + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found number of entries: %u\n", numberOfDatSetEntries); + break; + + case 0xab: + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Found all data with length: %i\n", elementLength); + dataSetBufferAddress = buffer + bufPos; + dataSetBufferLength = elementLength; + break; + + default: + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Unknown tag %02x\n", tag); + break; + } + + bufPos += elementLength; + } + + if (matchingSubscriber != NULL) { + + matchingSubscriber->timeAllowedToLive = timeAllowedToLive; + matchingSubscriber->confRev = confRev; + matchingSubscriber->ndsCom = ndsCom; + matchingSubscriber->simulation = simulation; + MmsValue_setUtcTimeByBuffer(matchingSubscriber->timestamp, timestampBufPos); + + if (matchingSubscriber->dataSetValues == NULL) + matchingSubscriber->dataSetValues = parseAllDataUnknownValue(matchingSubscriber, dataSetBufferAddress, dataSetBufferLength, false); + else + parseAllData(dataSetBufferAddress, dataSetBufferLength, matchingSubscriber->dataSetValues); + + bool isValid = true; + + if (matchingSubscriber->stNum == stNum) { + if (matchingSubscriber->sqNum >= sqNum) { + isValid = false; + } + } + + matchingSubscriber->stateValid = isValid; + + matchingSubscriber->stNum = stNum; + matchingSubscriber->sqNum = sqNum; + + matchingSubscriber->invalidityTime = Hal_getTimeInMs() + timeAllowedToLive; + + if (matchingSubscriber->listener != NULL) + matchingSubscriber->listener(matchingSubscriber, matchingSubscriber->listenerParameter); + + return 1; + } + + return 0; + } + +exit_with_fault: + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Invalid goose payload\n"); + return -1; +} + + +static void +parseGooseMessage(GooseReceiver self, int numbytes) +{ + int bufPos; + bool subscriberFound = false; + uint8_t* buffer = self->buffer; + + if (numbytes < 22) return; + + /* skip ethernet addresses */ + bufPos = 12; + int headerLength = 14; + + /* check for VLAN tag */ + if ((buffer[bufPos] == 0x81) && (buffer[bufPos + 1] == 0x00)) { + bufPos += 4; /* skip VLAN tag */ + headerLength += 4; + } + + /* check for GOOSE Ethertype */ + if (buffer[bufPos++] != 0x88) + return; + if (buffer[bufPos++] != 0xb8) + return; + + uint16_t appId; + + appId = buffer[bufPos++] * 0x100; + appId += buffer[bufPos++]; + + uint16_t length; + + length = buffer[bufPos++] * 0x100; + length += buffer[bufPos++]; + + /* skip reserved fields */ + bufPos += 4; + + int apduLength = length - 8; + + if (numbytes != length + headerLength) { + if (DEBUG) + printf("GOOSE_SUBSCRIBER: Invalid PDU size\n"); + return; + } + + if (DEBUG_GOOSE_SUBSCRIBER) { + printf("GOOSE_SUBSCRIBER: GOOSE message:\nGOOSE_SUBSCRIBER: ----------------\n"); + printf("GOOSE_SUBSCRIBER: APPID: %u\n", appId); + printf("GOOSE_SUBSCRIBER: LENGTH: %u\n", length); + printf("GOOSE_SUBSCRIBER: APDU length: %i\n", apduLength); + } + + + // check if there is an interested subscriber + LinkedList element = LinkedList_getNext(self->subscriberList); + + while (element != NULL) { + GooseSubscriber subscriber = (GooseSubscriber) LinkedList_getData(element); + + if (subscriber->appId == appId) { + subscriberFound = true; + break; + } + + element = LinkedList_getNext(element); + } + + if (subscriberFound) + parseGoosePayload(self, buffer + bufPos, apduLength); + else { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: GOOSE message ignored due to unknown APPID value\n"); + } +} + + +static void +gooseReceiverLoop(void* threadParameter) +{ + GooseReceiver self = (GooseReceiver) threadParameter; + + GooseReceiver_startThreadless(self); + + while (self->running) { + + GooseReceiver_tick(self); + + Thread_sleep(1); + } + + GooseReceiver_stopThreadless(self); +} + + +// start GOOSE receiver in a separate thread +void +GooseReceiver_start(GooseReceiver self) +{ + Thread thread = Thread_create((ThreadExecutionFunction) gooseReceiverLoop, (void*) self, true); + + if (thread != NULL) { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: GOOSE receiver started for interface %s\n", self->interfaceId); + + Thread_start(thread); + } + else { + if (DEBUG_GOOSE_SUBSCRIBER) + printf("GOOSE_SUBSCRIBER: Starting GOOSE receiver failed for interface %s\n", self->interfaceId); + } + +} + +void +GooseReceiver_stop(GooseReceiver self) +{ + self->running = false; +} + +void +GooseReceiver_destroy(GooseReceiver self) +{ + LinkedList_destroyDeep(self->subscriberList, + (LinkedListValueDeleteFunction) GooseSubscriber_destroy); + + GLOBAL_FREEMEM(self->buffer); + GLOBAL_FREEMEM(self); +} + +/*************************************** + * Functions for non-threaded operation + ***************************************/ +void +GooseReceiver_startThreadless(GooseReceiver self) +{ + + if (self->interfaceId == NULL) + self->ethSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, NULL); + else + self->ethSocket = Ethernet_createSocket(self->interfaceId, NULL); + + Ethernet_setProtocolFilter(self->ethSocket, ETH_P_GOOSE); + + self->running = true; +} + +void +GooseReceiver_stopThreadless(GooseReceiver self) +{ + Ethernet_destroySocket(self->ethSocket); + + self->running = false; +} + +// call after reception of ethernet frame and periodically to to house keeping tasks +void +GooseReceiver_tick(GooseReceiver self) +{ + int packetSize = Ethernet_receivePacket(self->ethSocket, self->buffer, ETH_BUFFER_LENGTH); + + if (packetSize > 0) + parseGooseMessage(self, packetSize); +} diff --git a/src/goose/goose_receiver.h b/src/goose/goose_receiver.h new file mode 100644 index 00000000..c57f1d72 --- /dev/null +++ b/src/goose/goose_receiver.h @@ -0,0 +1,70 @@ +/* + * goose_receiver.h + * + * Copyright 2014 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 GOOSE_RECEIVER_H_ +#define GOOSE_RECEIVER_H_ + +#include + +typedef struct sGooseReceiver* GooseReceiver; + +GooseReceiver +GooseReceiver_create(void); + +void +GooseReceiver_setInterfaceId(GooseReceiver self, const char* interfaceId); + +void +GooseReceiver_addSubscriber(GooseReceiver self, GooseSubscriber subscriber); + +void +GooseReceiver_removeSubscriber(GooseReceiver self, GooseSubscriber subscriber); + +// call backup listener if message is not handled by a subscriber +void +GooseReceiver_setBackupListener(GooseReceiver self); + +// start GOOSE receiver in a separate thread +void +GooseReceiver_start(GooseReceiver self); + +void +GooseReceiver_stop(GooseReceiver self); + +void +GooseReceiver_destroy(GooseReceiver self); + +/*************************************** + * Functions for non-threaded operation + ***************************************/ +void +GooseReceiver_startThreadless(GooseReceiver self); + +void +GooseReceiver_stopThreadless(GooseReceiver self); + +// call after reception of ethernet frame and periodically to to house keeping tasks +void +GooseReceiver_tick(GooseReceiver self); + +#endif /* GOOSE_RECEIVER_H_ */ diff --git a/src/goose/goose_receiver_internal.h b/src/goose/goose_receiver_internal.h new file mode 100644 index 00000000..e595da00 --- /dev/null +++ b/src/goose/goose_receiver_internal.h @@ -0,0 +1,62 @@ +/* + * goose_receiver_internal.h + * + * Copyright 2014 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 GOOSE_RECEIVER_INTERNAL_H_ +#define GOOSE_RECEIVER_INTERNAL_H_ + + +#define ETH_BUFFER_LENGTH 1518 + +#define ETH_P_GOOSE 0x88b8 + +#ifndef DEBUG_GOOSE_SUBSCRIBER +#define DEBUG_GOOSE_SUBSCRIBER 0 +#endif + + +struct sGooseSubscriber { + char* goCBRef; + int goCBRefLen; + uint32_t timeAllowedToLive; + uint32_t stNum; + uint32_t sqNum; + uint32_t confRev; + MmsValue* timestamp; + bool simulation; + bool ndsCom; + + uint64_t invalidityTime; + bool stateValid; + + int32_t appId; /* APPID or -1 if APPID should be ignored */ + + MmsValue* dataSetValues; + bool dataSetValuesSelfAllocated; + + GooseListener listener; + void* listenerParameter; +}; + + + +#endif /* GOOSE_RECEIVER_INTERNAL_H_ */ diff --git a/src/goose/goose_subscriber.c b/src/goose/goose_subscriber.c index ff615087..f1d1e8ea 100644 --- a/src/goose/goose_subscriber.c +++ b/src/goose/goose_subscriber.c @@ -25,622 +25,20 @@ #include "stack_config.h" #include "goose_subscriber.h" -#include "ethernet.h" -#include "thread.h" +#include "hal_ethernet.h" +#include "hal_thread.h" #include "ber_decode.h" #include "mms_value.h" #include "mms_value_internal.h" -#define ETH_BUFFER_LENGTH 1518 - -#define ETH_P_GOOSE 0x88b8 - -struct sGooseSubscriber { - char* goCBRef; - int goCBRefLen; - uint32_t timeAllowedToLive; - uint32_t stNum; - uint32_t sqNum; - uint32_t confRev; - MmsValue* timestamp; - bool simulation; - bool ndsCom; - - int32_t appId; /* APPID or -1 if APPID should be ignored */ - - MmsValue* dataSetValues; - bool dataSetValuesSelfAllocated; - - GooseListener listener; - void* listenerParameter; - bool running; - Thread receiver; - char* interfaceId; -}; - -static void -createNewStringFromBufferElement(MmsValue* value, uint8_t* bufferSrc, int elementLength) -{ - value->value.visibleString.buf = (char*) malloc(elementLength + 1); - memcpy(value->value.visibleString.buf, bufferSrc, elementLength); - value->value.visibleString.buf[elementLength] = 0; - value->value.visibleString.size = elementLength; -} - -static int -parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) -{ - int bufPos = 0; - int elementLength = 0; - - int elementIndex = 0; - - int maxIndex = MmsValue_getArraySize(dataSetValues) - 1; - - while (bufPos < allDataLength) { - uint8_t tag = buffer[bufPos++]; - - if (elementIndex > maxIndex) { - if (DEBUG) printf("Malformed message: too much elements!\n"); - return 0; - } - - MmsValue* value = MmsValue_getElement(dataSetValues, elementIndex); - - bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); - - if (bufPos + elementLength > allDataLength) { - if (DEBUG) printf("Malformed message: sub element is to large!\n"); - return 0; - } - - switch (tag) { - case 0x80: /* reserved for access result */ - printf(" found reserved value (tag 0x80)!\n"); - break; - case 0xa1: /* array */ - if (DEBUG) printf(" found array\n"); - if (MmsValue_getType(value) == MMS_ARRAY) { - if (!parseAllData(buffer + bufPos, elementLength, value)) - return -1; - } - break; - case 0xa2: /* structure */ - if (DEBUG) printf(" found structure\n"); - if (MmsValue_getType(value) == MMS_STRUCTURE) { - if (!parseAllData(buffer + bufPos, elementLength, value)) - return -1; - } - break; - case 0x83: /* boolean */ - if (DEBUG) printf(" found boolean\n"); - - if (MmsValue_getType(value) == MMS_BOOLEAN) { - MmsValue_setBoolean(value, BerDecoder_decodeBoolean(buffer, bufPos)); - } - else - if (DEBUG) printf(" message contains value of wrong type!\n"); - - break; - - case 0x84: /* BIT STRING */ - if (MmsValue_getType(value) == MMS_BIT_STRING) { - int padding = buffer[bufPos]; - int bitStringLength = (8 * (elementLength - 1)) - padding; - if (bitStringLength == value->value.bitString.size) { - memcpy(value->value.bitString.buf, buffer + bufPos + 1, - elementLength - 1); - } - else - printf("bit-string is of wrong size"); - } - break; - case 0x85: /* integer */ - if (MmsValue_getType(value) == MMS_INTEGER) { - if (elementLength <= value->value.integer->maxSize) { - value->value.integer->size = elementLength; - memcpy(value->value.integer->octets, buffer + bufPos, elementLength); - } - } - break; - case 0x86: /* unsigned integer */ - if (MmsValue_getType(value) == MMS_UNSIGNED) { - if (elementLength <= value->value.integer->maxSize) { - value->value.integer->size = elementLength; - memcpy(value->value.integer->octets, buffer + bufPos, elementLength); - } - } - break; - case 0x87: /* Float */ - if (MmsValue_getType(value) == MMS_FLOAT) { - if (elementLength == 9) { - MmsValue_setDouble(value, BerDecoder_decodeDouble(buffer, bufPos)); - } - else if (elementLength == 5) { - MmsValue_setFloat(value, BerDecoder_decodeFloat(buffer, bufPos)); - } - } - break; - - case 0x89: /* octet string */ - if (MmsValue_getType(value) == MMS_OCTET_STRING) { - if (elementLength <= value->value.octetString.maxSize) { - value->value.octetString.size = elementLength; - memcpy(value->value.octetString.buf, buffer + bufPos, elementLength); - } - } - break; - case 0x8a: /* visible string */ - if (MmsValue_getType(value) == MMS_VISIBLE_STRING) { - - if (value->value.visibleString.buf != NULL) { - if ((int32_t) value->value.visibleString.size >= elementLength) { - memcpy(value->value.visibleString.buf, buffer + bufPos, elementLength); - value->value.visibleString.buf[elementLength] = 0; - } - else { - free(value->value.visibleString.buf); - - createNewStringFromBufferElement(value, buffer + bufPos, elementLength); - } - } - else - createNewStringFromBufferElement(value, buffer + bufPos, elementLength); - - } - break; - case 0x8c: /* binary time */ - if (MmsValue_getType(value) == MMS_BINARY_TIME) { - if ((elementLength == 4) || (elementLength == 6)) { - memcpy(value->value.binaryTime.buf, buffer + bufPos, elementLength); - } - } - break; - case 0x91: /* Utctime */ - if (elementLength == 8) { - if (MmsValue_getType(value) == MMS_UTC_TIME) { - MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); - } - else - if (DEBUG) printf(" message contains value of wrong type!\n"); - } - else - if (DEBUG) printf(" UTCTime element is of wrong size!\n"); - break; - default: - printf(" found unkown tag %02x\n", tag); - break; - } - - bufPos += elementLength; - - elementIndex++; - } - - return 1; -} - -static MmsValue* -parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLength, bool isStructure) -{ - int bufPos = 0; - int elementLength = 0; - - int elementIndex = 0; - - MmsValue* dataSetValues = NULL; - - while (bufPos < allDataLength) { - uint8_t tag = buffer[bufPos++]; - - bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); - - if (bufPos + elementLength > allDataLength) { - if (DEBUG) printf("Malformed message: sub element is to large!\n"); - goto exit_with_error; - } - - switch (tag) { - case 0x80: /* reserved for access result */ - break; - case 0xa1: /* array */ - break; - case 0xa2: /* structure */ - break; - case 0x83: /* boolean */ - break; - case 0x84: /* BIT STRING */ - break; - case 0x85: /* integer */ - break; - case 0x86: /* unsigned integer */ - break; - case 0x87: /* Float */ - break; - case 0x89: /* octet string */ - break; - case 0x8a: /* visible string */ - break; - case 0x8c: /* binary time */ - break; - case 0x91: /* Utctime */ - break; - default: - printf(" found unkown tag %02x\n", tag); - goto exit_with_error; - } - - bufPos += elementLength; - - elementIndex++; - } - - if (isStructure) - dataSetValues = MmsValue_createEmptyStructure(elementIndex); - else - dataSetValues = MmsValue_createEmtpyArray(elementIndex); - - elementIndex = 0; - bufPos = 0; - - while (bufPos < allDataLength) { - uint8_t tag = buffer[bufPos++]; - - bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); - - if (bufPos + elementLength > allDataLength) { - if (DEBUG) printf("Malformed message: sub element is too large!\n"); - goto exit_with_error; - } - - MmsValue* value = NULL; - - switch (tag) { - case 0xa1: /* array */ - if (DEBUG) printf(" found array\n"); - - value = parseAllDataUnknownValue(self, buffer + bufPos, elementLength, false); - - if (value == NULL) - goto exit_with_error; - - break; - case 0xa2: /* structure */ - if (DEBUG) printf(" found structure\n"); - - value = parseAllDataUnknownValue(self, buffer + bufPos, elementLength, true); - - if (value == NULL) - goto exit_with_error; - - break; - case 0x83: /* boolean */ - if (DEBUG) printf(" found boolean\n"); - value = MmsValue_newBoolean(BerDecoder_decodeBoolean(buffer, bufPos)); - - break; - - case 0x84: /* BIT STRING */ - { - int padding = buffer[bufPos]; - int bitStringLength = (8 * (elementLength - 1)) - padding; - value = MmsValue_newBitString(bitStringLength); - memcpy(value->value.bitString.buf, buffer + bufPos + 1, elementLength - 1); - - } - break; - case 0x85: /* integer */ - value = MmsValue_newInteger(elementLength * 8); - memcpy(value->value.integer->octets, buffer + bufPos, elementLength); - break; - case 0x86: /* unsigned integer */ - value = MmsValue_newUnsigned(elementLength * 8); - memcpy(value->value.integer->octets, buffer + bufPos, elementLength); - break; - case 0x87: /* Float */ - if (elementLength == 9) - value = MmsValue_newDouble(BerDecoder_decodeDouble(buffer, bufPos)); - else if (elementLength == 5) - value = MmsValue_newFloat(BerDecoder_decodeFloat(buffer, bufPos)); - break; - - case 0x89: /* octet string */ - value = MmsValue_newOctetString(elementLength, elementLength); - memcpy(value->value.octetString.buf, buffer + bufPos, elementLength); - break; - case 0x8a: /* visible string */ - value = MmsValue_newVisibleStringFromByteArray(buffer + bufPos, elementLength); - break; - case 0x8c: /* binary time */ - if (elementLength == 4) - value = MmsValue_newBinaryTime(true); - else if (elementLength == 6) - value = MmsValue_newBinaryTime(false); - - if ((elementLength == 4) || (elementLength == 6)) - memcpy(value->value.binaryTime.buf, buffer + bufPos, elementLength); - - break; - case 0x91: /* Utctime */ - if (elementLength == 8) { - value = MmsValue_newUtcTime(0); - MmsValue_setUtcTimeByBuffer(value, buffer + bufPos); - } - else - if (DEBUG) printf(" UTCTime element is of wrong size!\n"); - break; - default: - if (DEBUG) printf(" found unkown tag %02x\n", tag); - goto exit_with_error; - } - - bufPos += elementLength; - - if (value != NULL) { - MmsValue_setElement(dataSetValues, elementIndex, value); - elementIndex++; - } - } - - self->dataSetValuesSelfAllocated = true; - - return dataSetValues; - -exit_with_error: - - if (dataSetValues != NULL) - MmsValue_delete(dataSetValues); - - return NULL; -} - - -static int -parseGoosePayload(uint8_t* buffer, int apduLength, GooseSubscriber self) -{ - int bufPos = 0; - uint32_t timeAllowedToLive = 0; - uint32_t stNum = 0; - uint32_t sqNum = 0; - uint32_t confRev; - bool simulation = false; - bool ndsCom = false; - bool isMatching = false; - - uint32_t numberOfDatSetEntries = 0; - - if (buffer[bufPos++] == 0x61) { - int gooseLength; - bufPos = BerDecoder_decodeLength(buffer, &gooseLength, bufPos, apduLength); - - int gooseEnd = bufPos + gooseLength; - - while (bufPos < gooseEnd) { - int elementLength; - - uint8_t tag = buffer[bufPos++]; - bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, apduLength); - - if (bufPos + elementLength > apduLength) { - if (DEBUG) printf("Malformed message: sub element is to large!\n"); - goto exit_with_fault; - } - - if (bufPos == -1) - goto exit_with_fault; - - switch(tag) { - case 0x80: /* gocbRef */ - if (DEBUG) printf(" Found gocbRef\n"); - - if (self->goCBRefLen == elementLength) { - if (memcmp(self->goCBRef, buffer + bufPos, elementLength) == 0) { - if (DEBUG) printf(" gocbRef is matching!\n"); - isMatching = true; - } - else return 0; - } - else - return 0; - - break; - - case 0x81: /* timeAllowedToLive */ - - timeAllowedToLive = BerDecoder_decodeUint32(buffer, elementLength, bufPos); - - if (DEBUG) printf(" Found timeAllowedToLive %u\n", timeAllowedToLive); - - break; - - case 0x82: - if (DEBUG) printf(" Found dataSet\n"); - break; - - case 0x83: - if (DEBUG) printf(" Found goId\n"); - break; - - case 0x84: - MmsValue_setUtcTimeByBuffer(self->timestamp, buffer + bufPos); - if (DEBUG) printf(" Found timestamp t: %" PRIu64 "\n", MmsValue_getUtcTimeInMs(self->timestamp)); - break; - - case 0x85: - stNum = BerDecoder_decodeUint32(buffer, elementLength, bufPos); - if (DEBUG) printf(" Found stNum: %u\n", stNum); - break; - - case 0x86: - sqNum = BerDecoder_decodeUint32(buffer, elementLength, bufPos); - if (DEBUG) printf(" Found sqNum: %u\n", sqNum); - break; - - case 0x87: - simulation = BerDecoder_decodeBoolean(buffer, bufPos); - if (DEBUG) printf(" Found simulation: %i\n", simulation); - break; - - case 0x88: - confRev = BerDecoder_decodeUint32(buffer, elementLength, bufPos); - if (DEBUG) printf(" Found confRev: %u\n", confRev); - break; - - case 0x89: - ndsCom = BerDecoder_decodeBoolean(buffer, bufPos); - if (DEBUG) printf(" Found ndsCom: %i\n", ndsCom); - break; - - case 0x8a: - numberOfDatSetEntries = BerDecoder_decodeUint32(buffer, elementLength, bufPos); - if (DEBUG) printf(" Found number of entries: %u\n", numberOfDatSetEntries); - break; - - case 0xab: - if (DEBUG) printf(" Found all data with length: %i\n", elementLength); - - if (self->dataSetValues == NULL) - self->dataSetValues = parseAllDataUnknownValue(self, buffer + bufPos, elementLength, false); - else - parseAllData(buffer + bufPos, elementLength, self->dataSetValues); - break; - - default: - if (DEBUG) printf(" Unknown tag %02x\n", tag); - break; - } - - bufPos += elementLength; - } - - if (isMatching) { - self->stNum = stNum; - self->sqNum = sqNum; - self->timeAllowedToLive = timeAllowedToLive; - self->confRev = confRev; - self->ndsCom = ndsCom; - self->simulation = simulation; - - return 1; - } - - return 0; - } - -exit_with_fault: - if (DEBUG) printf("Invalid goose payload\n"); - return -1; -} - -static int -parseGooseMessage(uint8_t* buffer, int numbytes, GooseSubscriber subscriber) -{ - int bufPos; - - if (numbytes < 22) return -1; - - /* skip ethernet addresses */ - bufPos = 12; - int headerLength = 14; - - /* check for VLAN tag */ - if ((buffer[bufPos] == 0x81) && (buffer[bufPos + 1] == 0x00)) { - bufPos += 4; /* skip VLAN tag */ - headerLength += 4; - } - - /* check for GOOSE Ethertype */ - if (buffer[bufPos++] != 0x88) - return -1; - if (buffer[bufPos++] != 0xb8) - return -1; - - uint16_t appId; - - appId = buffer[bufPos++] * 0x100; - appId += buffer[bufPos++]; - - uint16_t length; - - length = buffer[bufPos++] * 0x100; - length += buffer[bufPos++]; - - /* skip reserved fields */ - bufPos += 4; - - int apduLength = length - 8; - - if (numbytes != length + headerLength) { - if (DEBUG) - printf("Invalid PDU size\n"); - return -1; - } - - if (DEBUG) { - printf("GOOSE message:\n----------------\n"); - printf(" APPID: %u\n", appId); - printf(" LENGTH: %u\n", length); - printf(" APDU length: %i\n", apduLength); - } - - if (subscriber->appId >= 0) { - if (appId != (uint16_t) subscriber->appId) { - if (DEBUG) - printf("GOOSE message ignored due to wrong APPID value\n"); - - return 0; - } - } - - return parseGoosePayload(buffer + bufPos, apduLength, subscriber); -} - -static void -gooseSubscriberLoop(void* threadParameter) -{ - GooseSubscriber self = (GooseSubscriber) threadParameter; - - uint8_t* buffer = (uint8_t*) malloc(ETH_BUFFER_LENGTH); - - EthernetSocket socket; - - if (self->interfaceId == NULL) - socket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, NULL); - else - socket = Ethernet_createSocket(self->interfaceId, NULL); - - Ethernet_setProtocolFilter(socket, ETH_P_GOOSE); - - int running = 1; - - while (running) { - - int packetSize = Ethernet_receivePacket(socket, buffer, ETH_BUFFER_LENGTH); - - if (packetSize > 0) { - if (parseGooseMessage(buffer, packetSize, self) == 1) { - if (self->listener != NULL) { - self->listener(self, self->listenerParameter); - } - } - } - - Thread_sleep(1); - - running = self->running; - } - - free(buffer); - - Ethernet_destroySocket(socket); -} +#include "goose_receiver_internal.h" GooseSubscriber GooseSubscriber_create(char* goCbRef, MmsValue* dataSetValues) { - GooseSubscriber self = (GooseSubscriber) calloc(1, sizeof(struct sGooseSubscriber)); + GooseSubscriber self = (GooseSubscriber) GLOBAL_CALLOC(1, sizeof(struct sGooseSubscriber)); self->goCBRef = copyString(goCbRef); self->goCBRefLen = strlen(goCbRef); @@ -650,59 +48,40 @@ GooseSubscriber_create(char* goCbRef, MmsValue* dataSetValues) if (dataSetValues != NULL) self->dataSetValuesSelfAllocated = false; - self->running = false; - self->appId = -1; return self; } -void -GooseSubscriber_setAppId(GooseSubscriber self, uint16_t appId) +bool +GooseSubscriber_isValid(GooseSubscriber self) { - self->appId = (int32_t) appId; -} + if (self->stateValid == false) + return false; -void -GooseSubscriber_setInterfaceId(GooseSubscriber self, char* interfaceId) -{ - self->interfaceId = copyString(interfaceId); -} + if (Hal_getTimeInMs() > self->invalidityTime) + return false; -void -GooseSubscriber_subscribe(GooseSubscriber self) -{ - Thread thread = Thread_create((ThreadExecutionFunction) gooseSubscriberLoop, self, false); - self->receiver = thread; - self->running = true; - Thread_start(thread); + return true; } void -GooseSubscriber_unsubscribe(GooseSubscriber self) +GooseSubscriber_setAppId(GooseSubscriber self, uint16_t appId) { - if (self->running) { - self->running = false; - Thread_destroy(self->receiver); - } + self->appId = (int32_t) appId; } void GooseSubscriber_destroy(GooseSubscriber self) { - GooseSubscriber_unsubscribe(self); - - free(self->goCBRef); + GLOBAL_FREEMEM(self->goCBRef); MmsValue_delete(self->timestamp); if (self->dataSetValuesSelfAllocated) MmsValue_delete(self->dataSetValues); - if (self->interfaceId != NULL) - free(self->interfaceId); - - free(self); + GLOBAL_FREEMEM(self); } void diff --git a/src/goose/goose_subscriber.h b/src/goose/goose_subscriber.h index ade78e48..f9f09742 100644 --- a/src/goose/goose_subscriber.h +++ b/src/goose/goose_subscriber.h @@ -1,7 +1,7 @@ /* * goose_subscriber.h * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -70,6 +70,9 @@ typedef void (*GooseListener)(GooseSubscriber subscriber, void* parameter); GooseSubscriber GooseSubscriber_create(char* goCbRef, MmsValue* dataSetValues); +//char* +//GooseSubscriber_getGoCbRef(GooseSubscriber self); + /** * \brief set the APPID used by the subscriber to filter relevant messages. * @@ -82,30 +85,23 @@ void GooseSubscriber_setAppId(GooseSubscriber self, uint16_t appId); /** - * \brief set the ethernet interface that should be used. + * \brief Check if subscriber state is valid * - * \param self GooseSubscriber instance to operate on. - * \param interfaceId the id of the interface (e.g. a network device name like eth0 - * for linux or a numerical index for windows) - */ -void -GooseSubscriber_setInterfaceId(GooseSubscriber self, char* interfaceId); - -/** - * \brief Start listening to GOOSE messages + * A GOOSE subscriber is valid if TimeAllowedToLive timeout is not elapsed and GOOSE + * message were received with correct state and sequence ID. * - * \param self GooseSubscriber instance to operate on. */ -void -GooseSubscriber_subscribe(GooseSubscriber self); +bool +GooseSubscriber_isValid(GooseSubscriber self); + +//uint16_t +//GooseSubscriber_getAppId(GooseSubscriber self); -/** - * \brief Stop listening to GOOSE messages - * - * \param self GooseSubscriber instance to operate on. - */ void -GooseSubscriber_unsubscribe(GooseSubscriber self); +GooseSubscriber_setGoId(GooseSubscriber self, const char* goId); + +//char* +//GooseSubscriber_getGoId(GooseSubscriber self); void GooseSubscriber_destroy(GooseSubscriber self); @@ -138,6 +134,17 @@ GooseSubscriber_getTimeAllowedToLive(GooseSubscriber self); uint64_t GooseSubscriber_getTimestamp(GooseSubscriber self); +/** + * \brief get the data set values received with the last report + * + * Note: To prevent data corruption. The MmsValue instance received should + * only be used inside of the callback function, when the GOOSE receiver is + * running in a separate thread. + * + * \param self GooseSubscriber instance to operate on. + * + * \return MmsValue instance of the report data set + */ MmsValue* GooseSubscriber_getDataSetValues(GooseSubscriber self); diff --git a/src/hal/ethernet/bsd/ethernet_bsd.c b/src/hal/ethernet/bsd/ethernet_bsd.c index b9c8fe3c..b1eaf296 100644 --- a/src/hal/ethernet/bsd/ethernet_bsd.c +++ b/src/hal/ethernet/bsd/ethernet_bsd.c @@ -36,7 +36,8 @@ #include #include -#include "ethernet.h" +#include "libiec61850_platform_includes.h" +#include "hal_ethernet.h" struct sEthernetSocket { int bpf; // BPF device handle. @@ -181,7 +182,7 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) */ }; - EthernetSocket self = calloc(1, sizeof(struct sEthernetSocket)); + EthernetSocket self = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket)); if (!self) { printf("Could not allocate socket descriptor!\n"); @@ -189,7 +190,7 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) } // Copy default BPF filter program into descriptor. - self->bpfProgram.bf_insns = calloc(1, sizeof(destAddrFiltCode)); + self->bpfProgram.bf_insns = GLOBAL_CALLOC(1, sizeof(destAddrFiltCode)); if (!self->bpfProgram.bf_insns) { printf("Could not allocate memory for BPF filter program!\n"); @@ -212,8 +213,8 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) if (self->bpf == -1) { printf("Error opening BPF file handle!\n"); - free(self->bpfProgram.bf_insns); - free(self); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self); return NULL; } @@ -223,8 +224,8 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) if (fcntl(self->bpf, F_SETFL, &optval) == -1) { printf("Unable to change to non-blocking mode!\n"); - free(self->bpfProgram.bf_insns); - free(self); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self); return NULL; } @@ -233,8 +234,8 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) if (ioctl(self->bpf, BIOCSETIF, &ifr)) { printf("Unable to select interface %s!\n", interfaceId); - free(self->bpfProgram.bf_insns); - free(self); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self); return NULL; } @@ -242,8 +243,8 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) if (ioctl(self->bpf, BIOCIMMEDIATE, &self->bpfBufferSize) == -1) { printf("Unable to activate immediate mode!\n"); - free(self->bpfProgram.bf_insns); - free(self); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self); return NULL; } @@ -251,18 +252,18 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) if (ioctl(self->bpf, BIOCGBLEN, &self->bpfBufferSize) == -1) { printf("Unable to get BPF buffer lenght!\n"); - free(self->bpfProgram.bf_insns); - free(self); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self); return NULL; } // Allocate a buffer for the message reception. - self->bpfBuffer = calloc(1, self->bpfBufferSize); + self->bpfBuffer = GLOBAL_CALLOC(1, self->bpfBufferSize); if (!self->bpfBuffer) { printf("Unable to allocate BPF RX buffer!\n"); - free(self->bpfProgram.bf_insns); - free(self); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self); return NULL; } self->bpfPositon = self->bpfBuffer; @@ -273,9 +274,9 @@ EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) if (ioctl(self->bpf, BIOCPROMISC, &optval) == -1) { printf("Unable to activate promiscous mode!\n"); - free(self->bpfProgram.bf_insns); - free(self->bpfBuffer); - free(self); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self->bpfBuffer); + GLOBAL_FREEMEM(self); return NULL; } @@ -313,7 +314,7 @@ int Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize) struct bpf_hdr *header = (struct bpf_hdr *)(self->bpfPositon); // Check if the target buffer is big enough to hold the received ethernet frame. - if (bufferSize >= header->bh_caplen) + if ((unsigned int) bufferSize >= header->bh_caplen) { // Copy the frame to the target buffer. memcpy(buffer, self->bpfPositon + header->bh_hdrlen, header->bh_caplen); @@ -346,7 +347,14 @@ void Ethernet_destroySocket(EthernetSocket self) close(self->bpf); // Free all dynamic resources used by the ethernet socket. - free(self->bpfBuffer); - free(self->bpfProgram.bf_insns); - free(self); + GLOBAL_FREEMEM(self->bpfBuffer); + GLOBAL_FREEMEM(self->bpfProgram.bf_insns); + GLOBAL_FREEMEM(self); } + +bool +Ethernet_isSupported() +{ + return true; +} + diff --git a/src/hal/ethernet/linux/ethernet_linux.c b/src/hal/ethernet/linux/ethernet_linux.c index f9dfadf5..82e65e95 100644 --- a/src/hal/ethernet/linux/ethernet_linux.c +++ b/src/hal/ethernet/linux/ethernet_linux.c @@ -35,7 +35,8 @@ #include #include -#include "ethernet.h" +#include "libiec61850_platform_includes.h" +#include "hal_ethernet.h" struct sEthernetSocket { int rawSocket; @@ -101,13 +102,13 @@ Ethernet_getInterfaceMACAddress(char* interfaceId, uint8_t* addr) EthernetSocket Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) { - EthernetSocket ethernetSocket = calloc(1, sizeof(struct sEthernetSocket)); + EthernetSocket ethernetSocket = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket)); ethernetSocket->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (ethernetSocket->rawSocket == -1) { printf("Error creating raw socket!\n"); - free(ethernetSocket); + GLOBAL_FREEMEM(ethernetSocket); return NULL; } @@ -163,6 +164,12 @@ void Ethernet_destroySocket(EthernetSocket ethSocket) { close(ethSocket->rawSocket); - free(ethSocket); + GLOBAL_FREEMEM(ethSocket); +} + +bool +Ethernet_isSupported() +{ + return true; } diff --git a/src/hal/ethernet/win32/ethernet_win32.c b/src/hal/ethernet/win32/ethernet_win32.c index 05eb67f1..7095382a 100644 --- a/src/hal/ethernet/win32/ethernet_win32.c +++ b/src/hal/ethernet/win32/ethernet_win32.c @@ -23,12 +23,16 @@ #include "stack_config.h" -#if CONFIG_INCLUDE_ETHERNET_WINDOWS == 1 - #include #include #include #include + +#include "hal_ethernet.h" + +#if (CONFIG_INCLUDE_ETHERNET_WINDOWS == 1) + + #include #include @@ -38,8 +42,6 @@ #pragma comment (lib, "IPHLPAPI.lib") #endif -#include "ethernet.h" - #define HAVE_REMOTE #include "pcap.h" @@ -89,7 +91,7 @@ static pgetadaptersaddresses GetAdaptersAddresses; static bool dllLoaded = false; static void -loadDLLs() +loadDLLs(void) { HINSTANCE hDll = LoadLibrary("iphlpapi.dll"); @@ -103,7 +105,7 @@ loadDLLs() "GetAdaptersAddresses"); if (GetAdaptersAddresses == NULL) - printf("Error loading GetAdaptersAddresses from iphlpapi.dll (%d)\n", GetLastError()); + printf("Error loading GetAdaptersAddresses from iphlpapi.dll (%d)\n", (int) GetLastError()); } #endif /* __MINGW64_VERSION_MAJOR */ @@ -155,7 +157,7 @@ getInterfaceName(int interfaceIndex) return interfaceName; } -void +static void getAdapterMacAddress(char* pcapAdapterName, uint8_t* macAddress) { PIP_ADAPTER_ADDRESSES pAddresses = NULL; @@ -224,7 +226,7 @@ Ethernet_getInterfaceMACAddress(char* interfaceId, uint8_t* addr) long interfaceIndex = strtol(interfaceId, &endPtr, 10); - if (*endPtr != NULL) { + if (endPtr != NULL) { printf("Ethernet_getInterfaceMACAddress: invalid interface number %s\n", interfaceId); return; } @@ -315,4 +317,50 @@ Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize) } } -#endif +bool +Ethernet_isSupported() +{ + return true; +} + +#else + +bool +Ethernet_isSupported() +{ + return false; +} + +void +Ethernet_getInterfaceMACAddress(char* interfaceId, uint8_t* addr) +{ +} + +EthernetSocket +Ethernet_createSocket(char* interfaceId, uint8_t* destAddress) +{ + return NULL; +} + +void +Ethernet_destroySocket(EthernetSocket ethSocket) +{ +} + +void +Ethernet_sendPacket(EthernetSocket ethSocket, uint8_t* buffer, int packetSize) +{ +} + +void +Ethernet_setProtocolFilter(EthernetSocket ethSocket, uint16_t etherType) +{ +} + +int +Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize) +{ + return 0; +} + +#endif /* (CONFIG_INCLUDE_ETHERNET_WINDOWS == 1) */ diff --git a/src/hal/filesystem/linux/file_provider_linux.c b/src/hal/filesystem/linux/file_provider_linux.c index a5a88f45..91bf3d3e 100644 --- a/src/hal/filesystem/linux/file_provider_linux.c +++ b/src/hal/filesystem/linux/file_provider_linux.c @@ -32,7 +32,9 @@ #include #include -#include "filesystem.h" +#include "libiec61850_platform_includes.h" + +#include "hal_filesystem.h" #include "stack_config.h" @@ -154,7 +156,7 @@ FileSystem_openDirectory(char* directoryName) DirectoryHandle handle = NULL; if (dirHandle != NULL) { - handle = malloc(sizeof(struct sDirectoryHandle)); + handle = GLOBAL_MALLOC(sizeof(struct sDirectoryHandle)); handle->handle = dirHandle; } @@ -190,7 +192,7 @@ void FileSystem_closeDirectory(DirectoryHandle directory) { closedir(directory->handle); - free(directory); + GLOBAL_FREEMEM(directory); } #if 0 diff --git a/src/hal/filesystem/win32/file_provider_win32.c b/src/hal/filesystem/win32/file_provider_win32.c index c0b6a182..01250219 100644 --- a/src/hal/filesystem/win32/file_provider_win32.c +++ b/src/hal/filesystem/win32/file_provider_win32.c @@ -30,8 +30,9 @@ #include #include -#include "filesystem.h" +#include "hal_filesystem.h" +#include "libiec61850_platform_includes.h" #include "stack_config.h" #include @@ -131,7 +132,7 @@ FileSystem_openDirectory(char* directoryName) createFullPathFromFileName(fullPath, directoryName); - DirectoryHandle dirHandle = (DirectoryHandle) calloc(1, sizeof(struct sDirectoryHandle)); + DirectoryHandle dirHandle = (DirectoryHandle) GLOBAL_CALLOC(1, sizeof(struct sDirectoryHandle)); strcat(fullPath, "\\*"); @@ -141,7 +142,7 @@ FileSystem_openDirectory(char* directoryName) dirHandle->available = true; if (dirHandle->handle == INVALID_HANDLE_VALUE) { - free(dirHandle); + GLOBAL_FREEMEM(dirHandle); return NULL; } else @@ -226,27 +227,6 @@ void FileSystem_closeDirectory(DirectoryHandle directory) { FindClose(directory->handle); - free(directory); + GLOBAL_FREEMEM(directory); } -#if 0 -int -main(int argc, char** argv) -{ - DirectoryHandle directory = FileSystem_openDirectory("/"); - - if (directory != NULL) { - char* fileName = FileSystem_readDirectory(directory); - - - while (fileName != NULL) { - printf("FILE: (%s)\n", fileName); - fileName = FileSystem_readDirectory(directory); - } - - FileSystem_closeDirectory(directory); - } - else - printf("Error opening directory!\n"); -} -#endif diff --git a/src/hal/socket/bsd/socket_bsd.c b/src/hal/socket/bsd/socket_bsd.c index 770b0039..09457212 100644 --- a/src/hal/socket/bsd/socket_bsd.c +++ b/src/hal/socket/bsd/socket_bsd.c @@ -1,7 +1,7 @@ /* * socket_bsd.c * - * Copyright 2013 Michael Zillgith, contributed to the project by Michael Clausen (School of engineering Valais). + * Copyright 2013, 2014 Michael Zillgith, contributions by Michael Clausen (School of engineering Valais). * * This file is part of libIEC61850. * @@ -21,7 +21,7 @@ * See COPYING file for the complete license text. */ -#include "socket.h" +#include "hal_socket.h" #include #include #include @@ -38,7 +38,7 @@ #include // required for TCP keepalive -#include "thread.h" +#include "hal_thread.h" #include "libiec61850_platform_includes.h" @@ -48,6 +48,7 @@ struct sSocket { int fd; + uint32_t connectTimeout; }; struct sServerSocket { @@ -55,6 +56,58 @@ struct sServerSocket { int backLog; }; +struct sHandleSet { + fd_set handles; + int maxHandle; +}; + +HandleSet +Handleset_new(void) +{ + HandleSet result = (HandleSet) GLOBAL_MALLOC(sizeof(struct sHandleSet)); + + if (result != NULL) { + FD_ZERO(&result->handles); + result->maxHandle = -1; + } + return result; +} + +void +Handleset_addSocket(HandleSet self, const Socket sock) +{ + if (self != NULL && sock != NULL && sock->fd != -1) { + FD_SET(sock->fd, &self->handles); + if (sock->fd > self->maxHandle) { + self->maxHandle = sock->fd; + } + } +} + +int +Handleset_waitReady(HandleSet self, unsigned int timeoutMs) +{ + int result; + + if (self != NULL && self->maxHandle >= 0) { + struct timeval timeout; + + timeout.tv_sec = timeoutMs / 1000; + timeout.tv_usec = (timeoutMs % 1000) * 1000; + result = select(self->maxHandle + 1, &self->handles, NULL, NULL, &timeout); + } else { + result = -1; + } + + return result; +} + +void +Handleset_destroy(HandleSet self) +{ + GLOBAL_FREEMEM(self); +} + static void activateKeepAlive(int sd) { @@ -79,7 +132,7 @@ activateKeepAlive(int sd) } static bool -prepareServerAddress(char* address, int port, struct sockaddr_in* sockaddr) +prepareServerAddress(const char* address, int port, struct sockaddr_in* sockaddr) { memset((char *) sockaddr , 0, sizeof(struct sockaddr_in)); @@ -101,17 +154,15 @@ prepareServerAddress(char* address, int port, struct sockaddr_in* sockaddr) return true; } -#if 0 static void setSocketNonBlocking(Socket self) { int flags = fcntl(self->fd, F_GETFL, 0); fcntl(self->fd, F_SETFL, flags | O_NONBLOCK); } -#endif ServerSocket -TcpServerSocket_create(char* address, int port) +TcpServerSocket_create(const char* address, int port) { ServerSocket serverSocket = NULL; @@ -129,7 +180,7 @@ TcpServerSocket_create(char* address, int port) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optionReuseAddr, sizeof(int)); if (bind(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) >= 0) { - serverSocket = malloc(sizeof(struct sServerSocket)); + serverSocket = GLOBAL_MALLOC(sizeof(struct sServerSocket)); serverSocket->fd = fd; serverSocket->backLog = 0; } @@ -141,6 +192,8 @@ TcpServerSocket_create(char* address, int port) #if CONFIG_ACTIVATE_TCP_KEEPALIVE == 1 activateKeepAlive(fd); #endif + + setSocketNonBlocking((Socket) serverSocket); } return serverSocket; @@ -201,21 +254,28 @@ ServerSocket_destroy(ServerSocket self) Thread_sleep(10); - free(self); + GLOBAL_FREEMEM(self); } Socket TcpSocket_create() { - Socket self = malloc(sizeof(struct sSocket)); + Socket self = GLOBAL_MALLOC(sizeof(struct sSocket)); self->fd = -1; return self; } -int -Socket_connect(Socket self, char* address, int port) +void +Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs) +{ + self->connectTimeout = timeoutInMs; +} + + +bool +Socket_connect(Socket self, const char* address, int port) { struct sockaddr_in serverAddress; @@ -223,7 +283,7 @@ Socket_connect(Socket self, char* address, int port) printf("Socket_connect: %s:%i\n", address, port); if (!prepareServerAddress(address, port, &serverAddress)) - return 0; + return false; self->fd = socket(AF_INET, SOCK_STREAM, 0); @@ -231,10 +291,25 @@ Socket_connect(Socket self, char* address, int port) activateKeepAlive(self->fd); #endif - if (connect(self->fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) - return 0; + fd_set fdSet; + FD_ZERO(&fdSet); + FD_SET(self->fd, &fdSet); + + fcntl(self->fd, F_SETFL, O_NONBLOCK); + + if (connect(self->fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) { + if (errno != EINPROGRESS) + return false; + } + + struct timeval timeout; + timeout.tv_sec = self->connectTimeout / 1000; + timeout.tv_usec = (self->connectTimeout % 1000) * 1000; + + if (select(self->fd + 1, NULL, &fdSet, NULL, &timeout) < 0) + return false; else - return 1; + return true; } char* @@ -265,7 +340,7 @@ Socket_getPeerAddress(Socket self) else return NULL ; - char* clientConnection = malloc(strlen(addrString) + 9); + char* clientConnection = GLOBAL_MALLOC(strlen(addrString) + 9); if (isIPv6) @@ -284,7 +359,10 @@ Socket_read(Socket self, uint8_t* buf, int size) if (self->fd == -1) return -1; - int read_bytes = read(self->fd, buf, size); + int read_bytes = recv(self->fd, buf, size, MSG_DONTWAIT); + + if (read_bytes == 0) + return -1; if (read_bytes == -1) { int error = errno; @@ -300,9 +378,8 @@ Socket_read(Socket self, uint8_t* buf, int size) return -1; } } - else { - return read_bytes; - } + + return read_bytes; } int @@ -326,5 +403,5 @@ Socket_destroy(Socket self) Thread_sleep(10); - free(self); + GLOBAL_FREEMEM(self); } diff --git a/src/hal/socket/linux/socket_linux.c b/src/hal/socket/linux/socket_linux.c index be6f838e..87c22a5b 100644 --- a/src/hal/socket/linux/socket_linux.c +++ b/src/hal/socket/linux/socket_linux.c @@ -1,7 +1,7 @@ /* * socket_linux.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -21,7 +21,7 @@ * See COPYING file for the complete license text. */ -#include "socket.h" +#include "hal_socket.h" #include #include #include @@ -38,7 +38,7 @@ #include // required for TCP keepalive -#include "thread.h" +#include "hal_thread.h" #include "libiec61850_platform_includes.h" @@ -48,6 +48,7 @@ struct sSocket { int fd; + uint32_t connectTimeout; }; struct sServerSocket { @@ -55,6 +56,58 @@ struct sServerSocket { int backLog; }; +struct sHandleSet { + fd_set handles; + int maxHandle; +}; + +HandleSet +Handleset_new(void) +{ + HandleSet result = (HandleSet) GLOBAL_MALLOC(sizeof(struct sHandleSet)); + + if (result != NULL) { + FD_ZERO(&result->handles); + result->maxHandle = -1; + } + return result; +} + +void +Handleset_addSocket(HandleSet self, const Socket sock) +{ + if (self != NULL && sock != NULL && sock->fd != -1) { + FD_SET(sock->fd, &self->handles); + if (sock->fd > self->maxHandle) { + self->maxHandle = sock->fd; + } + } +} + +int +Handleset_waitReady(HandleSet self, unsigned int timeoutMs) +{ + int result; + + if (self != NULL && self->maxHandle >= 0) { + struct timeval timeout; + + timeout.tv_sec = timeoutMs / 1000; + timeout.tv_usec = (timeoutMs % 1000) * 1000; + result = select(self->maxHandle + 1, &self->handles, NULL, NULL, &timeout); + } else { + result = -1; + } + + return result; +} + +void +Handleset_destroy(HandleSet self) +{ + GLOBAL_FREEMEM(self); +} + static void activateKeepAlive(int sd) { @@ -80,7 +133,7 @@ activateKeepAlive(int sd) } static bool -prepareServerAddress(char* address, int port, struct sockaddr_in* sockaddr) +prepareServerAddress(const char* address, int port, struct sockaddr_in* sockaddr) { memset((char *) sockaddr , 0, sizeof(struct sockaddr_in)); @@ -102,17 +155,23 @@ prepareServerAddress(char* address, int port, struct sockaddr_in* sockaddr) return true; } -#if 0 static void setSocketNonBlocking(Socket self) { int flags = fcntl(self->fd, F_GETFL, 0); fcntl(self->fd, F_SETFL, flags | O_NONBLOCK); } -#endif + +static void +activateTcpNoDelay(Socket self) +{ + /* activate TCP_NODELAY option - packets will be sent immediately */ + int flag = 1; + setsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); +} ServerSocket -TcpServerSocket_create(char* address, int port) +TcpServerSocket_create(const char* address, int port) { ServerSocket serverSocket = NULL; @@ -130,9 +189,11 @@ TcpServerSocket_create(char* address, int port) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optionReuseAddr, sizeof(int)); if (bind(fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) >= 0) { - serverSocket = malloc(sizeof(struct sServerSocket)); + serverSocket = GLOBAL_MALLOC(sizeof(struct sServerSocket)); serverSocket->fd = fd; serverSocket->backLog = 0; + + setSocketNonBlocking((Socket) serverSocket); } else { close(fd); @@ -153,6 +214,8 @@ ServerSocket_listen(ServerSocket self) listen(self->fd, self->backLog); } + +/* CHANGED TO MAKE NON-BLOCKING --> RETURNS NULL IF NO CONNECTION IS PENDING */ Socket ServerSocket_accept(ServerSocket self) { @@ -165,6 +228,8 @@ ServerSocket_accept(ServerSocket self) if (fd >= 0) { conSocket = TcpSocket_create(); conSocket->fd = fd; + + activateTcpNoDelay(conSocket); } return conSocket; @@ -202,21 +267,30 @@ ServerSocket_destroy(ServerSocket self) Thread_sleep(10); - free(self); + GLOBAL_FREEMEM(self); } Socket TcpSocket_create() { - Socket self = malloc(sizeof(struct sSocket)); + Socket self = GLOBAL_MALLOC(sizeof(struct sSocket)); self->fd = -1; + self->connectTimeout = 5000; return self; } -int -Socket_connect(Socket self, char* address, int port) + +void +Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs) +{ + self->connectTimeout = timeoutInMs; +} + + +bool +Socket_connect(Socket self, const char* address, int port) { struct sockaddr_in serverAddress; @@ -224,18 +298,36 @@ Socket_connect(Socket self, char* address, int port) printf("Socket_connect: %s:%i\n", address, port); if (!prepareServerAddress(address, port, &serverAddress)) - return 0; + return false; self->fd = socket(AF_INET, SOCK_STREAM, 0); + fd_set fdSet; + FD_ZERO(&fdSet); + FD_SET(self->fd, &fdSet); + + activateTcpNoDelay(self); + #if CONFIG_ACTIVATE_TCP_KEEPALIVE == 1 activateKeepAlive(self->fd); #endif - if (connect(self->fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) - return 0; + fcntl(self->fd, F_SETFL, O_NONBLOCK); + + if (connect(self->fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) { + if (errno != EINPROGRESS) + return false; + } + + struct timeval timeout; + timeout.tv_sec = self->connectTimeout / 1000; + timeout.tv_usec = (self->connectTimeout % 1000) * 1000; + + if (select(self->fd + 1, NULL, &fdSet, NULL, &timeout) < 0) + return false; else - return 1; + return true; + } char* @@ -266,7 +358,7 @@ Socket_getPeerAddress(Socket self) else return NULL ; - char* clientConnection = malloc(strlen(addrString) + 9); + char* clientConnection = GLOBAL_MALLOC(strlen(addrString) + 9); if (isIPv6) @@ -285,7 +377,10 @@ Socket_read(Socket self, uint8_t* buf, int size) if (self->fd == -1) return -1; - int read_bytes = read(self->fd, buf, size); + int read_bytes = recv(self->fd, buf, size, MSG_DONTWAIT); + + if (read_bytes == 0) + return -1; if (read_bytes == -1) { int error = errno; @@ -301,9 +396,8 @@ Socket_read(Socket self, uint8_t* buf, int size) return -1; } } - else { - return read_bytes; - } + + return read_bytes; } int @@ -327,5 +421,5 @@ Socket_destroy(Socket self) Thread_sleep(10); - free(self); + GLOBAL_FREEMEM(self); } diff --git a/src/hal/socket/win32/socket_win32.c b/src/hal/socket/win32/socket_win32.c index 11430107..789de3b7 100644 --- a/src/hal/socket/win32/socket_win32.c +++ b/src/hal/socket/win32/socket_win32.c @@ -1,7 +1,7 @@ /* * socket_win32.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -29,7 +29,8 @@ #pragma comment (lib, "Ws2_32.lib") -#include "socket.h" +#include "libiec61850_platform_includes.h" +#include "hal_socket.h" #include "stack_config.h" @@ -45,6 +46,7 @@ struct tcp_keepalive { struct sSocket { SOCKET fd; + uint32_t connectTimeout; }; struct sServerSocket { @@ -52,6 +54,58 @@ struct sServerSocket { int backLog; }; +struct sHandleSet { + fd_set handles; + int maxHandle; +}; + +HandleSet +Handleset_new(void) +{ + HandleSet result = (HandleSet) GLOBAL_MALLOC(sizeof(struct sHandleSet)); + + if (result != NULL) { + FD_ZERO(&result->handles); + result->maxHandle = -1; + } + return result; +} + +void +Handleset_addSocket(HandleSet self, const Socket sock) +{ + if (self != NULL && sock != NULL && sock->fd != -1) { + FD_SET(sock->fd, &self->handles); + if (sock->fd > self->maxHandle) { + self->maxHandle = sock->fd; + } + } +} + +int +Handleset_waitReady(HandleSet self, unsigned int timeoutMs) +{ + int result; + + if (self != NULL && self->maxHandle >= 0) { + struct timeval timeout; + + timeout.tv_sec = timeoutMs / 1000; + timeout.tv_usec = (timeoutMs % 1000) * 1000; + result = select(self->maxHandle + 1, &self->handles, NULL, NULL, &timeout); + } else { + result = -1; + } + + return result; +} + +void +Handleset_destroy(HandleSet self) +{ + GLOBAL_FREEMEM(self); +} + static void activateKeepAlive(SOCKET s) { @@ -65,13 +119,30 @@ activateKeepAlive(SOCKET s) if (WSAIoctl(s, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &retVal, NULL, NULL) == SOCKET_ERROR) { - printf("WSAIotcl(SIO_KEEPALIVE_VALS) failed; %d\n", - WSAGetLastError()); + if (DEBUG_SOCKET) + printf("WIN32_SOCKET: WSAIotcl(SIO_KEEPALIVE_VALS) failed: %d\n", + WSAGetLastError()); } } +static void +setSocketNonBlocking(Socket self) +{ + unsigned long mode = 1; + if (ioctlsocket(self->fd, FIONBIO, &mode) != 0) { + if (DEBUG_SOCKET) + printf("WIN32_SOCKET: failed to set socket non-blocking!\n"); + } + + /* activate TCP_NODELAY */ + + int tcpNoDelay = 1; + + setsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&tcpNoDelay, sizeof(int)); +} + static bool -prepareServerAddress(char* address, int port, struct sockaddr_in* sockaddr) +prepareServerAddress(const char* address, int port, struct sockaddr_in* sockaddr) { memset((char *) sockaddr , 0, sizeof(struct sockaddr_in)); @@ -94,7 +165,7 @@ prepareServerAddress(char* address, int port, struct sockaddr_in* sockaddr) } ServerSocket -TcpServerSocket_create(char* address, int port) +TcpServerSocket_create(const char* address, int port) { ServerSocket serverSocket = NULL; int ec; @@ -102,7 +173,8 @@ TcpServerSocket_create(char* address, int port) SOCKET listen_socket = INVALID_SOCKET; if ((ec = WSAStartup(MAKEWORD(2,0), &wsa)) != 0) { - printf("winsock error: code %i\n"); + if (DEBUG_SOCKET) + printf("WIN32_SOCKET: winsock error: code %i\n", ec); return NULL; } @@ -118,7 +190,8 @@ TcpServerSocket_create(char* address, int port) #endif if (listen_socket == INVALID_SOCKET) { - printf("socket failed with error: %i\n", WSAGetLastError()); + if (DEBUG_SOCKET) + printf("WIN32_SOCKET: socket failed with error: %i\n", WSAGetLastError()); WSACleanup(); return NULL; } @@ -129,17 +202,20 @@ TcpServerSocket_create(char* address, int port) ec = bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (ec == SOCKET_ERROR) { - printf("bind failed with error:%i\n", WSAGetLastError()); + if (DEBUG_SOCKET) + printf("WIN32_SOCKET: bind failed with error:%i\n", WSAGetLastError()); closesocket(listen_socket); WSACleanup(); return NULL; } - serverSocket = (ServerSocket) malloc(sizeof(struct sServerSocket)); + serverSocket = (ServerSocket) GLOBAL_MALLOC(sizeof(struct sServerSocket)); serverSocket->fd = listen_socket; serverSocket->backLog = 10; + setSocketNonBlocking((Socket) serverSocket); + return serverSocket; } @@ -161,6 +237,8 @@ ServerSocket_accept(ServerSocket self) if (fd >= 0) { conSocket = TcpSocket_create(); conSocket->fd = fd; + + setSocketNonBlocking(conSocket); } return conSocket; @@ -183,27 +261,34 @@ ServerSocket_destroy(ServerSocket self) Socket TcpSocket_create() { - Socket self = (Socket) malloc(sizeof(struct sSocket)); + Socket self = (Socket) GLOBAL_MALLOC(sizeof(struct sSocket)); - self->fd = -1; + self->fd = INVALID_SOCKET; return self; } -int -Socket_connect(Socket self, char* address, int port) +void +Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs) +{ + self->connectTimeout = timeoutInMs; +} + +bool +Socket_connect(Socket self, const char* address, int port) { - struct hostent *server; struct sockaddr_in serverAddress; WSADATA wsa; + int ec; - if (WSAStartup(MAKEWORD(2,0), &wsa) != 0) { - printf("winsock error: code %i\n"); - return 0; + if ((ec = WSAStartup(MAKEWORD(2,0), &wsa)) != 0) { + if (DEBUG_SOCKET) + printf("WIN32_SOCKET: winsock error: code %i\n", ec); + return false; } if (!prepareServerAddress(address, port, &serverAddress)) - return 0; + return false; self->fd = socket(AF_INET, SOCK_STREAM, 0); @@ -211,12 +296,35 @@ Socket_connect(Socket self, char* address, int port) activateKeepAlive(self->fd); #endif - if (connect(self->fd, (struct sockaddr *) &serverAddress,sizeof(serverAddress)) < 0) { - printf("Socket failed connecting!\n"); - return 0; - } - else - return 1; + setSocketNonBlocking(self); + + fd_set fdSet; + FD_ZERO(&fdSet); + FD_SET(self->fd, &fdSet); + +// if (connect(self->fd, (struct sockaddr *) &serverAddress,sizeof(serverAddress)) < 0) { +// if (DEBUG_SOCKET) +// printf("WIN32_SOCKET: Socket failed connecting!\n"); +// return false; +// } +// else { +// +// return true; +// } + + if (connect(self->fd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) == SOCKET_ERROR) { + if (WSAGetLastError() != WSAEWOULDBLOCK) + return false; + } + + struct timeval timeout; + timeout.tv_sec = self->connectTimeout / 1000; + timeout.tv_usec = (self->connectTimeout % 1000) * 1000; + + if (select(self->fd + 1, NULL, &fdSet, NULL, &timeout) == SOCKET_ERROR) + return false; + else + return true; } char* @@ -252,7 +360,7 @@ Socket_getPeerAddress(Socket self) else return NULL; - char* clientConnection = (char*) malloc(strlen(addrString) + 9); + char* clientConnection = (char*) GLOBAL_MALLOC(strlen(addrString) + 9); if (isIPv6) sprintf(clientConnection, "[%s]:%i", addrString, port); @@ -265,7 +373,19 @@ Socket_getPeerAddress(Socket self) int Socket_read(Socket self, uint8_t* buf, int size) { - return recv(self->fd, (char*) buf, size, 0); + int bytes_read = recv(self->fd, (char*) buf, size, 0); + + if (bytes_read == 0) // peer has closed socket + return -1; + + if (bytes_read == SOCKET_ERROR) { + if (WSAGetLastError() == WSAEWOULDBLOCK) + return 0; + else + return -1; + } + + return bytes_read; } int @@ -277,7 +397,7 @@ Socket_write(Socket self, uint8_t* buf, int size) void Socket_destroy(Socket self) { - if (self->fd != -1) { + if (self->fd != INVALID_SOCKET) { closesocket(self->fd); } diff --git a/src/hal/thread/linux/thread_linux.c b/src/hal/thread/linux/thread_linux.c index a2b13343..cf8ec73e 100644 --- a/src/hal/thread/linux/thread_linux.c +++ b/src/hal/thread/linux/thread_linux.c @@ -26,7 +26,9 @@ #include #include #include -#include "thread.h" +#include "hal_thread.h" + +#include "libiec61850_platform_includes.h" struct sThread { ThreadExecutionFunction function; @@ -39,7 +41,7 @@ struct sThread { Semaphore Semaphore_create(int initialValue) { - Semaphore self = malloc(sizeof(sem_t)); + Semaphore self = GLOBAL_MALLOC(sizeof(sem_t)); sem_init((sem_t*) self, 0, initialValue); @@ -63,18 +65,20 @@ void Semaphore_destroy(Semaphore self) { sem_destroy((sem_t*) self); - free(self); + GLOBAL_FREEMEM(self); } Thread Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy) { - Thread thread = malloc(sizeof(struct sThread)); + Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread)); - thread->parameter = parameter; - thread->function = function; - thread->state = 0; - thread->autodestroy = autodestroy; + if (thread != NULL) { + thread->parameter = parameter; + thread->function = function; + thread->state = 0; + thread->autodestroy = autodestroy; + } return thread; } @@ -86,9 +90,9 @@ destroyAutomaticThread(void* parameter) thread->function(thread->parameter); - free(thread); + GLOBAL_FREEMEM(thread); - return NULL; + pthread_exit(NULL); } void @@ -111,7 +115,7 @@ Thread_destroy(Thread thread) pthread_join(thread->pthread, NULL); } - free(thread); + GLOBAL_FREEMEM(thread); } void diff --git a/src/hal/thread/win32/thread_win32.c b/src/hal/thread/win32/thread_win32.c index 81018585..02498ff1 100644 --- a/src/hal/thread/win32/thread_win32.c +++ b/src/hal/thread/win32/thread_win32.c @@ -23,7 +23,8 @@ #include #include -#include "thread.h" +#include "libiec61850_platform_includes.h" +#include "hal_thread.h" struct sThread { ThreadExecutionFunction function; @@ -59,7 +60,7 @@ Thread Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy) { DWORD threadId; - Thread thread = (Thread) malloc(sizeof(struct sThread)); + Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread)); thread->parameter = parameter; thread->function = function; @@ -89,7 +90,7 @@ Thread_destroy(Thread thread) CloseHandle(thread->handle); - free(thread); + GLOBAL_FREEMEM(thread); } void diff --git a/src/mms/asn1/asn1_ber_primitive_value.c b/src/mms/asn1/asn1_ber_primitive_value.c index 4d4d7e5a..d08e885b 100644 --- a/src/mms/asn1/asn1_ber_primitive_value.c +++ b/src/mms/asn1/asn1_ber_primitive_value.c @@ -28,11 +28,11 @@ Asn1PrimitiveValue* Asn1PrimitiveValue_create(int size) { - Asn1PrimitiveValue* self = (Asn1PrimitiveValue*) malloc(sizeof(Asn1PrimitiveValue)); + Asn1PrimitiveValue* self = (Asn1PrimitiveValue*) GLOBAL_MALLOC(sizeof(Asn1PrimitiveValue)); self->size = size; self->maxSize = size; - self->octets = (uint8_t*) calloc(1, size); + self->octets = (uint8_t*) GLOBAL_CALLOC(1, size); return self; } @@ -40,22 +40,22 @@ Asn1PrimitiveValue_create(int size) //Asn1PrimitiveValue* //Asn1PrimitiveValue_createFromBuffer(uint8_t buffer, int bufferSize) //{ -// Asn1PrimitiveValue* self = malloc(sizeof(Asn1PrimitiveValue)); +// Asn1PrimitiveValue* self = GLOBAL_MALLOC(sizeof(Asn1PrimitiveValue)); // self->size = bufferSize; // self->maxSize = bufferSize; -// self->octets = malloc(1, bufferSize); +// self->octets = GLOBAL_MALLOC(1, bufferSize); // //} Asn1PrimitiveValue* Asn1PrimitiveValue_clone(Asn1PrimitiveValue* self) { - Asn1PrimitiveValue* clone = (Asn1PrimitiveValue*) malloc(sizeof(Asn1PrimitiveValue)); + Asn1PrimitiveValue* clone = (Asn1PrimitiveValue*) GLOBAL_MALLOC(sizeof(Asn1PrimitiveValue)); clone->size = self->size; clone->maxSize = self->maxSize; - clone->octets = (uint8_t*) malloc(self->maxSize); + clone->octets = (uint8_t*) GLOBAL_MALLOC(self->maxSize); memcpy(clone->octets, self->octets, clone->maxSize); @@ -90,6 +90,6 @@ Asn1PrimitiveValue_getMaxSize(Asn1PrimitiveValue* self) void Asn1PrimitiveValue_destroy(Asn1PrimitiveValue* self) { - free(self->octets); - free(self); + GLOBAL_FREEMEM(self->octets); + GLOBAL_FREEMEM(self); } diff --git a/src/mms/asn1/ber_decode.c b/src/mms/asn1/ber_decode.c index 18f2fa52..bbd2231f 100644 --- a/src/mms/asn1/ber_decode.c +++ b/src/mms/asn1/ber_decode.c @@ -62,7 +62,7 @@ BerDecoder_decodeLength(uint8_t* buffer, int* length, int bufPos, int maxBufPos) char* BerDecoder_decodeString(uint8_t* buffer, int strlen, int bufPos, int maxBufPos) { - char* string = (char*) malloc(strlen + 1); + char* string = (char*) GLOBAL_MALLOC(strlen + 1); memcpy(string, buffer + bufPos, strlen); string[strlen] = 0; diff --git a/src/mms/asn1/ber_encoder.c b/src/mms/asn1/ber_encoder.c index c0bf2aef..f22f9e2b 100644 --- a/src/mms/asn1/ber_encoder.c +++ b/src/mms/asn1/ber_encoder.c @@ -70,7 +70,7 @@ BerEncoder_encodeBoolean(uint8_t tag, bool value, uint8_t* buffer, int bufPos) } int -BerEncoder_encodeStringWithTag(uint8_t tag, char* string, uint8_t* buffer, int bufPos) +BerEncoder_encodeStringWithTag(uint8_t tag, const char* string, uint8_t* buffer, int bufPos) { buffer[bufPos++] = tag; @@ -341,7 +341,7 @@ BerEncoder_determineLengthSize(uint32_t length) } int -BerEncoder_determineEncodedStringSize(char* string) +BerEncoder_determineEncodedStringSize(const char* string) { if (string != NULL) { int size = 1; @@ -359,13 +359,13 @@ BerEncoder_determineEncodedStringSize(char* string) } int -BerEncoder_encodeOIDToBuffer(char* oidString, uint8_t* buffer, int maxBufLen) +BerEncoder_encodeOIDToBuffer(const char* oidString, uint8_t* buffer, int maxBufLen) { int encodedBytes = 0; int x = atoi(oidString); - char* separator = strchr(oidString, '.'); + const char* separator = strchr(oidString, '.'); if (separator == NULL) return 0; diff --git a/src/mms/iso_client/iso_client_connection.c b/src/mms/iso_client/iso_client_connection.c new file mode 100644 index 00000000..b279f08f --- /dev/null +++ b/src/mms/iso_client/iso_client_connection.c @@ -0,0 +1,589 @@ +/* + * iso_client_connection.c + * + * Client side representation of the ISO stack (COTP, session, presentation, ACSE) + * + * Copyright 2013 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 "hal_socket.h" +#include "hal_thread.h" +#include "cotp.h" +#include "iso_session.h" +#include "iso_presentation.h" +#include "iso_client_connection.h" +#include "acse.h" + +#ifndef DEBUG_ISO_CLIENT +#ifdef DEBUG +#define DEBUG_ISO_CLIENT 1 +#else +#define DEBUG_ISO_CLIENT 0 +#endif /*DEBUG */ +#endif /* DEBUG_ISO_SERVER */ + +#define STATE_IDLE 0 +#define STATE_ASSOCIATED 1 +#define STATE_ERROR 2 + +#define TPKT_RFC1006_HEADER_SIZE 4 + +#define ISO_CLIENT_BUFFER_SIZE CONFIG_MMS_MAXIMUM_PDU_SIZE + 100 + +struct sIsoClientConnection +{ + IsoIndicationCallback callback; + void* callbackParameter; + volatile int state; + Socket socket; + CotpConnection* cotpConnection; + IsoPresentation* presentation; + IsoSession* session; + AcseConnection acseConnection; + + uint8_t* sendBuffer; /* ISO/MMS send buffer */ + uint8_t* receiveBuf; /* ISO/MMS receive buffer */ + ByteBuffer* receiveBuffer; + + ByteBuffer* transmitPayloadBuffer; + Semaphore transmitBufferMutex; + + ByteBuffer* receivePayloadBuffer; + Semaphore receiveBufferMutex; + + uint8_t* cotpReadBuf; + uint8_t* cotpWriteBuf; + + ByteBuffer* cotpReadBuffer; + ByteBuffer* cotpWriteBuffer; + + volatile bool handlingThreadRunning; + volatile bool stopHandlingThread; + volatile bool destroyHandlingThread; + volatile bool startHandlingThread; + + Thread thread; +}; + +static void +connectionHandlingThread(IsoClientConnection self) +{ + IsoSessionIndication sessionIndication; + + self->handlingThreadRunning = true; + self->stopHandlingThread = false; + + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT_CONNECTION: new connection %p\n", self); + + /* Wait until lower layer association is finished */ + Semaphore_wait(self->receiveBufferMutex); + + CotpConnection_resetPayload(self->cotpConnection); + + while (true) { + + TpktState packetState; + + while ((packetState = CotpConnection_readToTpktBuffer(self->cotpConnection)) == TPKT_WAITING) + { + Thread_sleep(1); + + if (self->stopHandlingThread) { + packetState = TPKT_ERROR; + break; + } + } + + if (packetState == TPKT_ERROR) + break; + + CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); + + if (cotpIndication == COTP_MORE_FRAGMENTS_FOLLOW) + continue; + + if (cotpIndication != COTP_DATA_INDICATION) + break; + + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT_CONNECTION: parse message\n"); + + sessionIndication = + IsoSession_parseMessage(self->session, + CotpConnection_getPayload(self->cotpConnection)); + + if (sessionIndication != SESSION_DATA) { + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT_CONNECTION: Invalid session message\n"); + break; + } + + if (!IsoPresentation_parseUserData(self->presentation, IsoSession_getUserData(self->session))) { + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT_CONNECTION: Invalid presentation message\n"); + break; + } + + self->callback(ISO_IND_DATA, self->callbackParameter, + &(self->presentation->nextPayload)); + + /* wait for user to release the buffer */ + Semaphore_wait(self->receiveBufferMutex); + + CotpConnection_resetPayload(self->cotpConnection); + } + + self->callback(ISO_IND_CLOSED, self->callbackParameter, NULL); + + self->state = STATE_IDLE; + + Socket_destroy(self->socket); + + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT_CONNECTION: exit connection %p\n", self); + + /* release buffer to enable resuse of client connection */ + Semaphore_post(self->receiveBufferMutex); + + self->handlingThreadRunning = false; +} + + +static void* +connectionThreadFunction(void* parameter) +{ + IsoClientConnection self = (IsoClientConnection) parameter; + + while (self->destroyHandlingThread == false) { + + if (self->startHandlingThread) { + self->startHandlingThread = false; + connectionHandlingThread(self); + } + + Thread_sleep(1); + } + + self->destroyHandlingThread = false; + + return NULL; +} + + +IsoClientConnection +IsoClientConnection_create(IsoIndicationCallback callback, void* callbackParameter) +{ + IsoClientConnection self = (IsoClientConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoClientConnection)); + + if (self == NULL) + return NULL; + + self->callback = callback; + self->callbackParameter = callbackParameter; + self->state = STATE_IDLE; + + self->sendBuffer = (uint8_t*) GLOBAL_MALLOC(ISO_CLIENT_BUFFER_SIZE); + + self->transmitPayloadBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer)); + self->transmitPayloadBuffer->buffer = self->sendBuffer; + self->transmitPayloadBuffer->maxSize = ISO_CLIENT_BUFFER_SIZE; + + self->receivePayloadBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer)); + + self->transmitBufferMutex = Semaphore_create(1); + + self->receiveBufferMutex = Semaphore_create(1); + + self->receiveBuf = (uint8_t*) GLOBAL_MALLOC(ISO_CLIENT_BUFFER_SIZE); + self->receiveBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer)); + ByteBuffer_wrap(self->receiveBuffer, self->receiveBuf, 0, ISO_CLIENT_BUFFER_SIZE); + + self->presentation = (IsoPresentation*) GLOBAL_CALLOC(1, sizeof(IsoPresentation)); + + self->session = (IsoSession*) GLOBAL_CALLOC(1, sizeof(IsoSession)); + + self->cotpReadBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + self->cotpWriteBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + + self->cotpReadBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer)); + ByteBuffer_wrap(self->cotpReadBuffer, self->cotpReadBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + + self->cotpWriteBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer)); + ByteBuffer_wrap(self->cotpWriteBuffer, self->cotpWriteBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + + self->cotpConnection = (CotpConnection*) GLOBAL_CALLOC(1, sizeof(CotpConnection)); + + return self; +} + +void +IsoClientConnection_associate(IsoClientConnection self, IsoConnectionParameters params, + ByteBuffer* payload, uint32_t connectTimeoutInMs) +{ + self->socket = TcpSocket_create(); + + Socket_setConnectTimeout(self->socket, connectTimeoutInMs); + + if (!Socket_connect(self->socket, params->hostname, params->tcpPort)) + goto returnError; + + /* COTP (ISO transport) handshake */ + CotpConnection_init(self->cotpConnection, self->socket, self->receiveBuffer, self->cotpReadBuffer, self->cotpWriteBuffer); + CotpIndication cotpIndication = + CotpConnection_sendConnectionRequestMessage(self->cotpConnection, params); + + TpktState packetState; + + uint64_t timeout = Hal_getTimeInMs() + CONFIG_TCP_READ_TIMEOUT_MS; + + while (((packetState = CotpConnection_readToTpktBuffer(self->cotpConnection)) == TPKT_WAITING) + && (Hal_getTimeInMs() < timeout)) + { + Thread_sleep(1); + } + + if (packetState != TPKT_PACKET_COMPLETE) + goto returnError; + + cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); + + if (cotpIndication != COTP_CONNECT_INDICATION) + goto returnError; + + /* Upper layers handshake */ + struct sBufferChain sAcsePayload; + BufferChain acsePayload = &sAcsePayload; + acsePayload->buffer = payload->buffer; + acsePayload->partLength = payload->size; + acsePayload->length = payload->size; + acsePayload->nextPart = NULL; + + AcseConnection_init(&(self->acseConnection), NULL, NULL); + + AcseAuthenticationParameter authParameter = NULL; + + if (params != NULL) + authParameter = params->acseAuthParameter; + + struct sBufferChain sAcseBuffer; + BufferChain acseBuffer = &sAcseBuffer; + + acseBuffer->buffer = self->sendBuffer + payload->size; + acseBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acsePayload->length; + + AcseConnection_createAssociateRequestMessage(&(self->acseConnection), params, acseBuffer, acsePayload, + authParameter); + + struct sBufferChain sPresentationBuffer; + BufferChain presentationBuffer = &sPresentationBuffer; + + presentationBuffer->buffer = self->sendBuffer + acseBuffer->length; + presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acseBuffer->length; + + IsoPresentation_init(self->presentation); + IsoPresentation_createConnectPdu(self->presentation, params, presentationBuffer, acseBuffer); + + struct sBufferChain sSessionBuffer; + BufferChain sessionBuffer = &sSessionBuffer; + sessionBuffer->buffer = self->sendBuffer + presentationBuffer->length; + + IsoSession_init(self->session); + IsoSession_createConnectSpdu(self->session, params, sessionBuffer, + presentationBuffer); + + CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer); + + Semaphore_post(self->transmitBufferMutex); + + while ((packetState = CotpConnection_readToTpktBuffer(self->cotpConnection)) == TPKT_WAITING) + { + Thread_sleep(1); + } + + cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); + + if (cotpIndication != COTP_DATA_INDICATION) + goto returnError; + + IsoSessionIndication sessionIndication; + + sessionIndication = + IsoSession_parseMessage(self->session, CotpConnection_getPayload(self->cotpConnection)); + + if (sessionIndication != SESSION_CONNECT) { + if (DEBUG_ISO_CLIENT) + printf("IsoClientConnection_associate: no session connect indication\n"); + goto returnError; + } + + if (!IsoPresentation_parseAcceptMessage(self->presentation, IsoSession_getUserData(self->session))) { + if (DEBUG_ISO_CLIENT) + printf("IsoClientConnection_associate: no presentation ok indication\n"); + goto returnError; + } + + AcseIndication acseIndication; + + acseIndication = AcseConnection_parseMessage(&(self->acseConnection), &self->presentation->nextPayload); + + if (acseIndication != ACSE_ASSOCIATE) { + if (DEBUG_ISO_CLIENT) + printf("IsoClientConnection_associate: no ACSE_ASSOCIATE indication\n"); + goto returnError; + } + + + ByteBuffer_wrap(self->receivePayloadBuffer, self->acseConnection.userDataBuffer, + self->acseConnection.userDataBufferSize, self->acseConnection.userDataBufferSize); + + Semaphore_wait(self->receiveBufferMutex); + + self->callback(ISO_IND_ASSOCIATION_SUCCESS, self->callbackParameter, self->receivePayloadBuffer); + + /* wait for upper layer to release buffer */ + Semaphore_wait(self->receiveBufferMutex); + + self->state = STATE_ASSOCIATED; + + if (self->thread == NULL) { + self->thread = Thread_create(connectionThreadFunction, self, false); + Thread_start(self->thread); + } + + self->startHandlingThread = true; + + while (self->handlingThreadRunning == false) + Thread_sleep(1); + + return; + +returnError: + self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); + + self->state = STATE_ERROR; + + Socket_destroy(self->socket); + self->socket = NULL; + + Semaphore_post(self->transmitBufferMutex); //TODO check + + return; +} + +void +IsoClientConnection_sendMessage(IsoClientConnection self, ByteBuffer* payloadBuffer) +{ + + struct sBufferChain payloadBCMemory; + BufferChain payload = &payloadBCMemory; + + BufferChain_init(payload, payloadBuffer->size, payloadBuffer->size, NULL, payloadBuffer->buffer); + + struct sBufferChain presentationBCMemory; + BufferChain presentationBuffer = &presentationBCMemory; + + presentationBuffer->buffer = self->sendBuffer + payload->length; + presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE; + + IsoPresentation_createUserData(self->presentation, presentationBuffer, payload); + + struct sBufferChain sessionBufferBCMemory; + BufferChain sessionBuffer = &sessionBufferBCMemory; + + IsoSession_createDataSpdu(self->session, sessionBuffer, presentationBuffer); + + CotpIndication indication = CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer); + + /* release transmit buffer for use by API client */ + Semaphore_post(self->transmitBufferMutex); + + if (indication != COTP_OK) + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: IsoClientConnection_sendMessage: send message failed!\n"); +} + +void +IsoClientConnection_close(IsoClientConnection self) +{ + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: IsoClientConnection_close\n"); + + if (self->handlingThreadRunning) { + self->stopHandlingThread = true; + while (self->handlingThreadRunning) + Thread_sleep(1); + } + + self->state = STATE_IDLE; +} + + +void +IsoClientConnection_destroy(IsoClientConnection self) +{ + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: IsoClientConnection_destroy\n"); + + if (self->state == STATE_ASSOCIATED) { + + if (DEBUG_ISO_CLIENT) + printf("ISO_CLIENT: call IsoClientConnection_close\n"); + + IsoClientConnection_close(self); + } + + /* stop handling thread */ + self->destroyHandlingThread = true; + + if (self->thread != NULL) { + while (self->destroyHandlingThread) + Thread_sleep(1); + + Thread_destroy(self->thread); + } + + if (self->receiveBuf != NULL) + GLOBAL_FREEMEM(self->receiveBuf); + if (self->receiveBuffer != NULL) + GLOBAL_FREEMEM(self->receiveBuffer); + if (self->cotpConnection != NULL) + GLOBAL_FREEMEM(self->cotpConnection); + + if (self->cotpReadBuffer != NULL) + GLOBAL_FREEMEM(self->cotpReadBuffer); + + if (self->cotpReadBuf != NULL) + GLOBAL_FREEMEM(self->cotpReadBuf); + + if (self->cotpWriteBuffer != NULL) + GLOBAL_FREEMEM(self->cotpWriteBuffer); + + if (self->cotpWriteBuf != NULL) + GLOBAL_FREEMEM(self->cotpWriteBuf); + + if (self->session != NULL) + GLOBAL_FREEMEM(self->session); + if (self->presentation != NULL) + GLOBAL_FREEMEM(self->presentation); + + GLOBAL_FREEMEM(self->transmitPayloadBuffer); + GLOBAL_FREEMEM(self->receivePayloadBuffer); + + Semaphore_destroy(self->receiveBufferMutex); + Semaphore_destroy(self->transmitBufferMutex); + + GLOBAL_FREEMEM(self->sendBuffer); + GLOBAL_FREEMEM(self); +} + +void +IsoClientConnection_abort(IsoClientConnection self) +{ + //TODO block other messages from being sent + IsoClientConnection_allocateTransmitBuffer(self); + + struct sBufferChain sAcseBuffer; + BufferChain acseBuffer = &sAcseBuffer; + acseBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE; + acseBuffer->buffer = self->sendBuffer; + acseBuffer->nextPart = NULL; + + AcseConnection_createAbortMessage(NULL, acseBuffer, false); + + struct sBufferChain sPresentationBuffer; + BufferChain presentationBuffer = &sPresentationBuffer; + presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acseBuffer->length; + presentationBuffer->buffer = self->sendBuffer + acseBuffer->length; + presentationBuffer->nextPart = acseBuffer; + + IsoPresentation_createAbortUserMessage(self->presentation, presentationBuffer, acseBuffer); + + struct sBufferChain sSessionBuffer; + BufferChain sessionBuffer = &sSessionBuffer; + sessionBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - presentationBuffer->length; + sessionBuffer->buffer = self->sendBuffer + presentationBuffer->length; + sessionBuffer->nextPart = presentationBuffer; + + IsoSession_createAbortSpdu(self->session, sessionBuffer, presentationBuffer); + + CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer); + + Semaphore_post(self->transmitBufferMutex); + + uint64_t timeout = Hal_getTimeInMs() + CONFIG_TCP_READ_TIMEOUT_MS; + + while ((self->handlingThreadRunning == true) && (Hal_getTimeInMs() < timeout)); +} + +void +IsoClientConnection_release(IsoClientConnection self) +{ + //TODO block other messages from being sent + IsoClientConnection_allocateTransmitBuffer(self); + + struct sBufferChain sAcseBuffer; + BufferChain acseBuffer = &sAcseBuffer; + acseBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE; + acseBuffer->buffer = self->sendBuffer; + acseBuffer->nextPart = NULL; + + AcseConnection_createReleaseRequestMessage(NULL, acseBuffer); + + struct sBufferChain sPresentationBuffer; + BufferChain presentationBuffer = &sPresentationBuffer; + presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acseBuffer->length; + presentationBuffer->buffer = self->sendBuffer + acseBuffer->length; + presentationBuffer->nextPart = acseBuffer; + + IsoPresentation_createUserDataACSE(self->presentation, presentationBuffer, acseBuffer); + + struct sBufferChain sSessionBuffer; + BufferChain sessionBuffer = &sSessionBuffer; + sessionBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - presentationBuffer->length; + sessionBuffer->buffer = self->sendBuffer + presentationBuffer->length; + sessionBuffer->nextPart = presentationBuffer; + + IsoSession_createFinishSpdu(NULL, sessionBuffer, presentationBuffer); + + CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer); + + Semaphore_post(self->transmitBufferMutex); +} + + +ByteBuffer* +IsoClientConnection_allocateTransmitBuffer(IsoClientConnection self) +{ + Semaphore_wait(self->transmitBufferMutex); + self->transmitPayloadBuffer->size = 0; + self->transmitPayloadBuffer->maxSize = ISO_CLIENT_BUFFER_SIZE; + return self->transmitPayloadBuffer; +} + +void +IsoClientConnection_releaseReceiveBuffer(IsoClientConnection self) +{ + Semaphore_post(self->receiveBufferMutex); +} diff --git a/src/mms/iso_common/iso_connection_parameters.c b/src/mms/iso_common/iso_connection_parameters.c index 2e71477a..8f4b2d2e 100644 --- a/src/mms/iso_common/iso_connection_parameters.c +++ b/src/mms/iso_common/iso_connection_parameters.c @@ -35,7 +35,7 @@ AcseAuthenticationParameter AcseAuthenticationParameter_create() { AcseAuthenticationParameter self = (AcseAuthenticationParameter) - calloc(1, sizeof(struct sAcseAuthenticationParameter)); + GLOBAL_CALLOC(1, sizeof(struct sAcseAuthenticationParameter)); return self; } @@ -45,9 +45,9 @@ AcseAuthenticationParameter_destroy(AcseAuthenticationParameter self) { if (self->mechanism == ACSE_AUTH_PASSWORD) if (self->value.password.octetString != NULL) - free(self->value.password.octetString); + GLOBAL_FREEMEM(self->value.password.octetString); - free(self); + GLOBAL_FREEMEM(self); } void @@ -67,7 +67,7 @@ AcseAuthenticationParameter_setAuthMechanism(AcseAuthenticationParameter self, A IsoConnectionParameters IsoConnectionParameters_create() { - IsoConnectionParameters self = (IsoConnectionParameters) calloc(1, sizeof(struct sIsoConnectionParameters)); + IsoConnectionParameters self = (IsoConnectionParameters) GLOBAL_CALLOC(1, sizeof(struct sIsoConnectionParameters)); return self; } @@ -75,7 +75,7 @@ IsoConnectionParameters_create() void IsoConnectionParameters_destroy(IsoConnectionParameters self) { - free(self); + GLOBAL_FREEMEM(self); } void @@ -86,14 +86,14 @@ IsoConnectionParameters_setAcseAuthenticationParameter(IsoConnectionParameters s } void -IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, char* hostname, int tcpPort) +IsoConnectionParameters_setTcpParameters(IsoConnectionParameters self, const char* hostname, int tcpPort) { self->hostname = hostname; self->tcpPort = tcpPort; } void -IsoConnectionParameters_setRemoteApTitle(IsoConnectionParameters self, char* apTitle, int aeQualifier) +IsoConnectionParameters_setRemoteApTitle(IsoConnectionParameters self, const char* apTitle, int aeQualifier) { if (apTitle == NULL) self->remoteApTitleLen = 0; @@ -104,7 +104,7 @@ IsoConnectionParameters_setRemoteApTitle(IsoConnectionParameters self, char* apT } void -IsoConnectionParameters_setRemoteAddresses(IsoConnectionParameters self, uint32_t pSelector, uint16_t sSelector, uint16_t tSelector) +IsoConnectionParameters_setRemoteAddresses(IsoConnectionParameters self, uint32_t pSelector, uint16_t sSelector, TSelector tSelector) { self->remotePSelector = pSelector; self->remoteSSelector = sSelector; @@ -124,7 +124,7 @@ IsoConnectionParameters_setLocalApTitle(IsoConnectionParameters self, char* apTi } void -IsoConnectionParameters_setLocalAddresses(IsoConnectionParameters self, uint32_t pSelector, uint16_t sSelector, uint16_t tSelector) +IsoConnectionParameters_setLocalAddresses(IsoConnectionParameters self, uint32_t pSelector, uint16_t sSelector, TSelector tSelector) { self->localPSelector = pSelector; self->localSSelector = sSelector; diff --git a/src/mms/iso_cotp/cotp.c b/src/mms/iso_cotp/cotp.c index 80e80a59..83089f01 100644 --- a/src/mms/iso_cotp/cotp.c +++ b/src/mms/iso_cotp/cotp.c @@ -3,10 +3,9 @@ * * ISO 8073 Connection Oriented Transport Protocol over TCP (RFC1006) * - * Partial implementation of the ISO 8073 COTP protocol for MMS. + * Partial implementation of the ISO 8073 COTP (ISO TP0) protocol for MMS. * - * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -28,11 +27,10 @@ #include "stack_config.h" #include "cotp.h" -#include "byte_stream.h" #include "byte_buffer.h" #include "buffer_chain.h" -#define COTP_RFC1006_HEADER_SIZE 4 +#define TPKT_RFC1006_HEADER_SIZE 4 #define COTP_DATA_HEADER_SIZE 3 @@ -46,8 +44,20 @@ #define DEBUG_COTP 0 #endif -static int -addPayloadToBuffer(CotpConnection* self, int rfc1006Length); +static bool +addPayloadToBuffer(CotpConnection* self, uint8_t* buffer, int payloadLength); + +static inline uint16_t +getUint16(uint8_t* buffer) +{ + return (buffer[0] * 0x100) + buffer[1]; +} + +static inline uint8_t +getUint8(uint8_t* buffer) +{ + return buffer[0]; +} static void writeOptions(CotpConnection* self) @@ -56,28 +66,32 @@ writeOptions(CotpConnection* self) uint8_t* buffer = self->writeBuffer->buffer; int bufPos = self->writeBuffer->size; - if (self->options.tpdu_size != 0) { + if (self->options.tpduSize != 0) { if (DEBUG_COTP) printf("COTP: send TPDU size: %i\n", CotpConnection_getTpduSize(self)); buffer[bufPos++] = 0xc0; buffer[bufPos++] = 0x01; - buffer[bufPos++] = self->options.tpdu_size; + buffer[bufPos++] = self->options.tpduSize; } - if (self->options.tsap_id_dst != -1) { + if (self->options.tSelDst.size != 0) { buffer[bufPos++] = 0xc2; - buffer[bufPos++] = 0x02; - buffer[bufPos++] = (uint8_t) (self->options.tsap_id_dst / 0x100); - buffer[bufPos++] = (uint8_t) (self->options.tsap_id_dst & 0xff); + buffer[bufPos++] = (uint8_t) self->options.tSelDst.size; + + int i; + for (i = 0; i < self->options.tSelDst.size; i++) + buffer[bufPos++] = (uint8_t) self->options.tSelDst.value[i]; } - if (self->options.tsap_id_src != -1) { + if (self->options.tSelSrc.size != 0) { buffer[bufPos++] = 0xc1; - buffer[bufPos++] = 0x02; - buffer[bufPos++] = (uint8_t) (self->options.tsap_id_src / 0x100); - buffer[bufPos++] = (uint8_t) (self->options.tsap_id_src & 0xff); + buffer[bufPos++] = (uint8_t) self->options.tSelSrc.size; + + int i; + for (i = 0; i < self->options.tSelSrc.size; i++) + buffer[bufPos++] = (uint8_t) self->options.tSelSrc.value[i]; } self->writeBuffer->size = bufPos; @@ -87,12 +101,16 @@ static int getOptionsLength(CotpConnection* self) { int optionsLength = 0; - if (self->options.tpdu_size != 0) + + if (self->options.tpduSize != 0) optionsLength += 3; - if (self->options.tsap_id_dst != -1) - optionsLength += 4; - if (self->options.tsap_id_src != -1) - optionsLength += 4; + + if (self->options.tSelDst.size != 0) + optionsLength += (2 + self->options.tSelDst.size); + + if (self->options.tSelSrc.size != 0) + optionsLength += (2 + self->options.tSelSrc.size); + return optionsLength; } @@ -225,12 +243,12 @@ CotpConnection_sendDataMessage(CotpConnection* self, BufferChain payload) printf("COTP: Send COTP fragment %i bufpos: %i\n", fragments, currentBufPos); if (!sendBuffer(self)) - return ERROR; + return COTP_ERROR; fragments--; } - return OK; + return COTP_OK; } static void @@ -238,7 +256,7 @@ allocateWriteBuffer(CotpConnection* self) { if (self->writeBuffer == NULL ) self->writeBuffer = ByteBuffer_create(NULL, - CotpConnection_getTpduSize(self) + COTP_RFC1006_HEADER_SIZE); + CotpConnection_getTpduSize(self) + TPKT_RFC1006_HEADER_SIZE); } /* client side */ @@ -247,17 +265,22 @@ CotpConnection_sendConnectionRequestMessage(CotpConnection* self, IsoConnectionP { allocateWriteBuffer(self); - const int CON_REQUEST_SIZE = 22; + self->options.tSelDst = isoParameters->remoteTSelector; + self->options.tSelSrc = isoParameters->localTSelector; - if(self->writeBuffer->maxSize < CON_REQUEST_SIZE) - return ERROR; + int cotpRequestSize = getOptionsLength(self) + 6; + + int conRequestSize = cotpRequestSize + 5; + + if(self->writeBuffer->maxSize < conRequestSize) + return COTP_ERROR; uint8_t* buffer = self->writeBuffer->buffer; - writeRfc1006Header(self, CON_REQUEST_SIZE); + writeRfc1006Header(self, conRequestSize); /* LI */ - buffer[4] = 17; + buffer[4] = (uint8_t) cotpRequestSize; /* TPDU CODE */ buffer[5] = 0xe0; @@ -275,15 +298,12 @@ CotpConnection_sendConnectionRequestMessage(CotpConnection* self, IsoConnectionP self->writeBuffer->size = 11; - self->options.tsap_id_dst = isoParameters->remoteTSelector; - self->options.tsap_id_src = 0; - writeOptions(self); if (sendBuffer(self)) - return OK; + return COTP_OK; else - return ERROR; + return COTP_ERROR; } CotpIndication @@ -301,127 +321,134 @@ CotpConnection_sendConnectionResponseMessage(CotpConnection* self) writeOptions(self); if (sendBuffer(self)) - return OK; + return COTP_OK; else - return ERROR; + return COTP_ERROR; } -static int -parseOptions(CotpConnection* self, int opt_len) +static bool +parseOptions(CotpConnection* self, uint8_t* buffer, int bufLen) { - int read_bytes = 0; - uint8_t option_type, option_len, uint8_value; - uint16_t uint16_value; - int i; - Socket sock = self->socket; - - while (read_bytes < opt_len) { - if (ByteStream_readUint8(sock, &option_type) == -1) - goto cpo_error; - if (ByteStream_readUint8(sock, &option_len) == -1) - goto cpo_error; + int bufPos = 0; + + while (bufPos < bufLen) { + uint8_t optionType = buffer[bufPos++]; + uint8_t optionLen = buffer[bufPos++]; - read_bytes += 2; + if (optionLen > (bufLen - bufPos)) { + if (DEBUG_COTP) + printf("COTP: option to long optionLen:%i bufPos:%i bufLen:%i\n", optionLen, bufPos, bufLen); + goto cpo_error; + } if (DEBUG_COTP) - printf("COTP: option: %02x len: %02x\n", option_type, option_len); + printf("COTP: option: %02x len: %02x\n", optionType, optionLen); - switch (option_type) { + switch (optionType) { case 0xc0: - { - if (ByteStream_readUint8(sock, &uint8_value) == -1) - goto cpo_error; - read_bytes++; + if (optionLen == 1) { + int requestedTpduSize = (1 << buffer[bufPos++]); - int requestedTpduSize = (1 << uint8_value); + CotpConnection_setTpduSize(self, requestedTpduSize); if (DEBUG_COTP) printf("COTP: requested TPDU size: %i\n", requestedTpduSize); - - CotpConnection_setTpduSize(self, requestedTpduSize); } + else + goto cpo_error; break; - case 0xc1: - if (option_len == 2) { - if (ByteStream_readUint16(sock, &uint16_value) == -1) - goto cpo_error; - read_bytes += 2; - self->options.tsap_id_src = (int32_t) uint16_value; - } else + + case 0xc1: /* remote T-selector */ + if (optionLen < 5) { + self->options.tSelSrc.size = optionLen; + + int i; + for (i = 0; i < optionLen; i++) + self->options.tSelSrc.value[i] = buffer[bufPos++]; + } + else goto cpo_error; break; - case 0xc2: - if (option_len == 2) { - if (ByteStream_readUint16(sock, &uint16_value) == -1) - goto cpo_error; - self->options.tsap_id_dst = (int32_t) uint16_value; - read_bytes += 2; - } else + + case 0xc2: /* local T-selector */ + if (optionLen < 5) { + self->options.tSelDst.size = optionLen; + + int i; + for (i = 0; i < optionLen; i++) + self->options.tSelDst.value[i] = buffer[bufPos++]; + } + else goto cpo_error; break; + case 0xc6: /* additional option selection */ - if (option_len == 1) { - ByteStream_readUint8(sock, &uint8_value); /* ignore value */ - read_bytes++; - } + if (optionLen == 1) + bufPos++; /* ignore value */ else goto cpo_error; break; - default: + default: if (DEBUG_COTP) - printf("COTP: Unknown option %02x\n", option_type); + printf("COTP: Unknown option %02x\n", optionType); - for (i = 0; i < opt_len; i++) { - if (ByteStream_readUint8(sock, &uint8_value) == -1) - goto cpo_error; - } - - read_bytes += opt_len; + bufPos += optionLen; /* ignore value */ break; } } - return 1; + return true; - cpo_error: +cpo_error: if (DEBUG_COTP) printf("COTP: cotp_parse_options: error parsing options!\n"); - return -1; + return false; } void CotpConnection_init(CotpConnection* self, Socket socket, - ByteBuffer* payloadBuffer) + ByteBuffer* payloadBuffer, ByteBuffer* readBuffer, ByteBuffer* writeBuffer) { self->state = 0; self->socket = socket; self->srcRef = -1; self->dstRef = -1; self->protocolClass = -1; - self->options.tpdu_size = 0; - self->options.tsap_id_src = -1; - self->options.tsap_id_dst = -1; + self->options.tpduSize = 0; + + TSelector tsel; + tsel.size = 2; + tsel.value[0] = 0; + tsel.value[1] = 1; + + self->options.tSelSrc = tsel; + self->options.tSelDst = tsel; self->payload = payloadBuffer; /* default TPDU size is maximum size */ CotpConnection_setTpduSize(self, COTP_MAX_TPDU_SIZE); - self->writeBuffer = NULL; + self->writeBuffer = writeBuffer; + self->readBuffer = readBuffer; + self->packetSize = 0; } -void -CotpConnection_destroy(CotpConnection* self) -{ - if (self->writeBuffer != NULL) - ByteBuffer_destroy(self->writeBuffer); -} +//void +//CotpConnection_destroy(CotpConnection* self) +//{ +// if (self->writeBuffer != NULL) +// ByteBuffer_destroy(self->writeBuffer); +// +// if (self->readBuffer != NULL) +// ByteBuffer_destroy(self->readBuffer); +//} int /* in byte */ CotpConnection_getTpduSize(CotpConnection* self) { - return (1 << self->options.tpdu_size); + return (1 << self->options.tpduSize); } void @@ -438,7 +465,7 @@ CotpConnection_setTpduSize(CotpConnection* self, int tpduSize /* in byte */) if ((1 << newTpduSize) > tpduSize) newTpduSize--; - self->options.tpdu_size = newTpduSize; + self->options.tpduSize = newTpduSize; } ByteBuffer* @@ -469,183 +496,202 @@ CotpConnection_getDstRef(CotpConnection* self) +--+------+---------+---------+---+---+------+-------+---------+ */ -static int -parseConnectRequestTpdu(CotpConnection* self, uint8_t len) + +static bool +parseConnectRequestTpdu(CotpConnection* self, uint8_t* buffer, uint8_t len) { - uint16_t dstRef; - uint16_t srcRef; - uint8_t protocolClass; - Socket sock = self->socket; + if (len < 6) + return false; - if (ByteStream_readUint16(sock, &dstRef) != 2) - return -1; - else - self->dstRef = dstRef; + self->dstRef = getUint16(buffer); + self->srcRef = getUint16(buffer + 2); + self->protocolClass = getUint8(buffer + 4); - if (ByteStream_readUint16(sock, &srcRef) != 2) - return -1; - else - self->srcRef = srcRef; + return parseOptions(self, buffer + 5, len - 6); +} - if (ByteStream_readUint8(sock, &protocolClass) != 1) - return -1; - else - self->protocolClass = protocolClass; +static bool +parseConnectConfirmTpdu(CotpConnection* self, uint8_t* buffer, uint8_t len) +{ + if (len < 6) + return false; + + self->srcRef = getUint16(buffer); + self->dstRef = getUint16(buffer + 2); + self->protocolClass = getUint8(buffer + 4); - return parseOptions(self, len - 6); + return parseOptions(self, buffer + 5, len - 6); } -static int -parseConnectConfirmTpdu(CotpConnection* self, uint8_t len) +static bool +parseDataTpdu(CotpConnection* self, uint8_t* buffer, uint8_t len) { - uint16_t dstRef; - uint16_t srcRef; - uint8_t protocolClass; - Socket sock = self->socket; - - if (ByteStream_readUint16(sock, &dstRef) != 2) - return -1; - else - self->srcRef = dstRef; + if (len != 2) + return false; - if (ByteStream_readUint16(sock, &srcRef) != 2) - return -1; - else - self->dstRef = srcRef; + uint8_t flowControl = getUint8(buffer); - if (ByteStream_readUint8(sock, &protocolClass) != 1) - return -1; + if (flowControl & 0x80) + self->isLastDataUnit = true; else - self->protocolClass = protocolClass; + self->isLastDataUnit = false; - return parseOptions(self, len - 6); + return true; } -static int -parseDataTpdu(CotpConnection* self, uint8_t len) +static bool +addPayloadToBuffer(CotpConnection* self, uint8_t* buffer, int payloadLength) { - uint8_t flowControl; + if (DEBUG_COTP) + printf("COTP: add to payload buffer (cur size: %i, len: %i)\n", self->payload->size, payloadLength); - if (len != 2) - return -1; + if ((self->payload->size + payloadLength) > self->payload->maxSize) + return false; - if (ByteStream_readUint8(self->socket, &flowControl) != 1) - return -1; - else { - if (flowControl & 0x80) - self->isLastDataUnit = true; - else - self->isLastDataUnit = false; - } + memcpy(self->payload->buffer + self->payload->size, buffer, payloadLength); - return 1; + self->payload->size += payloadLength; + + return true; } static CotpIndication -parseRFC1006Header(CotpConnection* self, uint16_t* rfc1006_length) +parseCotpMessage(CotpConnection* self) { - Socket sock = self->socket; - uint8_t b; + uint8_t* buffer = self->readBuffer->buffer + 4; + int tpduLength = self->readBuffer->size - 4; + + uint8_t len; + uint8_t tpduType; - if (ByteStream_readUint8(sock, &b) == -1) - return ERROR; - if (b != 3) - return ERROR; + len = buffer[0]; + assert(len <= tpduLength); - if (ByteStream_readUint8(sock, &b) == -1) - return ERROR; - if (b != 0) - return ERROR; + tpduType = buffer[1]; - if (ByteStream_readUint16(sock, rfc1006_length) == -1) - return ERROR; + switch (tpduType) { + case 0xe0: + if (parseConnectRequestTpdu(self, buffer + 2, len)) + return COTP_CONNECT_INDICATION; + else + return COTP_ERROR; + case 0xd0: + if (parseConnectConfirmTpdu(self, buffer + 2, len)) + return COTP_CONNECT_INDICATION; + else + return COTP_ERROR; + case 0xf0: + if (parseDataTpdu(self, buffer + 2, len)) { + + if (addPayloadToBuffer(self, buffer + 3, tpduLength - 3) != 1) + return COTP_ERROR; + + if (self->isLastDataUnit) + return COTP_DATA_INDICATION; + else + return COTP_MORE_FRAGMENTS_FOLLOW; + } + else + return COTP_ERROR; + default: + return COTP_ERROR; + } - return OK; } +CotpIndication +CotpConnection_parseIncomingMessage(CotpConnection* self) +{ + CotpIndication indication = parseCotpMessage(self); -static CotpIndication parseIncomingMessage(CotpConnection* self); + self->readBuffer->size = 0; + self->packetSize = 0; -static int -addPayloadToBuffer(CotpConnection* self, int rfc1006Length) + return indication; +} + +void +CotpConnection_resetPayload(CotpConnection* self) +{ + self->payload->size = 0; +} + +TpktState +CotpConnection_readToTpktBuffer(CotpConnection* self) { - int payloadLength = rfc1006Length - 7; + uint8_t* buffer = self->readBuffer->buffer; + int bufferSize = self->readBuffer->maxSize; + int bufPos = self->readBuffer->size; - if ((self->payload->size + payloadLength) > self->payload->maxSize) - return 0; + assert (bufferSize > 4); - int readLength = ByteStream_readOctets(self->socket, - self->payload->buffer + self->payload->size, - payloadLength); + int readBytes; - if (readLength != payloadLength) { - if (DEBUG_COTP) - printf("COTP: read %i bytes should have been %i\n", readLength, payloadLength); - return 0; - } - else { - self->payload->size += payloadLength; + if (bufPos < 4) { - if (self->isLastDataUnit == false) { - if (parseIncomingMessage(self) == DATA_INDICATION) - return 1; - else - return 0; + readBytes = Socket_read(self->socket, buffer + bufPos, 4 - bufPos); + + if (readBytes < 0) + goto exit_closed; + + if (DEBUG_COTP) { + if (readBytes > 0) + printf("TPKT: read %i bytes from socket\n", readBytes); } - else - return 1; - } -} -static CotpIndication -parseIncomingMessage(CotpConnection* self) -{ - uint8_t len; - uint8_t tpduType; - uint16_t rfc1006Length; - Socket sock = self->socket; + bufPos += readBytes; - if (parseRFC1006Header(self, &rfc1006Length) == ERROR) - return ERROR; + if (bufPos == 4) { + if ((buffer[0] == 3) && (buffer[1] == 0)) { + self->packetSize = (buffer[2] * 0x100) + buffer[3]; - if (ByteStream_readUint8(sock, &len) != 1) { - return ERROR; - } + if (DEBUG_COTP) + printf("TPKT: header complete (msg size = %i)\n", self->packetSize); - if (ByteStream_readUint8(sock, &tpduType) == 1) { - switch (tpduType) { - case 0xe0: - if (parseConnectRequestTpdu(self, len) == 1) - return CONNECT_INDICATION; - else - return ERROR; - case 0xd0: - self->isLastDataUnit = true; - if (parseConnectConfirmTpdu(self, len) == 1) - return CONNECT_INDICATION; - else - return ERROR; - case 0xf0: - if (parseDataTpdu(self, len) == 1) { - if (addPayloadToBuffer(self, rfc1006Length) == 1) - return DATA_INDICATION; - else - return ERROR; + if (self->packetSize > bufferSize) { + if (DEBUG_COTP) printf("TPKT: packet too large\n"); + goto exit_error; + } + } + else { + if (DEBUG_COTP) printf("TPKT: failed to decode TPKT header.\n"); + goto exit_error; } - else - return ERROR; - default: - return ERROR; } + else + goto exit_waiting; } - else - return ERROR; -} -CotpIndication -CotpConnection_parseIncomingMessage(CotpConnection* self) -{ - self->payload->size = 0; + readBytes = Socket_read(self->socket, buffer + bufPos, self->packetSize - bufPos); + + if (readBytes < 0) + goto exit_closed; + + bufPos += readBytes; + + if (bufPos < self->packetSize) + goto exit_waiting; - return parseIncomingMessage(self); + if (DEBUG_COTP) printf("TPKT: message complete (size = %i)\n", self->packetSize); + + self->readBuffer->size = bufPos; + return TPKT_PACKET_COMPLETE; + +exit_closed: + if (DEBUG_COTP) printf("TPKT: socket closed or socket error\n"); + return TPKT_ERROR; + +exit_error: + if (DEBUG_COTP) printf("TPKT: Error parsing message\n"); + return TPKT_ERROR; + +exit_waiting: + + if (DEBUG_COTP) + if (bufPos != 0) + printf("TPKT: waiting (read %i of %i)\n", bufPos, self->packetSize); + + self->readBuffer->size = bufPos; + return TPKT_WAITING; } + diff --git a/src/mms/iso_mms/asn1c/GeneralizedTime.c b/src/mms/iso_mms/asn1c/GeneralizedTime.c index 80341811..cf974496 100644 --- a/src/mms/iso_mms/asn1c/GeneralizedTime.c +++ b/src/mms/iso_mms/asn1c/GeneralizedTime.c @@ -14,6 +14,23 @@ #include #endif /* __CYGWIN__ */ +#ifdef __IAR_SYSTEMS_ICC__ + +static struct tm *localtime_r(const time_t *tloc, struct tm *result) { + struct tm *tm; + if((tm = localtime(tloc))) + return memcpy(result, tm, sizeof(struct tm)); + return 0; +} + +static struct tm *gmtime_r(const time_t *tloc, struct tm *result) { + struct tm *tm; + if((tm = gmtime(tloc))) + return memcpy(result, tm, sizeof(struct tm)); + return 0; +} +#endif /* __IAR_SYSTEMS_ICC__ */ + #if defined(WIN32) #pragma message( "PLEASE STOP AND READ!") #pragma message( " localtime_r is implemented via localtime(), which may be not thread-safe.") diff --git a/src/mms/iso_mms/asn1c/INTEGER.c b/src/mms/iso_mms/asn1c/INTEGER.c index 53a64285..c1d074f5 100644 --- a/src/mms/iso_mms/asn1c/INTEGER.c +++ b/src/mms/iso_mms/asn1c/INTEGER.c @@ -6,7 +6,7 @@ #include #include #include /* Encoder and decoder of a primitive type */ -#include +//#include /* * INTEGER basic type description. @@ -95,8 +95,6 @@ INTEGER_encode_der(asn_TYPE_descriptor_t *td, void *sptr, return der_encode_primitive(td, sptr, tag_mode, tag, cb, app_key); } -static const asn_INTEGER_enum_map_t *INTEGER_map_enum2value(asn_INTEGER_specifics_t *specs, const char *lstart, const char *lstop); - #if 0 /* * INTEGER specific human-readable output. @@ -220,6 +218,7 @@ INTEGER_print(asn_TYPE_descriptor_t *td, const void *sptr, int ilevel, } #endif +#if 0 struct e2v_key { const char *start; const char *stop; @@ -245,6 +244,7 @@ INTEGER__compar_enum2value(const void *kp, const void *am) { return name[0] ? -1 : 0; } + static const asn_INTEGER_enum_map_t * INTEGER_map_enum2value(asn_INTEGER_specifics_t *specs, const char *lstart, const char *lstop) { asn_INTEGER_enum_map_t *el_found; @@ -282,7 +282,9 @@ INTEGER_map_enum2value(asn_INTEGER_specifics_t *specs, const char *lstart, const } return el_found; } +#endif +#if 0 static int INTEGER__compar_value2enum(const void *kp, const void *am) { long a = *(const long *)kp; @@ -301,7 +303,9 @@ INTEGER_map_value2enum(asn_INTEGER_specifics_t *specs, long value) { count, sizeof(specs->value2enum[0]), INTEGER__compar_value2enum); } +#endif +#if 0 static int INTEGER_st_prealloc(INTEGER_t *st, int min_size) { void *p = MALLOC(min_size + 1); @@ -315,6 +319,7 @@ INTEGER_st_prealloc(INTEGER_t *st, int min_size) { return -1; } } +#endif #if 0 /* @@ -737,7 +742,7 @@ asn_INTEGER2long(const INTEGER_t *iptr, long *lptr) { /* Sanity checking */ if(!iptr || !iptr->buf || !lptr) { - errno = EINVAL; + //errno = EINVAL; return -1; } @@ -766,7 +771,7 @@ asn_INTEGER2long(const INTEGER_t *iptr, long *lptr) { size = end - b; if(size > sizeof(long)) { /* Still cannot fit the long */ - errno = ERANGE; + //errno = ERANGE; return -1; } } @@ -799,7 +804,7 @@ asn_long2INTEGER(INTEGER_t *st, long value) { int add; if(!st) { - errno = EINVAL; + //errno = EINVAL; return -1; } diff --git a/src/mms/iso_mms/asn1c/NativeEnumerated.c b/src/mms/iso_mms/asn1c/NativeEnumerated.c index 76269a35..76aab81b 100644 --- a/src/mms/iso_mms/asn1c/NativeEnumerated.c +++ b/src/mms/iso_mms/asn1c/NativeEnumerated.c @@ -27,7 +27,8 @@ asn_TYPE_descriptor_t asn_DEF_NativeEnumerated = { NativeInteger_decode_ber, NativeInteger_encode_der, NULL, - NativeEnumerated_encode_xer, + NULL, + //NativeEnumerated_encode_xer, NativeEnumerated_decode_uper, NativeEnumerated_encode_uper, 0, /* Use generic outmost tag fetcher */ @@ -40,6 +41,7 @@ asn_TYPE_descriptor_t asn_DEF_NativeEnumerated = { 0 /* No specifics */ }; +#if 0 asn_enc_rval_t NativeEnumerated_encode_xer(asn_TYPE_descriptor_t *td, void *sptr, int ilevel, enum xer_encoder_flags_e flags, @@ -69,6 +71,7 @@ NativeEnumerated_encode_xer(asn_TYPE_descriptor_t *td, void *sptr, _ASN_ENCODE_FAILED; } } +#endif asn_dec_rval_t NativeEnumerated_decode_uper(asn_codec_ctx_t *opt_codec_ctx, diff --git a/src/mms/iso_mms/asn1c/asn_internal.h b/src/mms/iso_mms/asn1c/asn_internal.h index fc5ee818..2ba232bc 100644 --- a/src/mms/iso_mms/asn1c/asn_internal.h +++ b/src/mms/iso_mms/asn1c/asn_internal.h @@ -11,6 +11,9 @@ #include "asn_application.h" /* Application-visible API */ +//#include "libiec61850_platform_includes.h" +#include "lib_memory.h" + #ifndef __NO_ASSERT_H__ /* Include assert.h only for internal use. */ #include /* for assert() macro */ #endif @@ -23,10 +26,23 @@ extern "C" { #define ASN1C_ENVIRONMENT_VERSION 920 /* Compile-time version */ int get_asn1c_environment_version(void); /* Run-time version */ +#if 0 +#ifndef CALLOC #define CALLOC(nmemb, size) calloc(nmemb, size) +#endif + +#ifndef MALLOC #define MALLOC(size) malloc(size) +#endif + +#ifndef REALLOC #define REALLOC(oldptr, size) realloc(oldptr, size) +#endif + +#ifndef FREEMEM #define FREEMEM(ptr) free(ptr) +#endif +#endif /* * A macro for debugging the ASN.1 internals. diff --git a/src/mms/iso_mms/asn1c/asn_system.h b/src/mms/iso_mms/asn1c/asn_system.h index 61c41512..0fdb37fa 100644 --- a/src/mms/iso_mms/asn1c/asn_system.h +++ b/src/mms/iso_mms/asn1c/asn_system.h @@ -1,110 +1,119 @@ -/*- - * Copyright (c) 2003, 2004 Lev Walkin . All rights reserved. - * Redistribution and modifications are permitted subject to BSD license. - */ -/* - * Miscellaneous system-dependent types. - */ -#ifndef _ASN_SYSTEM_H_ -#define _ASN_SYSTEM_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include /* For snprintf(3) */ -#include /* For *alloc(3) */ -#include /* For memcpy(3) */ -#include /* For size_t */ -#include /* For va_start */ -#include /* for offsetof and ptrdiff_t */ - -#ifdef _WIN32 -#ifndef WIN32 -#define WIN32 -#endif -#endif - -#ifdef WIN32 - -#include -#include -#define snprintf _snprintf -#define vsnprintf _vsnprintf - -#ifdef _MSC_VER /* MSVS.Net */ -#ifndef __cplusplus -#define inline __inline -#endif -#define ssize_t SSIZE_T -//typedef char int8_t; -//typedef short int16_t; -//typedef int int32_t; -//typedef unsigned char uint8_t; -//typedef unsigned short uint16_t; -//typedef unsigned int uint32_t; -#define WIN32_LEAN_AND_MEAN -#include -#include -#define isnan _isnan -#define finite _finite -#define copysign _copysign -#define ilogb _logb -#endif /* _MSC_VER */ - -#else /* !WIN32 */ - -#if defined(__vxworks) -#include -#else /* !defined(__vxworks) */ - -#include /* C99 specifies this file */ -/* - * 1. Earlier FreeBSD version didn't have , - * but was present. - * 2. Sun Solaris requires for alloca(3), - * but does not have . - */ -#if (!defined(__FreeBSD__) || !defined(_SYS_INTTYPES_H_)) -#if defined(sun) -#include /* For alloca(3) */ -#include /* for finite(3) */ -#elif defined(__hpux) -#ifdef __GNUC__ -#include /* For alloca(3) */ -#else /* !__GNUC__ */ -#define inline -#endif /* __GNUC__ */ -#else -#include /* SUSv2+ and C99 specify this file, for uintXX_t */ -#endif /* defined(sun) */ -#endif - -#endif /* defined(__vxworks) */ - -#endif /* WIN32 */ - -#if __GNUC__ >= 3 -#ifndef GCC_PRINTFLIKE -#define GCC_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) -#endif -#else -#ifndef GCC_PRINTFLIKE -#define GCC_PRINTFLIKE(fmt,var) /* nothing */ -#endif -#endif - -#ifndef offsetof /* If not defined by */ -#define offsetof(s, m) ((ptrdiff_t)&(((s *)0)->m) - (ptrdiff_t)((s *)0)) -#endif /* offsetof */ - -#ifndef MIN /* Suitable for comparing primitive types (integers) */ -#if defined(__GNUC__) -#define MIN(a,b) ({ __typeof a _a = a; __typeof b _b = b; \ - ((_a)<(_b)?(_a):(_b)); }) -#else /* !__GNUC__ */ -#define MIN(a,b) ((a)<(b)?(a):(b)) /* Unsafe variant */ -#endif /* __GNUC__ */ -#endif /* MIN */ - -#endif /* _ASN_SYSTEM_H_ */ +/*- + * Copyright (c) 2003, 2004 Lev Walkin . All rights reserved. + * Redistribution and modifications are permitted subject to BSD license. + */ +/* + * Miscellaneous system-dependent types. + */ +#ifndef _ASN_SYSTEM_H_ +#define _ASN_SYSTEM_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include /* For snprintf(3) */ +#include /* For *alloc(3) */ +#include /* For memcpy(3) */ + +#include "stack_config.h" +#if CONFIG_INCLUDE_PLATFORM_SPECIFIC_HEADERS +#include "libiec61850_platform_specific.h" +#else +#include /* For size_t */ +#endif + +#include /* For va_start */ +#include /* for offsetof and ptrdiff_t */ + + + +#ifdef _WIN32 +#ifndef WIN32 +#define WIN32 +#endif +#endif + +#ifdef WIN32 + +#include +#include +#define snprintf _snprintf +#define vsnprintf _vsnprintf + +#ifdef _MSC_VER /* MSVS.Net */ +#ifndef __cplusplus +#define inline __inline +#endif +#define ssize_t SSIZE_T +//typedef char int8_t; +//typedef short int16_t; +//typedef int int32_t; +//typedef unsigned char uint8_t; +//typedef unsigned short uint16_t; +//typedef unsigned int uint32_t; +#define WIN32_LEAN_AND_MEAN +#include +#include +#define isnan _isnan +#define finite _finite +#define copysign _copysign +#define ilogb _logb +#endif /* _MSC_VER */ + +#else /* !WIN32 */ + +#if defined(__vxworks) +#include +#else /* !defined(__vxworks) */ + +#include /* C99 specifies this file */ +/* + * 1. Earlier FreeBSD version didn't have , + * but was present. + * 2. Sun Solaris requires for alloca(3), + * but does not have . + */ +#if (!defined(__FreeBSD__) || !defined(_SYS_INTTYPES_H_)) +#if defined(sun) +#include /* For alloca(3) */ +#include /* for finite(3) */ +#elif defined(__hpux) +#ifdef __GNUC__ +#include /* For alloca(3) */ +#else /* !__GNUC__ */ +#define inline +#endif /* __GNUC__ */ +#else +#include /* SUSv2+ and C99 specify this file, for uintXX_t */ +#endif /* defined(sun) */ +#endif + +#endif /* defined(__vxworks) */ + +#endif /* WIN32 */ + +#if __GNUC__ >= 3 +#ifndef GCC_PRINTFLIKE +#define GCC_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) +#endif +#else +#ifndef GCC_PRINTFLIKE +#define GCC_PRINTFLIKE(fmt,var) /* nothing */ +#endif +#endif + +#ifndef offsetof /* If not defined by */ +#define offsetof(s, m) ((ptrdiff_t)&(((s *)0)->m) - (ptrdiff_t)((s *)0)) +#endif /* offsetof */ + +#ifndef MIN /* Suitable for comparing primitive types (integers) */ +#if defined(__GNUC__) +#define MIN(a,b) ({ __typeof a _a = a; __typeof b _b = b; \ + ((_a)<(_b)?(_a):(_b)); }) +#else /* !__GNUC__ */ +#define MIN(a,b) ((a)<(b)?(a):(b)) /* Unsafe variant */ +#endif /* __GNUC__ */ +#endif /* MIN */ + +#endif /* _ASN_SYSTEM_H_ */ diff --git a/src/mms/iso_mms/asn1c/der_encoder.c b/src/mms/iso_mms/asn1c/der_encoder.c index 6c859e1b..cc2000e0 100644 --- a/src/mms/iso_mms/asn1c/der_encoder.c +++ b/src/mms/iso_mms/asn1c/der_encoder.c @@ -80,20 +80,18 @@ der_write_tags(asn_TYPE_descriptor_t *sd, ber_tlv_tag_t tag, /* EXPLICIT or IMPLICIT tag */ asn_app_consume_bytes_f *cb, void *app_key) { - ber_tlv_tag_t *tags; /* Copy of tags stream */ + ber_tlv_tag_t tag_mem[5]; /* Copy of tags stream */ + ber_tlv_tag_t* tags; int tags_count; /* Number of tags */ size_t overall_length; - ssize_t *lens; + ssize_t lens[5]; int i; - ASN_DEBUG("Writing tags (%s, tm=%d, tc=%d, tag=%s, mtc=%d)", - sd->name, tag_mode, sd->tags_count, - ber_tlv_tag_string(tag), - tag_mode - ?(sd->tags_count+1 - -((tag_mode == -1) && sd->tags_count)) - :sd->tags_count - ); + if (sd->tags_count > 4) { + printf("TO MUCH TAGS"); + errno = ENOMEM; + return -1; + } if(tag_mode) { /* @@ -102,11 +100,9 @@ der_write_tags(asn_TYPE_descriptor_t *sd, * and initialize it appropriately. */ int stag_offset; - tags = (ber_tlv_tag_t *)alloca((sd->tags_count + 1) * sizeof(ber_tlv_tag_t)); - if(!tags) { /* Can fail on !x86 */ - errno = ENOMEM; - return -1; - } + + tags = tag_mem; + tags_count = sd->tags_count + 1 /* EXPLICIT or IMPLICIT tag is given */ - ((tag_mode == -1) && sd->tags_count); @@ -124,12 +120,6 @@ der_write_tags(asn_TYPE_descriptor_t *sd, if(tags_count == 0) return 0; - lens = (ssize_t *)alloca(tags_count * sizeof(lens[0])); - if(!lens) { - errno = ENOMEM; - return -1; - } - /* * Array of tags is initialized. * Now, compute the size of the TLV pairs, from right to left. diff --git a/src/mms/iso_mms/asn1c/xer_encoder.h b/src/mms/iso_mms/asn1c/xer_encoder.h index 055e73c0..1cb546a3 100644 --- a/src/mms/iso_mms/asn1c/xer_encoder.h +++ b/src/mms/iso_mms/asn1c/xer_encoder.h @@ -1,59 +1,59 @@ -/*- - * Copyright (c) 2004 Lev Walkin . All rights reserved. - * Redistribution and modifications are permitted subject to BSD license. - */ -#ifndef _XER_ENCODER_H_ -#define _XER_ENCODER_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct asn_TYPE_descriptor_s; /* Forward declaration */ - -/* Flags used by the xer_encode() and (*xer_type_encoder_f), defined below */ -enum xer_encoder_flags_e { - /* Mode of encoding */ - XER_F_BASIC = 0x01, /* BASIC-XER (pretty-printing) */ - XER_F_CANONICAL = 0x02 /* Canonical XER (strict rules) */ -}; - -/* - * The XER encoder of any type. May be invoked by the application. - */ -asn_enc_rval_t xer_encode(struct asn_TYPE_descriptor_s *type_descriptor, - void *struct_ptr, /* Structure to be encoded */ - enum xer_encoder_flags_e xer_flags, - asn_app_consume_bytes_f *consume_bytes_cb, - void *app_key /* Arbitrary callback argument */ - ); - -/* - * The variant of the above function which dumps the BASIC-XER (XER_F_BASIC) - * output into the chosen file pointer. - * RETURN VALUES: - * 0: The structure is printed. - * -1: Problem printing the structure. - * WARNING: No sensible errno value is returned. - */ -int xer_fprint(FILE *stream, struct asn_TYPE_descriptor_s *td, void *sptr); - -/* - * Type of the generic XER encoder. - */ -typedef asn_enc_rval_t (xer_type_encoder_f)( - struct asn_TYPE_descriptor_s *type_descriptor, - void *struct_ptr, /* Structure to be encoded */ - int ilevel, /* Level of indentation */ - enum xer_encoder_flags_e xer_flags, - asn_app_consume_bytes_f *consume_bytes_cb, /* Callback */ - void *app_key /* Arbitrary callback argument */ - ); - -#ifdef __cplusplus -} -#endif - -#endif /* _XER_ENCODER_H_ */ +/*- + * Copyright (c) 2004 Lev Walkin . All rights reserved. + * Redistribution and modifications are permitted subject to BSD license. + */ +#ifndef _XER_ENCODER_H_ +#define _XER_ENCODER_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct asn_TYPE_descriptor_s; /* Forward declaration */ + +/* Flags used by the xer_encode() and (*xer_type_encoder_f), defined below */ +enum xer_encoder_flags_e { + /* Mode of encoding */ + XER_F_BASIC = 0x01, /* BASIC-XER (pretty-printing) */ + XER_F_CANONICAL = 0x02 /* Canonical XER (strict rules) */ +}; + +/* + * The XER encoder of any type. May be invoked by the application. + */ +asn_enc_rval_t xer_encode(struct asn_TYPE_descriptor_s *type_descriptor, + void *struct_ptr, /* Structure to be encoded */ + enum xer_encoder_flags_e xer_flags, + asn_app_consume_bytes_f *consume_bytes_cb, + void *app_key /* Arbitrary callback argument */ + ); + +/* + * The variant of the above function which dumps the BASIC-XER (XER_F_BASIC) + * output into the chosen file pointer. + * RETURN VALUES: + * 0: The structure is printed. + * -1: Problem printing the structure. + * WARNING: No sensible errno value is returned. + */ +int xer_fprint(FILE *stream, struct asn_TYPE_descriptor_s *td, void *sptr); + +/* + * Type of the generic XER encoder. + */ +typedef asn_enc_rval_t (xer_type_encoder_f)( + struct asn_TYPE_descriptor_s *type_descriptor, + void *struct_ptr, /* Structure to be encoded */ + int ilevel, /* Level of indentation */ + enum xer_encoder_flags_e xer_flags, + asn_app_consume_bytes_f *consume_bytes_cb, /* Callback */ + void *app_key /* Arbitrary callback argument */ + ); + +#ifdef __cplusplus +} +#endif + +#endif /* _XER_ENCODER_H_ */ diff --git a/src/mms/iso_mms/client/mms_client_common.c b/src/mms/iso_mms/client/mms_client_common.c index e0f02925..f06072e6 100644 --- a/src/mms/iso_mms/client/mms_client_common.c +++ b/src/mms/iso_mms/client/mms_client_common.c @@ -49,7 +49,7 @@ mmsClient_getInvokeId(ConfirmedResponsePdu_t* confirmedResponse) MmsPdu_t* mmsClient_createConfirmedRequestPdu(uint32_t invokeId) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) calloc(1, sizeof(MmsPdu_t)); + MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); mmsPdu->present = MmsPdu_PR_confirmedRequestPdu; asn_long2INTEGER(&(mmsPdu->choice.confirmedRequestPdu.invokeID), invokeId); diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index c41c7dae..793d50d7 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1,7 +1,7 @@ /* * mms_client_connection.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -36,6 +36,7 @@ #include #define CONFIG_MMS_CONNECTION_DEFAULT_TIMEOUT 5000 +#define CONFIG_MMS_CONNECTION_DEFAULT_CONNECT_TIMEOUT 10000 #define OUTSTANDING_CALLS 10 static void @@ -45,7 +46,7 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) MmsPdu_t* mmsPdu = 0; /* allow asn1c to allocate structure */ if (DEBUG_MMS_CLIENT) - printf("MMS_CLIENT: report handler revd size:%i\n", ByteBuffer_getSize(message)); + printf("MMS_CLIENT: report handler rcvd size:%i\n", ByteBuffer_getSize(message)); asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu, (void**) &mmsPdu, ByteBuffer_getBuffer(message), ByteBuffer_getSize(message)); @@ -58,7 +59,7 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) if (mmsPdu->choice.unconfirmedPDU.unconfirmedService.present == UnconfirmedService_PR_informationReport) - { + { char* domainId = NULL; InformationReport_t* report = @@ -84,18 +85,15 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) self->reportHandler(self->reportHandlerParameter, domainId, variableListName, values, true); - free(variableListName); + GLOBAL_FREEMEM(variableListName); } else { // Ignore domain and association specific information reports (not used by IEC 61850) } } - else if (report->variableAccessSpecification.present == - VariableAccessSpecification_PR_listOfVariable) - { - //TODO add code to handle reports with more than one variable - + else if (report->variableAccessSpecification.present == VariableAccessSpecification_PR_listOfVariable) + { int listSize = report->listOfAccessResult.list.count; int variableSpecSize = report->variableAccessSpecification.choice.listOfVariable.list.count; @@ -111,12 +109,11 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) int i; for (i = 0; i < variableSpecSize; i++) { if (report->variableAccessSpecification.choice.listOfVariable.list.array[i]->variableSpecification.present - == - VariableSpecification_PR_name) - { + == VariableSpecification_PR_name) + { if (report->variableAccessSpecification.choice.listOfVariable.list.array[i] - ->variableSpecification.choice.name.present == ObjectName_PR_vmdspecific) { - + ->variableSpecification.choice.name.present == ObjectName_PR_vmdspecific) + { int nameSize = report->variableAccessSpecification.choice.listOfVariable.list.array[i] ->variableSpecification.choice.name.choice.vmdspecific.size; @@ -130,14 +127,20 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) memcpy(variableListName, buffer, nameSize); variableListName[nameSize] = 0; + MmsValue* value = values; + + if (variableSpecSize != 1) + value = MmsValue_getElement(values, i); + self->reportHandler(self->reportHandlerParameter, domainId, variableListName, - values, false); + value, false); // report handler should have deleted the MmsValue! - values = NULL; + if (variableSpecSize != 1) + MmsValue_setElement(values, i, NULL); + else + values = NULL; } - else - MmsValue_delete(values); } else if (report->variableAccessSpecification.choice.listOfVariable.list.array[i] ->variableSpecification.choice.name.present == ObjectName_PR_domainspecific) { @@ -167,22 +170,25 @@ handleUnconfirmedMmsPdu(MmsConnection self, ByteBuffer* message) memcpy(itemNameStr, itemNamebuffer, itemNameSize); itemNameStr[itemNameSize] = 0; + MmsValue* value = values; + + if (variableSpecSize != 1) + value = MmsValue_getElement(values, i); + self->reportHandler(self->reportHandlerParameter, domainNameStr, itemNameStr, - values, false); + value, false); // report handler should have deleted the MmsValue! - values = NULL; + if (variableSpecSize != 1) + MmsValue_setElement(values, i, NULL); + else + values = NULL; + } - else - MmsValue_delete(values); } - } - } - //TODO handle CommandTermination request! - if (values != NULL) MmsValue_delete(values); } @@ -615,7 +621,7 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) } self->lastResponse = payload; - return; + IsoClientConnection_releaseReceiveBuffer(self->isoClient); } else if (tag == 0xa3) { /* unconfirmed PDU */ handleUnconfirmedMmsPdu(self, payload); @@ -627,7 +633,8 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) self->concludeState = CONCLUDE_STATE_REQUESTED; - /* block all new user requests */ + /* TODO block all new user requests */ + IsoClientConnection_releaseReceiveBuffer(self->isoClient); } else if (tag == 0x8c) { /* conclude response PDU */ @@ -742,7 +749,7 @@ mmsIsoCallback(IsoIndication indication, void* parameter, ByteBuffer* payload) MmsConnection MmsConnection_create() { - MmsConnection self = (MmsConnection) calloc(1, sizeof(struct sMmsConnection)); + MmsConnection self = (MmsConnection) GLOBAL_CALLOC(1, sizeof(struct sMmsConnection)); self->parameters.dataStructureNestingLevel = -1; self->parameters.maxServOutstandingCalled = -1; @@ -759,16 +766,23 @@ MmsConnection_create() self->lastResponseError = MMS_ERROR_NONE; - self->outstandingCalls = (uint32_t*) calloc(OUTSTANDING_CALLS, sizeof(uint32_t)); + self->outstandingCalls = (uint32_t*) GLOBAL_CALLOC(OUTSTANDING_CALLS, sizeof(uint32_t)); self->isoParameters = IsoConnectionParameters_create(); /* Load default values for connection parameters */ - IsoConnectionParameters_setLocalAddresses(self->isoParameters, 1, 1, 1); + TSelector selector1 = { 2, { 0, 0 } }; + TSelector selector2 = { 2, { 0, 0 } }; + + IsoConnectionParameters_setLocalAddresses(self->isoParameters, 1, 1, selector1); IsoConnectionParameters_setLocalApTitle(self->isoParameters, "1.1.1.999", 12); - IsoConnectionParameters_setRemoteAddresses(self->isoParameters, 1, 1, 1); + IsoConnectionParameters_setRemoteAddresses(self->isoParameters, 1, 1, selector2); IsoConnectionParameters_setRemoteApTitle(self->isoParameters, "1.1.1.999.1", 12); + self->connectTimeout = CONFIG_MMS_CONNECTION_DEFAULT_CONNECT_TIMEOUT; + + self->isoClient = IsoClientConnection_create((IsoIndicationCallback) mmsIsoCallback, (void*) self); + return self; } @@ -785,9 +799,9 @@ MmsConnection_destroy(MmsConnection self) Semaphore_destroy(self->lastResponseLock); Semaphore_destroy(self->outstandingCallsLock); - free(self->outstandingCalls); + GLOBAL_FREEMEM(self->outstandingCalls); - free(self); + GLOBAL_FREEMEM(self); } void @@ -803,6 +817,12 @@ MmsConnection_setRequestTimeout(MmsConnection self, uint32_t timeoutInMs) self->requestTimeout = timeoutInMs; } +void +MmsConnection_setConnectTimeout(MmsConnection self, uint32_t timeoutInMs) +{ + self->connectTimeout = timeoutInMs; +} + void MmsConnection_setLocalDetail(MmsConnection self, int32_t localDetail) { @@ -842,10 +862,8 @@ waitForConnectResponse(MmsConnection self) } bool -MmsConnection_connect(MmsConnection self, MmsError* mmsError, char* serverName, int serverPort) +MmsConnection_connect(MmsConnection self, MmsError* mmsError, const char* serverName, int serverPort) { - self->isoClient = IsoClientConnection_create((IsoIndicationCallback) mmsIsoCallback, (void*) self); - IsoConnectionParameters_setTcpParameters(self->isoParameters, serverName, serverPort); if (self->parameters.maxPduSize == -1) @@ -857,7 +875,8 @@ MmsConnection_connect(MmsConnection self, MmsError* mmsError, char* serverName, self->connectionState = MMS_CON_WAITING; - IsoClientConnection_associate(self->isoClient, self->isoParameters, payload); + IsoClientConnection_associate(self->isoClient, self->isoParameters, payload, + self->connectTimeout); waitForConnectResponse(self); @@ -890,12 +909,26 @@ MmsConnection_connect(MmsConnection self, MmsError* mmsError, char* serverName, } } +void +MmsConnection_close(MmsConnection self) +{ + self->connectionLostHandler = NULL; + + if (self->associationState == MMS_STATE_CONNECTED) + IsoClientConnection_close(self->isoClient); +} + void MmsConnection_abort(MmsConnection self, MmsError* mmsError) { *mmsError = MMS_ERROR_NONE; - IsoClientConnection_abort(self->isoClient); + self->connectionLostHandler = NULL; + + if (self->associationState == MMS_STATE_CONNECTED) + IsoClientConnection_abort(self->isoClient); + + //TODO wait for connection to be closed } static void @@ -960,6 +993,8 @@ MmsConnection_conclude(MmsConnection self, MmsError* mmsError) if (self->concludeState == CONCLUDE_STATE_REJECTED) *mmsError = MMS_ERROR_CONCLUDE_REJECTED; } + + self->connectionLostHandler = NULL; } void @@ -975,10 +1010,10 @@ mmsClient_getNameListSingleRequest( LinkedList* nameList, MmsConnection self, MmsError* mmsError, - char* domainId, + const char* domainId, MmsObjectClass objectClass, bool associationSpecific, - char* continueAfter) + const char* continueAfter) { *mmsError = MMS_ERROR_NONE; @@ -1016,7 +1051,7 @@ mmsClient_getNameListSingleRequest( static LinkedList /* */ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, - char* domainId, + const char* domainId, MmsObjectClass objectClass, bool associationSpecific) { @@ -1055,13 +1090,13 @@ MmsConnection_getDomainNames(MmsConnection self, MmsError* mmsError) } LinkedList /* */ -MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, char* domainId) +MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, const char* domainId) { return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE, false); } LinkedList /* */ -MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, char* domainId) +MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, const char* domainId) { return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE_LIST, false); } @@ -1074,7 +1109,7 @@ MmsConnection_getVariableListNamesAssociationSpecific(MmsConnection self, MmsErr MmsValue* MmsConnection_readVariable(MmsConnection self, MmsError* mmsError, - char* domainId, char* itemId) + const char* domainId, const char* itemId) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1103,7 +1138,7 @@ MmsConnection_readVariable(MmsConnection self, MmsError* mmsError, MmsValue* MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError, - char* domainId, char* itemId, + const char* domainId, const char* itemId, uint32_t startIndex, uint32_t numberOfElements) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1134,7 +1169,7 @@ MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError, MmsValue* MmsConnection_readMultipleVariables(MmsConnection self, MmsError* mmsError, - char* domainId, LinkedList /**/items) + const char* domainId, LinkedList /**/items) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1163,7 +1198,7 @@ MmsConnection_readMultipleVariables(MmsConnection self, MmsError* mmsError, MmsValue* MmsConnection_readNamedVariableListValues(MmsConnection self, MmsError* mmsError, - char* domainId, char* listName, + const char* domainId, const char* listName, bool specWithResult) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1197,7 +1232,7 @@ MmsConnection_readNamedVariableListValues(MmsConnection self, MmsError* mmsError MmsValue* MmsConnection_readNamedVariableListValuesAssociationSpecific( MmsConnection self, MmsError* mmsError, - char* listName, + const char* listName, bool specWithResult) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1228,7 +1263,7 @@ MmsConnection_readNamedVariableListValuesAssociationSpecific( LinkedList /* */ MmsConnection_readNamedVariableListDirectory(MmsConnection self, MmsError* mmsError, - char* domainId, char* listName, bool* deletable) + const char* domainId, const char* listName, bool* deletable) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1259,7 +1294,7 @@ MmsConnection_readNamedVariableListDirectory(MmsConnection self, MmsError* mmsEr LinkedList /* */ MmsConnection_readNamedVariableListDirectoryAssociationSpecific(MmsConnection self, MmsError* mmsError, - char* listName, bool* deletable) + const char* listName, bool* deletable) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1290,7 +1325,7 @@ MmsConnection_readNamedVariableListDirectoryAssociationSpecific(MmsConnection se void MmsConnection_defineNamedVariableList(MmsConnection self, MmsError* mmsError, - char* domainId, char* listName, LinkedList variableSpecs) + const char* domainId, const char* listName, LinkedList variableSpecs) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1317,7 +1352,7 @@ MmsConnection_defineNamedVariableList(MmsConnection self, MmsError* mmsError, void MmsConnection_defineNamedVariableListAssociationSpecific(MmsConnection self, - MmsError* mmsError, char* listName, LinkedList variableSpecs) + MmsError* mmsError, const char* listName, LinkedList variableSpecs) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1344,7 +1379,7 @@ MmsConnection_defineNamedVariableListAssociationSpecific(MmsConnection self, void MmsConnection_deleteNamedVariableList(MmsConnection self, MmsError* mmsError, - char* domainId, char* listName) + const char* domainId, const char* listName) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1370,7 +1405,7 @@ MmsConnection_deleteNamedVariableList(MmsConnection self, MmsError* mmsError, void MmsConnection_deleteAssociationSpecificNamedVariableList(MmsConnection self, - MmsError* mmsError, char* listName) + MmsError* mmsError, const char* listName) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1397,7 +1432,7 @@ MmsConnection_deleteAssociationSpecificNamedVariableList(MmsConnection self, MmsVariableSpecification* MmsConnection_getVariableAccessAttributes(MmsConnection self, MmsError* mmsError, - char* domainId, char* itemId) + const char* domainId, const char* itemId) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1483,7 +1518,7 @@ MmsConnection_getServerStatus(MmsConnection self, MmsError* mmsError, int* vmdLo int32_t -MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, char* filename, uint32_t initialPosition, +MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filename, uint32_t initialPosition, uint32_t* fileSize, uint64_t* lastModified) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1538,7 +1573,7 @@ MmsConnection_fileClose(MmsConnection self, MmsError* mmsError, int32_t frsmId) } void -MmsConnection_fileDelete(MmsConnection self, MmsError* mmsError, char* fileName) +MmsConnection_fileDelete(MmsConnection self, MmsError* mmsError, const char* fileName) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1594,7 +1629,7 @@ MmsConnection_fileRead(MmsConnection self, MmsError* mmsError, int32_t frsmId, M bool -MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, char* fileSpecification, char* continueAfter, +MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const char* fileSpecification, const char* continueAfter, MmsFileDirectoryHandler handler, void* handlerParameter) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1625,7 +1660,7 @@ MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, char* fil } void -MmsConnection_fileRename(MmsConnection self, MmsError* mmsError, char* currentFileName, char* newFileName) +MmsConnection_fileRename(MmsConnection self, MmsError* mmsError, const char* currentFileName, const char* newFileName) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1651,7 +1686,7 @@ MmsConnection_fileRename(MmsConnection self, MmsError* mmsError, char* currentFi void MmsConnection_writeVariable(MmsConnection self, MmsError* mmsError, - char* domainId, char* itemId, + const char* domainId, const char* itemId, MmsValue* value) { ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); @@ -1676,7 +1711,7 @@ MmsConnection_writeVariable(MmsConnection self, MmsError* mmsError, } void -MmsConnection_writeMultipleVariables(MmsConnection self, MmsError* mmsError, char* domainId, +MmsConnection_writeMultipleVariables(MmsConnection self, MmsError* mmsError, const char* domainId, LinkedList /**/items, LinkedList /* */values, /* OUTPUT */LinkedList* /* */accessResults) @@ -1711,22 +1746,22 @@ void MmsServerIdentity_destroy(MmsServerIdentity* self) { if (self->modelName != NULL) - free(self->modelName); + GLOBAL_FREEMEM(self->modelName); if (self->vendorName != NULL) - free(self->vendorName); + GLOBAL_FREEMEM(self->vendorName); if (self->revision != NULL) - free(self->revision); + GLOBAL_FREEMEM(self->revision); - free(self); + GLOBAL_FREEMEM(self); } MmsVariableAccessSpecification* MmsVariableAccessSpecification_create(char* domainId, char* itemId) { MmsVariableAccessSpecification* self = (MmsVariableAccessSpecification*) - malloc(sizeof(MmsVariableAccessSpecification)); + GLOBAL_MALLOC(sizeof(MmsVariableAccessSpecification)); self->domainId = domainId; self->itemId = itemId; @@ -1741,7 +1776,7 @@ MmsVariableAccessSpecification_createAlternateAccess(char* domainId, char* itemI char* componentName) { MmsVariableAccessSpecification* self = (MmsVariableAccessSpecification*) - malloc(sizeof(MmsVariableAccessSpecification)); + GLOBAL_MALLOC(sizeof(MmsVariableAccessSpecification)); self->domainId = domainId; self->itemId = itemId; @@ -1755,14 +1790,14 @@ void MmsVariableAccessSpecification_destroy(MmsVariableAccessSpecification* self) { if (self->domainId != NULL) - free(self->domainId); + GLOBAL_FREEMEM((void*) self->domainId); if (self->itemId != NULL) - free(self->itemId); + GLOBAL_FREEMEM((void*) self->itemId); if (self->componentName != NULL) - free(self->componentName); + GLOBAL_FREEMEM((void*) self->componentName); - free(self); + GLOBAL_FREEMEM(self); } diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index 1b46d32f..a66590e3 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -33,7 +33,7 @@ #include "conversions.h" void -mmsClient_createFileOpenRequest(uint32_t invokeId, ByteBuffer* request, char* fileName, uint32_t initialPosition) +mmsClient_createFileOpenRequest(uint32_t invokeId, ByteBuffer* request, const char* fileName, uint32_t initialPosition) { uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); @@ -66,7 +66,7 @@ mmsClient_createFileOpenRequest(uint32_t invokeId, ByteBuffer* request, char* fi } void -mmsClient_createFileDeleteRequest(uint32_t invokeId, ByteBuffer* request, char* fileName) +mmsClient_createFileDeleteRequest(uint32_t invokeId, ByteBuffer* request, const char* fileName) { uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); @@ -122,7 +122,7 @@ mmsClient_createFileReadRequest(uint32_t invokeId, ByteBuffer* request, int32_t } static int -encodeFileSpecification(uint8_t tag, char* fileSpecification, uint8_t* buffer, int bufPos) +encodeFileSpecification(uint8_t tag, const char* fileSpecification, uint8_t* buffer, int bufPos) { uint32_t fileNameStringSize = strlen(fileSpecification); uint32_t fileNameSeqSize = 1 + BerEncoder_determineLengthSize(fileNameStringSize) + fileNameStringSize; @@ -140,7 +140,7 @@ encodeFileSpecification(uint8_t tag, char* fileSpecification, uint8_t* buffer, i } void -mmsClient_createFileDirectoryRequest(uint32_t invokeId, ByteBuffer* request, char* fileSpecification, char* continueAfter) +mmsClient_createFileDirectoryRequest(uint32_t invokeId, ByteBuffer* request, const char* fileSpecification, const char* continueAfter) { uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); @@ -179,7 +179,7 @@ mmsClient_createFileDirectoryRequest(uint32_t invokeId, ByteBuffer* request, cha void -mmsClient_createFileRenameRequest(uint32_t invokeId, ByteBuffer* request, char* currentFileName, char* newFileName) +mmsClient_createFileRenameRequest(uint32_t invokeId, ByteBuffer* request, const char* currentFileName, const char* newFileName) { uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); @@ -314,8 +314,6 @@ parseListOfDirectoryEntries(uint8_t* buffer, int bufPos, int maxBufPos, int endPos = bufPos + length; - // printf("bufPos: %i, length: %i, maxBufPos: %i\n", bufPos, length, maxBufPos); - if (endPos > maxBufPos) { if (DEBUG_MMS_CLIENT) printf("parseListOfDirectoryEntries: message to short!\n"); 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 4a9340e9..9332a120 100644 --- a/src/mms/iso_mms/client/mms_client_get_namelist.c +++ b/src/mms/iso_mms/client/mms_client_get_namelist.c @@ -33,7 +33,7 @@ int mmsClient_createMmsGetNameListRequestVMDspecific(long invokeId, ByteBuffer* writeBuffer, - char* continueAfter) + const char* continueAfter) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -45,7 +45,7 @@ mmsClient_createMmsGetNameListRequestVMDspecific(long invokeId, ByteBuffer* writ request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.getNameList); if (continueAfter != NULL) { - request->continueAfter = (Identifier_t*) calloc(1, sizeof(Identifier_t)); + request->continueAfter = (Identifier_t*) GLOBAL_CALLOC(1, sizeof(Identifier_t)); request->continueAfter->buf = (uint8_t*) copyString(continueAfter); request->continueAfter->size = strlen(continueAfter); } @@ -70,7 +70,7 @@ mmsClient_createMmsGetNameListRequestVMDspecific(long invokeId, ByteBuffer* writ int mmsClient_createMmsGetNameListRequestAssociationSpecific(long invokeId, ByteBuffer* writeBuffer, - char* continueAfter) + const char* continueAfter) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -83,7 +83,7 @@ mmsClient_createMmsGetNameListRequestAssociationSpecific(long invokeId, ByteBuff if (continueAfter != NULL) { - request->continueAfter = (Identifier_t*) calloc(1, sizeof(Identifier_t)); + request->continueAfter = (Identifier_t*) GLOBAL_CALLOC(1, sizeof(Identifier_t)); request->continueAfter->buf = (uint8_t*) copyString(continueAfter); request->continueAfter->size = strlen(continueAfter); } @@ -197,8 +197,8 @@ exit_error: } int -mmsClient_createGetNameListRequestDomainOrVMDSpecific(long invokeId, char* domainName, - ByteBuffer* writeBuffer, MmsObjectClass objectClass, char* continueAfter) +mmsClient_createGetNameListRequestDomainOrVMDSpecific(long invokeId, const char* domainName, + ByteBuffer* writeBuffer, MmsObjectClass objectClass, const char* continueAfter) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -210,7 +210,7 @@ mmsClient_createGetNameListRequestDomainOrVMDSpecific(long invokeId, char* domai request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.getNameList); if (continueAfter != NULL) { - request->continueAfter = (Identifier_t*) calloc(1, sizeof(Identifier_t)); + request->continueAfter = (Identifier_t*) GLOBAL_CALLOC(1, sizeof(Identifier_t)); request->continueAfter->buf = (uint8_t*) copyString(continueAfter); request->continueAfter->size = strlen(continueAfter); } diff --git a/src/mms/iso_mms/client/mms_client_get_var_access.c b/src/mms/iso_mms/client/mms_client_get_var_access.c index de5fb409..4e45b34e 100644 --- a/src/mms/iso_mms/client/mms_client_get_var_access.c +++ b/src/mms/iso_mms/client/mms_client_get_var_access.c @@ -33,7 +33,7 @@ static MmsVariableSpecification* createTypeSpecification(TypeSpecification_t* asnTypeSpec) { MmsVariableSpecification* typeSpec = (MmsVariableSpecification*) - calloc(1, sizeof(MmsVariableSpecification)); + GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); switch (asnTypeSpec->present) { case TypeSpecification_PR_structure: @@ -44,7 +44,7 @@ createTypeSpecification(TypeSpecification_t* asnTypeSpec) { typeSpec->typeSpec.structure.elementCount = elementCount; typeSpec->typeSpec.structure.elements = (MmsVariableSpecification**) - calloc(elementCount, sizeof(MmsVariableSpecification*)); + GLOBAL_CALLOC(elementCount, sizeof(MmsVariableSpecification*)); int i; @@ -164,7 +164,7 @@ mmsClient_parseGetVariableAccessAttributesResponse(ByteBuffer* message, uint32_t int mmsClient_createGetVariableAccessAttributesRequest( uint32_t invokeId, - char* domainId, char* itemId, + const char* domainId, const char* itemId, ByteBuffer* writeBuffer) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); diff --git a/src/mms/iso_mms/client/mms_client_identify.c b/src/mms/iso_mms/client/mms_client_identify.c index 39f0d651..d20f2eae 100644 --- a/src/mms/iso_mms/client/mms_client_identify.c +++ b/src/mms/iso_mms/client/mms_client_identify.c @@ -101,7 +101,7 @@ mmsClient_parseIdentifyResponse(MmsConnection self) } } - identityInfo = (MmsServerIdentity*) malloc(sizeof(MmsServerIdentity)); + identityInfo = (MmsServerIdentity*) GLOBAL_MALLOC(sizeof(MmsServerIdentity)); identityInfo->vendorName = vendorName; identityInfo->modelName = modelName; diff --git a/src/mms/iso_mms/client/mms_client_initiate.c b/src/mms/iso_mms/client/mms_client_initiate.c index 544cdc67..5eed3403 100644 --- a/src/mms/iso_mms/client/mms_client_initiate.c +++ b/src/mms/iso_mms/client/mms_client_initiate.c @@ -38,18 +38,18 @@ createInitiateRequestPdu(MmsConnection self) { InitiateRequestPdu_t request; - request.localDetailCalling = (Integer32_t*) calloc(1, sizeof(Integer32_t)); + request.localDetailCalling = (Integer32_t*) GLOBAL_CALLOC(1, sizeof(Integer32_t)); *(request.localDetailCalling) = self->parameters.maxPduSize; request.proposedMaxServOutstandingCalled = DEFAULT_MAX_SERV_OUTSTANDING_CALLED; request.proposedMaxServOutstandingCalling = DEFAULT_MAX_SERV_OUTSTANDING_CALLING; - request.proposedDataStructureNestingLevel = (Integer8_t*) calloc(1, sizeof(Integer8_t)); + request.proposedDataStructureNestingLevel = (Integer8_t*) GLOBAL_CALLOC(1, sizeof(Integer8_t)); *(request.proposedDataStructureNestingLevel) = DEFAULT_DATA_STRUCTURE_NESTING_LEVEL; request.mmsInitRequestDetail.proposedParameterCBB.size = 2; request.mmsInitRequestDetail.proposedParameterCBB.bits_unused = 5; - request.mmsInitRequestDetail.proposedParameterCBB.buf = (uint8_t*) calloc(2, sizeof(uint8_t)); + request.mmsInitRequestDetail.proposedParameterCBB.buf = (uint8_t*) GLOBAL_CALLOC(2, sizeof(uint8_t)); request.mmsInitRequestDetail.proposedParameterCBB.buf[0] = 0xf1; request.mmsInitRequestDetail.proposedParameterCBB.buf[1] = 0x00; @@ -66,15 +66,15 @@ createInitiateRequestPdu(MmsConnection self) static void freeInitiateRequestPdu(InitiateRequestPdu_t request) { - free(request.localDetailCalling); - free(request.proposedDataStructureNestingLevel); - free(request.mmsInitRequestDetail.proposedParameterCBB.buf); + GLOBAL_FREEMEM(request.localDetailCalling); + GLOBAL_FREEMEM(request.proposedDataStructureNestingLevel); + GLOBAL_FREEMEM(request.mmsInitRequestDetail.proposedParameterCBB.buf); } int mmsClient_createInitiateRequest(MmsConnection self, ByteBuffer* message) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) calloc(1, sizeof(MmsPdu_t)); + MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); mmsPdu->present = MmsPdu_PR_initiateRequestPdu; @@ -84,7 +84,7 @@ mmsClient_createInitiateRequest(MmsConnection self, ByteBuffer* message) (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) message); freeInitiateRequestPdu(mmsPdu->choice.initiateRequestPdu); - free(mmsPdu); + GLOBAL_FREEMEM(mmsPdu); return rval.encoded; } diff --git a/src/mms/iso_mms/client/mms_client_named_variable_list.c b/src/mms/iso_mms/client/mms_client_named_variable_list.c index 3939bf40..161a5c70 100644 --- a/src/mms/iso_mms/client/mms_client_named_variable_list.c +++ b/src/mms/iso_mms/client/mms_client_named_variable_list.c @@ -35,7 +35,7 @@ void mmsClient_createDeleteNamedVariableListRequest(long invokeId, ByteBuffer* writeBuffer, - char* domainId, char* listNameId) + const char* domainId, const char* listNameId) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -45,14 +45,14 @@ mmsClient_createDeleteNamedVariableListRequest(long invokeId, ByteBuffer* writeB DeleteNamedVariableListRequest_t* request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList); - request->listOfVariableListName = (struct DeleteNamedVariableListRequest__listOfVariableListName*) calloc(1, + request->listOfVariableListName = (struct DeleteNamedVariableListRequest__listOfVariableListName*) GLOBAL_CALLOC(1, sizeof(struct DeleteNamedVariableListRequest__listOfVariableListName)); request->listOfVariableListName->list.count = 1; request->listOfVariableListName->list.size = 1; - request->listOfVariableListName->list.array = (ObjectName_t**) calloc(1, sizeof(ObjectName_t*)); - request->listOfVariableListName->list.array[0] = (ObjectName_t*) calloc(1, sizeof(ObjectName_t)); + request->listOfVariableListName->list.array = (ObjectName_t**) GLOBAL_CALLOC(1, sizeof(ObjectName_t*)); + request->listOfVariableListName->list.array[0] = (ObjectName_t*) GLOBAL_CALLOC(1, sizeof(ObjectName_t)); request->listOfVariableListName->list.array[0]->present = ObjectName_PR_domainspecific; request->listOfVariableListName->list.array[0]->choice.domainspecific.domainId.size = strlen(domainId); @@ -60,7 +60,7 @@ mmsClient_createDeleteNamedVariableListRequest(long invokeId, ByteBuffer* writeB request->listOfVariableListName->list.array[0]->choice.domainspecific.itemId.size = strlen(listNameId); request->listOfVariableListName->list.array[0]->choice.domainspecific.itemId.buf = (uint8_t*) copyString(listNameId); - request->scopeOfDelete = (INTEGER_t*) calloc(1, sizeof(INTEGER_t)); + request->scopeOfDelete = (INTEGER_t*) GLOBAL_CALLOC(1, sizeof(INTEGER_t)); asn_long2INTEGER(request->scopeOfDelete, DeleteNamedVariableListRequest__scopeOfDelete_specific); der_encode(&asn_DEF_MmsPdu, mmsPdu, @@ -73,7 +73,7 @@ void mmsClient_createDeleteAssociationSpecificNamedVariableListRequest( long invokeId, ByteBuffer* writeBuffer, - char* listNameId) + const char* listNameId) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -83,21 +83,21 @@ mmsClient_createDeleteAssociationSpecificNamedVariableListRequest( DeleteNamedVariableListRequest_t* request = &(mmsPdu->choice.confirmedRequestPdu.confirmedServiceRequest.choice.deleteNamedVariableList); - request->listOfVariableListName = (struct DeleteNamedVariableListRequest__listOfVariableListName*) calloc(1, + request->listOfVariableListName = (struct DeleteNamedVariableListRequest__listOfVariableListName*) GLOBAL_CALLOC(1, sizeof(struct DeleteNamedVariableListRequest__listOfVariableListName)); request->listOfVariableListName->list.count = 1; request->listOfVariableListName->list.size = 1; - request->listOfVariableListName->list.array = (ObjectName_t**) calloc(1, sizeof(ObjectName_t*)); - request->listOfVariableListName->list.array[0] = (ObjectName_t*) calloc(1, sizeof(ObjectName_t)); + request->listOfVariableListName->list.array = (ObjectName_t**) GLOBAL_CALLOC(1, sizeof(ObjectName_t*)); + request->listOfVariableListName->list.array[0] = (ObjectName_t*) GLOBAL_CALLOC(1, sizeof(ObjectName_t)); request->listOfVariableListName->list.array[0]->present = ObjectName_PR_aaspecific; request->listOfVariableListName->list.array[0]->choice.aaspecific.size = strlen(listNameId); request->listOfVariableListName->list.array[0]->choice.aaspecific.buf = (uint8_t*) copyString(listNameId); - request->scopeOfDelete = (INTEGER_t*) calloc(1, sizeof(INTEGER_t)); + request->scopeOfDelete = (INTEGER_t*) GLOBAL_CALLOC(1, sizeof(INTEGER_t)); asn_long2INTEGER(request->scopeOfDelete, DeleteNamedVariableListRequest__scopeOfDelete_specific); der_encode(&asn_DEF_MmsPdu, mmsPdu, @@ -146,7 +146,7 @@ mmsClient_parseDeleteNamedVariableListResponse(ByteBuffer* message, uint32_t* in void mmsClient_createGetNamedVariableListAttributesRequest(uint32_t invokeId, ByteBuffer* writeBuffer, - char* domainId, char* listNameId) + const char* domainId, const char* listNameId) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -172,7 +172,7 @@ mmsClient_createGetNamedVariableListAttributesRequest(uint32_t invokeId, ByteBuf void mmsClient_createGetNamedVariableListAttributesRequestAssociationSpecific(uint32_t invokeId, - ByteBuffer* writeBuffer, char* listNameId) + ByteBuffer* writeBuffer, const char* listNameId) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -257,8 +257,8 @@ void mmsClient_createDefineNamedVariableListRequest( uint32_t invokeId, ByteBuffer* writeBuffer, - char* domainId, - char* listNameId, + const char* domainId, + const char* listNameId, LinkedList /**/ listOfVariables, bool associationSpecific) { @@ -292,7 +292,7 @@ mmsClient_createDefineNamedVariableListRequest( request->listOfVariable.list.size = listSize; request->listOfVariable.list.array = - (struct DefineNamedVariableListRequest__listOfVariable__Member**) calloc(listSize, sizeof(void*)); + (struct DefineNamedVariableListRequest__listOfVariable__Member**) GLOBAL_CALLOC(listSize, sizeof(void*)); int i = 0; LinkedList element = LinkedList_getNext(listOfVariables); @@ -301,7 +301,7 @@ mmsClient_createDefineNamedVariableListRequest( MmsVariableAccessSpecification* variableSpec = (MmsVariableAccessSpecification*) element->data; request->listOfVariable.list.array[i] = (struct DefineNamedVariableListRequest__listOfVariable__Member*) - calloc(1, sizeof(struct DefineNamedVariableListRequest__listOfVariable__Member)); + GLOBAL_CALLOC(1, sizeof(struct DefineNamedVariableListRequest__listOfVariable__Member)); request->listOfVariable.list.array[i]->variableSpecification.present = VariableSpecification_PR_name; @@ -324,13 +324,13 @@ mmsClient_createDefineNamedVariableListRequest( //TODO add alternate access if (variableSpec->arrayIndex != -1) { - AlternateAccess_t* alternateAccess = (AlternateAccess_t*) calloc(1, sizeof(AlternateAccess_t)); + AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); alternateAccess->list.count = 1; - alternateAccess->list.array = (struct AlternateAccess__Member**) calloc(1, sizeof(struct AlternateAccess__Member*)); - alternateAccess->list.array[0] = (struct AlternateAccess__Member*) calloc(1, sizeof(struct AlternateAccess__Member)); + alternateAccess->list.array = (struct AlternateAccess__Member**) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member*)); + alternateAccess->list.array[0] = (struct AlternateAccess__Member*) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member)); alternateAccess->list.array[0]->present = AlternateAccess__Member_PR_unnamed; - alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) calloc(1, sizeof(AlternateAccessSelection_t)); + alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAlternateAccess; @@ -343,14 +343,14 @@ mmsClient_createDefineNamedVariableListRequest( if (variableSpec->componentName != NULL) { - AlternateAccess_t* componentAccess = (AlternateAccess_t*) calloc(1, sizeof(AlternateAccess_t)); + AlternateAccess_t* componentAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); componentAccess->list.count = 1; - componentAccess->list.array = (struct AlternateAccess__Member**) calloc(1, sizeof(struct AlternateAccess__Member*)); - componentAccess->list.array[0] = (struct AlternateAccess__Member*) calloc(1, sizeof(struct AlternateAccess__Member)); + componentAccess->list.array = (struct AlternateAccess__Member**) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member*)); + componentAccess->list.array[0] = (struct AlternateAccess__Member*) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member)); componentAccess->list.array[0]->present = AlternateAccess__Member_PR_unnamed; - componentAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) calloc(1, sizeof(AlternateAccessSelection_t)); + componentAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); componentAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess; diff --git a/src/mms/iso_mms/client/mms_client_read.c b/src/mms/iso_mms/client/mms_client_read.c index f860269d..5c39235e 100644 --- a/src/mms/iso_mms/client/mms_client_read.c +++ b/src/mms/iso_mms/client/mms_client_read.c @@ -67,14 +67,14 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi value = MmsValue_newDataAccessError(DATA_ACCESS_ERROR_UNKNOWN); } else if (presentType == AccessResult_PR_array) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_ARRAY; int arrayElementCount = accessResultList[i]->choice.array.list.count; value->value.structure.size = arrayElementCount; - value->value.structure.components = (MmsValue**) calloc(arrayElementCount, sizeof(MmsValue*)); + value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(arrayElementCount, sizeof(MmsValue*)); int j; @@ -84,14 +84,14 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi } } else if (presentType == AccessResult_PR_structure) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_STRUCTURE; int componentCount = accessResultList[i]->choice.structure.list.count; value->value.structure.size = componentCount; - value->value.structure.components = (MmsValue**) calloc(componentCount, sizeof(MmsValue*)); + value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*)); int j; for (j = 0; j < componentCount; j++) { @@ -100,14 +100,14 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi } } else if (presentType == AccessResult_PR_bitstring) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_BIT_STRING; int size = accessResultList[i]->choice.bitstring.size; value->value.bitString.size = (size * 8) - accessResultList[i]->choice.bitstring.bits_unused; - value->value.bitString.buf = (uint8_t*) malloc(size); + value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size); memcpy(value->value.bitString.buf, accessResultList[i]->choice.bitstring.buf, size); @@ -129,7 +129,7 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi else if (presentType == AccessResult_PR_floatingpoint) { int size = accessResultList[i]->choice.floatingpoint.size; - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_FLOAT; if (size == 5) { /* FLOAT32 */ @@ -138,7 +138,7 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi uint8_t* floatBuf = (accessResultList[i]->choice.floatingpoint.buf + 1); - value->value.floatingPoint.buf = (uint8_t*) malloc(4); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(4); #if (ORDER_LITTLE_ENDIAN == 1) memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 4); @@ -154,7 +154,7 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi uint8_t* floatBuf = (accessResultList[i]->choice.floatingpoint.buf + 1); - value->value.floatingPoint.buf = (uint8_t*) malloc(8); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(8); #if (ORDER_LITTLE_ENDIAN == 1) memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 8); @@ -165,13 +165,13 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi } else if (presentType == AccessResult_PR_visiblestring) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_VISIBLE_STRING; int strSize = accessResultList[i]->choice.visiblestring.size; - value->value.visibleString.buf = (char*) malloc(strSize + 1); + value->value.visibleString.buf = (char*) GLOBAL_MALLOC(strSize + 1); value->value.visibleString.size = strSize; memcpy(value->value.visibleString.buf, @@ -181,13 +181,13 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi value->value.visibleString.buf[strSize] = 0; } else if (presentType == AccessResult_PR_mMSString) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_STRING; int strSize = accessResultList[i]->choice.mMSString.size; - value->value.visibleString.buf = (char*) malloc(strSize + 1); + value->value.visibleString.buf = (char*) GLOBAL_MALLOC(strSize + 1); value->value.visibleString.size = strSize; memcpy(value->value.visibleString.buf, @@ -197,7 +197,7 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi } else if (presentType == AccessResult_PR_utctime) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_UTC_TIME; memcpy(value->value.utcTime, @@ -210,7 +210,7 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi int size = accessResultList[i]->choice.binarytime.size; if (size <= 6) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_BINARY_TIME; value->value.binaryTime.size = size; memcpy(value->value.binaryTime.buf, accessResultList[i]->choice.binarytime.buf, size); @@ -219,11 +219,11 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi else if (presentType == AccessResult_PR_octetstring) { int size = accessResultList[i]->choice.octetstring.size; - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_OCTET_STRING; value->value.octetString.maxSize = size; value->value.octetString.size = size; - value->value.octetString.buf = (uint8_t*) malloc(size); + value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); memcpy(value->value.octetString.buf, accessResultList[i]->choice.octetstring.buf, size); } else { @@ -291,7 +291,7 @@ createReadRequest (MmsPdu_t* mmsPdu) int -mmsClient_createReadNamedVariableListRequest(uint32_t invokeId, char* domainId, char* itemId, +mmsClient_createReadNamedVariableListRequest(uint32_t invokeId, const char* domainId, const char* itemId, ByteBuffer* writeBuffer, bool specWithResult) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -330,7 +330,7 @@ mmsClient_createReadNamedVariableListRequest(uint32_t invokeId, char* domainId, int mmsClient_createReadAssociationSpecificNamedVariableListRequest( uint32_t invokeId, - char* itemId, + const char* itemId, ByteBuffer* writeBuffer, bool specWithResult) { @@ -368,7 +368,7 @@ mmsClient_createReadAssociationSpecificNamedVariableListRequest( * Request a single value */ int -mmsClient_createReadRequest(uint32_t invokeId, char* domainId, char* itemId, ByteBuffer* writeBuffer) +mmsClient_createReadRequest(uint32_t invokeId, const char* domainId, const char* itemId, ByteBuffer* writeBuffer) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -379,10 +379,10 @@ mmsClient_createReadRequest(uint32_t invokeId, char* domainId, char* itemId, Byt readRequest->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable; readRequest->variableAccessSpecification.choice.listOfVariable.list.array = - (ListOfVariableSeq_t**) calloc(1, sizeof(ListOfVariableSeq_t*)); + (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); readRequest->variableAccessSpecification.choice.listOfVariable.list.count = 1; - ListOfVariableSeq_t* listOfVars = (ListOfVariableSeq_t*) calloc(1, sizeof(ListOfVariableSeq_t)); + ListOfVariableSeq_t* listOfVars = (ListOfVariableSeq_t*) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t)); readRequest->variableAccessSpecification.choice.listOfVariable.list.array[0] = listOfVars; @@ -408,8 +408,8 @@ mmsClient_createReadRequest(uint32_t invokeId, char* domainId, char* itemId, Byt (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer); /* clean up data structures */ - free(listOfVars); - free(readRequest->variableAccessSpecification.choice.listOfVariable.list.array); + GLOBAL_FREEMEM(listOfVars); + GLOBAL_FREEMEM(readRequest->variableAccessSpecification.choice.listOfVariable.list.array); readRequest->variableAccessSpecification.choice.listOfVariable.list.array = NULL; readRequest->variableAccessSpecification.choice.listOfVariable.list.count = 0; asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); @@ -420,13 +420,13 @@ mmsClient_createReadRequest(uint32_t invokeId, char* domainId, char* itemId, Byt static AlternateAccess_t* createAlternateAccess(uint32_t index, uint32_t elementCount) { - AlternateAccess_t* alternateAccess = (AlternateAccess_t*) calloc(1, sizeof(AlternateAccess_t)); + AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); alternateAccess->list.count = 1; - alternateAccess->list.array = (struct AlternateAccess__Member**) calloc(1, sizeof(struct AlternateAccess__Member*)); - alternateAccess->list.array[0] = (struct AlternateAccess__Member*) calloc(1, sizeof(struct AlternateAccess__Member)); + alternateAccess->list.array = (struct AlternateAccess__Member**) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member*)); + alternateAccess->list.array[0] = (struct AlternateAccess__Member*) GLOBAL_CALLOC(1, sizeof(struct AlternateAccess__Member)); alternateAccess->list.array[0]->present = AlternateAccess__Member_PR_unnamed; - alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) calloc(1, sizeof(AlternateAccessSelection_t)); + alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess; @@ -458,9 +458,9 @@ createAlternateAccess(uint32_t index, uint32_t elementCount) } static ListOfVariableSeq_t* -createVariableIdentifier(char* domainId, char* itemId) +createVariableIdentifier(const char* domainId, const char* itemId) { - ListOfVariableSeq_t* variableIdentifier = (ListOfVariableSeq_t*) calloc(1, sizeof(ListOfVariableSeq_t)); + ListOfVariableSeq_t* variableIdentifier = (ListOfVariableSeq_t*) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t)); variableIdentifier->variableSpecification.present = VariableSpecification_PR_name; variableIdentifier->variableSpecification.choice.name.present = ObjectName_PR_domainspecific; @@ -473,7 +473,7 @@ createVariableIdentifier(char* domainId, char* itemId) } int -mmsClient_createReadRequestAlternateAccessIndex(uint32_t invokeId, char* domainId, char* itemId, +mmsClient_createReadRequestAlternateAccessIndex(uint32_t invokeId, const char* domainId, const char* itemId, uint32_t index, uint32_t elementCount, ByteBuffer* writeBuffer) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -483,7 +483,7 @@ mmsClient_createReadRequestAlternateAccessIndex(uint32_t invokeId, char* domainI readRequest->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable; - readRequest->variableAccessSpecification.choice.listOfVariable.list.array = (ListOfVariableSeq_t**) calloc(1, sizeof(ListOfVariableSeq_t*)); + readRequest->variableAccessSpecification.choice.listOfVariable.list.array = (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); readRequest->variableAccessSpecification.choice.listOfVariable.list.count = 1; ListOfVariableSeq_t* variableIdentifier = createVariableIdentifier(domainId, itemId); @@ -512,7 +512,7 @@ createListOfVariables(ReadRequest_t* readRequest, int valuesCount) { readRequest->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable; readRequest->variableAccessSpecification.choice.listOfVariable.list.array = (ListOfVariableSeq_t**) - calloc(valuesCount, sizeof(ListOfVariableSeq_t*)); + GLOBAL_CALLOC(valuesCount, sizeof(ListOfVariableSeq_t*)); readRequest->variableAccessSpecification.choice.listOfVariable.list.count = valuesCount; readRequest->variableAccessSpecification.choice.listOfVariable.list.size = valuesCount; @@ -523,7 +523,7 @@ createListOfVariables(ReadRequest_t* readRequest, int valuesCount) { * Request multiple values of a single domain */ int -mmsClient_createReadRequestMultipleValues(uint32_t invokeId, char* domainId, LinkedList items, +mmsClient_createReadRequestMultipleValues(uint32_t invokeId, const char* domainId, LinkedList items, ByteBuffer* writeBuffer) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -551,9 +551,9 @@ mmsClient_createReadRequestMultipleValues(uint32_t invokeId, char* domainId, Lin (asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer); for (i = 0; i < valuesCount; i++) { - free(listOfVars[i]); + GLOBAL_FREEMEM(listOfVars[i]); } - free(listOfVars); + GLOBAL_FREEMEM(listOfVars); readRequest->variableAccessSpecification.choice.listOfVariable.list.count = 0; readRequest->variableAccessSpecification.choice.listOfVariable.list.size = 0; diff --git a/src/mms/iso_mms/client/mms_client_write.c b/src/mms/iso_mms/client/mms_client_write.c index 623461f8..5657ef45 100644 --- a/src/mms/iso_mms/client/mms_client_write.c +++ b/src/mms/iso_mms/client/mms_client_write.c @@ -172,9 +172,9 @@ mmsClient_parseWriteResponse(ByteBuffer* message, int32_t bufPos, MmsError* mmsE } static VariableSpecification_t* -createNewDomainVariableSpecification(char* domainId, char* itemId) +createNewDomainVariableSpecification(const char* domainId, const char* itemId) { - VariableSpecification_t* varSpec = (VariableSpecification_t*) calloc(1, sizeof(ListOfVariableSeq_t)); + VariableSpecification_t* varSpec = (VariableSpecification_t*) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t)); //VariableSpecification_t* varSpec = (VariableSpecification_t*) calloc(1, sizeof(VariableSpecification_t)); @@ -204,8 +204,8 @@ deleteDataElement(Data_t* dataElement) deleteDataElement(dataElement->choice.structure->list.array[i]); } - free(dataElement->choice.structure->list.array); - free(dataElement->choice.structure); + GLOBAL_FREEMEM(dataElement->choice.structure->list.array); + GLOBAL_FREEMEM(dataElement->choice.structure); } else if (dataElement->present == Data_PR_array) { int elementCount = dataElement->choice.array->list.count; @@ -215,21 +215,21 @@ deleteDataElement(Data_t* dataElement) deleteDataElement(dataElement->choice.array->list.array[i]); } - free(dataElement->choice.array->list.array); - free(dataElement->choice.array); + GLOBAL_FREEMEM(dataElement->choice.array->list.array); + GLOBAL_FREEMEM(dataElement->choice.array); } else if (dataElement->present == Data_PR_floatingpoint) { - free(dataElement->choice.floatingpoint.buf); + GLOBAL_FREEMEM(dataElement->choice.floatingpoint.buf); } else if (dataElement->present == Data_PR_utctime) { - free(dataElement->choice.utctime.buf); + GLOBAL_FREEMEM(dataElement->choice.utctime.buf); } - free(dataElement); + GLOBAL_FREEMEM(dataElement); } int -mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, char* domainId, LinkedList itemIds, LinkedList values, +mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, const char* domainId, LinkedList itemIds, LinkedList values, ByteBuffer* writeBuffer) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -246,12 +246,12 @@ mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, char* domainId, Lin request->variableAccessSpecification.choice.listOfVariable.list.count = numberOfItems; request->variableAccessSpecification.choice.listOfVariable.list.size = numberOfItems; request->variableAccessSpecification.choice.listOfVariable.list.array = - (ListOfVariableSeq_t**) calloc(numberOfItems, sizeof(ListOfVariableSeq_t*)); + (ListOfVariableSeq_t**) GLOBAL_CALLOC(numberOfItems, sizeof(ListOfVariableSeq_t*)); /* Create list of data values */ request->listOfData.list.count = numberOfItems; request->listOfData.list.size = numberOfItems; - request->listOfData.list.array = (Data_t**) calloc(numberOfItems, sizeof(struct Data*)); + request->listOfData.list.array = (Data_t**) GLOBAL_CALLOC(numberOfItems, sizeof(struct Data*)); int i; @@ -283,16 +283,16 @@ mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, char* domainId, Lin request->variableAccessSpecification.choice.listOfVariable.list.count = 0; for (i = 0; i < numberOfItems; i++) { - free(request->variableAccessSpecification.choice.listOfVariable.list.array[i]); + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array[i]); deleteDataElement(request->listOfData.list.array[i]); } - free(request->variableAccessSpecification.choice.listOfVariable.list.array); + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array); request->variableAccessSpecification.choice.listOfVariable.list.array = 0; request->listOfData.list.count = 0; - free(request->listOfData.list.array); + GLOBAL_FREEMEM(request->listOfData.list.array); request->listOfData.list.array = 0; asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); @@ -302,7 +302,7 @@ mmsClient_createWriteMultipleItemsRequest(uint32_t invokeId, char* domainId, Lin } int -mmsClient_createWriteRequest(uint32_t invokeId, char* domainId, char* itemId, MmsValue* value, +mmsClient_createWriteRequest(uint32_t invokeId, const char* domainId, const char* itemId, MmsValue* value, ByteBuffer* writeBuffer) { MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId); @@ -317,14 +317,14 @@ mmsClient_createWriteRequest(uint32_t invokeId, char* domainId, char* itemId, Mm request->variableAccessSpecification.choice.listOfVariable.list.count = 1; request->variableAccessSpecification.choice.listOfVariable.list.size = 1; request->variableAccessSpecification.choice.listOfVariable.list.array = - (ListOfVariableSeq_t**) calloc(1, sizeof(ListOfVariableSeq_t*)); + (ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*)); request->variableAccessSpecification.choice.listOfVariable.list.array[0] = (ListOfVariableSeq_t*) createNewDomainVariableSpecification(domainId, itemId); /* Create list of typed data values */ request->listOfData.list.count = 1; request->listOfData.list.size = 1; - request->listOfData.list.array = (Data_t**) calloc(1, sizeof(struct Data*)); + request->listOfData.list.array = (Data_t**) GLOBAL_CALLOC(1, sizeof(struct Data*)); request->listOfData.list.array[0] = mmsMsg_createBasicDataElement(value); asn_enc_rval_t rval; @@ -335,15 +335,15 @@ mmsClient_createWriteRequest(uint32_t invokeId, char* domainId, char* itemId, Mm /* Free ASN structure */ request->variableAccessSpecification.choice.listOfVariable.list.count = 0; - free(request->variableAccessSpecification.choice.listOfVariable.list.array[0]); - free(request->variableAccessSpecification.choice.listOfVariable.list.array); + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array[0]); + GLOBAL_FREEMEM(request->variableAccessSpecification.choice.listOfVariable.list.array); request->variableAccessSpecification.choice.listOfVariable.list.array = 0; request->listOfData.list.count = 0; deleteDataElement(request->listOfData.list.array[0]); - free(request->listOfData.list.array); + GLOBAL_FREEMEM(request->listOfData.list.array); request->listOfData.list.array = 0; asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); diff --git a/src/mms/iso_mms/common/mms_common_msg.c b/src/mms/iso_mms/common/mms_common_msg.c index 019705c5..6b2e07c5 100644 --- a/src/mms/iso_mms/common/mms_common_msg.c +++ b/src/mms/iso_mms/common/mms_common_msg.c @@ -33,7 +33,7 @@ mmsMsg_createFloatData(MmsValue* value, int* size, uint8_t** buf) { if (value->value.floatingPoint.formatWidth == 64) { *size = 9; - *buf = (uint8_t*) malloc(9); + *buf = (uint8_t*) GLOBAL_MALLOC(9); (*buf)[0] = 11; #if (ORDER_LITTLE_ENDIAN == 1) memcpyReverseByteOrder((*buf) + 1, value->value.floatingPoint.buf, 8); @@ -42,7 +42,7 @@ mmsMsg_createFloatData(MmsValue* value, int* size, uint8_t** buf) #endif } else { *size = 5; - *buf = (uint8_t*) malloc(5); + *buf = (uint8_t*) GLOBAL_MALLOC(5); (*buf)[0] = 8; #if (ORDER_LITTLE_ENDIAN == 1) memcpyReverseByteOrder((*buf) + 1, value->value.floatingPoint.buf, 4); @@ -55,17 +55,17 @@ mmsMsg_createFloatData(MmsValue* value, int* size, uint8_t** buf) Data_t* mmsMsg_createBasicDataElement(MmsValue* value) { - Data_t* dataElement = (Data_t*) calloc(1, sizeof(Data_t)); + Data_t* dataElement = (Data_t*) GLOBAL_CALLOC(1, sizeof(Data_t)); switch (value->type) { case MMS_ARRAY: { int size = MmsValue_getArraySize(value); dataElement->present = Data_PR_array; - dataElement->choice.array = (DataSequence_t*) calloc(1, sizeof(DataSequence_t)); + dataElement->choice.array = (DataSequence_t*) GLOBAL_CALLOC(1, sizeof(DataSequence_t)); dataElement->choice.array->list.count = size; dataElement->choice.array->list.size = size; - dataElement->choice.array->list.array = (Data_t**) calloc(size, sizeof(Data_t*)); + dataElement->choice.array->list.array = (Data_t**) GLOBAL_CALLOC(size, sizeof(Data_t*)); int i; for (i = 0; i < size; i++) { dataElement->choice.array->list.array[i] = @@ -79,10 +79,10 @@ mmsMsg_createBasicDataElement(MmsValue* value) int size = value->value.structure.size; dataElement->present = Data_PR_structure; - dataElement->choice.structure = (DataSequence_t*) calloc(1, sizeof(DataSequence_t)); + dataElement->choice.structure = (DataSequence_t*) GLOBAL_CALLOC(1, sizeof(DataSequence_t)); dataElement->choice.structure->list.count = size; dataElement->choice.structure->list.size = size; - dataElement->choice.structure->list.array = (Data_t**) calloc(size, sizeof(Data_t*)); + dataElement->choice.structure->list.array = (Data_t**) GLOBAL_CALLOC(size, sizeof(Data_t*)); int i; for (i = 0; i < size; i++) { dataElement->choice.structure->list.array[i] = mmsMsg_createBasicDataElement( @@ -117,7 +117,7 @@ mmsMsg_createBasicDataElement(MmsValue* value) case MMS_UTC_TIME: dataElement->present = Data_PR_utctime; - dataElement->choice.utctime.buf = (uint8_t*) malloc(8); + dataElement->choice.utctime.buf = (uint8_t*) GLOBAL_MALLOC(8); memcpy(dataElement->choice.utctime.buf, value->value.utcTime, 8); dataElement->choice.utctime.size = 8; break; @@ -174,7 +174,7 @@ mmsMsg_createBasicDataElement(MmsValue* value) default: dataElement->present = Data_PR_NOTHING; - printf("MMS read: unknown value type %i in result\n", value->type); + break; } @@ -187,13 +187,13 @@ mmsMsg_parseDataElement(Data_t* dataElement) MmsValue* value = NULL; if (dataElement->present == Data_PR_structure) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); int componentCount = dataElement->choice.structure->list.count; value->type = MMS_STRUCTURE; value->value.structure.size = componentCount; - value->value.structure.components = (MmsValue**) calloc(componentCount, sizeof(MmsValue*)); + value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*)); int i; @@ -203,13 +203,13 @@ mmsMsg_parseDataElement(Data_t* dataElement) } } else if (dataElement->present == Data_PR_array) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); int componentCount = dataElement->choice.array->list.count; value->type = MMS_ARRAY; value->value.structure.size = componentCount; - value->value.structure.components = (MmsValue**) calloc(componentCount, sizeof(MmsValue*)); + value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*)); int i; @@ -240,7 +240,7 @@ mmsMsg_parseDataElement(Data_t* dataElement) dataElement->choice.mMSString.size); } else if (dataElement->present == Data_PR_bitstring) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_BIT_STRING; int size = dataElement->choice.bitstring.size; @@ -248,13 +248,13 @@ mmsMsg_parseDataElement(Data_t* dataElement) value->value.bitString.size = (size * 8) - dataElement->choice.bitstring.bits_unused; - value->value.bitString.buf = (uint8_t*) malloc(size); + value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size); memcpy(value->value.bitString.buf, dataElement->choice.bitstring.buf, size); } else if (dataElement->present == Data_PR_floatingpoint) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); int size = dataElement->choice.floatingpoint.size; value->type = MMS_FLOAT; @@ -265,7 +265,7 @@ mmsMsg_parseDataElement(Data_t* dataElement) uint8_t* floatBuf = (dataElement->choice.floatingpoint.buf + 1); - value->value.floatingPoint.buf = (uint8_t*) malloc(4); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(4); #if (ORDER_LITTLE_ENDIAN == 1) memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 4); #else @@ -279,7 +279,7 @@ mmsMsg_parseDataElement(Data_t* dataElement) uint8_t* floatBuf = (dataElement->choice.floatingpoint.buf + 1); - value->value.floatingPoint.buf = (uint8_t*) malloc(8); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(8); #if (ORDER_LITTLE_ENDIAN == 1) memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 8); #else @@ -288,24 +288,24 @@ mmsMsg_parseDataElement(Data_t* dataElement) } } else if (dataElement->present == Data_PR_utctime) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_UTC_TIME; memcpy(value->value.utcTime, dataElement->choice.utctime.buf, 8); } else if (dataElement->present == Data_PR_octetstring) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_OCTET_STRING; int size = dataElement->choice.octetstring.size; value->value.octetString.size = size; value->value.octetString.maxSize = size; - value->value.octetString.buf = (uint8_t*) malloc(size); + value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); memcpy(value->value.octetString.buf, dataElement->choice.octetstring.buf, size); } else if (dataElement->present == Data_PR_binarytime) { int size = dataElement->choice.binarytime.size; if (size <= 6) { - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_BINARY_TIME; value->value.binaryTime.size = size; memcpy(value->value.binaryTime.buf, dataElement->choice.binarytime.buf, size); @@ -324,18 +324,18 @@ Data_t* mmsMsg_createDataElement(MmsValue* value) { if (value->type == MMS_STRUCTURE) { - Data_t* dataElement = (Data_t*) calloc(1, sizeof(Data_t)); + Data_t* dataElement = (Data_t*) GLOBAL_CALLOC(1, sizeof(Data_t)); dataElement->present = Data_PR_structure; int elementCount = value->value.structure.size; - dataElement->choice.structure = (DataSequence_t*) calloc(1, sizeof(DataSequence_t)); + dataElement->choice.structure = (DataSequence_t*) GLOBAL_CALLOC(1, sizeof(DataSequence_t)); dataElement->choice.structure->list.size = elementCount; dataElement->choice.structure->list.count = elementCount; - dataElement->choice.structure->list.array = (Data_t**) calloc(elementCount, sizeof(Data_t*)); + dataElement->choice.structure->list.array = (Data_t**) GLOBAL_CALLOC(elementCount, sizeof(Data_t*)); int i; @@ -370,7 +370,7 @@ mmsMsg_addResultToResultList(AccessResult_t* accessResult, MmsValue* value) accessResult->present = AccessResult_PR_array; accessResult->choice.array.list.count = size; accessResult->choice.array.list.size = size; - accessResult->choice.array.list.array = (Data_t**) calloc(size, sizeof(Data_t*)); + accessResult->choice.array.list.array = (Data_t**) GLOBAL_CALLOC(size, sizeof(Data_t*)); int i; for (i = 0; i < size; i++) { accessResult->choice.array.list.array[i] = @@ -384,7 +384,7 @@ mmsMsg_addResultToResultList(AccessResult_t* accessResult, MmsValue* value) accessResult->present = AccessResult_PR_structure; accessResult->choice.structure.list.count = size; accessResult->choice.structure.list.size = size; - accessResult->choice.structure.list.array = (Data_t**) calloc(size, sizeof(Data_t*)); + accessResult->choice.structure.list.array = (Data_t**) GLOBAL_CALLOC(size, sizeof(Data_t*)); int i; for (i = 0; i < size; i++) { accessResult->choice.structure.list.array[i] = @@ -416,7 +416,7 @@ mmsMsg_addResultToResultList(AccessResult_t* accessResult, MmsValue* value) break; case MMS_UTC_TIME: accessResult->present = AccessResult_PR_utctime; - accessResult->choice.utctime.buf = (uint8_t*) malloc(8); + accessResult->choice.utctime.buf = (uint8_t*) GLOBAL_MALLOC(8); memcpy(accessResult->choice.utctime.buf, value->value.utcTime, 8); accessResult->choice.utctime.size = 8; break; @@ -470,8 +470,8 @@ mmsMsg_addResultToResultList(AccessResult_t* accessResult, MmsValue* value) asn_long2INTEGER(&accessResult->choice.failure, DataAccessError_typeinconsistent); - if (1) - printf("MMS read: unknown value type %i in result\n", value->type); + + break; } } @@ -485,12 +485,12 @@ mmsMsg_createAccessResultsList(MmsPdu_t* mmsPdu, int resultsCount) readResponse->listOfAccessResult.list.size = resultsCount; readResponse->listOfAccessResult.list.count = resultsCount; - readResponse->listOfAccessResult.list.array = (AccessResult_t**) calloc(resultsCount, sizeof(AccessResult_t*)); + readResponse->listOfAccessResult.list.array = (AccessResult_t**) GLOBAL_CALLOC(resultsCount, sizeof(AccessResult_t*)); int i; for (i = 0; i < resultsCount; i++) { - readResponse->listOfAccessResult.list.array[i] = (AccessResult_t*) calloc(1, sizeof(AccessResult_t)); + readResponse->listOfAccessResult.list.array[i] = (AccessResult_t*) GLOBAL_CALLOC(1, sizeof(AccessResult_t)); } AccessResult_t** accessResultList = readResponse->listOfAccessResult.list.array; @@ -514,8 +514,8 @@ deleteDataElement(Data_t* dataElement) deleteDataElement(dataElement->choice.structure->list.array[i]); } - free(dataElement->choice.structure->list.array); - free(dataElement->choice.structure); + GLOBAL_FREEMEM(dataElement->choice.structure->list.array); + GLOBAL_FREEMEM(dataElement->choice.structure); } else if (dataElement->present == Data_PR_array) { int elementCount = dataElement->choice.array->list.count; @@ -525,17 +525,17 @@ deleteDataElement(Data_t* dataElement) deleteDataElement(dataElement->choice.array->list.array[i]); } - free(dataElement->choice.array->list.array); - free(dataElement->choice.array); + GLOBAL_FREEMEM(dataElement->choice.array->list.array); + GLOBAL_FREEMEM(dataElement->choice.array); } else if (dataElement->present == Data_PR_floatingpoint) { - free(dataElement->choice.floatingpoint.buf); + GLOBAL_FREEMEM(dataElement->choice.floatingpoint.buf); } else if (dataElement->present == Data_PR_utctime) { - free(dataElement->choice.utctime.buf); + GLOBAL_FREEMEM(dataElement->choice.utctime.buf); } - free(dataElement); + GLOBAL_FREEMEM(dataElement); } void @@ -554,7 +554,7 @@ mmsMsg_deleteAccessResultList(AccessResult_t** accessResult, int variableCount) deleteDataElement(accessResult[i]->choice.structure.list.array[j]); } - free(accessResult[i]->choice.structure.list.array); + GLOBAL_FREEMEM(accessResult[i]->choice.structure.list.array); } else if (accessResult[i]->present == AccessResult_PR_array) { int elementCount = accessResult[i]->choice.array.list.count; @@ -565,23 +565,23 @@ mmsMsg_deleteAccessResultList(AccessResult_t** accessResult, int variableCount) deleteDataElement(accessResult[i]->choice.array.list.array[j]); } - free(accessResult[i]->choice.array.list.array); + GLOBAL_FREEMEM(accessResult[i]->choice.array.list.array); } else if (accessResult[i]->present == AccessResult_PR_integer) - free(accessResult[i]->choice.integer.buf); + GLOBAL_FREEMEM(accessResult[i]->choice.integer.buf); else if (accessResult[i]->present == AccessResult_PR_unsigned) - free(accessResult[i]->choice.Unsigned.buf); + GLOBAL_FREEMEM(accessResult[i]->choice.Unsigned.buf); else if (accessResult[i]->present == AccessResult_PR_floatingpoint) - free(accessResult[i]->choice.floatingpoint.buf); + GLOBAL_FREEMEM(accessResult[i]->choice.floatingpoint.buf); else if (accessResult[i]->present == AccessResult_PR_utctime) - free(accessResult[i]->choice.utctime.buf); + GLOBAL_FREEMEM(accessResult[i]->choice.utctime.buf); else if (accessResult[i]->present == AccessResult_PR_failure) - free(accessResult[i]->choice.failure.buf); + GLOBAL_FREEMEM(accessResult[i]->choice.failure.buf); - free(accessResult[i]); + GLOBAL_FREEMEM(accessResult[i]); } - free(accessResult); + GLOBAL_FREEMEM(accessResult); } diff --git a/src/mms/iso_mms/common/mms_type_spec.c b/src/mms/iso_mms/common/mms_type_spec.c index b4ce3a04..e88e70d5 100644 --- a/src/mms/iso_mms/common/mms_type_spec.c +++ b/src/mms/iso_mms/common/mms_type_spec.c @@ -30,7 +30,7 @@ void MmsVariableSpecification_destroy(MmsVariableSpecification* typeSpec) { if (typeSpec->name != NULL) - free(typeSpec->name); + GLOBAL_FREEMEM(typeSpec->name); if (typeSpec->type == MMS_STRUCTURE) { int elementCount = typeSpec->typeSpec.structure.elementCount; @@ -39,17 +39,17 @@ MmsVariableSpecification_destroy(MmsVariableSpecification* typeSpec) MmsVariableSpecification_destroy(typeSpec->typeSpec.structure.elements[i]); } - free(typeSpec->typeSpec.structure.elements); + GLOBAL_FREEMEM(typeSpec->typeSpec.structure.elements); } else if (typeSpec->type == MMS_ARRAY) { MmsVariableSpecification_destroy(typeSpec->typeSpec.array.elementTypeSpec); } - free(typeSpec); + GLOBAL_FREEMEM(typeSpec); } static size_t -directChildStrLen(char* childId) +directChildStrLen(const char* childId) { size_t i = 0; size_t childIdLen = strlen(childId); @@ -63,7 +63,7 @@ directChildStrLen(char* childId) } MmsValue* -MmsVariableSpecification_getChildValue(MmsVariableSpecification* typeSpec, MmsValue* value, char* childId) +MmsVariableSpecification_getChildValue(MmsVariableSpecification* typeSpec, MmsValue* value, const char* childId) { if (typeSpec->type == MMS_STRUCTURE) { size_t childLen = directChildStrLen(childId); @@ -95,7 +95,7 @@ MmsVariableSpecification_getType(MmsVariableSpecification* self) return self->type; } -char* +const char* MmsVariableSpecification_getName(MmsVariableSpecification* self) { return self->name; @@ -120,9 +120,9 @@ MmsVariableSpecification_getStructureElements(MmsVariableSpecification* self) } MmsVariableSpecification* -MmsVariableSpecification_getNamedVariableRecursive(MmsVariableSpecification* variable, char* nameId) +MmsVariableSpecification_getNamedVariableRecursive(MmsVariableSpecification* variable, const char* nameId) { - char* separator = strchr(nameId, '$'); + const char* separator = strchr(nameId, '$'); int i; diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index bf753eae..0fc58d29 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -70,7 +70,7 @@ updateStructuredComponent(MmsValue* self, MmsValue* update) MmsValue* MmsValue_newIntegerFromBerInteger(Asn1PrimitiveValue* berInteger) { - MmsValue* self = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); self->type = MMS_INTEGER; self->value.integer = berInteger; @@ -81,7 +81,7 @@ MmsValue_newIntegerFromBerInteger(Asn1PrimitiveValue* berInteger) MmsValue* MmsValue_newUnsignedFromBerInteger(Asn1PrimitiveValue* berInteger) { - MmsValue* self = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); self->type = MMS_UNSIGNED; self->value.integer = berInteger; @@ -281,7 +281,7 @@ MmsValue_update(MmsValue* self, MmsValue* update) MmsValue* MmsValue_newDataAccessError(MmsDataAccessError accessError) { - MmsValue* self = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); self->type = MMS_DATA_ACCESS_ERROR; self->value.dataAccessError = accessError; @@ -292,11 +292,11 @@ MmsValue_newDataAccessError(MmsDataAccessError accessError) MmsValue* MmsValue_newBitString(int bitSize) { - MmsValue* self = (MmsValue*) calloc(1, sizeof(MmsValue));; + MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));; self->type = MMS_BIT_STRING; self->value.bitString.size = bitSize; - self->value.bitString.buf = (uint8_t*) calloc(bitStringByteSize(self), 1); + self->value.bitString.buf = (uint8_t*) GLOBAL_CALLOC(bitStringByteSize(self), 1); return self; } @@ -441,16 +441,47 @@ MmsValue_setBitStringFromInteger(MmsValue* self, uint32_t intValue) } } +uint32_t +MmsValue_getBitStringAsIntegerBigEndian(MmsValue* self) +{ + uint32_t value = 0; + + int bitPos; + + for (bitPos = (self->value.bitString.size - 1); bitPos >= 0; bitPos--) { + if (MmsValue_getBitStringBit(self, bitPos)) { + value += (1 << bitPos); + } + } + + return value; +} + +void +MmsValue_setBitStringFromIntegerBigEndian(MmsValue* self, uint32_t intValue) +{ + int bitPos; + + for (bitPos = (self->value.bitString.size - 1); bitPos >= 0; bitPos--) { + if ((intValue & 1) == 1) + MmsValue_setBitStringBit(self, bitPos, true); + else + MmsValue_setBitStringBit(self, bitPos, false); + + intValue = intValue >> 1; + } +} + MmsValue* MmsValue_newFloat(float variable) { - MmsValue* value = (MmsValue*) malloc(sizeof(MmsValue));; + MmsValue* value = (MmsValue*) GLOBAL_MALLOC(sizeof(MmsValue));; value->type = MMS_FLOAT; value->value.floatingPoint.formatWidth = 32; value->value.floatingPoint.exponentWidth = 8; - value->value.floatingPoint.buf = (uint8_t*) malloc(4); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(4); *((float*) value->value.floatingPoint.buf) = variable; @@ -486,12 +517,12 @@ MmsValue_setDouble(MmsValue* value, double newFloatValue) MmsValue* MmsValue_newDouble(double variable) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_FLOAT; value->value.floatingPoint.formatWidth = 64; value->value.floatingPoint.exponentWidth = 11; - value->value.floatingPoint.buf = (uint8_t*) malloc(8); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(8); *((double*) value->value.floatingPoint.buf) = variable; @@ -501,7 +532,7 @@ MmsValue_newDouble(double variable) MmsValue* MmsValue_newIntegerFromInt8(int8_t integer) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue));; + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));; value->type = MMS_INTEGER; value->value.integer = BerInteger_createFromInt32((int32_t) integer); @@ -512,7 +543,7 @@ MmsValue_newIntegerFromInt8(int8_t integer) MmsValue* MmsValue_newIntegerFromInt16(int16_t integer) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue));; + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));; value->type = MMS_INTEGER; value->value.integer = BerInteger_createFromInt32((int32_t) integer); @@ -704,7 +735,7 @@ MmsValue_getUtcTimeInMs(MmsValue* self) MmsValue* MmsValue_newIntegerFromInt32(int32_t integer) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_INTEGER; value->value.integer = BerInteger_createFromInt32(integer); @@ -715,7 +746,7 @@ MmsValue_newIntegerFromInt32(int32_t integer) MmsValue* MmsValue_newUnsignedFromUint32(uint32_t integer) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue));; + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));; value->type = MMS_UNSIGNED; value->value.integer = BerInteger_createFromUint32(integer); @@ -726,7 +757,7 @@ MmsValue_newUnsignedFromUint32(uint32_t integer) MmsValue* MmsValue_newIntegerFromInt64(int64_t integer) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue));; + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));; value->type = MMS_INTEGER; value->value.integer = BerInteger_createFromInt64(integer); @@ -949,7 +980,7 @@ MmsValue_cloneToBuffer(MmsValue* self, uint8_t* destinationAddress) MmsValue* MmsValue_clone(MmsValue* value) { - MmsValue* newValue = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* newValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); newValue->deleteValue = value->deleteValue; newValue->type = value->type; int size; @@ -961,7 +992,7 @@ MmsValue_clone(MmsValue* value) { int componentCount = value->value.structure.size; newValue->value.structure.size = componentCount; - newValue->value.structure.components = (MmsValue**) calloc(componentCount, sizeof(MmsValue*)); + newValue->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*)); int i; for (i = 0; i < componentCount; i++) { newValue->value.structure.components[i] = @@ -978,13 +1009,13 @@ MmsValue_clone(MmsValue* value) newValue->value.floatingPoint.formatWidth = value->value.floatingPoint.formatWidth; newValue->value.floatingPoint.exponentWidth = value->value.floatingPoint.exponentWidth; size = value->value.floatingPoint.formatWidth / 8; - newValue->value.floatingPoint.buf = (uint8_t*) malloc(size); + newValue->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(size); memcpy(newValue->value.floatingPoint.buf, value->value.floatingPoint.buf, size); break; case MMS_BIT_STRING: newValue->value.bitString.size = value->value.bitString.size; size = bitStringByteSize(value); - newValue->value.bitString.buf = (uint8_t*) malloc(size); + newValue->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size); memcpy(newValue->value.bitString.buf, value->value.bitString.buf, size); break; case MMS_BOOLEAN: @@ -994,7 +1025,7 @@ MmsValue_clone(MmsValue* value) size = value->value.octetString.size; newValue->value.octetString.size = size; newValue->value.octetString.maxSize = value->value.octetString.maxSize; - newValue->value.octetString.buf = (uint8_t*) malloc(value->value.octetString.maxSize); + newValue->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(value->value.octetString.maxSize); memcpy(newValue->value.octetString.buf, value->value.octetString.buf, size); break; case MMS_UTC_TIME: @@ -1007,7 +1038,7 @@ MmsValue_clone(MmsValue* value) case MMS_VISIBLE_STRING: case MMS_STRING: size = value->value.visibleString.size; - newValue->value.visibleString.buf = (char*) malloc(size + 1); + newValue->value.visibleString.buf = (char*) GLOBAL_MALLOC(size + 1); newValue->value.visibleString.size = size; strcpy(newValue->value.visibleString.buf, value->value.visibleString.buf); break; @@ -1043,18 +1074,18 @@ MmsValue_delete(MmsValue* value) Asn1PrimitiveValue_destroy(value->value.integer); break; case MMS_FLOAT: - free(value->value.floatingPoint.buf); + GLOBAL_FREEMEM(value->value.floatingPoint.buf); break; case MMS_BIT_STRING: - free(value->value.bitString.buf); + GLOBAL_FREEMEM(value->value.bitString.buf); break; case MMS_OCTET_STRING: - free(value->value.octetString.buf); + GLOBAL_FREEMEM(value->value.octetString.buf); break; case MMS_VISIBLE_STRING: case MMS_STRING: if (value->value.visibleString.buf != NULL) - free(value->value.visibleString.buf); + GLOBAL_FREEMEM(value->value.visibleString.buf); break; case MMS_ARRAY: case MMS_STRUCTURE: @@ -1067,13 +1098,13 @@ MmsValue_delete(MmsValue* value) MmsValue_delete(value->value.structure.components[i]); } } - free(value->value.structure.components); + GLOBAL_FREEMEM(value->value.structure.components); break; default: break; } - free(value); + GLOBAL_FREEMEM(value); } /* delete only when deleteValue field set */ @@ -1088,18 +1119,18 @@ MmsValue_deleteConditional(MmsValue* value) Asn1PrimitiveValue_destroy(value->value.integer); break; case MMS_FLOAT: - free(value->value.floatingPoint.buf); + GLOBAL_FREEMEM(value->value.floatingPoint.buf); break; case MMS_BIT_STRING: - free(value->value.bitString.buf); + GLOBAL_FREEMEM(value->value.bitString.buf); break; case MMS_OCTET_STRING: - free(value->value.octetString.buf); + GLOBAL_FREEMEM(value->value.octetString.buf); break; case MMS_VISIBLE_STRING: case MMS_STRING: if (value->value.visibleString.buf != NULL) - free(value->value.visibleString.buf); + GLOBAL_FREEMEM(value->value.visibleString.buf); break; case MMS_ARRAY: case MMS_STRUCTURE: @@ -1112,20 +1143,20 @@ MmsValue_deleteConditional(MmsValue* value) MmsValue_deleteConditional(value->value.structure.components[i]); } } - free(value->value.structure.components); + GLOBAL_FREEMEM(value->value.structure.components); break; default: break; } - free(value); + GLOBAL_FREEMEM(value); } } MmsValue* MmsValue_newInteger(int size /*integer size in bits*/) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_INTEGER; if (size <= 32) @@ -1139,7 +1170,7 @@ MmsValue_newInteger(int size /*integer size in bits*/) MmsValue* MmsValue_newUnsigned(int size /*integer size in bits*/) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_UNSIGNED; if (size <= 32) @@ -1153,7 +1184,7 @@ MmsValue_newUnsigned(int size /*integer size in bits*/) MmsValue* MmsValue_newBoolean(bool boolean) { - MmsValue* self = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); self->type = MMS_BOOLEAN; if (boolean == true) self->value.boolean = 1; @@ -1166,11 +1197,11 @@ MmsValue_newBoolean(bool boolean) MmsValue* MmsValue_newOctetString(int size, int maxSize) { - MmsValue* self = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); self->type = MMS_OCTET_STRING; self->value.octetString.size = size; self->value.octetString.maxSize = maxSize; - self->value.octetString.buf = (uint8_t*) calloc(1, maxSize); + self->value.octetString.buf = (uint8_t*) GLOBAL_CALLOC(1, maxSize); return self; } @@ -1205,12 +1236,12 @@ MmsValue_getOctetStringBuffer(MmsValue* self) MmsValue* MmsValue_newStructure(MmsVariableSpecification* typeSpec) { - MmsValue* self = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* self = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); self->type = MMS_STRUCTURE; int componentCount = typeSpec->typeSpec.structure.elementCount; self->value.structure.size = componentCount; - self->value.structure.components = (MmsValue**) calloc(componentCount, sizeof(MmsValue*)); + self->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*)); int i; for (i = 0; i < componentCount; i++) { @@ -1234,24 +1265,24 @@ MmsValue_newDefaultValue(MmsVariableSpecification* typeSpec) value = MmsValue_newUnsigned(typeSpec->typeSpec.unsignedInteger); break; case MMS_FLOAT: - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_FLOAT; value->value.floatingPoint.exponentWidth = typeSpec->typeSpec.floatingpoint.exponentWidth; value->value.floatingPoint.formatWidth = typeSpec->typeSpec.floatingpoint.formatWidth; - value->value.floatingPoint.buf = (uint8_t*) calloc(1, typeSpec->typeSpec.floatingpoint.formatWidth / 8); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_CALLOC(1, typeSpec->typeSpec.floatingpoint.formatWidth / 8); break; case MMS_BIT_STRING: - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_BIT_STRING; { int bitSize = abs(typeSpec->typeSpec.bitString); value->value.bitString.size = bitSize; int size = (bitSize / 8) + ((bitSize % 8) > 0); - value->value.bitString.buf = (uint8_t*) calloc(1, size); + value->value.bitString.buf = (uint8_t*) GLOBAL_CALLOC(1, size); } break; case MMS_OCTET_STRING: - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_OCTET_STRING; if (typeSpec->typeSpec.octetString < 0) @@ -1260,7 +1291,7 @@ MmsValue_newDefaultValue(MmsVariableSpecification* typeSpec) value->value.octetString.size = typeSpec->typeSpec.octetString; value->value.octetString.maxSize = abs(typeSpec->typeSpec.octetString); - value->value.octetString.buf = (uint8_t*) calloc(1, abs(typeSpec->typeSpec.octetString)); + value->value.octetString.buf = (uint8_t*) GLOBAL_CALLOC(1, abs(typeSpec->typeSpec.octetString)); break; case MMS_VISIBLE_STRING: value = MmsValue_newVisibleStringWithSize(abs(typeSpec->typeSpec.visibleString)); @@ -1269,7 +1300,7 @@ MmsValue_newDefaultValue(MmsVariableSpecification* typeSpec) value = MmsValue_newBoolean(false); break; case MMS_UTC_TIME: - value = (MmsValue*) calloc(1, sizeof(MmsValue)); + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_UTC_TIME; break; case MMS_ARRAY: @@ -1307,8 +1338,8 @@ setVisibleStringValue(MmsValue* value, char* string) int newStringSize = strlen(string); if (newStringSize > value->value.visibleString.size) { - free(value->value.visibleString.buf); - value->value.visibleString.buf = (char*) malloc(newStringSize + 1); + GLOBAL_FREEMEM(value->value.visibleString.buf); + value->value.visibleString.buf = (char*) GLOBAL_MALLOC(newStringSize + 1); value->value.visibleString.size = newStringSize; } @@ -1323,7 +1354,7 @@ setVisibleStringValue(MmsValue* value, char* string) static MmsValue* MmsValue_newString(char* string, MmsType type) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = type; if (string == NULL) { @@ -1334,7 +1365,7 @@ MmsValue_newString(char* string, MmsType type) int stringSize = strlen(string); value->value.visibleString.size = stringSize; - value->value.visibleString.buf = (char*) malloc(stringSize + 1); + value->value.visibleString.buf = (char*) GLOBAL_MALLOC(stringSize + 1); setVisibleStringValue(value, string); } @@ -1352,11 +1383,11 @@ MmsValue_newVisibleString(char* string) static MmsValue* MmsValue_newStringWithSize(int size, MmsType type) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = type; value->value.visibleString.size = size; - value->value.visibleString.buf = (char*) malloc(size + 1); + value->value.visibleString.buf = (char*) GLOBAL_MALLOC(size + 1); value->value.visibleString.buf[0] = 0; return value; @@ -1384,7 +1415,7 @@ MmsValue_newMmsStringWithSize(int size) MmsValue* MmsValue_newBinaryTime(bool timeOfDay) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_BINARY_TIME; if (timeOfDay == true) @@ -1485,7 +1516,7 @@ MmsValue_setMmsString(MmsValue* value, char* string) static MmsValue* MmsValue_newStringFromByteArray(uint8_t* byteArray, int size, MmsType type) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = type; value->value.visibleString.buf = createStringFromBuffer(byteArray, size); @@ -1528,7 +1559,7 @@ MmsValue_toString(MmsValue* value) MmsValue* MmsValue_newUtcTime(uint32_t timeval) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_UTC_TIME; uint8_t* timeArray = (uint8_t*) &timeval; @@ -1553,7 +1584,7 @@ MmsValue_newUtcTime(uint32_t timeval) MmsValue* MmsValue_newUtcTimeByMsTime(uint64_t timeval) { - MmsValue* value = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value->type = MMS_UTC_TIME; MmsValue_setUtcTimeMs(value, timeval); @@ -1564,11 +1595,11 @@ MmsValue_newUtcTimeByMsTime(uint64_t timeval) MmsValue* MmsValue_createArray(MmsVariableSpecification* elementType, int size) { - MmsValue* array = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* array = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); array->type = MMS_ARRAY; array->value.structure.size = size; - array->value.structure.components = (MmsValue**) calloc(size, sizeof(MmsValue*)); + array->value.structure.components = (MmsValue**) GLOBAL_CALLOC(size, sizeof(MmsValue*)); int i; for (i = 0; i < size; i++) { @@ -1581,11 +1612,11 @@ MmsValue_createArray(MmsVariableSpecification* elementType, int size) MmsValue* MmsValue_createEmtpyArray(int size) { - MmsValue* array = (MmsValue*) calloc(1, sizeof(MmsValue)); + MmsValue* array = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); array->type = MMS_ARRAY; array->value.structure.size = size; - array->value.structure.components = (MmsValue**) calloc(size, sizeof(MmsValue*)); + array->value.structure.components = (MmsValue**) GLOBAL_CALLOC(size, sizeof(MmsValue*)); int i; for (i = 0; i < size; i++) { diff --git a/src/mms/iso_mms/server/mms_device.c b/src/mms/iso_mms/server/mms_device.c index 53d15732..f071a63f 100644 --- a/src/mms/iso_mms/server/mms_device.c +++ b/src/mms/iso_mms/server/mms_device.c @@ -21,13 +21,14 @@ * See COPYING file for the complete license text. */ +#include "mms_server_internal.h" #include "mms_device_model.h" #include "stack_config.h" MmsDevice* MmsDevice_create(char* deviceName) { - MmsDevice* self = (MmsDevice*) calloc(1, sizeof(MmsDevice)); + MmsDevice* self = (MmsDevice*) GLOBAL_CALLOC(1, sizeof(MmsDevice)); self->deviceName = deviceName; self->namedVariableLists = LinkedList_create(); @@ -56,8 +57,8 @@ MmsDevice_destroy(MmsDevice* self) LinkedList_destroyDeep(self->namedVariableLists, (LinkedListValueDeleteFunction) MmsNamedVariableList_destroy); - free(self->domains); - free(self); + GLOBAL_FREEMEM(self->domains); + GLOBAL_FREEMEM(self); } MmsDomain* diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index 3ec82e2c..7d07dc39 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -3,22 +3,22 @@ * * Copyright 2013, 2014 Michael Zillgith * - * This file is part of libIEC61850. + * 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 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. + * 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 . + * 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. + * See COPYING file for the complete license text. */ #include "mms_device_model.h" @@ -36,7 +36,7 @@ freeNamedVariables(MmsVariableSpecification** variables, int variablesCount) MmsDomain* MmsDomain_create(char* domainName) { - MmsDomain* self = (MmsDomain*) calloc(1, sizeof(MmsDomain)); + MmsDomain* self = (MmsDomain*) GLOBAL_CALLOC(1, sizeof(MmsDomain)); self->domainName = copyString(domainName); self->namedVariableLists = LinkedList_create(); @@ -47,18 +47,18 @@ MmsDomain_create(char* domainName) void MmsDomain_destroy(MmsDomain* self) { - free(self->domainName); + GLOBAL_FREEMEM(self->domainName); if (self->namedVariables != NULL) { freeNamedVariables(self->namedVariables, self->namedVariablesCount); - free(self->namedVariables); + GLOBAL_FREEMEM(self->namedVariables); } LinkedList_destroyDeep(self->namedVariableLists, (LinkedListValueDeleteFunction) MmsNamedVariableList_destroy); - free(self); + GLOBAL_FREEMEM(self); } char* @@ -70,8 +70,6 @@ MmsDomain_getName(MmsDomain* self) bool MmsDomain_addNamedVariableList(MmsDomain* self, MmsNamedVariableList variableList) { - //TODO check if operation is allowed! - LinkedList_add(self->namedVariableLists, variableList); return true; diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index a2af0378..ce1833df 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -25,7 +25,7 @@ #if (MMS_FILE_SERVICE == 1) -#include "filesystem.h" +#include "hal_filesystem.h" #include "conversions.h" #define CONFIG_MMS_FILE_SERVICE_MAX_FILENAME_LENGTH 256 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 31d6600e..a6c5b37a 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -84,7 +84,7 @@ appendMmsSubVariable(char* name, char* child) int newSize = nameLen + childLen + 2; - char* newName = (char*) malloc(newSize); + char* newName = (char*) GLOBAL_MALLOC(newSize); int bufPos = 0; int i; 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 97d3d81e..92201944 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 @@ -42,7 +42,7 @@ createTypeSpecification ( (long) namedVariable->typeSpec.array.elementCount); typeSpec->choice.array.packed = NULL; - typeSpec->choice.array.elementType = (TypeSpecification_t*) calloc(1, sizeof(TypeSpecification_t)); + typeSpec->choice.array.elementType = (TypeSpecification_t*) GLOBAL_CALLOC(1, sizeof(TypeSpecification_t)); createTypeSpecification(namedVariable->typeSpec.array.elementTypeSpec, typeSpec->choice.array.elementType); @@ -57,17 +57,17 @@ createTypeSpecification ( typeSpec->choice.structure.components.list.size = componentCount; typeSpec->choice.structure.components.list.array - = (StructComponent_t**) calloc(componentCount, sizeof(StructComponent_t*)); + = (StructComponent_t**) GLOBAL_CALLOC(componentCount, sizeof(StructComponent_t*)); int i; for (i = 0; i < componentCount; i++) { typeSpec->choice.structure.components.list.array[i] = - (StructComponent_t*) calloc(1, sizeof(StructComponent_t)); + (StructComponent_t*) GLOBAL_CALLOC(1, sizeof(StructComponent_t)); typeSpec->choice.structure.components.list.array[i]->componentName = - (Identifier_t*) calloc(1, sizeof(Identifier_t)); + (Identifier_t*) GLOBAL_CALLOC(1, sizeof(Identifier_t)); typeSpec->choice.structure.components.list.array[i]->componentName->buf = (uint8_t*) copyString(namedVariable->typeSpec.structure.elements[i]->name); @@ -76,7 +76,7 @@ createTypeSpecification ( strlen(namedVariable->typeSpec.structure.elements[i]->name); typeSpec->choice.structure.components.list.array[i]->componentType = - (TypeSpecification_t*) calloc(1, sizeof(TypeSpecification_t)); + (TypeSpecification_t*) GLOBAL_CALLOC(1, sizeof(TypeSpecification_t)); createTypeSpecification(namedVariable->typeSpec.structure.elements[i], typeSpec->choice.structure.components.list.array[i]->componentType); @@ -151,19 +151,19 @@ freeTypeSpecRecursive(TypeSpecification_t* typeSpec) { int i; for (i = 0; i < elementCount; i++) { - free(typeSpec->choice.structure.components.list.array[i]->componentName->buf); - free(typeSpec->choice.structure.components.list.array[i]->componentName); + GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentName->buf); + GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentName); freeTypeSpecRecursive(typeSpec->choice.structure.components.list.array[i]->componentType); - free(typeSpec->choice.structure.components.list.array[i]->componentType); - free(typeSpec->choice.structure.components.list.array[i]); + GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]->componentType); + GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array[i]); } - free(typeSpec->choice.structure.components.list.array); + GLOBAL_FREEMEM(typeSpec->choice.structure.components.list.array); } else if (typeSpec->present == TypeSpecification_PR_array) { - free(typeSpec->choice.array.numberOfElements.buf); + GLOBAL_FREEMEM(typeSpec->choice.array.numberOfElements.buf); freeTypeSpecRecursive(typeSpec->choice.array.elementType); - free(typeSpec->choice.array.elementType); + GLOBAL_FREEMEM(typeSpec->choice.array.elementType); } } @@ -176,27 +176,27 @@ deleteVariableAccessAttributesResponse( int i; for (i = 0; i < count; i++) { - free(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName->buf); - free(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName); + GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName->buf); + GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentName); TypeSpecification_t* typeSpec = getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]->componentType; freeTypeSpecRecursive(typeSpec); - free(typeSpec); - free(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]); + GLOBAL_FREEMEM(typeSpec); + GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array[i]); } - free(getVarAccessAttr->typeSpecification.choice.structure.components.list.array); + GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.structure.components.list.array); getVarAccessAttr->typeSpecification.choice.structure.components.list.array = NULL; getVarAccessAttr->typeSpecification.choice.structure.components.list.count = 0; getVarAccessAttr->typeSpecification.choice.structure.components.list.size = 0; } else if (getVarAccessAttr->typeSpecification.present == TypeSpecification_PR_array) { - free(getVarAccessAttr->typeSpecification.choice.array.numberOfElements.buf); + GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.array.numberOfElements.buf); getVarAccessAttr->typeSpecification.choice.array.numberOfElements.buf = NULL; getVarAccessAttr->typeSpecification.choice.array.numberOfElements.size = 0; freeTypeSpecRecursive(getVarAccessAttr->typeSpecification.choice.array.elementType); - free(getVarAccessAttr->typeSpecification.choice.array.elementType); + GLOBAL_FREEMEM(getVarAccessAttr->typeSpecification.choice.array.elementType); getVarAccessAttr->typeSpecification.choice.array.elementType = NULL; } @@ -301,8 +301,8 @@ mmsServer_handleGetVariableAccessAttributesRequest( createVariableAccessAttributesResponse(connection, domainIdStr, nameIdStr, invokeId, response); - free(domainIdStr); - free(nameIdStr); + GLOBAL_FREEMEM(domainIdStr); + GLOBAL_FREEMEM(nameIdStr); } #if (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) else if (request->choice.name.present == ObjectName_PR_vmdspecific) { @@ -314,7 +314,7 @@ mmsServer_handleGetVariableAccessAttributesRequest( createVariableAccessAttributesResponse(connection, NULL, nameIdStr, invokeId, response); - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); } #endif /* (CONFIG_MMS_SUPPORT_VMD_SCOPE_NAMED_VARIABLES == 1) */ else { diff --git a/src/mms/iso_mms/server/mms_information_report.c b/src/mms/iso_mms/server/mms_information_report.c index 2349df73..4380d727 100644 --- a/src/mms/iso_mms/server/mms_information_report.c +++ b/src/mms/iso_mms/server/mms_information_report.c @@ -97,19 +97,18 @@ MmsServerConnection_sendInformationReportListOfVariables( uint32_t varSpecSize = BerEncoder_determineEncodedStringSize(spec->itemId); - if (spec->domainId != NULL) { + if (spec->domainId != NULL) varSpecSize += BerEncoder_determineEncodedStringSize(spec->domainId); - varSpecSize += BerEncoder_determineLengthSize(varSpecSize) + 1; - } - listOfVarSpecSize += varSpecSize; + uint32_t sequenceSize = (varSpecSize + 1 + BerEncoder_determineLengthSize(varSpecSize)); + + listOfVarSpecSize += (1 + BerEncoder_determineLengthSize(sequenceSize) + sequenceSize); i++; specElement = LinkedList_getNext(specElement); } - uint32_t sequenceSize = 1 + BerEncoder_determineLengthSize(listOfVarSpecSize) + listOfVarSpecSize; - uint32_t listOfVariableSize = 1 + BerEncoder_determineLengthSize(sequenceSize) + sequenceSize; + uint32_t listOfVariableSize = 1 + BerEncoder_determineLengthSize(listOfVarSpecSize) + listOfVarSpecSize; uint32_t accessResultSize = 0; @@ -146,7 +145,7 @@ MmsServerConnection_sendInformationReportListOfVariables( /* encode list of variable access specifications */ bufPos = BerEncoder_encodeTL(0xa0, listOfVariableSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x30, sequenceSize, buffer, bufPos); + specElement = LinkedList_getNext(variableAccessDeclarations); i = 0; @@ -160,13 +159,17 @@ MmsServerConnection_sendInformationReportListOfVariables( varSpecSize += BerEncoder_determineEncodedStringSize(spec->domainId); uint32_t varSpecSizeComplete = varSpecSize + BerEncoder_determineLengthSize(varSpecSize) + 1; + uint32_t sequenceSize = varSpecSizeComplete + BerEncoder_determineLengthSize(varSpecSizeComplete) + 1; + bufPos = BerEncoder_encodeTL(0x30, sequenceSize, buffer, bufPos); bufPos = BerEncoder_encodeTL(0xa0, varSpecSizeComplete, buffer, bufPos); /* domain-specific */ bufPos = BerEncoder_encodeTL(0xa1, varSpecSize, buffer, bufPos); bufPos = BerEncoder_encodeStringWithTag(0x1a, spec->domainId, buffer, bufPos); bufPos = BerEncoder_encodeStringWithTag(0x1a, spec->itemId, buffer, bufPos); } else { + uint32_t sequenceSize = varSpecSize + BerEncoder_determineLengthSize(varSpecSize) + 1; + bufPos = BerEncoder_encodeTL(0x30, sequenceSize, buffer, bufPos); bufPos = BerEncoder_encodeTL(0xa0, varSpecSize, buffer, bufPos); /* vmd-specific */ bufPos = BerEncoder_encodeStringWithTag(0x80, spec->itemId, buffer, bufPos); } diff --git a/src/mms/iso_mms/server/mms_named_variable_list.c b/src/mms/iso_mms/server/mms_named_variable_list.c index bd318301..9fe16a9c 100644 --- a/src/mms/iso_mms/server/mms_named_variable_list.c +++ b/src/mms/iso_mms/server/mms_named_variable_list.c @@ -28,7 +28,7 @@ MmsNamedVariableListEntry MmsNamedVariableListEntry_create(MmsAccessSpecifier accessSpecifier) { - MmsNamedVariableListEntry listEntry = (MmsNamedVariableListEntry) malloc(sizeof(MmsAccessSpecifier)); + MmsNamedVariableListEntry listEntry = (MmsNamedVariableListEntry) GLOBAL_MALLOC(sizeof(MmsAccessSpecifier)); listEntry->domain = accessSpecifier.domain; listEntry->variableName = copyString(accessSpecifier.variableName); @@ -45,8 +45,8 @@ MmsNamedVariableListEntry_create(MmsAccessSpecifier accessSpecifier) void MmsNamedVariableListEntry_destroy(MmsNamedVariableListEntry self) { - free(self->variableName); - free(self); + GLOBAL_FREEMEM(self->variableName); + GLOBAL_FREEMEM(self); } @@ -64,7 +64,7 @@ MmsNamedVariableListEntry_getVariableName(MmsNamedVariableListEntry self) { MmsNamedVariableList MmsNamedVariableList_create(char* name, bool deletable) { - MmsNamedVariableList self = (MmsNamedVariableList) malloc(sizeof(struct sMmsNamedVariableList)); + MmsNamedVariableList self = (MmsNamedVariableList) GLOBAL_MALLOC(sizeof(struct sMmsNamedVariableList)); self->deletable = deletable; self->name = copyString(name); @@ -108,8 +108,8 @@ void MmsNamedVariableList_destroy(MmsNamedVariableList self) { LinkedList_destroyDeep(self->listOfVariables, deleteVariableListEntry); - free(self->name); - free(self); + GLOBAL_FREEMEM(self->name); + GLOBAL_FREEMEM(self); } 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 61d7ebd1..e12a3c07 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 @@ -1,7 +1,7 @@ /* * mms_named_variable_list_service.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * @@ -29,8 +29,20 @@ #if (MMS_DATA_SET_SERVICE == 1) - #if (MMS_DYNAMIC_DATA_SETS == 1) + +#ifndef CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS +#define CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS 10 +#endif + +#ifndef CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS +#define CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS 10 +#endif + +#ifndef CONFIG_MMS_MAX_NUMBER_OF_VMD_SPECIFIC_DATA_SETS +#define CONFIG_MMS_MAX_NUMBER_OF_VMD_SPECIFIC_DATA_SETS 10 +#endif + static void createDeleteNamedVariableListResponse(uint32_t invokeId, ByteBuffer* response, uint32_t numberMatched, uint32_t numberDeleted) @@ -134,9 +146,8 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection* connection, MmsServerConnection_deleteNamedVariableList(connection, itemId); } - free(itemId); + GLOBAL_FREEMEM(itemId); } - //TODO else send error??? } createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted); @@ -168,10 +179,39 @@ createDefineNamedVariableListResponse(uint32_t invokeId, ByteBuffer* response) response->size = bufPos; } +static bool +checkIfVariableExists(MmsDevice* device, MmsAccessSpecifier* accessSpecifier) +{ + if (accessSpecifier->domain == NULL) + return false; + + MmsVariableSpecification* variableSpec = + MmsDomain_getNamedVariable(accessSpecifier->domain, accessSpecifier->variableName); + + if (variableSpec == NULL) + return false; + + if (accessSpecifier->arrayIndex != -1) { + if (variableSpec->type != MMS_ARRAY) + return false; + + if (accessSpecifier->arrayIndex >= variableSpec->typeSpec.array.elementCount) + return false; + + if (accessSpecifier->componentName != NULL) { + if (MmsVariableSpecification_getNamedVariableRecursive(variableSpec, accessSpecifier->componentName) == NULL) + return false; + } + } + + return true; +} + + static MmsNamedVariableList createNamedVariableList(MmsDevice* device, DefineNamedVariableListRequest_t* request, - char* variableListName) + char* variableListName, MmsError* mmsError) { MmsNamedVariableList namedVariableList = MmsNamedVariableList_create(variableListName, true); @@ -199,10 +239,11 @@ createNamedVariableList(MmsDevice* device, struct AlternateAccess__Member* alternateAccess = request->listOfVariable.list.array[i]->alternateAccess->list.array[0]; - if (alternateAccess->present == AlternateAccess__Member_PR_unnamed) + if ((alternateAccess->present == AlternateAccess__Member_PR_unnamed) + &&(alternateAccess->choice.unnamed->present == AlternateAccessSelection_PR_selectAlternateAccess) + && (alternateAccess->choice.unnamed->choice.selectAlternateAccess.accessSelection.present == + AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_index)) { - //TODO add checks! - asn_INTEGER2long(&(alternateAccess->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.index), &arrayIndex); @@ -215,6 +256,7 @@ createNamedVariableList(MmsDevice* device, else { MmsNamedVariableList_destroy(namedVariableList); namedVariableList = NULL; + *mmsError = MMS_ERROR_DEFINITION_INVALID_ADDRESS; break; } @@ -240,17 +282,28 @@ createNamedVariableList(MmsDevice* device, accessSpecifier.arrayIndex = arrayIndex; accessSpecifier.componentName = componentName; - MmsNamedVariableListEntry variable = - MmsNamedVariableListEntry_create(accessSpecifier); + // check if element exists + if (checkIfVariableExists(device, &accessSpecifier) == true) { - MmsNamedVariableList_addVariable(namedVariableList, variable); + MmsNamedVariableListEntry variable = + MmsNamedVariableListEntry_create(accessSpecifier); - free(domainId); - free(variableName); + MmsNamedVariableList_addVariable(namedVariableList, variable); + } + else { + MmsNamedVariableList_destroy(namedVariableList); + namedVariableList = NULL; + i = variableCount; // exit loop after freeing loop variables + *mmsError = MMS_ERROR_DEFINITION_OBJECT_UNDEFINED; + } + + GLOBAL_FREEMEM(domainId); + GLOBAL_FREEMEM(variableName); } else { MmsNamedVariableList_destroy(namedVariableList); namedVariableList = NULL; + *mmsError = MMS_ERROR_DEFINITION_INVALID_ADDRESS; break; } } @@ -286,60 +339,75 @@ mmsServer_handleDefineNamedVariableListRequest( MmsDomain* domain = MmsDevice_getDomain(device, domainName); - free(domainName); + GLOBAL_FREEMEM(domainName); if (domain == NULL) { mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); return; } - char* variableListName = createStringFromBuffer( - request->variableListName.choice.domainspecific.itemId.buf, - request->variableListName.choice.domainspecific.itemId.size); + if (LinkedList_size(domain->namedVariableLists) < CONFIG_MMS_MAX_NUMBER_OF_DOMAIN_SPECIFIC_DATA_SETS) { + char* variableListName = createStringFromBuffer( + request->variableListName.choice.domainspecific.itemId.buf, + request->variableListName.choice.domainspecific.itemId.size); - if (MmsDomain_getNamedVariableList(domain, variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); - } - else { - MmsNamedVariableList namedVariableList = createNamedVariableList(device, - request, variableListName); - - if (namedVariableList != NULL) { - MmsDomain_addNamedVariableList(domain, namedVariableList); - createDefineNamedVariableListResponse(invokeId, response); - } - else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + if (MmsDomain_getNamedVariableList(domain, variableListName) != NULL) { + mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + } + else { + MmsError mmsError; + + MmsNamedVariableList namedVariableList = createNamedVariableList(device, + request, variableListName, &mmsError); + + if (namedVariableList != NULL) { + MmsDomain_addNamedVariableList(domain, namedVariableList); + createDefineNamedVariableListResponse(invokeId, response); + } + else + mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); + } + + GLOBAL_FREEMEM(variableListName); } + else + mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); + - free(variableListName); } else if (request->variableListName.present == ObjectName_PR_aaspecific) { - char* variableListName = createStringFromBuffer( - request->variableListName.choice.aaspecific.buf, - request->variableListName.choice.aaspecific.size); + if (LinkedList_size(connection->namedVariableLists) < CONFIG_MMS_MAX_NUMBER_OF_ASSOCIATION_SPECIFIC_DATA_SETS) { + char* variableListName = createStringFromBuffer( + request->variableListName.choice.aaspecific.buf, + request->variableListName.choice.aaspecific.size); - if (MmsServerConnection_getNamedVariableList(connection, variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); - } - else { - MmsNamedVariableList namedVariableList = createNamedVariableList(device, - request, variableListName); - if (namedVariableList != NULL) { - MmsServerConnection_addNamedVariableList(connection, namedVariableList); - createDefineNamedVariableListResponse(invokeId, response); + if (MmsServerConnection_getNamedVariableList(connection, variableListName) != NULL) { + mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + } + else { + MmsError mmsError; + + MmsNamedVariableList namedVariableList = createNamedVariableList(device, + request, variableListName, &mmsError); + + if (namedVariableList != NULL) { + MmsServerConnection_addNamedVariableList(connection, namedVariableList); + createDefineNamedVariableListResponse(invokeId, response); + } + else + mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); } - else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); - } - free(variableListName); + GLOBAL_FREEMEM(variableListName); + } + else + mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED); asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); } @@ -371,7 +439,7 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, varListResponse->listOfVariable.list.size = variableCount; varListResponse->listOfVariable.list.array = (struct GetNamedVariableListAttributesResponse__listOfVariable__Member**) - calloc(variableCount, sizeof(void*)); + GLOBAL_CALLOC(variableCount, sizeof(void*)); LinkedList variable = LinkedList_getNext(variables); @@ -380,7 +448,7 @@ createGetNamedVariableListAttributesResponse(int invokeId, ByteBuffer* response, MmsNamedVariableListEntry variableEntry = (MmsNamedVariableListEntry) variable->data; varListResponse->listOfVariable.list.array[i] = (struct GetNamedVariableListAttributesResponse__listOfVariable__Member*) - calloc(1, sizeof(struct GetNamedVariableListAttributesResponse__listOfVariable__Member)); + GLOBAL_CALLOC(1, sizeof(struct GetNamedVariableListAttributesResponse__listOfVariable__Member)); varListResponse->listOfVariable.list.array[i]->variableSpecification.present = VariableSpecification_PR_name; @@ -454,8 +522,8 @@ mmsServer_handleGetNamedVariableListAttributesRequest( mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); - free(domainName); - free(itemName); + GLOBAL_FREEMEM(domainName); + GLOBAL_FREEMEM(itemName); } else { mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index 4a9fea2c..80399ec7 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -78,7 +78,7 @@ addNamedVariableValue(MmsVariableSpecification* namedVariable, MmsServerConnecti MmsValue_setElement(value, i, element); - free(newNameIdStr); + GLOBAL_FREEMEM(newNameIdStr); } } } diff --git a/src/mms/iso_mms/server/mms_server.c b/src/mms/iso_mms/server/mms_server.c index 3e9fa375..42550b3d 100644 --- a/src/mms/iso_mms/server/mms_server.c +++ b/src/mms/iso_mms/server/mms_server.c @@ -49,7 +49,7 @@ createValueCaches(MmsDevice* device) MmsServer MmsServer_create(IsoServer isoServer, MmsDevice* device) { - MmsServer self = (MmsServer) malloc(sizeof(struct sMmsServer)); + MmsServer self = (MmsServer) GLOBAL_MALLOC(sizeof(struct sMmsServer)); memset(self, 0, sizeof(struct sMmsServer)); @@ -58,9 +58,12 @@ MmsServer_create(IsoServer isoServer, MmsDevice* device) self->openConnections = Map_create(); self->valueCaches = createValueCaches(device); self->isLocked = false; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) self->modelMutex = Semaphore_create(1); IsoServer_setUserLock(isoServer, self->modelMutex); +#endif return self; } @@ -68,24 +71,35 @@ MmsServer_create(IsoServer isoServer, MmsDevice* device) void MmsServer_lockModel(MmsServer self) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->modelMutex); +#endif } void MmsServer_unlockModel(MmsServer self) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->modelMutex); +#endif } void -MmsServer_installReadHandler(MmsServer self, ReadVariableHandler readHandler, void* parameter) +MmsServer_installReadHandler(MmsServer self, MmsReadVariableHandler readHandler, void* parameter) { self->readHandler = readHandler; self->readHandlerParameter = parameter; } void -MmsServer_installWriteHandler(MmsServer self, WriteVariableHandler writeHandler, void* parameter) +MmsServer_installReadAccessHandler(MmsServer self, MmsReadAccessHandler readAccessHandler, void* parameter) +{ + self->readAccessHandler = readAccessHandler; + self->readAccessHandlerParameter = parameter; +} + +void +MmsServer_installWriteHandler(MmsServer self, MmsWriteVariableHandler writeHandler, void* parameter) { self->writeHandler = writeHandler; self->writeHandlerParameter = parameter; @@ -124,8 +138,12 @@ MmsServer_destroy(MmsServer self) { Map_deleteDeep(self->openConnections, false, closeConnection); Map_deleteDeep(self->valueCaches, false, (void (*) (void*)) deleteSingleCache); + +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_destroy(self->modelMutex); - free(self); +#endif + + GLOBAL_FREEMEM(self); } MmsValue* @@ -177,11 +195,20 @@ mmsServer_setValue(MmsServer self, MmsDomain* domain, char* itemId, MmsValue* va return indication; } + MmsValue* mmsServer_getValue(MmsServer self, MmsDomain* domain, char* itemId, MmsServerConnection* connection) { MmsValue* value = NULL; + if (self->readAccessHandler != NULL) { + MmsDataAccessError accessError = + self->readAccessHandler(self->readAccessHandlerParameter, domain, itemId, connection); + + if (accessError != DATA_ACCESS_ERROR_SUCCESS) + return NULL; + } + value = MmsServer_getValueFromCache(self, domain, itemId); if (value == NULL) @@ -233,6 +260,7 @@ isoConnectionIndicationHandler(IsoConnectionIndication indication, } } +#if (CONFIG_MMS_THREADLESS_STACK != 1) void MmsServer_startListening(MmsServer server, int tcpPort) { @@ -246,3 +274,31 @@ MmsServer_stopListening(MmsServer server) { IsoServer_stopListening(server->isoServer); } +#endif /* (CONFIG_MMS_THREADLESS_STACK != 1)*/ + +void +MmsServer_startListeningThreadless(MmsServer self, int tcpPort) +{ + IsoServer_setConnectionHandler(self->isoServer, isoConnectionIndicationHandler, (void*) self); + IsoServer_setTcpPort(self->isoServer, tcpPort); + IsoServer_startListeningThreadless(self->isoServer); +} + +int +MmsServer_waitReady(MmsServer self, unsigned int timeoutMs) +{ + return IsoServer_waitReady(self->isoServer, timeoutMs); +} + +void +MmsServer_handleIncomingMessages(MmsServer self) +{ + IsoServer_processIncomingMessages(self->isoServer); +} + +void +MmsServer_stopListeningThreadless(MmsServer self) +{ + IsoServer_stopListeningThreadless(self->isoServer); +} + diff --git a/src/mms/iso_mms/server/mms_server_common.c b/src/mms/iso_mms/server/mms_server_common.c index c34220aa..31348c57 100644 --- a/src/mms/iso_mms/server/mms_server_common.c +++ b/src/mms/iso_mms/server/mms_server_common.c @@ -34,7 +34,7 @@ mmsServer_write_out(const void *buffer, size_t size, void *app_key) MmsPdu_t* mmsServer_createConfirmedResponse(uint32_t invokeId) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) calloc(1, sizeof(MmsPdu_t)); + MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); mmsPdu->present = MmsPdu_PR_confirmedResponsePdu; @@ -48,7 +48,7 @@ mmsServer_createConfirmedResponse(uint32_t invokeId) void mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) calloc(1, sizeof(MmsPdu_t)); + MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); mmsPdu->present = MmsPdu_PR_confirmedErrorPDU; asn_long2INTEGER(&(mmsPdu->choice.confirmedErrorPDU.invokeID), @@ -87,6 +87,18 @@ mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsEr 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; @@ -105,6 +117,12 @@ mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsEr 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); + } der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response); @@ -188,7 +206,7 @@ mmsServer_deleteVariableList(LinkedList namedVariableLists, char* variableListNa if (strcmp(MmsNamedVariableList_getName(varList), variableListName) == 0) { previousElement->next = element->next; - free(element); + GLOBAL_FREEMEM(element); MmsNamedVariableList_destroy(varList); break; diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index 66cbff4f..3292d6c3 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -38,12 +38,12 @@ void mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) calloc(1, sizeof(MmsPdu_t)); + MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); mmsPdu->present = MmsPdu_PR_rejectPDU; if (invokeId != NULL) { - mmsPdu->choice.rejectPDU.originalInvokeID = (Unsigned32_t*) calloc(1, sizeof(Unsigned32_t)); + mmsPdu->choice.rejectPDU.originalInvokeID = (Unsigned32_t*) GLOBAL_CALLOC(1, sizeof(Unsigned32_t)); asn_long2INTEGER(mmsPdu->choice.rejectPDU.originalInvokeID, *invokeId); } @@ -291,7 +291,7 @@ MmsServerConnection_init(MmsServerConnection* connection, MmsServer server, IsoC MmsServerConnection* self; if (connection == NULL) - self = (MmsServerConnection*) calloc(1, sizeof(MmsServerConnection)); + self = (MmsServerConnection*) GLOBAL_CALLOC(1, sizeof(MmsServerConnection)); else self = connection; @@ -321,7 +321,7 @@ MmsServerConnection_destroy(MmsServerConnection* self) #endif LinkedList_destroyDeep(self->namedVariableLists, (LinkedListValueDeleteFunction) MmsNamedVariableList_destroy); - free(self); + GLOBAL_FREEMEM(self); } bool diff --git a/src/mms/iso_mms/server/mms_value_cache.c b/src/mms/iso_mms/server/mms_value_cache.c index ae584eeb..161ae9b9 100644 --- a/src/mms/iso_mms/server/mms_value_cache.c +++ b/src/mms/iso_mms/server/mms_value_cache.c @@ -40,7 +40,7 @@ typedef struct sMmsValueCacheEntry { MmsValueCache MmsValueCache_create(MmsDomain* domain) { - MmsValueCache self = (MmsValueCache) calloc(1, sizeof(struct sMmsValueCache)); + MmsValueCache self = (MmsValueCache) GLOBAL_CALLOC(1, sizeof(struct sMmsValueCache)); self->domain = domain; @@ -55,7 +55,7 @@ MmsValueCache_insertValue(MmsValueCache self, char* itemId, MmsValue* value) MmsVariableSpecification* typeSpec = MmsDomain_getNamedVariable(self->domain, itemId); if (typeSpec != NULL) { - MmsValueCacheEntry* cacheEntry = (MmsValueCacheEntry*) malloc(sizeof(MmsValueCacheEntry)); + MmsValueCacheEntry* cacheEntry = (MmsValueCacheEntry*) GLOBAL_MALLOC(sizeof(MmsValueCacheEntry)); cacheEntry->value = value; cacheEntry->typeSpec = typeSpec; @@ -135,7 +135,7 @@ MmsValueCache_lookupValue(MmsValueCache self, char* itemId) value = searchCacheForValue(self, itemId, parentItemId); } - free(itemIdCopy); + GLOBAL_FREEMEM(itemIdCopy); } if (cacheEntry != NULL) @@ -149,7 +149,7 @@ cacheEntryDelete(MmsValueCacheEntry* entry) { if (entry != NULL) { MmsValue_delete(entry->value); - free(entry); + GLOBAL_FREEMEM(entry); } } @@ -157,5 +157,5 @@ void MmsValueCache_destroy(MmsValueCache self) { Map_deleteDeep(self->map, true, (void (*) (void*)) cacheEntryDelete); - free(self); + GLOBAL_FREEMEM(self); } diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index 26deacdb..afd531a7 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -47,13 +47,13 @@ mmsServer_createMmsWriteResponse(MmsServerConnection* connection, writeResponse->list.count = numberOfItems; writeResponse->list.size = numberOfItems; - writeResponse->list.array = (struct WriteResponse__Member**) calloc(numberOfItems, + writeResponse->list.array = (struct WriteResponse__Member**) GLOBAL_CALLOC(numberOfItems, sizeof(struct WriteResponse__Member*)); int i; for (i = 0; i < numberOfItems; i++) { - writeResponse->list.array[i] = (struct WriteResponse__Member*) calloc(1, sizeof(struct WriteResponse__Member)); + writeResponse->list.array[i] = (struct WriteResponse__Member*) GLOBAL_CALLOC(1, sizeof(struct WriteResponse__Member)); if (accessResults[i] == DATA_ACCESS_ERROR_SUCCESS) writeResponse->list.array[i]->present = WriteResponse__Member_PR_success; @@ -72,13 +72,13 @@ mmsServer_createMmsWriteResponse(MmsServerConnection* connection, void -MmsServerConnection_sendWriteResponse(MmsServerConnection* self, uint32_t invokeId, MmsDataAccessError indication) +MmsServerConnection_sendWriteResponse(MmsServerConnection* self, uint32_t invokeId, MmsDataAccessError indication, bool handlerMode) { ByteBuffer* response = ByteBuffer_create(NULL, self->maxPduSize); mmsServer_createMmsWriteResponse(self, invokeId, response, 1, &indication); - IsoConnection_sendMessage(self->isoConnection, response, false); + IsoConnection_sendMessage(self->isoConnection, response, handlerMode); ByteBuffer_destroy(response); } @@ -151,7 +151,7 @@ mmsServer_handleWriteRequest( domain = MmsDevice_getDomain(device, domainIdStr); - free(domainIdStr); + GLOBAL_FREEMEM(domainIdStr); if (domain == NULL) { accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; @@ -180,7 +180,7 @@ mmsServer_handleWriteRequest( } if (variable == NULL) { - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); accessResults[i] = DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; continue; } @@ -189,13 +189,13 @@ mmsServer_handleWriteRequest( if (alternateAccess != NULL) { if (variable->type != MMS_ARRAY) { - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; } if (!mmsServer_isIndexAccess(alternateAccess)) { - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ACCESS_UNSUPPORTED; continue; } @@ -206,7 +206,7 @@ mmsServer_handleWriteRequest( MmsValue* value = mmsMsg_parseDataElement(dataElement); if (value == NULL) { - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; } @@ -219,7 +219,7 @@ mmsServer_handleWriteRequest( MmsValue* cachedArray = MmsServer_getValueFromCache(connection->server, domain, nameIdStr); if (cachedArray == NULL) { - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); MmsValue_delete(value); accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; @@ -230,20 +230,20 @@ mmsServer_handleWriteRequest( MmsValue* elementValue = MmsValue_getElement(cachedArray, index); if (elementValue == NULL) { - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); MmsValue_delete(value); accessResults[i] = DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; continue; } if (MmsValue_update(elementValue, value) == false) { - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); MmsValue_delete(value); accessResults[i] = DATA_ACCESS_ERROR_TYPE_INCONSISTENT; continue; } - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); MmsValue_delete(value); accessResults[i] = DATA_ACCESS_ERROR_SUCCESS; continue; @@ -260,7 +260,7 @@ mmsServer_handleWriteRequest( MmsValue_delete(value); - free(nameIdStr); + GLOBAL_FREEMEM(nameIdStr); } if (sendResponse) { diff --git a/src/mms/iso_server/iso_connection.c b/src/mms/iso_server/iso_connection.c index f3539b71..19201cd5 100644 --- a/src/mms/iso_server/iso_connection.c +++ b/src/mms/iso_server/iso_connection.c @@ -24,15 +24,14 @@ #include "libiec61850_platform_includes.h" #include "stack_config.h" -#include "byte_stream.h" #include "buffer_chain.h" #include "cotp.h" #include "iso_session.h" #include "iso_presentation.h" #include "acse.h" #include "iso_server.h" -#include "socket.h" -#include "thread.h" +#include "hal_socket.h" +#include "hal_thread.h" #include "iso_server_private.h" @@ -47,6 +46,8 @@ #define RECEIVE_BUF_SIZE CONFIG_MMS_MAXIMUM_PDU_SIZE + 100 #define SEND_BUF_SIZE CONFIG_MMS_MAXIMUM_PDU_SIZE + 100 +#define TPKT_RFC1006_HEADER_SIZE 4 + #define ISO_CON_STATE_RUNNING 1 #define ISO_CON_STATE_STOPPED 0 @@ -54,353 +55,448 @@ struct sIsoConnection { uint8_t* receiveBuffer; uint8_t* sendBuffer; + + ByteBuffer rcvBuffer; + + uint8_t* cotpReadBuf; + uint8_t* cotpWriteBuf; + ByteBuffer cotpReadBuffer; + ByteBuffer cotpWriteBuffer; + MessageReceivedHandler msgRcvdHandler; - IsoServer isoServer; void* msgRcvdHandlerParameter; + + IsoServer isoServer; + Socket socket; int state; IsoSession* session; IsoPresentation* presentation; CotpConnection* cotpConnection; + + AcseConnection* acseConnection; + char* clientAddress; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) Thread thread; Semaphore conMutex; - - void* securityToken; + bool isInsideCallback; +#endif }; static void -handleTcpConnection(IsoConnection self) +finalizeIsoConnection(IsoConnection self) { if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: connection %p started\n", self); + printf("ISO_SERVER: finalizeIsoConnection --> close transport connection\n"); - CotpIndication cotpIndication; + IsoServer_closeConnection(self->isoServer, self); + if (self->socket != NULL) + Socket_destroy(self->socket); - IsoSessionIndication sIndication; + GLOBAL_FREEMEM(self->session); + GLOBAL_FREEMEM(self->presentation); + AcseConnection_destroy(self->acseConnection); + GLOBAL_FREEMEM(self->acseConnection); - AcseIndication aIndication; - AcseConnection acseConnection; + GLOBAL_FREEMEM(self->cotpReadBuf); + GLOBAL_FREEMEM(self->cotpWriteBuf); - ByteBuffer receiveBuffer; + GLOBAL_FREEMEM(self->cotpConnection); - self->cotpConnection = (CotpConnection*) calloc(1, sizeof(CotpConnection)); - CotpConnection_init(self->cotpConnection, self->socket, &receiveBuffer); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_destroy(self->conMutex); +#endif - self->session = (IsoSession*) calloc(1, sizeof(IsoSession)); - IsoSession_init(self->session); + GLOBAL_FREEMEM(self->receiveBuffer); + GLOBAL_FREEMEM(self->sendBuffer); + GLOBAL_FREEMEM(self->clientAddress); + IsoServer isoServer = self->isoServer; + GLOBAL_FREEMEM(self); + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: connection %p closed\n", self); - self->presentation = (IsoPresentation*) calloc(1, sizeof(IsoPresentation)); - IsoPresentation_init(self->presentation); + private_IsoServer_decreaseConnectionCounter(isoServer); +} - AcseConnection_init(&acseConnection, IsoServer_getAuthenticator(self->isoServer), - IsoServer_getAuthenticatorParameter(self->isoServer)); +void +IsoConnection_addHandleSet(const IsoConnection self, HandleSet handles) +{ + Handleset_addSocket(handles, self->socket); +} - while (self->msgRcvdHandlerParameter == NULL) - Thread_sleep(1); +void +IsoConnection_handleTcpConnection(IsoConnection self) +{ + assert(self->msgRcvdHandler != NULL); - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: IsoConnection: Start to handle connection for client %s\n", self->clientAddress); + TpktState tpktState = CotpConnection_readToTpktBuffer(self->cotpConnection); - while (self->state == ISO_CON_STATE_RUNNING) { - ByteBuffer_wrap(&receiveBuffer, self->receiveBuffer, 0, RECEIVE_BUF_SIZE); + if (tpktState == TPKT_ERROR) + self->state = ISO_CON_STATE_STOPPED; - cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); + if (tpktState != TPKT_PACKET_COMPLETE) + return; - switch (cotpIndication) { - case CONNECT_INDICATION: - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: COTP connection indication\n"); + CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); + + switch (cotpIndication) { + case COTP_MORE_FRAGMENTS_FOLLOW: + return; + case COTP_CONNECT_INDICATION: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: COTP connection indication\n"); - Semaphore_wait(self->conMutex); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->conMutex); +#endif - CotpConnection_sendConnectionResponseMessage(self->cotpConnection); + CotpConnection_sendConnectionResponseMessage(self->cotpConnection); - Semaphore_post(self->conMutex); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->conMutex); +#endif - break; - case DATA_INDICATION: - { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: COTP data indication\n"); + break; + case COTP_DATA_INDICATION: + { + ByteBuffer* cotpPayload = CotpConnection_getPayload(self->cotpConnection); - ByteBuffer* cotpPayload = CotpConnection_getPayload(self->cotpConnection); + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: COTP data indication (payload size = %i)\n", cotpPayload->size); - sIndication = IsoSession_parseMessage(self->session, cotpPayload); + IsoSessionIndication sIndication = IsoSession_parseMessage(self->session, cotpPayload); - ByteBuffer* sessionUserData = IsoSession_getUserData(self->session); + ByteBuffer* sessionUserData = IsoSession_getUserData(self->session); - switch (sIndication) { - case SESSION_CONNECT: + switch (sIndication) { + case SESSION_CONNECT: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: session connect indication\n"); + + if (IsoPresentation_parseConnect(self->presentation, sessionUserData)) { if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: session connect indication\n"); + printf("ISO_SERVER: iso_connection: presentation ok\n"); - if (IsoPresentation_parseConnect(self->presentation, sessionUserData)) { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: presentation ok\n"); + ByteBuffer* acseBuffer = &(self->presentation->nextPayload); - ByteBuffer* acseBuffer = &(self->presentation->nextPayload); + AcseIndication aIndication = AcseConnection_parseMessage(self->acseConnection, acseBuffer); - aIndication = AcseConnection_parseMessage(&acseConnection, acseBuffer); + if (aIndication == ACSE_ASSOCIATE) { - self->securityToken = acseConnection.securityToken; +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_wait(self->conMutex); +#endif - if (aIndication == ACSE_ASSOCIATE) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: cotp_server: acse associate\n"); - Semaphore_wait(self->conMutex); + ByteBuffer mmsRequest; - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: cotp_server: acse associate\n"); + ByteBuffer_wrap(&mmsRequest, self->acseConnection->userDataBuffer, + self->acseConnection->userDataBufferSize, self->acseConnection->userDataBufferSize); + ByteBuffer mmsResponseBuffer; /* new */ - ByteBuffer mmsRequest; + ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); - ByteBuffer_wrap(&mmsRequest, acseConnection.userDataBuffer, - acseConnection.userDataBufferSize, acseConnection.userDataBufferSize); - ByteBuffer mmsResponseBuffer; /* new */ + if (self->msgRcvdHandler != NULL) { - ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->isInsideCallback = true; +#endif self->msgRcvdHandler(self->msgRcvdHandlerParameter, &mmsRequest, &mmsResponseBuffer); - struct sBufferChain mmsBufferPartStruct; - BufferChain mmsBufferPart = &mmsBufferPartStruct; +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->isInsideCallback = false; +#endif + } - BufferChain_init(mmsBufferPart, mmsResponseBuffer.size, mmsResponseBuffer.size, NULL, - self->sendBuffer); + struct sBufferChain mmsBufferPartStruct; + BufferChain mmsBufferPart = &mmsBufferPartStruct; - if (mmsResponseBuffer.size > 0) { - if (DEBUG_ISO_SERVER) - printf("iso_connection: application payload size: %i\n", - mmsResponseBuffer.size); + BufferChain_init(mmsBufferPart, mmsResponseBuffer.size, mmsResponseBuffer.size, NULL, + self->sendBuffer); - struct sBufferChain acseBufferPartStruct; - BufferChain acseBufferPart = &acseBufferPartStruct; + if (mmsResponseBuffer.size > 0) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: application payload size: %i\n", + mmsResponseBuffer.size); - acseBufferPart->buffer = self->sendBuffer + mmsBufferPart->length; - acseBufferPart->partMaxLength = SEND_BUF_SIZE - mmsBufferPart->length; + struct sBufferChain acseBufferPartStruct; + BufferChain acseBufferPart = &acseBufferPartStruct; - AcseConnection_createAssociateResponseMessage(&acseConnection, - ACSE_RESULT_ACCEPT, acseBufferPart, mmsBufferPart); + acseBufferPart->buffer = self->sendBuffer + mmsBufferPart->length; + acseBufferPart->partMaxLength = SEND_BUF_SIZE - mmsBufferPart->length; - struct sBufferChain presentationBufferPartStruct; - BufferChain presentationBufferPart = &presentationBufferPartStruct; + AcseConnection_createAssociateResponseMessage(self->acseConnection, + ACSE_RESULT_ACCEPT, acseBufferPart, mmsBufferPart); - presentationBufferPart->buffer = self->sendBuffer + acseBufferPart->length; - presentationBufferPart->partMaxLength = SEND_BUF_SIZE - acseBufferPart->length; + struct sBufferChain presentationBufferPartStruct; + BufferChain presentationBufferPart = &presentationBufferPartStruct; - IsoPresentation_createCpaMessage(self->presentation, presentationBufferPart, - acseBufferPart); + presentationBufferPart->buffer = self->sendBuffer + acseBufferPart->length; + presentationBufferPart->partMaxLength = SEND_BUF_SIZE - acseBufferPart->length; - struct sBufferChain sessionBufferPartStruct; - BufferChain sessionBufferPart = &sessionBufferPartStruct; - sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; - sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; + IsoPresentation_createCpaMessage(self->presentation, presentationBufferPart, + acseBufferPart); - IsoSession_createAcceptSpdu(self->session, sessionBufferPart, presentationBufferPart); + struct sBufferChain sessionBufferPartStruct; + BufferChain sessionBufferPart = &sessionBufferPartStruct; + sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; + sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; - CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); - } - else { - if (DEBUG_ISO_SERVER) - printf( - "ISO_SERVER: iso_connection: association error. No response from application!\n"); - } + IsoSession_createAcceptSpdu(self->session, sessionBufferPart, presentationBufferPart); - Semaphore_post(self->conMutex); + CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); } else { if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: acse association failed\n"); - self->state = ISO_CON_STATE_STOPPED; + printf("ISO_SERVER: iso_connection: association error. No response from application!\n"); } +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->conMutex); +#endif } - break; - case SESSION_DATA: - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: session data indication\n"); - - if (!IsoPresentation_parseUserData(self->presentation, sessionUserData)) { + else { if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: cotp_server: presentation error\n"); + printf("ISO_SERVER: iso_connection: acse association failed\n"); self->state = ISO_CON_STATE_STOPPED; - break; } - if (self->presentation->nextContextId == self->presentation->mmsContextId) { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: mms message\n"); - - ByteBuffer* mmsRequest = &(self->presentation->nextPayload); - - ByteBuffer mmsResponseBuffer; - - IsoServer_userLock(self->isoServer); - Semaphore_wait(self->conMutex); - - ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); + } + break; + case SESSION_DATA: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: session data indication\n"); - self->msgRcvdHandler(self->msgRcvdHandlerParameter, - mmsRequest, &mmsResponseBuffer); + if (!IsoPresentation_parseUserData(self->presentation, sessionUserData)) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: presentation layer error\n"); + self->state = ISO_CON_STATE_STOPPED; + break; + } - if (mmsResponseBuffer.size > 0) { + if (self->presentation->nextContextId == self->presentation->mmsContextId) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: mms message\n"); + ByteBuffer* mmsRequest = &(self->presentation->nextPayload); - struct sBufferChain mmsBufferPartStruct; - BufferChain mmsBufferPart = &mmsBufferPartStruct; + ByteBuffer mmsResponseBuffer; - BufferChain_init(mmsBufferPart, mmsResponseBuffer.size, - mmsResponseBuffer.size, NULL, self->sendBuffer); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + IsoServer_userLock(self->isoServer); + Semaphore_wait(self->conMutex); +#endif - struct sBufferChain presentationBufferPartStruct; - BufferChain presentationBufferPart = &presentationBufferPartStruct; - presentationBufferPart->buffer = self->sendBuffer + mmsBufferPart->length; - presentationBufferPart->partMaxLength = SEND_BUF_SIZE - mmsBufferPart->length; + ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); - IsoPresentation_createUserData(self->presentation, - presentationBufferPart, mmsBufferPart); + if (self->msgRcvdHandler != NULL) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->isInsideCallback = true; +#endif - struct sBufferChain sessionBufferPartStruct; - BufferChain sessionBufferPart = &sessionBufferPartStruct; - sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; - sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; - - IsoSession_createDataSpdu(self->session, sessionBufferPart, presentationBufferPart); - - CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); - } + self->msgRcvdHandler(self->msgRcvdHandlerParameter, + mmsRequest, &mmsResponseBuffer); - Semaphore_post(self->conMutex); - IsoServer_userUnlock(self->isoServer); - } - else { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: unknown presentation layer context!"); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->isInsideCallback = false; +#endif } - break; - - case SESSION_FINISH: - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: session finish indication\n"); - - if (IsoPresentation_parseUserData(self->presentation, sessionUserData)) { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: iso_connection: presentation ok\n"); + /* send a response if required */ + if (mmsResponseBuffer.size > 0) { - struct sBufferChain acseBufferPartStruct; - BufferChain acseBufferPart = &acseBufferPartStruct; - acseBufferPart->buffer = self->sendBuffer; - acseBufferPart->partMaxLength = SEND_BUF_SIZE; + struct sBufferChain mmsBufferPartStruct; + BufferChain mmsBufferPart = &mmsBufferPartStruct; - AcseConnection_createReleaseResponseMessage(&acseConnection, acseBufferPart); + BufferChain_init(mmsBufferPart, mmsResponseBuffer.size, + mmsResponseBuffer.size, NULL, self->sendBuffer); struct sBufferChain presentationBufferPartStruct; BufferChain presentationBufferPart = &presentationBufferPartStruct; - presentationBufferPart->buffer = self->sendBuffer + acseBufferPart->length; - presentationBufferPart->partMaxLength = SEND_BUF_SIZE - acseBufferPart->length; + presentationBufferPart->buffer = self->sendBuffer + mmsBufferPart->length; + presentationBufferPart->partMaxLength = SEND_BUF_SIZE - mmsBufferPart->length; - IsoPresentation_createUserDataACSE(self->presentation, presentationBufferPart, acseBufferPart); + IsoPresentation_createUserData(self->presentation, + presentationBufferPart, mmsBufferPart); struct sBufferChain sessionBufferPartStruct; BufferChain sessionBufferPart = &sessionBufferPartStruct; sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; - IsoSession_createDisconnectSpdu(self->session, sessionBufferPart, presentationBufferPart); + IsoSession_createDataSpdu(self->session, sessionBufferPart, presentationBufferPart); CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); } - self->state = ISO_CON_STATE_STOPPED; +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_post(self->conMutex); + IsoServer_userUnlock(self->isoServer); +#endif + } + else { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: unknown presentation layer context!"); + } - break; + break; - case SESSION_ABORT: - self->state = ISO_CON_STATE_STOPPED; - break; + case SESSION_FINISH: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: session finish indication\n"); - case SESSION_ERROR: - self->state = ISO_CON_STATE_STOPPED; - break; + if (IsoPresentation_parseUserData(self->presentation, sessionUserData)) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: presentation ok\n"); - default: /* illegal state */ - self->state = ISO_CON_STATE_STOPPED; - break; - } - } - break; - case ERROR: - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: Connection closed\n"); - self->state = ISO_CON_STATE_STOPPED; - break; - default: - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: COTP Unknown Indication: %i\n", cotpIndication); - self->state = ISO_CON_STATE_STOPPED; - break; - } - } + struct sBufferChain acseBufferPartStruct; + BufferChain acseBufferPart = &acseBufferPartStruct; + acseBufferPart->buffer = self->sendBuffer; + acseBufferPart->partMaxLength = SEND_BUF_SIZE; - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: Connection handling loop finished --> close transport connection\n"); + AcseConnection_createReleaseResponseMessage(self->acseConnection, acseBufferPart); - IsoServer_closeConnection(self->isoServer, self); + struct sBufferChain presentationBufferPartStruct; + BufferChain presentationBufferPart = &presentationBufferPartStruct; + presentationBufferPart->buffer = self->sendBuffer + acseBufferPart->length; + presentationBufferPart->partMaxLength = SEND_BUF_SIZE - acseBufferPart->length; - if (self->socket != NULL) - Socket_destroy(self->socket); + IsoPresentation_createUserDataACSE(self->presentation, presentationBufferPart, acseBufferPart); - free(self->session); - free(self->presentation); + struct sBufferChain sessionBufferPartStruct; + BufferChain sessionBufferPart = &sessionBufferPartStruct; + sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; + sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; - AcseConnection_destroy(&acseConnection); + IsoSession_createDisconnectSpdu(self->session, sessionBufferPart, presentationBufferPart); - CotpConnection_destroy(self->cotpConnection); - free(self->cotpConnection); + CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); + } - Semaphore_destroy(self->conMutex); + self->state = ISO_CON_STATE_STOPPED; - free(self->receiveBuffer); - free(self->sendBuffer); - free(self->clientAddress); + break; - IsoServer isoServer = self->isoServer; + case SESSION_ABORT: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: session abort indication\n"); + self->state = ISO_CON_STATE_STOPPED; + break; - free(self); + case SESSION_ERROR: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: session error indication\n"); + self->state = ISO_CON_STATE_STOPPED; + break; - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: connection %p closed\n", self); + default: /* illegal state */ + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: iso_connection: session illegal state\n"); - private_IsoServer_decreaseConnectionCounter(isoServer); + self->state = ISO_CON_STATE_STOPPED; + break; + } + + CotpConnection_resetPayload(self->cotpConnection); + } + break; + case COTP_ERROR: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: Connection closed\n"); + self->state = ISO_CON_STATE_STOPPED; + break; + default: + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: COTP unknown indication: %i\n", cotpIndication); + self->state = ISO_CON_STATE_STOPPED; + break; + } } +#if (CONFIG_MMS_SINGLE_THREADED == 0) +static void +handleTcpConnection(void* parameter) +{ + IsoConnection self = (IsoConnection) parameter; + + while(self->state == ISO_CON_STATE_RUNNING) { + IsoConnection_handleTcpConnection(self); + Thread_sleep(1); + } + + finalizeIsoConnection(self); +} +#endif /* (CONFIG_MMS_SINGLE_THREADED == 0) */ + IsoConnection IsoConnection_create(Socket socket, IsoServer isoServer) { - IsoConnection self = (IsoConnection) calloc(1, sizeof(struct sIsoConnection)); + IsoConnection self = (IsoConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoConnection)); self->socket = socket; - self->receiveBuffer = (uint8_t*) malloc(RECEIVE_BUF_SIZE); - self->sendBuffer = (uint8_t*) malloc(SEND_BUF_SIZE); + self->receiveBuffer = (uint8_t*) GLOBAL_MALLOC(RECEIVE_BUF_SIZE); + self->sendBuffer = (uint8_t*) GLOBAL_MALLOC(SEND_BUF_SIZE); self->msgRcvdHandler = NULL; self->msgRcvdHandlerParameter = NULL; self->isoServer = isoServer; self->state = ISO_CON_STATE_RUNNING; self->clientAddress = Socket_getPeerAddress(self->socket); - self->thread = Thread_create((ThreadExecutionFunction) handleTcpConnection, self, true); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->isInsideCallback = false; self->conMutex = Semaphore_create(1); +#endif - Thread_start(self->thread); + ByteBuffer_wrap(&(self->rcvBuffer), self->receiveBuffer, 0, RECEIVE_BUF_SIZE); + + self->cotpReadBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + self->cotpWriteBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + + ByteBuffer_wrap(&(self->cotpReadBuffer), self->cotpReadBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + ByteBuffer_wrap(&(self->cotpWriteBuffer), self->cotpWriteBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); + + self->cotpConnection = (CotpConnection*) GLOBAL_CALLOC(1, sizeof(CotpConnection)); + CotpConnection_init(self->cotpConnection, self->socket, &(self->rcvBuffer), &(self->cotpReadBuffer), &(self->cotpWriteBuffer)); + + self->session = (IsoSession*) GLOBAL_CALLOC(1, sizeof(IsoSession)); + IsoSession_init(self->session); + + self->presentation = (IsoPresentation*) GLOBAL_CALLOC(1, sizeof(IsoPresentation)); + IsoPresentation_init(self->presentation); + + self->acseConnection = (AcseConnection*) GLOBAL_CALLOC(1, sizeof(AcseConnection)); + AcseConnection_init(self->acseConnection, IsoServer_getAuthenticator(self->isoServer), + IsoServer_getAuthenticatorParameter(self->isoServer)); if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: new iso connection thread started\n"); + printf("ISO_SERVER: IsoConnection: Start to handle connection for client %s\n", self->clientAddress); + +#if (CONFIG_MMS_SINGLE_THREADED == 0) +#if (CONFIG_MMS_THREADLESS_STACK == 0) + self->thread = Thread_create((ThreadExecutionFunction) handleTcpConnection, self, true); + + Thread_start(self->thread); +#endif +#endif return self; } +void +IsoConnection_destroy(IsoConnection self) +{ + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: destroy called for IsoConnection.\n"); + + finalizeIsoConnection(self); +} + char* IsoConnection_getPeerAddress(IsoConnection self) { @@ -416,8 +512,10 @@ IsoConnection_sendMessage(IsoConnection self, ByteBuffer* message, bool handlerM return; } - if (!handlerMode) +#if (CONFIG_MMS_THREADLESS_STACK != 1) + if (self->isInsideCallback == false) Semaphore_wait(self->conMutex); +#endif struct sBufferChain payloadBufferStruct; BufferChain payloadBuffer = &payloadBufferStruct; @@ -446,14 +544,16 @@ IsoConnection_sendMessage(IsoConnection self, ByteBuffer* message, bool handlerM indication = CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer); if (DEBUG_ISO_SERVER) { - if (indication != OK) + if (indication != COTP_OK) printf("ISO_SERVER: IsoConnection_sendMessage failed!\n"); else printf("ISO_SERVER: IsoConnection_sendMessage success!\n"); } - if (!handlerMode) +#if (CONFIG_MMS_THREADLESS_STACK != 1) + if (self->isInsideCallback == false) Semaphore_post(self->conMutex); +#endif } void @@ -479,6 +579,14 @@ IsoConnection_installListener(IsoConnection self, MessageReceivedHandler handler void* IsoConnection_getSecurityToken(IsoConnection self) { - return self->securityToken; + return self->acseConnection->securityToken; } +bool +IsoConnection_isRunning(IsoConnection self) +{ + if (self->state == ISO_CON_STATE_RUNNING) + return true; + else + return false; +} diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index dcb7e445..7bdbbba1 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -35,7 +35,7 @@ #include "mms_server_connection.h" -#include "thread.h" +#include "hal_thread.h" #include "iso_server.h" @@ -57,23 +57,47 @@ struct sIsoServer { AcseAuthenticator authenticator; void* authenticatorParameter; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Thread serverThread; +#endif + Socket serverSocket; int tcpPort; char* localIpAddress; #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 +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore userLock; Semaphore connectionCounterMutex; +#endif int connectionCounter; }; +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) +static inline void +lockClientConnections(IsoServer self) +{ + Semaphore_wait(self->openClientConnectionsMutex); +} + +static inline void +unlockClientConnections(IsoServer self) +{ + Semaphore_post(self->openClientConnectionsMutex); +} +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ + static void addClientConnection(IsoServer self, IsoConnection connection) { @@ -86,6 +110,10 @@ addClientConnection(IsoServer self, IsoConnection connection) for (i = 0; i < CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS; i++) { if (self->openClientConnections[i] == NULL) { self->openClientConnections[i] = connection; + + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: added connection (%p) index:%i\n", connection, i); + break; } } @@ -96,17 +124,37 @@ static void removeClientConnection(IsoServer self, IsoConnection connection) { #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++) { if (self->openClientConnections[i] == connection) { + + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: removed connection (%p) index:%i\n", connection, i); + self->openClientConnections[i] = NULL; break; } } -#endif +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ } static void @@ -114,14 +162,35 @@ closeAllOpenClientConnections(IsoServer self) { #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + lockClientConnections(self); +#endif + LinkedList openConnection = LinkedList_getNext(self->openClientConnections); while (openConnection != NULL) { IsoConnection isoConnection = (IsoConnection) openConnection->data; IsoConnection_close(isoConnection); +#if (CONFIG_MMS_SINGLE_THREADED == 1) + /* if CONFIG_MMS_SINGLE_THREADED == 0 connection instance will be destroyed by connection thread. */ + IsoConnection_destroy(isoConnection); +#endif + openConnection = LinkedList_getNext(openConnection); } + +#if (CONFIG_MMS_SINGLE_THREADED == 1) + LinkedList_destroyStatic(self->openClientConnections); + self->openClientConnections = NULL; +#endif + + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + unlockClientConnections(self); +#endif + #else int i; @@ -130,27 +199,78 @@ closeAllOpenClientConnections(IsoServer self) IsoConnection_close(self->openClientConnections[i]); - break; +#if (CONFIG_MMS_SINGLE_THREADED == 1) + /* if CONFIG_MMS_SINGLE_THREADED == 0 connection instance will be destroyed by connection thread. */ + IsoConnection_destroy(self->openClientConnections[i]); +#endif + } } #endif } static void -isoServerThread(void* isoServerParam) +handleClientConnections(IsoServer self) { - IsoServer self = (IsoServer) isoServerParam; +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: isoServerThread %p started\n", &isoServerParam); +#if (CONFIG_MMS_THREADLESS_STACK != 1) + lockClientConnections(self); +#endif - Socket connectionSocket; + LinkedList openConnection = LinkedList_getNext(self->openClientConnections); + LinkedList lastConnection = self->openClientConnections; + + while (openConnection != NULL) { + IsoConnection isoConnection = (IsoConnection) openConnection->data; + if (IsoConnection_isRunning(isoConnection)) + IsoConnection_handleTcpConnection(isoConnection); + else { + IsoConnection_destroy(isoConnection); + + lastConnection->next = openConnection->next; + + GLOBAL_FREEMEM(openConnection); + } + + openConnection = LinkedList_getNext(openConnection); + } + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + unlockClientConnections(self); +#endif + +#else + + + int i; + + for (i = 0; i < CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS; i++) { + if (self->openClientConnections[i] != NULL) { + if (IsoConnection_isRunning(self->openClientConnections[i])) { + + IsoConnection_handleTcpConnection(self->openClientConnections[i]); + } + else { + IsoConnection_destroy(self->openClientConnections[i]); + + self->openClientConnections[i] = NULL; + } + + } + } +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ +} + +static bool +setupIsoServer(IsoServer self) +{ self->serverSocket = (Socket) TcpServerSocket_create(self->localIpAddress, self->tcpPort); if (self->serverSocket == NULL) { self->state = ISO_SVR_STATE_ERROR; - goto cleanUp; + return false; } ServerSocket_setBacklog((ServerSocket) self->serverSocket, BACKLOG); @@ -159,36 +279,113 @@ isoServerThread(void* isoServerParam) self->state = ISO_SVR_STATE_RUNNING; - while (self->state == ISO_SVR_STATE_RUNNING) - { +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1) + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: server is limited to %i client connections.\n", (int) CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS); +#endif - if ((connectionSocket = ServerSocket_accept((ServerSocket) self->serverSocket)) == NULL) { - break; - } - else { + return true; +} + + +#if (CONFIG_MMS_THREADLESS_STACK == 0) +// used by single and multi-threaded versions +static void +handleIsoConnections(IsoServer self) +{ + Socket connectionSocket; + + if ((connectionSocket = ServerSocket_accept((ServerSocket) self->serverSocket)) != NULL) { #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1) - if (private_IsoServer_getConnectionCounter(self) >= CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS) { - if (DEBUG_ISO_SERVER) - printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); + if (private_IsoServer_getConnectionCounter(self) >= CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); - Socket_destroy(connectionSocket); - continue; - } + Socket_destroy(connectionSocket); + +#if (CONFIG_MMS_SINGLE_THREADED == 1) + handleClientConnections(self); #endif - IsoConnection isoConnection = IsoConnection_create(connectionSocket, self); + return; + } +#endif - private_IsoServer_increaseConnectionCounter(self); + IsoConnection isoConnection = IsoConnection_create(connectionSocket, self); - addClientConnection(self, isoConnection); + private_IsoServer_increaseConnectionCounter(self); + addClientConnection(self, isoConnection); - self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter, - isoConnection); + self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter, + isoConnection); + } + +#if (CONFIG_MMS_SINGLE_THREADED == 1) + handleClientConnections(self); +#endif +} +#endif /* (CONFIG_MMS_THREADLESS_STACK == 0) */ + +// used by non-threaded version +static void +handleIsoConnectionsThreadless(IsoServer self) +{ + Socket connectionSocket; + + if ((connectionSocket = ServerSocket_accept((ServerSocket) self->serverSocket)) != NULL) { + +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS != -1) + if (private_IsoServer_getConnectionCounter(self) >= CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: maximum number of connections reached -> reject connection attempt.\n"); + + Socket_destroy(connectionSocket); + + handleClientConnections(self); + + return; } +#endif + + IsoConnection isoConnection = IsoConnection_create(connectionSocket, self); + + private_IsoServer_increaseConnectionCounter(self); + + addClientConnection(self, isoConnection); + + self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter, + isoConnection); + + } + + handleClientConnections(self); +} + +#if (CONFIG_MMS_THREADLESS_STACK != 1) +// only required for multi-threaded server! +static void +isoServerThread(void* isoServerParam) +{ + IsoServer self = (IsoServer) isoServerParam; + + if (!setupIsoServer(self)) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: starting server failed!\n"); + + goto cleanUp; + } + + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: isoServerThread %p started\n", &isoServerParam); + while (self->state == ISO_SVR_STATE_RUNNING) + { + handleIsoConnections(self); + + Thread_sleep(1); } self->state = ISO_SVR_STATE_STOPPED; @@ -200,11 +397,12 @@ isoServerThread(void* isoServerParam) printf("ISO_SERVER: isoServerThread %p stopped\n", &isoServerParam); } +#endif IsoServer IsoServer_create() { - IsoServer self = (IsoServer) calloc(1, sizeof(struct sIsoServer)); + IsoServer self = (IsoServer) GLOBAL_CALLOC(1, sizeof(struct sIsoServer)); self->state = ISO_SVR_STATE_IDLE; self->tcpPort = TCP_PORT; @@ -213,10 +411,17 @@ IsoServer_create() self->openClientConnections = LinkedList_create(); #else self->openClientConnections = (IsoConnection*) - calloc(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS, sizeof(IsoConnection)); + GLOBAL_CALLOC(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS, sizeof(IsoConnection)); #endif +#if (CONFIG_MMS_THREADLESS_STACK != 1) 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; return self; @@ -259,6 +464,7 @@ IsoServer_getAuthenticatorParameter(IsoServer self) return self->authenticatorParameter; } +#if (CONFIG_MMS_THREADLESS_STACK != 1) void IsoServer_startListening(IsoServer self) { @@ -266,22 +472,132 @@ IsoServer_startListening(IsoServer self) Thread_start(self->serverThread); + /* wait until server is up */ while (self->state == ISO_SVR_STATE_IDLE) Thread_sleep(1); if (DEBUG_ISO_SERVER) printf("ISO_SERVER: new iso server thread started\n"); } +#endif void -IsoServer_stopListening(IsoServer self) +IsoServer_startListeningThreadless(IsoServer self) { - self->state = ISO_SVR_STATE_STOPPED; + if (!setupIsoServer(self)) { + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: starting server failed!\n"); + + self->serverSocket = NULL; + } + else { + self->state = ISO_SVR_STATE_RUNNING; + + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: new iso server (threadless) started\n"); + } +} + +int +IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) +{ + int result; + + if (self->state == ISO_SVR_STATE_RUNNING) { + HandleSet handles; + + handles = Handleset_new(); + if (handles != NULL) { +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + lockClientConnections(self); +#endif + + LinkedList openConnection = LinkedList_getNext(self->openClientConnections); + LinkedList lastConnection = self->openClientConnections; + + while (openConnection != NULL) { + IsoConnection isoConnection = (IsoConnection) openConnection->data; + + if (IsoConnection_isRunning(isoConnection)) { + IsoConnection_addHandleSet(isoConnection, handles); + openConnection = LinkedList_getNext(openConnection); + } else { + IsoConnection_destroy(isoConnection); + lastConnection->next = openConnection->next; + free(openConnection); + openConnection = lastConnection->next; + } + + lastConnection = lastConnection->next; + } + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + unlockClientConnections(self); +#endif +#else + int i; + + for (i = 0; i < CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS; i++) { + if (self->openClientConnections[i] != NULL) { + if (IsoConnection_isRunning(self->openClientConnections[i])) { + IsoConnection_addHandleSet(self->openClientConnections[i], handles); + } else { + IsoConnection_destroy(self->openClientConnections[i]); + self->openClientConnections[i] = NULL; + } + } + } +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ + Handleset_addSocket(handles, self->serverSocket); + result = Handleset_waitReady(handles, timeoutMs); + Handleset_destroy(handles); + } else { + result = -1; + } + } else { + result = -1; + } + + return result; +} + + +void +IsoServer_processIncomingMessages(IsoServer self) +{ + if (self->state == ISO_SVR_STATE_RUNNING) + handleIsoConnectionsThreadless(self); +} + +static void +stopListening(IsoServer self) +{ + self->state = ISO_SVR_STATE_STOPPED; if (self->serverSocket != NULL) { ServerSocket_destroy((ServerSocket) self->serverSocket); self->serverSocket = NULL; } +} + +void +IsoServer_stopListeningThreadless(IsoServer self) +{ + stopListening(self); + + closeAllOpenClientConnections(self); + + if (DEBUG_ISO_SERVER) + printf("ISO_SERVER: IsoServer_stopListeningThreadless finished!\n"); +} + +#if (CONFIG_MMS_THREADLESS_STACK != 1) +void +IsoServer_stopListening(IsoServer self) +{ + stopListening(self); if (self->serverThread != NULL) Thread_destroy(self->serverThread); @@ -295,6 +611,7 @@ IsoServer_stopListening(IsoServer self) if (DEBUG_ISO_SERVER) printf("ISO_SERVER: IsoServer_stopListening finished!\n"); } +#endif void IsoServer_closeConnection(IsoServer self, IsoConnection isoConnection) @@ -318,40 +635,69 @@ IsoServer_setConnectionHandler(IsoServer self, ConnectionIndicationHandler handl void IsoServer_destroy(IsoServer self) { + +#if (CONFIG_MMS_THREADLESS_STACK != 1) if (self->state == ISO_SVR_STATE_RUNNING) IsoServer_stopListening(self); +#endif #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) - LinkedList_destroy(self->openClientConnections); + +#if (CONFIG_MMS_SINGLE_THREADED == 1) + if (self->openClientConnections != NULL) + LinkedList_destroy(self->openClientConnections); #else - free(self->openClientConnections); + if (self->openClientConnections != NULL) + LinkedList_destroyStatic(self->openClientConnections); +#endif /* (CONFIG_MMS_SINGLE_THREADED == 1) */ + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + 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) Semaphore_destroy(self->connectionCounterMutex); +#endif - free(self); + 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) Semaphore_wait(self->connectionCounterMutex); +#endif + self->connectionCounter--; if (DEBUG_ISO_SERVER) printf("IsoServer: decrease connection counter to %i!\n", self->connectionCounter); + +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->connectionCounterMutex); +#endif } int @@ -359,13 +705,20 @@ private_IsoServer_getConnectionCounter(IsoServer self) { int connectionCounter; +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->connectionCounterMutex); +#endif + connectionCounter = self->connectionCounter; + +#if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->connectionCounterMutex); +#endif return connectionCounter; } +#if (CONFIG_MMS_THREADLESS_STACK != 1) void IsoServer_setUserLock(IsoServer self, Semaphore userLock) { @@ -385,3 +738,4 @@ IsoServer_userUnlock(IsoServer self) if (self->userLock != NULL) Semaphore_post(self->userLock); } +#endif diff --git a/src/mms/iso_session/iso_session.c b/src/mms/iso_session/iso_session.c index 0b3b1614..8e0fe0ca 100644 --- a/src/mms/iso_session/iso_session.c +++ b/src/mms/iso_session/iso_session.c @@ -127,12 +127,13 @@ parseSessionHeaderParameters(IsoSession* session, ByteBuffer* message, int param case 1: /* Connection Identifier */ if (DEBUG_SESSION) printf("SESSION: PGI - connection identifier\n"); - printf("TODO: PGI not implemented!"); + offset += parameterLength; break; case 5: /* Connection/Accept Item */ if (DEBUG_SESSION) printf("SESSION: PGI - Connection/Accept Item\n"); + int connectAcceptLen; connectAcceptLen = parseAcceptParameters(session, message, offset, parameterLength); diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index aa3b2b60..d98c9e2f 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -95,7 +95,7 @@ preparePacketBuffer(SampledValuesPublisher self, CommParameters* parameters, cha else self->ethernetSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, dstAddr); - self->buffer = (uint8_t*) malloc(SV_MAX_MESSAGE_SIZE); + self->buffer = (uint8_t*) GLOBAL_MALLOC(SV_MAX_MESSAGE_SIZE); memcpy(self->buffer, dstAddr, 6); memcpy(self->buffer + 6, srcAddr, 6); diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index b550d1dd..a531d9ab 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -93,6 +93,7 @@ EXPORTS ControlObjectClient_select @220 ControlObjectClient_selectWithValue @221 ControlObjectClient_setLastApplError @222 + ControlObjectClient_setCommandTerminationHandler DataAttribute_create @273 DataObject_create @274 DataObject_hasFCData @275 @@ -378,6 +379,8 @@ EXPORTS MmsValue_toUint32 @774 MmsValue_toUnixTimestamp @775 MmsValue_update @776 + MmsValue_getBitStringAsIntegerBigEndian + MmsValue_setBitStringFromIntegerBigEndian MmsVariableAccessSpecification_create @777 MmsVariableAccessSpecification_createAlternateAccess @778 MmsVariableAccessSpecification_destroy @779 @@ -390,6 +393,7 @@ EXPORTS MmsVariableSpecification_getSize @786 MmsVariableSpecification_getStructureElements @787 MmsVariableSpecification_getType @788 + MmsVariableSpecification_getNamedVariableRecursive ModelNode_getChild @789 ModelNode_getChildCount @790 ModelNode_getObjectReference @791 @@ -438,3 +442,32 @@ EXPORTS AcseAuthenticationParameter_destroy AcseAuthenticationParameter_setAuthMechanism AcseAuthenticationParameter_setPassword + IedServer_startThreadless + IedServer_processIncomingData + IedServer_performPeriodicTasks + IedServer_stopThreadless + Dbpos_fromMmsValue + Dbpos_toMmsValue + SettingGroupControlBlock_create + MmsConnection_setConnectTimeout + IedConnection_setConnectTimeout + MmsValue_newVisibleStringWithSize + MmsValue_newMmsStringWithSize + MmsValue_setInt8 + MmsValue_setInt16 + LogicalDevice_getSettingGroupControlBlock + IedServer_changeActiveSettingGroup + IedServer_setActiveSettingGroupChangedHandler + IedServer_disableGoosePublishing + IedServer_setEditSettingGroupChangedHandler + IedServer_setEditSettingGroupConfirmationHandler + IedConnection_getDataDirectoryByFC + IedServer_getActiveSettingGroup + ClientReport_hasSeqNum + ClientReport_getSeqNum + ClientReport_hasDataSetName + ClientReport_hasReasonForInclusion + ClientReport_hasConfRev + ClientReport_getConfRev + ClientReport_hasBufOvfl + ClientReport_hasDataReference diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 807393a8..2fc47403 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -91,8 +91,9 @@ EXPORTS ControlObjectClient_getObjectReference @218 ControlObjectClient_operate @219 ControlObjectClient_select @220 - ControlObjectClient_selectWithValue @221 + ControlObjectClient_selectWithValue @221MmsConnection_setConnectTimeout ControlObjectClient_setLastApplError @222 + ControlObjectClient_setCommandTerminationHandler DataAttribute_create @273 DataObject_create @274 DataObject_hasFCData @275 @@ -138,10 +139,7 @@ EXPORTS GooseSubscriber_isTest @347 GooseSubscriber_needsCommission @348 GooseSubscriber_setAppId @349 - GooseSubscriber_setInterfaceId @350 GooseSubscriber_setListener @351 - GooseSubscriber_subscribe @352 - GooseSubscriber_unsubscribe @353 Hal_getTimeInMs @354 IedConnection_abort @366 IedConnection_close @367 @@ -405,6 +403,8 @@ EXPORTS MmsValue_toUint32 @774 MmsValue_toUnixTimestamp @775 MmsValue_update @776 + MmsValue_getBitStringAsIntegerBigEndian + MmsValue_setBitStringFromIntegerBigEndian MmsVariableAccessSpecification_create @777 MmsVariableAccessSpecification_createAlternateAccess @778 MmsVariableAccessSpecification_destroy @779 @@ -417,6 +417,7 @@ EXPORTS MmsVariableSpecification_getSize @786 MmsVariableSpecification_getStructureElements @787 MmsVariableSpecification_getType @788 + MmsVariableSpecification_getNamedVariableRecursive ModelNode_getChild @789 ModelNode_getChildCount @790 ModelNode_getObjectReference @791 @@ -465,5 +466,32 @@ EXPORTS AcseAuthenticationParameter_destroy AcseAuthenticationParameter_setAuthMechanism AcseAuthenticationParameter_setPassword - - + IedServer_startThreadless + IedServer_processIncomingData + IedServer_performPeriodicTasks + IedServer_stopThreadless + Dbpos_fromMmsValue + Dbpos_toMmsValue + SettingGroupControlBlock_create + MmsConnection_setConnectTimeout + IedConnection_setConnectTimeout + MmsValue_newVisibleStringWithSize + MmsValue_newMmsStringWithSize + MmsValue_setInt8 + MmsValue_setInt16 + LogicalDevice_getSettingGroupControlBlock + IedServer_changeActiveSettingGroup + IedServer_setActiveSettingGroupChangedHandler + IedServer_disableGoosePublishing + IedServer_setEditSettingGroupChangedHandler + IedServer_setEditSettingGroupConfirmationHandler + IedConnection_getDataDirectoryByFC + IedServer_getActiveSettingGroup + ClientReport_hasSeqNum + ClientReport_getSeqNum + ClientReport_hasDataSetName + ClientReport_hasReasonForInclusion + ClientReport_hasConfRev + ClientReport_getConfRev + ClientReport_hasBufOvfl + ClientReport_hasDataReference diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 9d65c0bc4a4427315ce90132f607625377c16423..51a607704ed83d7937fabac716ab1ede51eb763a 100644 GIT binary patch delta 30707 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 delta 28978 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�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 WLp1-vXCeVR5CO0e3sC`3*8c&)v%9kZ delta 27861 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 logs; private List settingGroupControlBlocks; - private LogicalDevice parentLogicalDevice; + private LogicalDevice parentLogicalDevice; public LogicalNode(Node lnNode, TypeDeclarations typeDeclarations, LogicalDevice parent) throws SclParserException { @@ -133,6 +133,9 @@ public class LogicalNode implements DataModelNode { List sgNodes = ParserUtils.getChildNodesWithTag(lnNode, "SettingControl"); + if ((this.lnClass.equals("LLN0") == false) && (sgNodes.size() > 0)) + throw new SclParserException(lnNode, "LN other than LN0 is not allowed to contain SettingControl"); + if (sgNodes.size() > 1) throw new SclParserException(lnNode, "LN contains more then one SettingControl"); @@ -257,6 +260,10 @@ public class LogicalNode implements DataModelNode { public List getGSEControlBlocks() { return gseControlBlocks; } + + public List getSettingGroupControlBlocks() { + return settingGroupControlBlocks; + } @Override public DataModelNode getChildByName(String childName) { diff --git a/tools/model_generator/src/com/libiec61850/scl/types/DataAttributeType.java b/tools/model_generator/src/com/libiec61850/scl/types/DataAttributeType.java index ed56dda3..928547c8 100644 --- a/tools/model_generator/src/com/libiec61850/scl/types/DataAttributeType.java +++ b/tools/model_generator/src/com/libiec61850/scl/types/DataAttributeType.java @@ -66,6 +66,7 @@ public class DataAttributeType extends SclType { subDataAttributes.add(dad); } + } } } diff --git a/tools/model_generator/src/com/libiec61850/scl/types/DataObjectType.java b/tools/model_generator/src/com/libiec61850/scl/types/DataObjectType.java index b818f225..3e0f3456 100644 --- a/tools/model_generator/src/com/libiec61850/scl/types/DataObjectType.java +++ b/tools/model_generator/src/com/libiec61850/scl/types/DataObjectType.java @@ -75,12 +75,22 @@ public class DataObjectType extends SclType { if (elementNode.getNodeName().equals("DA")) { DataAttributeDefinition dad = new DataAttributeDefinition(elementNode); + + DataAttributeDefinition otherDefinition = getDataAttributeByName(dad.getName()); + + if (otherDefinition != null) { + + if (otherDefinition.getFc() == dad.getFc()) + throw new SclParserException(xmlNode, + "DO type definition contains multiple elements of name \"" + + dad.getName() + "\""); + } - if ((getDataAttributeByName(dad.getName()) != null) || - (getDataObjectByName(dad.getName()) != null)) + if (getDataObjectByName(dad.getName()) != null) { throw new SclParserException(xmlNode, "DO type definition contains multiple elements of name \"" + dad.getName() + "\""); + } dataAttributes.add(dad); diff --git a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java index 9b5868f5..2fb76e4f 100644 --- a/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/DynamicModelGenerator.java @@ -31,6 +31,7 @@ import java.io.InputStream; import java.io.PrintStream; import java.util.List; +import com.libiec61850.scl.DataAttributeDefinition; import com.libiec61850.scl.SclParser; import com.libiec61850.scl.SclParserException; import com.libiec61850.scl.communication.ConnectedAP; @@ -46,6 +47,7 @@ import com.libiec61850.scl.model.IED; import com.libiec61850.scl.model.LogicalDevice; import com.libiec61850.scl.model.LogicalNode; import com.libiec61850.scl.model.ReportControlBlock; +import com.libiec61850.scl.model.SettingControl; public class DynamicModelGenerator { @@ -82,7 +84,7 @@ public class DynamicModelGenerator { output.println("MODEL(" + ied.getName() + "){"); for (LogicalDevice logicalDevice : logicalDevices) { output.print("LD("); - output.print(ied.getName() + logicalDevice.getInst() + "){\n"); + output.print(logicalDevice.getInst() + "){\n"); exportLogicalNodes(output, logicalDevice); @@ -106,6 +108,11 @@ public class DynamicModelGenerator { } private void exportLogicalNode(PrintStream output, LogicalNode logicalNode) { + + for (SettingControl sgcb : logicalNode.getSettingGroupControlBlocks()) { + output.print("SG(" + sgcb.getActSG() + " " + sgcb.getNumOfSGs() + ")\n"); + } + for (DataObject dataObject : logicalNode.getDataObjects()) { output.print("DO(" + dataObject.getName() + " " + dataObject.getCount() + "){\n"); @@ -274,8 +281,12 @@ public class DynamicModelGenerator { output.print(")"); if (dataAttribute.isBasicAttribute()) { - DataModelValue value = dataAttribute.getValue(); - + DataModelValue value = dataAttribute.getValue(); + + /* if no value is given use default value for type if present */ + if (value == null) + value = dataAttribute.getDefinition().getValue(); + if (value != null) { switch (dataAttribute.getType()) { diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index bd744a06..0c6afc0b 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -44,6 +44,7 @@ import com.libiec61850.scl.model.DataAttribute; import com.libiec61850.scl.model.DataModelValue; import com.libiec61850.scl.model.DataObject; import com.libiec61850.scl.model.DataSet; +import com.libiec61850.scl.model.FunctionalConstraint; import com.libiec61850.scl.model.FunctionalConstraintData; import com.libiec61850.scl.model.GSEControl; import com.libiec61850.scl.model.IED; @@ -51,6 +52,7 @@ import com.libiec61850.scl.model.LogicalDevice; import com.libiec61850.scl.model.LogicalNode; import com.libiec61850.scl.model.ReportControlBlock; import com.libiec61850.scl.model.Server; +import com.libiec61850.scl.model.SettingControl; import com.libiec61850.scl.model.TriggerOptions; public class StaticModelGenerator { @@ -68,6 +70,10 @@ public class StaticModelGenerator { private StringBuffer gseControlBlocks; private List gseVariableNames; private int currentGseVariableNumber = 0; + + private StringBuffer settingGroupControlBlocks; + private List sgcbVariableNames; + private int currentSGCBVariableNumber = 0; private List dataSetNames; @@ -88,6 +94,9 @@ public class StaticModelGenerator { this.rcbVariableNames = new LinkedList(); this.gseControlBlocks = new StringBuffer(); this.gseVariableNames = new LinkedList(); + + this.settingGroupControlBlocks = new StringBuffer(); + this.sgcbVariableNames = new LinkedList(); SclParser sclParser = new SclParser(stream); @@ -195,15 +204,19 @@ public class StaticModelGenerator { } } - private String getLogicalDeviceName(LogicalDevice logicalDevice) { - String logicalDeviceName = logicalDevice.getLdName(); - - if (logicalDeviceName == null) - logicalDeviceName = ied.getName() + logicalDevice.getInst(); - - return logicalDeviceName; +// 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(); } - + private void printDeviceModelDefinitions() { printDataSets(); @@ -213,6 +226,8 @@ public class StaticModelGenerator { createReportVariableList(logicalDevices); createGooseVariableList(logicalDevices); + + createSettingControlsVariableList(logicalDevices); for (int i = 0; i < logicalDevices.size(); i++) { @@ -222,7 +237,7 @@ public class StaticModelGenerator { variablesList.add(ldName); - String logicalDeviceName = getLogicalDeviceName(logicalDevice); + String logicalDeviceName = getLogicalDeviceInst(logicalDevice); cOut.println("\nLogicalDevice " + ldName + " = {"); @@ -257,6 +272,11 @@ public class StaticModelGenerator { cOut.println("extern GSEControlBlock " + gcb + ";"); cOut.println(gseControlBlocks); + + for (String sgcb : sgcbVariableNames) + cOut.println("extern SettingGroupControlBlock " + sgcb + ";"); + + cOut.println(settingGroupControlBlocks); String firstLogicalDeviceName = logicalDevices.get(0).getInst(); cOut.println("\nIedModel iedModel = {"); @@ -277,6 +297,11 @@ public class StaticModelGenerator { cOut.println(" &" + gseVariableNames.get(0) + ","); else cOut.println(" NULL,"); + + if (sgcbVariableNames.size() > 0) + cOut.println(" &" + sgcbVariableNames.get(0) + ","); + else + cOut.println(" NULL,"); cOut.println(" initializeValues\n};"); } @@ -315,7 +340,12 @@ public class StaticModelGenerator { for (ReportControlBlock rcb : rcbs) { - for (int i = 0; i < rcb.getRptEna().getMaxInstances(); i++) { + int maxInstances = 1; + + if (rcb.getRptEna() != null) + maxInstances = rcb.getRptEna().getMaxInstances(); + + for (int i = 0; i < maxInstances; i++) { String rcbVariableName = "iedModel_" + ldName + "_" + ln.getName() + "_report" + rcbCount; rcbVariableNames.add(rcbVariableName); rcbCount++; @@ -325,6 +355,26 @@ public class StaticModelGenerator { } } } + + private void createSettingControlsVariableList(List logicalDevices) { + for (LogicalDevice ld : logicalDevices) { + List lnodes = ld.getLogicalNodes(); + + String ldName = ld.getInst(); + + for (LogicalNode ln : lnodes) { + List sgcbs = ln.getSettingGroupControlBlocks(); + + for (SettingControl sgcb : sgcbs) { + + String sgcbVariableName = "iedModel_" + ldName + "_" + ln.getName() + "_sgcb"; + + sgcbVariableNames.add(sgcbVariableName); + + } + } + } + } private void printLogicalNodeDefinitions(String ldName, List logicalNodes) { for (int i = 0; i < logicalNodes.size(); i++) { @@ -354,6 +404,8 @@ public class StaticModelGenerator { printReportControlBlocks(lnName, logicalNode); printGSEControlBlocks(ldName, lnName, logicalNode); + + printSettingControlBlock(lnName, logicalNode); } } @@ -412,6 +464,13 @@ public class StaticModelGenerator { DataAttribute dataAttribute = dataAttributes.get(i); String daName = doName + "_" + dataAttribute.getName(); + + if (dataAttribute.getFc() == FunctionalConstraint.SE) { + + if (daName.startsWith("iedModel_SE_") == false) + daName = daName.substring(0, 9) + "SE_" + daName.substring(9); + + } variablesList.add(daName); @@ -419,8 +478,18 @@ public class StaticModelGenerator { cOut.println(" DataAttributeModelType,"); cOut.println(" \"" + dataAttribute.getName() + "\","); cOut.println(" (ModelNode*) &" + doName + ","); - if (i < (dataAttributes.size() - 1)) - cOut.println(" (ModelNode*) &" + doName + "_" + dataAttributes.get(i + 1).getName() + ","); + if (i < (dataAttributes.size() - 1)) { + DataAttribute sibling = dataAttributes.get(i + 1); + + String siblingDoName = doName; + + if (sibling.getFc() == FunctionalConstraint.SE) { + if (siblingDoName.startsWith("iedModel_SE_") == false) + siblingDoName = siblingDoName.substring(0, 9) + "SE_" + siblingDoName.substring(9); + } + + cOut.println(" (ModelNode*) &" + siblingDoName + "_" + dataAttributes.get(i + 1).getName() + ","); + } else cOut.println(" NULL,"); if ((dataAttribute.getSubDataAttributes() != null) && (dataAttribute.getSubDataAttributes().size() > 0)) @@ -570,7 +639,13 @@ public class StaticModelGenerator { private void printDataAttributeForwardDeclarations(String doName, List dataAttributes) { for (DataAttribute dataAttribute : dataAttributes) { String daName = doName + "_" + dataAttribute.getName(); - + + if (dataAttribute.getFc() == FunctionalConstraint.SE) { + + if (daName.startsWith("iedModel_SE_") == false) + daName = daName.substring(0, 9) + "SE_" + daName.substring(9); + } + cOut.println("extern DataAttribute " + daName + ";"); hOut.println("extern DataAttribute " + daName + ";"); @@ -587,7 +662,7 @@ public class StaticModelGenerator { cOut.println(" * automatically generated from " + filename); cOut.println(" */"); cOut.println("#include "); - cOut.println("#include \"model.h\""); + cOut.println("#include \"iec61850_model.h\""); cOut.println(); } @@ -600,7 +675,7 @@ public class StaticModelGenerator { hOut.println("#ifndef STATIC_MODEL_H_"); hOut.println("#define STATIC_MODEL_H_\n"); hOut.println("#include "); - hOut.println("#include \"model.h\""); + hOut.println("#include \"iec61850_model.h\""); hOut.println(); } @@ -718,9 +793,37 @@ public class StaticModelGenerator { printReportControlBlockInstance(lnPrefix, rcb, "", reportNumber, reportsCount); reportNumber++; } - } - + } + + private void printSettingControlBlock(String lnPrefix, LogicalNode logicalNode) + { + List settingControls = logicalNode.getSettingGroupControlBlocks(); + + if (settingControls.size() > 0) { + System.out.println("print SGCB for " + lnPrefix); + + SettingControl sgcb = settingControls.get(0); + + String sgcbVariableName = lnPrefix + "_sgcb"; + + String sgcbString = "\nSettingGroupControlBlock " + sgcbVariableName + " = {"; + + sgcbString += "&" + lnPrefix + ", "; + + sgcbString += sgcb.getActSG() + ", " + sgcb.getNumOfSGs() + ", 0, false, 0, 0, "; + + if (currentSGCBVariableNumber < (sgcbVariableNames.size() - 1)) + sgcbString += "&" + sgcbVariableNames.get(currentGseVariableNumber + 1); + else + sgcbString += "NULL"; + + sgcbString += "};\n"; + + this.settingGroupControlBlocks.append(sgcbString); + + currentSGCBVariableNumber++; + } } private void printReportControlBlockInstance(String lnPrefix, ReportControlBlock rcb, String index, int reportNumber, int reportsCount) { @@ -798,8 +901,6 @@ public class StaticModelGenerator { rcbString += "NULL"; rcbString += "};\n"; - - System.out.println("RCB: " + rcbString); this.reportControlBlocks.append(rcbString); } @@ -873,7 +974,7 @@ public class StaticModelGenerator { String dataSetEntryName = dataSetVariableName + "_fcda" + fcdaCount; cOut.println("DataSetEntry " + dataSetEntryName + " = {"); - cOut.println(" \"" + ied.getName() + fcda.getLdInstance() + "\","); + cOut.println(" \"" + fcda.getLdInstance() + "\","); String mmsVariableName = ""; @@ -918,7 +1019,7 @@ public class StaticModelGenerator { String lnVariableName = "iedModel_" + logicalDevice.getInst() + "_" + logicalNode.getName(); - cOut.println(" \"" + ied.getName() + logicalDevice.getInst() + "\","); + cOut.println(" \"" + logicalDevice.getInst() + "\","); cOut.println(" \"" + logicalNode.getName() + "$" + dataSet.getName() + "\","); cOut.println(" " + dataSet.getFcda().size() + ","); cOut.println(" &" + dataSetVariableName + "_fcda0,");