Merge branch 'logging'

pull/6/head
Michael Zillgith 10 years ago
commit 9c8da7f94c

@ -1,3 +1,18 @@
Changes to version 0.9.2
------------------------
- client/server: support for MMS journals and IEC 61850 log service
- Abstract interface for log storage providers: logging_api.h and LogStorage class
- log storage implementation using sqlite
- server: negative delete data set response is now compatible with new test procedures (TPCL 1.1)
- FileSystem API is now intended to access the complete file system. Path translation for VMD filestore is done by MMS file service implementation (removed FileSystem_setBasePath function)
- added CDC_DPL_create function
- MMS server: fixed race condition when opening/closing connections in multi-threaded configuration.
- client: IedConnection_readObject and IedConnection_getVariableSpecification functions can now be applied to whole LNs
- client: GetLogicalNodeDirectory for data set and log ACSI classes will not use cached model data but request the information from server. This way the client can get up to date information about newly created dynamic data sets
- changed HAL thread implementation for bsd to be compatible with MacOS X 10.10
- fixed problem with test case sSgN4: return temporarily-unavailable when no EditSG is selected
- fixed bug in ethernet_win32.c
Changes to version 0.9.1
------------------------
- client: added function MmsConnection_getMmsConnectionParameters

@ -12,7 +12,9 @@ ENABLE_TESTING()
set(LIB_VERSION_MAJOR "0")
set(LIB_VERSION_MINOR "9")
set(LIB_VERSION_PATCH "1")
set(LIB_VERSION_PATCH "2")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/")
# feature checks
include(CheckLibraryExists)
@ -28,7 +30,7 @@ set(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS 5 CACHE STRING "Configure the maximum
option(BUILD_EXAMPLES "Build the examples" ON)
option(BUILD_PYTHON_BINDINGS "Build Python bindings" OFF)
option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" OFF)
option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" ON)
option(CONFIG_MMS_THREADLESS_STACK "Optimize stack for threadless operation (warning: single- or multi-threaded server will not work!)" OFF)
# choose the library features which shall be included
@ -38,6 +40,8 @@ option(CONFIG_IEC61850_CONTROL_SERVICE "Build with support for IEC 61850 control
option(CONFIG_IEC61850_REPORT_SERVICE "Build with support for IEC 61850 reporting services" ON)
option(CONFIG_IEC61850_LOG_SERVICE "Build with support for IEC 61850 logging services" ON)
option(CONFIG_IEC61850_SETTING_GROUPS "Build with support for IEC 61850 setting group services" ON)
option(CONFIG_ACTIVATE_TCP_KEEPALIVE "Activate TCP keepalive" ON)
@ -68,6 +72,7 @@ include_directories(
src/mms/inc
src/mms/inc_private
src/mms/iso_mms/asn1c
src/logging
)
set(API_HEADERS
@ -102,6 +107,7 @@ set(API_HEADERS
src/goose/goose_publisher.h
src/sampled_values/sv_subscriber.h
src/sampled_values/sv_publisher.h
src/logging/logging_api.h
)
IF(MSVC)
@ -119,7 +125,7 @@ endif(BUILD_EXAMPLES)
add_subdirectory(src)
INSTALL(FILES ${API_HEADERS} DESTINATION include/libiec61850)
INSTALL(FILES ${API_HEADERS} DESTINATION include/libiec61850 COMPONENT Development)
IF(BUILD_PYTHON_BINDINGS)
add_subdirectory(pyiec61850)
@ -128,10 +134,6 @@ ENDIF(BUILD_PYTHON_BINDINGS)
IF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")
INCLUDE(InstallRequiredSystemLibraries)
SET(CPACK_SET_DESTDIR "on")
SET(CPACK_INSTALL_PREFIX "/usr")
SET(CPACK_GENERATOR "DEB")
SET(CPACK_PACKAGE_DESCRIPTION "IEC 61850 MMS/GOOSE client and server library")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IEC 61850 MMS/GOOSE client and server library")
SET(CPACK_PACKAGE_VENDOR "The libIEC61850 project")
@ -142,10 +144,8 @@ SET(CPACK_PACKAGE_VERSION_PATCH "${LIB_VERSION_PATCH}")
SET(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CMAKE_SYSTEM_PROCESSOR}")
SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
SET(CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
SET(CPACK_COMPONENTS_ALL Libraries ApplicationData)
SET(CPACK_COMPONENTS_ALL Libraries Development Applications)
#set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CMAKE_PROJECT_NAME}")
INCLUDE(CPack)
ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")

@ -17,6 +17,9 @@ LIB_SOURCE_DIRS += src/mms/iso_common
LIB_SOURCE_DIRS += src/mms/iso_mms/common
LIB_SOURCE_DIRS += src/mms/iso_mms/asn1c
LIB_SOURCE_DIRS += src/mms/iso_server
LIB_SOURCE_DIRS += src/logging
ifndef EXCLUDE_ETHERNET_WINDOWS
LIB_SOURCE_DIRS += src/goose
LIB_SOURCE_DIRS += src/sampled_values
@ -41,7 +44,7 @@ LIB_SOURCE_DIRS += src/hal/filesystem/linux
LIB_SOURCE_DIRS += src/hal/time/unix
else ifeq ($(HAL_IMPL), BSD)
LIB_SOURCE_DIRS += src/hal/socket/bsd
LIB_SOURCE_DIRS += src/hal/thread/linux
LIB_SOURCE_DIRS += src/hal/thread/bsd
LIB_SOURCE_DIRS += src/hal/ethernet/bsd
LIB_SOURCE_DIRS += src/hal/filesystem/linux
LIB_SOURCE_DIRS += src/hal/time/unix
@ -56,6 +59,7 @@ LIB_INCLUDE_DIRS += src/goose
LIB_INCLUDE_DIRS += src/sampled_values
LIB_INCLUDE_DIRS += src/iec61850/inc
LIB_INCLUDE_DIRS += src/iec61850/inc_private
LIB_INCLUDE_DIRS += src/logging
ifeq ($(HAL_IMPL), WIN32)
LIB_INCLUDE_DIRS += third_party/winpcap/Include
endif
@ -97,6 +101,7 @@ LIB_API_HEADER_FILES += src/goose/goose_receiver.h
LIB_API_HEADER_FILES += src/goose/goose_publisher.h
LIB_API_HEADER_FILES += src/sampled_values/sv_subscriber.h
LIB_API_HEADER_FILES += src/sampled_values/sv_publisher.h
LIB_API_HEADER_FILES += src/logging/logging_api.h
get_sources_from_directory = $(wildcard $1/*.c)
get_sources = $(foreach dir, $1, $(call get_sources_from_directory,$(dir)))

@ -85,11 +85,28 @@ Note: The ".." at the end of the command line tells cmake where to find the main
To select some configuration options you can use ccmake or cmake-gui.
Building the sqlite logging driver
-----------------------------------
You can use the driver by including the src/logging/drivers/sqlite/log_storage_sqlite.c file into your application build.
On Ubuntu Linux (and simpilar Linux distributions) it is enough to install the sqlite dev packages from the standard repository. For other OSes (e.g. Windows) and cross-compiling it is recomended to download the amalagation source code (from https://www.sqlite.org/download.html) of sqlite and copy them to the third_party/sqlite folder.
On windows the cmake skript will detect the sqlite source code and also creates the example project for logging.
C# client API
--------------
A C#/.NET wrapper and examples and Visual Studio/MonoDevelop project files can be found in the dotnet folder. The examples and the C# wrapper API can be build and run on .NET or Mono.
Experimental Python bindings
----------------------------
The experimental Python binding can be created using SWIG with cmake.
To enable the bindings you have to select the phyton configuration option with ccmake of cmake-gui.
Commercial licenses
-------------------

@ -153,12 +153,15 @@
/* default reservation time of a setting group control block in s */
#define CONFIG_IEC61850_SG_RESVTMS 300
/* default results for MMS identify service */
#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com"
#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850"
#define CONFIG_DEFAULT_MMS_REVISION "0.9.1"
/* include support for IEC 61850 log services */
#define CONFIG_IEC61850_LOG_SERVICE 1
/* MMS virtual file store base path - where file services are looking for files */
/* overwrite default results for MMS identify service */
//#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com"
//#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850"
//#define CONFIG_DEFAULT_MMS_REVISION "0.9.2"
/* MMS virtual file store base path - where MMS file services are looking for files */
#define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/"
/* Maximum number of open file per MMS connection (for MMS file read service) */
@ -183,6 +186,7 @@
#define MMS_READ_SERVICE 1
#define MMS_WRITE_SERVICE 1
#define MMS_GET_NAME_LIST 1
#define MMS_JOURNAL_SERVICE 1
#define MMS_GET_VARIABLE_ACCESS_ATTRIBUTES 1
#define MMS_DATA_SET_SERVICE 1
#define MMS_DYNAMIC_DATA_SETS 1

@ -142,6 +142,9 @@
/* default reservation time of a setting group control block in s */
#define CONFIG_IEC61850_SG_RESVTMS 100
/* include support for IEC 61850 log services */
#cmakedefine01 CONFIG_IEC61850_LOG_SERVICE
/* default results for MMS identify service */
#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com"
#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850"
@ -175,6 +178,7 @@
#define MMS_READ_SERVICE 1
#define MMS_WRITE_SERVICE 1
#define MMS_GET_NAME_LIST 1
#define MMS_JOURNAL_SERVICE 1
#define MMS_GET_VARIABLE_ACCESS_ATTRIBUTES 1
#define MMS_DATA_SET_SERVICE 1
#define MMS_DYNAMIC_DATA_SETS 1

@ -1,24 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<SCL xmlns="http://www.iec.ch/61850/2003/SCL">
<Header id="" nameStructure="IEDName">
<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007" revision="B">
<Header id="" version="1" revision="" nameStructure="IEDName">
</Header>
<Communication>
<SubNetwork name="subnetwork1" type="8-MMS">
<Text>Station bus</Text>
<BitRate unit="b/s">10</BitRate>
<ConnectedAP iedName="beagle" apName="accessPoint1">
<Address>
<P type="IP">10.0.0.2</P>
<P type="IP-SUBNET">255.255.255.0</P>
<P type="IP-GATEWAY">10.0.0.1</P>
<P type="OSI-TSEL">0001</P>
<P type="OSI-PSEL">00000001</P>
<P type="OSI-SSEL">0001</P>
</Address>
</ConnectedAP>
</SubNetwork>
</Communication>
<IED name="beagle">
<IED name="Template">
<Services>
<DynAssociation max="5"/>
@ -30,17 +15,13 @@
<ReadWrite />
<GetCBValues />
<ConfDataSet max="1" modify="false" />
<DynDataSet max="10"/>
<ConfReportControl max="1" bufConf="false" />
<DynDataSet max="5"/>
<ConfReportControl max="5" bufConf="false" />
<ReportSettings cbName="Fix" datSet="Dyn" rptID="Dyn" optFields="Dyn" bufTime="Dyn" trgOps="Dyn" intgPd="Dyn" owner="true"/>
<ConfLNs fixPrefix="true" fixLnInst="true"/>
<ClientServices >
<TimeSyncProt sntp="true" c37_238="false" other="false"/>
</ClientServices>
<GOOSE max="5" />
<GSSE max="5" />
<GSEDir />
<TimerActivatedControl />
</Services>
<AccessPoint name="accessPoint1">
@ -63,22 +44,12 @@
<RptEnabled max="5" />
</ReportControl>
<DOI name="Mod">
<DAI name="ctlModel">
<Val>status-only</Val>
</DAI>
</DOI>
</LN0>
<LN lnClass="LPHD" lnType="LPHD1" inst="1" prefix="" />
<LN lnClass="GGIO" lnType="GGIO1" inst="1" prefix="">
<DOI name="Mod">
<DAI name="ctlModel">
<Val>status-only</Val>
</DAI>
</DOI>
<DOI name="SPCSO1">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
@ -86,7 +57,7 @@
</DOI>
<DOI name="SPCSO2">
<DAI name="ctlModel">
<Val>sbo-with-normal-security</Val>
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
<DOI name="SPCSO3">
@ -96,7 +67,7 @@
</DOI>
<DOI name="DPCSO1">
<DAI name="ctlModel">
<Val>direct-with-enhanced-security</Val>
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
</LN>
@ -112,20 +83,20 @@
<LNodeType id="LLN01" lnClass="LLN0">
<DO name="Beh" type="ENS_Beh" />
<DO name="Mod" type="ENC_Mod_status_only" />
<DO name="Health" type="ENS_Beh" />
<DO name="Health" type="ENS_Health" />
<DO name="NamPlt" type="LPL_1_NamPlt" />
</LNodeType>
<LNodeType id="LPHD1" lnClass="LPHD">
<DO name="PhyNam" type="DPL_1_PhyNam" />
<DO name="PhyHealth" type="ENS_Beh" />
<DO name="PhyHealth" type="ENS_Health" />
<DO name="Proxy" type="SPS_1_Proxy" />
</LNodeType>
<LNodeType id="GGIO1" lnClass="GGIO">
<DO name="Beh" type="ENS_Beh" />
<DO name="Mod" type="ENC_Mod_status_only" />
<DO name="Health" type="ENS_Beh" />
<DO name="Health" type="ENS_Health" />
<DO name="NamPlt" type="LPL_2_NamPlt" />
<DO name="AnIn1" type="MV_1_AnIn1" />
<DO name="AnIn2" type="MV_1_AnIn1" />
@ -167,16 +138,22 @@
<DA name="t" fc="ST" bType="Timestamp" />
</DOType>
<DOType id="ENS_Health" cdc="ENS">
<DA name="stVal" fc="ST" bType="Enum" type="healthEnum" dchg="true" />
<DA name="q" fc="ST" bType="Quality" qchg="true" />
<DA name="t" fc="ST" bType="Timestamp" />
</DOType>
<DOType id="INC_OpCntRs" cdc="INC">
<DA name="stVal" bType="INT32" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="t" bType="Timestamp" fc="ST" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF">
<DA name="Oper" type="INCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF">
<Val>direct-with-normal-security</Val>
</DA>
</DOType>
<DOType cdc="ACD" id="ACD_Str">
<DA bType="BOOLEAN" dchg="true" fc="ST" name="general" />
<DA bType="Enum" dchg="true" fc="ST" name="dirGeneral" type="dirGeneral" />
@ -194,7 +171,7 @@
<DA bType="Enum" dchg="true" fc="ST" name="stVal" type="Beh"/>
<DA bType="Quality" fc="ST" name="q" qchg="true"/>
<DA bType="Timestamp" fc="ST" name="t"/>
<DA bType="Enum" fc="CF" name="ctlModel" type="CtlModels">
<DA bType="Enum" fc="CF" name="ctlModel" valKind="RO" type="CtlModels">
<Val>status-only</Val>
</DA>
</DOType>
@ -233,31 +210,25 @@
<DA name="stVal" bType="Dbpos" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="INC_2_Mod" cdc="INC">
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
</DOType>
<DOType id="SPC_2_SPCSO1" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="SPC_1_SPCSO2" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="SBO" fc="CO" bType="VisString64" />
<DA name="SBO" fc="CO" bType="VisString129" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="Cancel" fc="CO" bType="Struct" type="SPCCancel" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
@ -265,15 +236,7 @@
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="INC_OpCntRs" cdc="SPC">
<DA name="stVal" bType="INT32" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="INCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
@ -286,15 +249,6 @@
<BDA name="orIdent" bType="Octet64" />
</DAType>
<DAType id="INCOperate_1">
<BDA name="ctlVal" bType="INT32" />
<BDA name="origin" type="Originator_1" bType="Struct" />
<BDA name="ctlNum" bType="INT8U" />
<BDA name="T" bType="Timestamp" />
<BDA name="Test" bType="BOOLEAN" />
<BDA name="Check" bType="Check" />
</DAType>
<DAType id="SPCOperate_1">
<BDA name="ctlVal" bType="BOOLEAN" />
<BDA name="origin" type="Originator_1" bType="Struct" />
@ -305,12 +259,21 @@
</DAType>
<DAType id="SPCCancel">
<BDA name="ctlVal" bType="INT8" />
<BDA name="ctlVal" bType="BOOLEAN" />
<BDA name="origin" bType="Struct" type="Originator_1" />
<BDA name="ctlNum" bType="INT8U" />
<BDA name="T" bType="Timestamp" />
<BDA name="Test" bType="BOOLEAN" />
</DAType>
<DAType id="INCOperate_1">
<BDA name="ctlVal" bType="INT32" />
<BDA name="origin" type="Originator_1" bType="Struct" />
<BDA name="ctlNum" bType="INT8U" />
<BDA name="T" bType="Timestamp" />
<BDA name="Test" bType="BOOLEAN" />
<BDA name="Check" bType="Check" />
</DAType>
<EnumType id="Beh">
<EnumVal ord="1">on</EnumVal>
@ -320,6 +283,12 @@
<EnumVal ord="5">off</EnumVal>
</EnumType>
<EnumType id="healthEnum">
<EnumVal ord="1">Ok</EnumVal>
<EnumVal ord="2">Warning</EnumVal>
<EnumVal ord="3">Alarm</EnumVal>
</EnumType>
<EnumType id="dirGeneral">
<EnumVal ord="0">unknown</EnumVal>
<EnumVal ord="1">forward</EnumVal>
@ -330,9 +299,6 @@
<EnumType id="CtlModels">
<EnumVal ord="0">status-only</EnumVal>
<EnumVal ord="1">direct-with-normal-security</EnumVal>
<EnumVal ord="2">sbo-with-normal-security</EnumVal>
<EnumVal ord="3">direct-with-enhanced-security</EnumVal>
<EnumVal ord="4">sbo-with-enhanced-security</EnumVal>
</EnumType>
<EnumType id="OrCat">

@ -0,0 +1,331 @@
<?xml version="1.0" encoding="UTF-8"?>
<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007" revision="B">
<Header id="" version="1" revision="" nameStructure="IEDName">
</Header>
<Communication>
<SubNetwork name="subnetwork1" type="8-MMS">
<Text>Station bus</Text>
<BitRate unit="b/s">10</BitRate>
<ConnectedAP iedName="beagle" apName="accessPoint1">
<Address>
<P type="IP">10.0.0.2</P>
<P type="IP-SUBNET">255.255.255.0</P>
<P type="IP-GATEWAY">10.0.0.1</P>
<P type="OSI-TSEL">0001</P>
<P type="OSI-PSEL">00000001</P>
<P type="OSI-SSEL">0001</P>
</Address>
</ConnectedAP>
</SubNetwork>
</Communication>
<IED name="beagle">
<Services>
<DynAssociation max="5"/>
<GetDirectory />
<DataObjectDirectory />
<GetDataObjectDefinition />
<GetDataSetValue />
<DataSetDirectory />
<ReadWrite />
<GetCBValues />
<ConfDataSet max="1" modify="false" />
<DynDataSet max="5"/>
<ConfReportControl max="5" bufConf="false" />
<ReportSettings cbName="Fix" datSet="Dyn" rptID="Dyn" optFields="Dyn" bufTime="Dyn" trgOps="Dyn" intgPd="Dyn" owner="true"/>
<ConfLNs fixPrefix="true" fixLnInst="true"/>
<ClientServices >
<TimeSyncProt sntp="true" c37_238="false" other="false"/>
</ClientServices>
</Services>
<AccessPoint name="accessPoint1">
<Server>
<Authentication />
<LDevice inst="GenericIO">
<LN0 lnClass="LLN0" lnType="LLN01" inst="">
<DataSet name="Events" desc="Events">
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1" doName="SPCSO1" daName="stVal" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1" doName="SPCSO2" daName="stVal" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1" doName="SPCSO3" daName="stVal" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1" doName="DPCSO1" daName="stVal" />
</DataSet>
<ReportControl name="EventsRCB" confRev="1" datSet="Events" rptID="Events1" buffered="false" intgPd="1000" bufTime="50">
<TrgOps period="true" />
<OptFields seqNum="true" timeStamp="true" dataSet="true" reasonCode="true" entryID="true" configRef="true" />
<RptEnabled max="5" />
</ReportControl>
</LN0>
<LN lnClass="LPHD" lnType="LPHD1" inst="1" prefix="" />
<LN lnClass="GGIO" lnType="GGIO1" inst="1" prefix="">
<DOI name="SPCSO1">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
<DOI name="SPCSO2">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
<DOI name="SPCSO3">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
<DOI name="DPCSO1">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
</LN>
<LN lnClass="GAPC" lnType="TIM_GAPC" inst="1" prefix="TIM_" />
</LDevice>
</Server>
</AccessPoint>
</IED>
<DataTypeTemplates>
<LNodeType id="LLN01" lnClass="LLN0">
<DO name="Beh" type="ENS_Beh" />
<DO name="Mod" type="ENC_Mod_status_only" />
<DO name="Health" type="ENS_Health" />
<DO name="NamPlt" type="LPL_1_NamPlt" />
</LNodeType>
<LNodeType id="LPHD1" lnClass="LPHD">
<DO name="PhyNam" type="DPL_1_PhyNam" />
<DO name="PhyHealth" type="ENS_Health" />
<DO name="Proxy" type="SPS_1_Proxy" />
</LNodeType>
<LNodeType id="GGIO1" lnClass="GGIO">
<DO name="Beh" type="ENS_Beh" />
<DO name="Mod" type="ENC_Mod_status_only" />
<DO name="Health" type="ENS_Health" />
<DO name="NamPlt" type="LPL_2_NamPlt" />
<DO name="AnIn1" type="MV_1_AnIn1" />
<DO name="AnIn2" type="MV_1_AnIn1" />
<DO name="AnIn3" type="MV_1_AnIn1" />
<DO name="AnIn4" type="MV_1_AnIn1" />
<DO name="SPCSO1" type="SPC_2_SPCSO1" />
<DO name="SPCSO2" type="SPC_1_SPCSO2" />
<DO name="SPCSO3" type="SPC_1_SPCSO3" />
<DO name="DPCSO1" type="DPC_1_DPCSO1" />
<DO name="Ind1" type="SPS_1_Proxy" />
<DO name="Ind2" type="SPS_1_Proxy" />
<DO name="Ind3" type="SPS_1_Proxy" />
<DO name="Ind4" type="SPS_1_Proxy" />
</LNodeType>
<LNodeType id="TIM_GAPC" lnClass="GAPC">
<DO name="Beh" type="ENS_Beh" />
<DO name="Mod" type="ENC_Mod_status_only" />
<DO name="Str" type="ACD_Str" />
<DO name="Op" type="ACT_Op" />
<DO name="OpDlTmms" type="ING_EXT" />
<DO name="RsDlTmms" type="ING_EXT" />
<DO name="OpCntRs" type="INC_OpCntRs" />
</LNodeType>
<DOType id="ING_EXT" cdc="ING">
<DA name="setVal" bType="INT32" fc="SP" dchg="true" />
<DA name="dataNs" bType="VisString255" fc="EX">
<Val>EXT:2015</Val>
</DA>
</DOType>
<DOType id="ENS_Beh" cdc="ENS">
<DA name="stVal" fc="ST" bType="Enum" type="Beh" dchg="true" />
<DA name="q" fc="ST" bType="Quality" qchg="true" />
<DA name="t" fc="ST" bType="Timestamp" />
</DOType>
<DOType id="ENS_Health" cdc="ENS">
<DA name="stVal" fc="ST" bType="Enum" type="healthEnum" dchg="true" />
<DA name="q" fc="ST" bType="Quality" qchg="true" />
<DA name="t" fc="ST" bType="Timestamp" />
</DOType>
<DOType id="INC_OpCntRs" cdc="INC">
<DA name="stVal" bType="INT32" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="t" bType="Timestamp" fc="ST" />
<DA name="Oper" type="INCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF">
<Val>direct-with-normal-security</Val>
</DA>
</DOType>
<DOType cdc="ACD" id="ACD_Str">
<DA bType="BOOLEAN" dchg="true" fc="ST" name="general" />
<DA bType="Enum" dchg="true" fc="ST" name="dirGeneral" type="dirGeneral" />
<DA bType="Quality" fc="ST" name="q" qchg="true" />
<DA bType="Timestamp" fc="ST" name="t" />
</DOType>
<DOType cdc="ACT" id="ACT_Op">
<DA bType="BOOLEAN" dchg="true" fc="ST" name="general"/>
<DA bType="Quality" fc="ST" name="q" qchg="true"/>
<DA bType="Timestamp" fc="ST" name="t"/>
</DOType>
<DOType cdc="ENC" id="ENC_Mod_status_only">
<DA bType="Enum" dchg="true" fc="ST" name="stVal" type="Beh"/>
<DA bType="Quality" fc="ST" name="q" qchg="true"/>
<DA bType="Timestamp" fc="ST" name="t"/>
<DA bType="Enum" fc="CF" name="ctlModel" valKind="RO" type="CtlModels">
<Val>status-only</Val>
</DA>
</DOType>
<DOType id="LPL_1_NamPlt" cdc="LPL">
<DA name="vendor" bType="VisString255" fc="DC" />
<DA name="swRev" bType="VisString255" fc="DC" />
<DA name="d" bType="VisString255" fc="DC" />
<DA name="configRev" bType="VisString255" fc="DC" />
<DA name="ldNs" bType="VisString255" fc="EX" />
</DOType>
<DOType id="DPL_1_PhyNam" cdc="DPL">
<DA name="vendor" bType="VisString255" fc="DC" />
</DOType>
<DOType id="SPS_1_Proxy" cdc="SPS">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="LPL_2_NamPlt" cdc="LPL">
<DA name="vendor" bType="VisString255" fc="DC" />
<DA name="swRev" bType="VisString255" fc="DC" />
<DA name="d" bType="VisString255" fc="DC" />
</DOType>
<DOType id="MV_1_AnIn1" cdc="MV">
<DA name="mag" type="AnalogueValue_1" bType="Struct" fc="MX" dchg="true" />
<DA name="q" bType="Quality" fc="MX" qchg="true" />
<DA name="t" bType="Timestamp" fc="MX" />
</DOType>
<DOType id="DPC_1_DPCSO1" cdc="DPC">
<DA name="stVal" bType="Dbpos" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="SPC_2_SPCSO1" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="SPC_1_SPCSO2" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="SBO" fc="CO" bType="VisString129" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="Cancel" fc="CO" bType="Struct" type="SPCCancel" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="SPC_1_SPCSO3" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" valKind="RO" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DAType id="AnalogueValue_1">
<BDA name="f" bType="FLOAT32" />
</DAType>
<DAType id="Originator_1">
<BDA name="orCat" type="OrCat" bType="Enum" />
<BDA name="orIdent" bType="Octet64" />
</DAType>
<DAType id="SPCOperate_1">
<BDA name="ctlVal" bType="BOOLEAN" />
<BDA name="origin" type="Originator_1" bType="Struct" />
<BDA name="ctlNum" bType="INT8U" />
<BDA name="T" bType="Timestamp" />
<BDA name="Test" bType="BOOLEAN" />
<BDA name="Check" bType="Check" />
</DAType>
<DAType id="SPCCancel">
<BDA name="ctlVal" bType="BOOLEAN" />
<BDA name="origin" bType="Struct" type="Originator_1" />
<BDA name="ctlNum" bType="INT8U" />
<BDA name="T" bType="Timestamp" />
<BDA name="Test" bType="BOOLEAN" />
</DAType>
<DAType id="INCOperate_1">
<BDA name="ctlVal" bType="INT32" />
<BDA name="origin" type="Originator_1" bType="Struct" />
<BDA name="ctlNum" bType="INT8U" />
<BDA name="T" bType="Timestamp" />
<BDA name="Test" bType="BOOLEAN" />
<BDA name="Check" bType="Check" />
</DAType>
<EnumType id="Beh">
<EnumVal ord="1">on</EnumVal>
<EnumVal ord="2">blocked</EnumVal>
<EnumVal ord="3">test</EnumVal>
<EnumVal ord="4">test/blocked</EnumVal>
<EnumVal ord="5">off</EnumVal>
</EnumType>
<EnumType id="healthEnum">
<EnumVal ord="1">Ok</EnumVal>
<EnumVal ord="2">Warning</EnumVal>
<EnumVal ord="3">Alarm</EnumVal>
</EnumType>
<EnumType id="dirGeneral">
<EnumVal ord="0">unknown</EnumVal>
<EnumVal ord="1">forward</EnumVal>
<EnumVal ord="2">backward</EnumVal>
<EnumVal ord="3">both</EnumVal>
</EnumType>
<EnumType id="CtlModels">
<EnumVal ord="0">status-only</EnumVal>
<EnumVal ord="1">direct-with-normal-security</EnumVal>
</EnumType>
<EnumType id="OrCat">
<EnumVal ord="0">not-supported</EnumVal>
<EnumVal ord="1">bay-control</EnumVal>
<EnumVal ord="2">station-control</EnumVal>
<EnumVal ord="3">remote-control</EnumVal>
<EnumVal ord="4">automatic-bay</EnumVal>
<EnumVal ord="5">automatic-station</EnumVal>
<EnumVal ord="6">automatic-remote</EnumVal>
<EnumVal ord="7">maintenance</EnumVal>
<EnumVal ord="8">process</EnumVal>
</EnumType>
</DataTypeTemplates>
</SCL>

@ -1,7 +1,7 @@
/*
* static_model.c
*
* automatically generated from beagle_demo.icd
* automatically generated from beagle_demo.iid
*/
#include "static_model.h"
@ -1114,7 +1114,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_SBO = {
NULL,
0,
IEC61850_FC_CO,
IEC61850_VISIBLE_STRING_64,
IEC61850_VISIBLE_STRING_129,
0,
NULL,
0};
@ -1257,7 +1257,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Cancel_ctlVal = {
NULL,
0,
IEC61850_FC_CO,
IEC61850_INT8,
IEC61850_BOOLEAN,
0,
NULL,
0};
@ -2279,7 +2279,7 @@ DataAttribute iedModel_GenericIO_TIM_GAPC1_OpCntRs_Oper_ctlVal = {
NULL,
0,
IEC61850_FC_CO,
IEC61850_BOOLEAN,
IEC61850_INT32,
0,
NULL,
0};
@ -2404,6 +2404,8 @@ ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0,
IedModel iedModel = {
"beagle",
&iedModel_GenericIO,
@ -2412,6 +2414,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};
@ -2425,11 +2429,11 @@ iedModel_GenericIO_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0)
iedModel_GenericIO_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1);
iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(2);
iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1);
iedModel_GenericIO_GGIO1_SPCSO3_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1);
iedModel_GenericIO_GGIO1_DPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(3);
iedModel_GenericIO_GGIO1_DPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1);
iedModel_GenericIO_TIM_GAPC1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0);

@ -1,7 +1,7 @@
/*
* static_model.h
*
* automatically generated from beagle_demo.icd
* automatically generated from beagle_demo.iid
*/
#ifndef STATIC_MODEL_H_

@ -10,6 +10,7 @@ add_subdirectory(server_example_complex_array)
add_subdirectory(server_example_threadless)
add_subdirectory(server_example_61400_25)
add_subdirectory(server_example_setting_groups)
add_subdirectory(server_example_logging)
add_subdirectory(iec61850_client_example1)
add_subdirectory(iec61850_client_example2)
add_subdirectory(iec61850_client_example3)
@ -17,6 +18,7 @@ add_subdirectory(iec61850_client_example4)
add_subdirectory(iec61850_client_example5)
add_subdirectory(iec61850_client_example_files)
add_subdirectory(iec61850_client_example_reporting)
add_subdirectory(iec61850_client_example_log)
add_subdirectory(mms_client_example1)
add_subdirectory(mms_client_example2)
add_subdirectory(mms_client_example3)

@ -10,6 +10,7 @@ EXAMPLE_DIRS += iec61850_client_example3
EXAMPLE_DIRS += iec61850_client_example4
EXAMPLE_DIRS += iec61850_client_example5
EXAMPLE_DIRS += iec61850_client_example_reporting
EXAMPLE_DIRS += iec61850_client_example_log
EXAMPLE_DIRS += server_example1
EXAMPLE_DIRS += server_example2
EXAMPLE_DIRS += server_example3
@ -23,6 +24,7 @@ EXAMPLE_DIRS += server_example_complex_array
EXAMPLE_DIRS += server_example_61400_25
EXAMPLE_DIRS += server_example_threadless
EXAMPLE_DIRS += server_example_setting_groups
EXAMPLE_DIRS += server_example_logging
EXAMPLE_DIRS += goose_subscriber
EXAMPLE_DIRS += goose_publisher
EXAMPLE_DIRS += sv_subscriber
@ -44,6 +46,7 @@ MODEL_DIRS += server_example_complex_array
MODEL_DIRS += server_example_61400_25
MODEL_DIRS += server_example_threadless
MODEL_DIRS += server_example_setting_groups
MODEL_DIRS += server_example_logging
MODEL_DIRS += iec61850_9_2_LE_example
all: examples

@ -12,7 +12,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <inttypes.h>
static int running = 1;
@ -28,7 +28,7 @@ gooseListener(GooseSubscriber subscriber, void* parameter)
printf(" stNum: %u sqNum: %u\n", GooseSubscriber_getStNum(subscriber),
GooseSubscriber_getSqNum(subscriber));
printf(" timeToLive: %u\n", GooseSubscriber_getTimeAllowedToLive(subscriber));
printf(" timestamp: %llu\n", GooseSubscriber_getTimestamp(subscriber));
printf(" timestamp: %"PRIu64"\n", GooseSubscriber_getTimestamp(subscriber));
MmsValue* values = GooseSubscriber_getDataSetValues(subscriber);

@ -947,6 +947,8 @@ SVControlBlock iedModel_MUnn_LLN0_smv0 = {&iedModel_MUnn_LLN0, "MSVCB01", "xxxxM
IedModel iedModel = {
"TEMPLATE",
&iedModel_MUnn,
@ -955,6 +957,8 @@ IedModel iedModel = {
NULL,
&iedModel_MUnn_LLN0_smv0,
NULL,
NULL,
NULL,
initializeValues
};

@ -0,0 +1,17 @@
set(iec61850_client_example_log_SRCS
client_example_log.c
)
IF(MSVC)
set_source_files_properties(${iec61850_client_example_log_SRCS}
PROPERTIES LANGUAGE CXX)
ENDIF(MSVC)
add_executable(iec61850_client_example_log
${iec61850_client_example_log_SRCS}
)
target_link_libraries(iec61850_client_example_log
iec61850
)

@ -0,0 +1,17 @@
LIBIEC_HOME=../..
PROJECT_BINARY_NAME = client_example_log
PROJECT_SOURCES = client_example_log.c
include $(LIBIEC_HOME)/make/target_system.mk
include $(LIBIEC_HOME)/make/stack_includes.mk
all: $(PROJECT_BINARY_NAME)
include $(LIBIEC_HOME)/make/common_targets.mk
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
$(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
clean:
rm -f $(PROJECT_BINARY_NAME)

@ -0,0 +1,133 @@
/*
* client_example_log.c
*
* This example is intended to be used with server_example_logging
*/
#include "iec61850_client.h"
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
//#include "hal_thread.h"
static void
printJournalEntries(LinkedList journalEntries)
{
char buf[1024];
LinkedList journalEntriesElem = LinkedList_getNext(journalEntries);
while (journalEntriesElem != NULL) {
MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem);
MmsValue_printToBuffer(MmsJournalEntry_getEntryID(journalEntry), buf, 1024);
printf("EntryID: %s\n", buf);
MmsValue_printToBuffer(MmsJournalEntry_getOccurenceTime(journalEntry), buf, 1024);
printf(" occurence time: %s\n", buf);
LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables);
while (journalVariableElem != NULL) {
MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem);
printf(" variable-tag: %s\n", MmsJournalVariable_getTag(journalVariable));
MmsValue_printToBuffer(MmsJournalVariable_getValue(journalVariable), buf, 1024);
printf(" variable-value: %s\n", buf);
journalVariableElem = LinkedList_getNext(journalVariableElem);
}
journalEntriesElem = LinkedList_getNext(journalEntriesElem);
}
}
int main(int argc, char** argv) {
char* hostname;
int tcpPort = 102;
if (argc > 1)
hostname = argv[1];
else
hostname = "localhost";
if (argc > 2)
tcpPort = atoi(argv[2]);
char* logRef = "simpleIOGenericIO/LLN0$EventLog";
IedClientError error;
IedConnection con = IedConnection_create();
IedConnection_connect(con, &error, hostname, tcpPort);
if (error == IED_ERROR_OK) {
/* read list of logs in LN (optional - if you don't know the existing logs) */
LinkedList logs = IedConnection_getLogicalNodeDirectory(con, &error, "simpleIOGenericIO/LLN0", ACSI_CLASS_LOG);
if (error == IED_ERROR_OK) {
printf("Found log in LN simpleIOGenericIO/LLN0:\n");
LinkedList log = LinkedList_getNext(logs);
while (log != NULL) {
char* logName = (char*) LinkedList_getData(log);
printf(" %s\n", logName);
log = LinkedList_getNext(log);
}
LinkedList_destroy(logs);
}
/* read log control block (using the generic read function) */
MmsValue* lcbValue = IedConnection_readObject(con, &error, "simpleIOGenericIO/LLN0.EventLog", IEC61850_FC_LG);
if (error == IED_ERROR_OK) {
MmsValue* oldEntryTm = MmsValue_getElement(lcbValue, 3);
MmsValue* oldEntry = MmsValue_getElement(lcbValue, 5);
uint64_t timestamp = MmsValue_getUtcTimeInMs(oldEntryTm);
bool moreFollows;
/*
* read the log contents. Be aware that the logRef uses the '$' sign as separator between the LN and
* the log name! This is in contrast to the LCB object reference above.
*/
LinkedList logEntries = IedConnection_queryLogAfter(con, &error, "simpleIOGenericIO/LLN0$EventLog", oldEntry, timestamp, &moreFollows);
if (error == IED_ERROR_OK) {
printJournalEntries(logEntries);
LinkedList_destroyDeep(logEntries, (LinkedListValueDeleteFunction) MmsJournalEntry_destroy);
}
else
printf("QueryLog failed (error code: %i)!\n", error);
//TODO handle moreFollows
MmsValue_delete(lcbValue);
}
else
printf("Read LCB failed!\n");
IedConnection_abort(con, &error);
}
else {
printf("Failed to connect to %s:%i\n", hostname, tcpPort);
}
IedConnection_destroy(con);
}

@ -20,6 +20,7 @@ print_help()
printf("-a <domain_name> specify domain for read or write command\n");
printf("-f show file list\n");
printf("-g <filename> get file attributes\n");
printf("-j <domainName/journalName> read journal\n");
}
static void
@ -43,6 +44,39 @@ mmsGetFileAttributeHandler (void* parameter, char* filename, uint32_t size, uint
printf("DATE: %s\n", gtString);
}
static void
printJournalEntries(LinkedList journalEntries)
{
char buf[1024];
LinkedList journalEntriesElem = LinkedList_getNext(journalEntries);
while (journalEntriesElem != NULL) {
MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem);
MmsValue_printToBuffer(MmsJournalEntry_getEntryID(journalEntry), buf, 1024);
printf("EntryID: %s\n", buf);
MmsValue_printToBuffer(MmsJournalEntry_getOccurenceTime(journalEntry), buf, 1024);
printf(" occurence time: %s\n", buf);
LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables);
while (journalVariableElem != NULL) {
MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem);
printf(" variable-tag: %s\n", MmsJournalVariable_getTag(journalVariable));
MmsValue_printToBuffer(MmsJournalVariable_getValue(journalVariable), buf, 1024);
printf(" variable-value: %s\n", buf);
journalVariableElem = LinkedList_getNext(journalVariableElem);
}
journalEntriesElem = LinkedList_getNext(journalEntriesElem);
}
}
int main(int argc, char** argv) {
@ -53,6 +87,7 @@ int main(int argc, char** argv) {
char* domainName = NULL;
char* variableName = NULL;
char* filename = NULL;
char* journalName = NULL;
int readDeviceList = 0;
int getDeviceDirectory = 0;
@ -61,11 +96,11 @@ int main(int argc, char** argv) {
int readVariable = 0;
int showFileList = 0;
int getFileAttributes = 0;
int readJournal = 0;
int c;
while ((c = getopt(argc, argv, "ifdh:p:l:t:a:r:g:")) != -1)
while ((c = getopt(argc, argv, "ifdh:p:l:t:a:r:g:j:")) != -1)
switch (c) {
case 'h':
hostname = copyString(optarg);
@ -102,6 +137,11 @@ int main(int argc, char** argv) {
filename = copyString(optarg);
break;
case 'j':
readJournal = 1;
journalName = copyString(optarg);
break;
default:
print_help();
return 0;
@ -146,16 +186,102 @@ int main(int argc, char** argv) {
LinkedList variableList = MmsConnection_getDomainVariableNames(con, &error,
domainName);
LinkedList element = variableList;
LinkedList element = LinkedList_getNext(variableList);
printf("\nMMS domain variables for domain %s\n", domainName);
while ((element = LinkedList_getNext(element)) != NULL) {
while (element != NULL) {
char* name = (char*) element->data;
printf(" %s\n", name);
element = LinkedList_getNext(element);
}
LinkedList_destroy(variableList);
variableList = MmsConnection_getDomainJournals(con, &error, domainName);
if (variableList != NULL) {
element = variableList;
printf("\nMMS journals for domain %s\n", domainName);
while ((element = LinkedList_getNext(element)) != NULL) {
char* name = (char*) element->data;
printf(" %s\n", name);
}
LinkedList_destroy(variableList);
}
}
if (readJournal) {
printf(" read journal %s...\n", journalName);
char* logDomain = journalName;
char* logName = strchr(journalName, '/');
if (logName != NULL) {
logName[0] = 0;
logName++;
uint64_t timestamp = Hal_getTimeInMs();
MmsValue* startTime = MmsValue_newBinaryTime(false);
MmsValue_setBinaryTime(startTime, timestamp - 6000000000);
MmsValue* endTime = MmsValue_newBinaryTime(false);
MmsValue_setBinaryTime(endTime, timestamp);
bool moreFollows;
LinkedList journalEntries = MmsConnection_readJournalTimeRange(con, &error, logDomain, logName, startTime, endTime,
&moreFollows);
MmsValue_delete(startTime);
MmsValue_delete(endTime);
if (journalEntries != NULL) {
bool readNext;
do {
readNext = false;
LinkedList lastEntry = LinkedList_getLastElement(journalEntries);
MmsJournalEntry lastJournalEntry = (MmsJournalEntry) LinkedList_getData(lastEntry);
MmsValue* nextEntryId = MmsValue_clone(MmsJournalEntry_getEntryID(lastJournalEntry));
MmsValue* nextTimestamp = MmsValue_clone(MmsJournalEntry_getOccurenceTime(lastJournalEntry));
printJournalEntries(journalEntries);
LinkedList_destroyDeep(journalEntries, (LinkedListValueDeleteFunction)
MmsJournalEntry_destroy);
if (moreFollows) {
char buf[100];
MmsValue_printToBuffer(nextEntryId, buf, 100);
printf("READ NEXT AFTER entryID: %s ...\n", buf);
journalEntries = MmsConnection_readJournalStartAfter(con, &error, logDomain, logName, nextTimestamp, nextEntryId, &moreFollows);
MmsValue_delete(nextEntryId);
MmsValue_delete(nextTimestamp);
readNext = true;
}
} while ((moreFollows == true) || (readNext == true));
}
}
else
printf(" Invalid log name!\n");
}
if (readVariable) {

@ -49,8 +49,6 @@ int main(int argc, char** argv) {
IedServer iedServer = IedServer_create(&iedModel);
// get stored values from persistent storage
// set initial measurement and status values from process
/* MMS server will be instructed to start listening to client connections. */

@ -1596,6 +1596,8 @@ ReportControlBlock iedModel_Device1_LLN0_report0 = {&iedModel_Device1_LLN0, "LLN
IedModel iedModel = {
"SampleIED",
&iedModel_Device1,
@ -1604,6 +1606,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -3586,6 +3586,8 @@ ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "r
IedModel iedModel = {
"ied1",
&iedModel_Inverter,
@ -3594,6 +3596,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -85,7 +85,15 @@
<LogControl name="EventLog" datSet="Events" logName="EventLog">
<TrgOps dchg="true" qchg="true"/>
</LogControl>
</LogControl>
<LogControl name="GeneralLog" datSet="" logName="">
<TrgOps dchg="true" qchg="true"/>
</LogControl>
<Log />
<Log name="EventLog" />
<DOI name="Mod">
<DAI name="ctlModel">

@ -1955,6 +1955,16 @@ ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0,
extern LogControlBlock iedModel_GenericIO_LLN0_lcb0;
extern LogControlBlock iedModel_GenericIO_LLN0_lcb1;
LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, &iedModel_GenericIO_LLN0_lcb1};
LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", NULL, NULL, 3, 0, true, true, NULL};
extern Log iedModel_GenericIO_LLN0_log0;
extern Log iedModel_GenericIO_LLN0_log1;
Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1};
Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL};
IedModel iedModel = {
"simpleIO",
@ -1964,6 +1974,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
&iedModel_GenericIO_LLN0_lcb0,
&iedModel_GenericIO_LLN0_log0,
initializeValues
};

@ -1779,6 +1779,8 @@ ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0,
IedModel iedModel = {
"simpleIO",
&iedModel_GenericIO,
@ -1787,6 +1789,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -3586,6 +3586,8 @@ ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "r
IedModel iedModel = {
"ied1",
&iedModel_Inverter,
@ -3594,6 +3596,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -4134,6 +4134,8 @@ DataAttribute iedModel_WTG_WTUR1_SetTurOp_cmAcs = {
IedModel iedModel = {
"WIND",
&iedModel_WTG,
@ -4142,6 +4144,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -544,6 +544,8 @@ DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency = {
IedModel iedModel = {
"test",
&iedModel_ComplexArray,
@ -552,6 +554,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -1,8 +1,3 @@
Dynamic model generator
parse data type templates ...
parse IED section ...
parse communication section ...
Found connectedAP accessPoint1 for IED simpleIO
MODEL(simpleIO){
LD(GenericIO){
LN(LLN0){
@ -42,6 +37,10 @@ DE(GGIO1$MX$AnIn4);
}
RC(EventsRCB01 Events 0 Events 1 24 111 50 1000);
RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 24 111 50 1000);
LC(EventLog Events GenericIO/LLN0$EventLog 19 0 0 1)
LC(GeneralLog - - 19 0 0 1)
LOG(GeneralLog)
LOG(EventLog)
GC(gcbEvents events Events 2 0 -1 -1 ){
PA(4 273 4096 010ccd010001);
}

@ -67,7 +67,19 @@
<TrgOps period="true" />
<OptFields seqNum="true" timeStamp="true" dataSet="true" reasonCode="true" entryID="true" configRef="true" />
<RptEnabled max="1" />
</ReportControl>
</ReportControl>
<LogControl name="EventLog" datSet="Events" logName="EventLog" logEna="false">
<TrgOps dchg="true" qchg="true" />
</LogControl>
<LogControl name="GeneralLog" datSet="" logName="" logEna="false">
<TrgOps dchg="true" qchg="true" />
</LogControl>
<Log />
<Log name="EventLog" />
<GSEControl appID="events" name="gcbEvents" type="GOOSE" datSet="Events" confRev="2"/>
<GSEControl appID="analog" name="gcbAnalogValues" type="GOOSE" datSet="AnalogValues" confRev="2"/>
<DOI name="Mod">

@ -3874,6 +3874,8 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = {
IedModel iedModel = {
"simpleIO",
&iedModel_GenericIO,
@ -3882,6 +3884,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -1909,6 +1909,8 @@ GSEControlBlock iedModel_GenericIO_LLN0_gse1 = {&iedModel_GenericIO_LLN0, "gcbAn
IedModel iedModel = {
"simpleIO",
&iedModel_GenericIO,
@ -1917,6 +1919,8 @@ IedModel iedModel = {
&iedModel_GenericIO_LLN0_gse0,
NULL,
NULL,
NULL,
NULL,
initializeValues
};

@ -0,0 +1,41 @@
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/sqlite/sqlite3.h")
message("Found sqlite source code -> compile sqlite-log driver with static sqlite library")
include_directories(
.
${CMAKE_SOURCE_DIR}/third_party/sqlite
)
set(server_example_SRCS
server_example_logging.c
static_model.c
${CMAKE_SOURCE_DIR}/src/logging/drivers/sqlite/log_storage_sqlite.c
)
set(sqlite_SRCS
${CMAKE_SOURCE_DIR}/third_party/sqlite/sqlite3.c
)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION")
IF(WIN32)
set_source_files_properties(${server_example_SRCS}
PROPERTIES LANGUAGE CXX)
ENDIF(WIN32)
add_executable(server_example_logging
${server_example_SRCS}
${sqlite_SRCS}
)
target_link_libraries(server_example_logging
iec61850
)
ELSE()
message("server-example-logging: sqlite not found")
ENDIF()

@ -0,0 +1,30 @@
LIBIEC_HOME=../..
PROJECT_BINARY_NAME = server_example_logging
PROJECT_SOURCES = server_example_logging.c
PROJECT_SOURCES += static_model.c
PROJECT_SOURCES += $(LIBIEC_HOME)/src/logging/drivers/sqlite/log_storage_sqlite.c
PROJECT_ICD_FILE = simpleIO_direct_control.icd
include $(LIBIEC_HOME)/make/target_system.mk
include $(LIBIEC_HOME)/make/stack_includes.mk
all: $(PROJECT_BINARY_NAME)
include $(LIBIEC_HOME)/make/common_targets.mk
LDLIBS += -lm -lsqlite3
CP = cp
model: $(PROJECT_ICD_FILE)
java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE)
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
$(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
clean:
rm -f $(PROJECT_BINARY_NAME)

@ -0,0 +1,34 @@
LIBIEC_HOME=../..
PROJECT_BINARY_NAME = server_example_logging
PROJECT_SOURCES = server_example_logging.c
PROJECT_SOURCES += static_model.c
PROJECT_SOURCES += $(LIBIEC_HOME)/src/logging/drivers/sqlite/log_storage_sqlite.c
PROJECT_SOURCES += $(LIBIEC_HOME)/third_party/sqlite/sqlite3.c
PROJECT_ICD_FILE = simpleIO_direct_control.icd
include $(LIBIEC_HOME)/make/target_system.mk
include $(LIBIEC_HOME)/make/stack_includes.mk
all: $(PROJECT_BINARY_NAME)
include $(LIBIEC_HOME)/make/common_targets.mk
CFLAGS += -I$(LIBIEC_HOME)/third_party/sqlite
CFLAGS += -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DHAVE_USLEEP
LDLIBS += -lm
CP = cp
model: $(PROJECT_ICD_FILE)
java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE)
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
$(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
clean:
rm -f $(PROJECT_BINARY_NAME)

@ -0,0 +1,8 @@
BUILD THE EXAMPLE:
To build the logging example it is required to have sqlite present!
If you have sqlite installed on the system (including the header files) e.g. in an Ubuntu installation with the sqlite3 package installed, you can simply use the Makefile.
If you don't have sqlite installed you have to download the sqlite amalgamation package and install the files sqlite3.c, sqlite3.h in the libiec61850/third_party/sqlite folder and use the Makefile.sqliteStatic instead. This will build a version of the example with the sqlite code statically linked.

@ -0,0 +1,234 @@
/*
* server_example_logging.c
*
* - How to use a server with logging service
* - How to store arbitrary data in a log
*
*/
#include "iec61850_server.h"
#include "hal_thread.h"
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "static_model.h"
#include "logging_api.h"
LogStorage SqliteLogStorage_createInstance(const char*);
/* import IEC 61850 device model created from SCL-File */
extern IedModel iedModel;
static int running = 0;
static IedServer iedServer = NULL;
void
sigint_handler(int signalId)
{
running = 0;
}
static ControlHandlerResult
controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test)
{
if (test)
return CONTROL_RESULT_FAILED;
if (MmsValue_getType(value) == MMS_BOOLEAN) {
printf("received binary control command: ");
if (MmsValue_getBoolean(value))
printf("on\n");
else
printf("off\n");
}
else
return CONTROL_RESULT_FAILED;
uint64_t timeStamp = Hal_getTimeInMs();
if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO1) {
IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_t, timeStamp);
IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal, value);
}
if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO2) {
IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_t, timeStamp);
IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal, value);
}
if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO3) {
IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_t, timeStamp);
IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal, value);
}
if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO4) {
IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_t, timeStamp);
IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal, value);
}
return CONTROL_RESULT_OK;
}
static void
connectionHandler (IedServer self, ClientConnection connection, bool connected, void* parameter)
{
if (connected)
printf("Connection opened\n");
else
printf("Connection closed\n");
}
static bool
entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow)
{
#if 0
if (moreFollow)
printf("Found entry ID:%llu timestamp:%llu\n", entryID, timestamp);
#endif
return true;
}
static bool
entryDataCallback (void* parameter, const char* dataRef, const uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow)
{
#if 0
if (moreFollow) {
printf(" EntryData: ref: %s\n", dataRef);
MmsValue* value = MmsValue_decodeMmsData(data, 0, dataSize);
char buffer[256];
MmsValue_printToBuffer(value, buffer, 256);
printf(" value: %s\n", buffer);
MmsValue_delete(value);
}
#endif
return true;
}
int
main(int argc, char** argv)
{
printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString());
iedServer = IedServer_create(&iedModel);
/* Install handler for operate command */
IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1,
(ControlHandler) controlHandlerForBinaryOutput,
IEDMODEL_GenericIO_GGIO1_SPCSO1);
IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2,
(ControlHandler) controlHandlerForBinaryOutput,
IEDMODEL_GenericIO_GGIO1_SPCSO2);
IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3,
(ControlHandler) controlHandlerForBinaryOutput,
IEDMODEL_GenericIO_GGIO1_SPCSO3);
IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4,
(ControlHandler) controlHandlerForBinaryOutput,
IEDMODEL_GenericIO_GGIO1_SPCSO4);
IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL);
LogStorage statusLog = SqliteLogStorage_createInstance("log_status.db");
IedServer_setLogStorage(iedServer, "GenericIO/LLN0$EventLog", statusLog);
uint64_t entryID = LogStorage_addEntry(statusLog, Hal_getTimeInMs());
MmsValue* value = MmsValue_newIntegerFromInt32(123);
uint8_t blob[256];
int blobSize = MmsValue_encodeMmsData(value, blob, 0, true);
LogStorage_addEntryData(statusLog, entryID, "simpleIOGenerioIO/GPIO1$ST$SPCSO1$stVal", blob, blobSize, 0);
MmsValue_delete(value);
value = MmsValue_newUtcTimeByMsTime(Hal_getTimeInMs());
blobSize = MmsValue_encodeMmsData(value, blob, 0, true);
MmsValue_delete(value);
LogStorage_addEntryData(statusLog, entryID, "simpleIOGenerioIO/GPIO1$ST$SPCSO1$t", blob, blobSize, 0);
LogStorage_getEntries(statusLog, 0, Hal_getTimeInMs(), entryCallback, (LogEntryDataCallback) entryDataCallback, NULL);
/* MMS server will be instructed to start listening to client connections. */
IedServer_start(iedServer, 102);
if (!IedServer_isRunning(iedServer)) {
printf("Starting server failed! Exit.\n");
IedServer_destroy(iedServer);
exit(-1);
}
running = 1;
signal(SIGINT, sigint_handler);
float t = 0.f;
while (running) {
uint64_t timestamp = Hal_getTimeInMs();
t += 0.1f;
float an1 = sinf(t);
float an2 = sinf(t + 1.f);
float an3 = sinf(t + 2.f);
float an4 = sinf(t + 3.f);
IedServer_lockDataModel(iedServer);
Timestamp iecTimestamp;
Timestamp_clearFlags(&iecTimestamp);
Timestamp_setTimeInMilliseconds(&iecTimestamp, timestamp);
Timestamp_setLeapSecondKnown(&iecTimestamp, true);
/* toggle clock-not-synchronized flag in timestamp */
if (((int) t % 2) == 0)
Timestamp_setClockNotSynchronized(&iecTimestamp, true);
IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_t, &iecTimestamp);
IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f, an1);
IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_t, &iecTimestamp);
IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f, an2);
IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_t, &iecTimestamp);
IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f, an3);
IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_t, &iecTimestamp);
IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f, an4);
IedServer_unlockDataModel(iedServer);
Thread_sleep(100);
}
/* stop MMS server - close TCP server socket and all client sockets */
IedServer_stop(iedServer);
/* Cleanup - free all resources */
IedServer_destroy(iedServer);
/* Release connection to database and free resources */
LogStorage_destroy(statusLog);
} /* main() */

@ -0,0 +1,306 @@
<?xml version="1.0" encoding="UTF-8"?>
<SCL xmlns="http://www.iec.ch/61850/2003/SCL">
<Header id="" nameStructure="IEDName">
</Header>
<Communication>
<SubNetwork name="subnetwork1" type="8-MMS">
<Text>Station bus</Text>
<BitRate unit="b/s">10</BitRate>
<ConnectedAP iedName="simpleIO" apName="accessPoint1">
<Address>
<P type="IP">10.0.0.2</P>
<P type="IP-SUBNET">255.255.255.0</P>
<P type="IP-GATEWAY">10.0.0.1</P>
<P type="OSI-TSEL">0001</P>
<P type="OSI-PSEL">00000001</P>
<P type="OSI-SSEL">0001</P>
</Address>
</ConnectedAP>
</SubNetwork>
</Communication>
<IED name="simpleIO">
<Services>
<DynAssociation />
<GetDirectory />
<GetDataObjectDefinition />
<GetDataSetValue />
<DataSetDirectory />
<ReadWrite />
<GetCBValues />
<ConfLNs fixPrefix="true" fixLnInst="true" />
<GOOSE max="5" />
<GSSE max="5" />
<FileHandling />
<GSEDir />
<TimerActivatedControl />
</Services>
<AccessPoint name="accessPoint1">
<Server>
<Authentication />
<LDevice inst="GenericIO">
<LN0 lnClass="LLN0" lnType="LLN01" inst="">
<DataSet name="Events" desc="Events">
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO1" daName="stVal" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO2" daName="stVal" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO3" daName="stVal" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO4" daName="stVal" />
</DataSet>
<DataSet name="Events2" desc="Events2">
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO1" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO2" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO3" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="ST" lnInst="1"
doName="SPCSO4" />
</DataSet>
<DataSet name="Measurements" desc="Measurements">
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn1" daName="mag.f" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn1" daName="q" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn2" daName="mag.f" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn2" daName="q" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn3" daName="mag.f" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn3" daName="q" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn4" daName="mag.f" />
<FCDA ldInst="GenericIO" lnClass="GGIO" fc="MX" lnInst="1"
doName="AnIn4" daName="q" />
</DataSet>
<ReportControl name="EventsRCB" confRev="4294967295"
datSet="Events" rptID="Events1" buffered="false" intgPd="1000"
bufTime="50">
<TrgOps period="true" />
<OptFields seqNum="true" timeStamp="true" dataSet="true"
reasonCode="true" entryID="true" configRef="true" />
<RptEnabled max="1" />
</ReportControl>
<ReportControl name="EventsIndexed" indexed="true"
confRev="1" datSet="Events" rptID="Events2" buffered="false"
intgPd="1000" bufTime="50">
<TrgOps period="true" />
<OptFields seqNum="true" timeStamp="true" dataSet="true"
reasonCode="true" entryID="true" configRef="true" />
<RptEnabled max="3" />
</ReportControl>
<ReportControl name="Measurements" indexed="true"
confRev="1" datSet="Measurements" rptID="Measurements" buffered="true"
intgPd="1000" bufTime="50">
<TrgOps period="false" />
<OptFields seqNum="true" timeStamp="true" dataSet="true"
reasonCode="true" entryID="true" configRef="true" />
<RptEnabled max="3" />
</ReportControl>
<LogControl name="EventLog" datSet="Events" logName="EventLog">
<TrgOps dchg="true" qchg="true" />
</LogControl>
<LogControl name="GeneralLog" datSet="" logName="">
<TrgOps dchg="true" qchg="true" />
</LogControl>
<Log />
<Log name="EventLog" />
<DOI name="Mod">
<DAI name="ctlModel">
<Val>status-only</Val>
</DAI>
</DOI>
<DOI name="NamPlt">
<DAI name="vendor">
<Val>MZ Automation</Val>
</DAI>
<DAI name="swRev">
<Val>0.7.3</Val>
</DAI>
<DAI name="d">
<Val>libiec61850 server example</Val>
</DAI>
</DOI>
</LN0>
<LN lnClass="LPHD" lnType="LPHD1" inst="1" prefix="" />
<LN lnClass="GGIO" lnType="GGIO1" inst="1" prefix="">
<DOI name="Mod">
<DAI name="ctlModel">
<Val>status-only</Val>
</DAI>
</DOI>
<DOI name="SPCSO1">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
<DOI name="SPCSO2">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
<DOI name="SPCSO3">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
<DOI name="SPCSO4">
<DAI name="ctlModel">
<Val>direct-with-normal-security</Val>
</DAI>
</DOI>
</LN>
</LDevice>
</Server>
</AccessPoint>
</IED>
<DataTypeTemplates>
<LNodeType id="LLN01" lnClass="LLN0">
<DO name="Mod" type="INC_1_Mod" />
<DO name="Beh" type="INS_1_Beh" />
<DO name="Health" type="INS_1_Beh" />
<DO name="NamPlt" type="LPL_1_NamPlt" />
</LNodeType>
<LNodeType id="LPHD1" lnClass="LPHD">
<DO name="PhyNam" type="DPL_1_PhyNam" />
<DO name="PhyHealth" type="INS_1_Beh" />
<DO name="Proxy" type="SPS_1_Proxy" />
</LNodeType>
<LNodeType id="GGIO1" lnClass="GGIO">
<DO name="Mod" type="INC_2_Mod" />
<DO name="Beh" type="INS_1_Beh" />
<DO name="Health" type="INS_1_Beh" />
<DO name="NamPlt" type="LPL_2_NamPlt" />
<DO name="AnIn1" type="MV_1_AnIn1" />
<DO name="AnIn2" type="MV_1_AnIn1" />
<DO name="AnIn3" type="MV_1_AnIn1" />
<DO name="AnIn4" type="MV_1_AnIn1" />
<DO name="SPCSO1" type="SPC_2_SPCSO1" />
<DO name="SPCSO2" type="SPC_1_SPCSO2" />
<DO name="SPCSO3" type="SPC_1_SPCSO3" />
<DO name="SPCSO4" type="SPC_1_SPCSO1" />
<DO name="Ind1" type="SPS_1_Proxy" />
<DO name="Ind2" type="SPS_1_Proxy" />
<DO name="Ind3" type="SPS_1_Proxy" />
<DO name="Ind4" type="SPS_1_Proxy" />
</LNodeType>
<DOType id="INC_1_Mod" cdc="INC">
<DA name="stVal" bType="INT32" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="t" bType="Timestamp" fc="ST" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
</DOType>
<DOType id="INS_1_Beh" cdc="INS">
<DA name="stVal" bType="INT32" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="LPL_1_NamPlt" cdc="LPL">
<DA name="vendor" bType="VisString255" fc="DC" />
<DA name="swRev" bType="VisString255" fc="DC" />
<DA name="d" bType="VisString255" fc="DC" />
<DA name="configRev" bType="VisString255" fc="DC" />
<DA name="ldNs" bType="VisString255" fc="EX" />
</DOType>
<DOType id="DPL_1_PhyNam" cdc="DPL">
<DA name="vendor" bType="VisString255" fc="DC" />
</DOType>
<DOType id="SPS_1_Proxy" cdc="SPS">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="LPL_2_NamPlt" cdc="LPL">
<DA name="vendor" bType="VisString255" fc="DC" />
<DA name="swRev" bType="VisString255" fc="DC" />
<DA name="d" bType="VisString255" fc="DC" />
</DOType>
<DOType id="MV_1_AnIn1" cdc="MV">
<DA name="mag" type="AnalogueValue_1" bType="Struct" fc="MX" dchg="true" />
<DA name="q" bType="Quality" fc="MX" qchg="true" />
<DA name="t" bType="Timestamp" fc="MX" />
</DOType>
<DOType id="SPC_1_SPCSO1" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="INC_2_Mod" cdc="INC">
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="t" bType="Timestamp" fc="ST" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
</DOType>
<DOType id="SPC_2_SPCSO1" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="SPC_1_SPCSO2" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DOType id="SPC_1_SPCSO3" cdc="SPC">
<DA name="stVal" bType="BOOLEAN" fc="ST" dchg="true" />
<DA name="q" bType="Quality" fc="ST" qchg="true" />
<DA name="Oper" type="SPCOperate_1" bType="Struct" fc="CO" />
<DA name="ctlModel" type="CtlModels" bType="Enum" fc="CF" />
<DA name="t" bType="Timestamp" fc="ST" />
</DOType>
<DAType id="AnalogueValue_1">
<BDA name="f" bType="FLOAT32" />
</DAType>
<DAType id="Originator_1">
<BDA name="orCat" type="OrCat" bType="Enum" />
<BDA name="orIdent" bType="Octet64" />
</DAType>
<DAType id="SPCOperate_1">
<BDA name="ctlVal" bType="BOOLEAN" />
<BDA name="origin" type="Originator_1" bType="Struct" />
<BDA name="ctlNum" bType="INT8U" />
<BDA name="T" bType="Timestamp" />
<BDA name="Test" bType="BOOLEAN" />
<BDA name="Check" bType="Check" />
</DAType>
<EnumType id="CtlModels">
<EnumVal ord="0">status-only</EnumVal>
<EnumVal ord="1">direct-with-normal-security</EnumVal>
<EnumVal ord="2">sbo-with-normal-security</EnumVal>
<EnumVal ord="3">direct-with-enhanced-security</EnumVal>
<EnumVal ord="4">sbo-with-enhanced-security</EnumVal>
</EnumType>
<EnumType id="OrCat">
<EnumVal ord="0">not-supported</EnumVal>
<EnumVal ord="1">bay-control</EnumVal>
<EnumVal ord="2">station-control</EnumVal>
<EnumVal ord="3">remote-control</EnumVal>
<EnumVal ord="4">automatic-bay</EnumVal>
<EnumVal ord="5">automatic-station</EnumVal>
<EnumVal ord="6">automatic-remote</EnumVal>
<EnumVal ord="7">maintenance</EnumVal>
<EnumVal ord="8">process</EnumVal>
</EnumType>
</DataTypeTemplates>
</SCL>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,301 @@
/*
* static_model.h
*
* automatically generated from simpleIO_direct_control.icd
*/
#ifndef STATIC_MODEL_H_
#define STATIC_MODEL_H_
#include <stdlib.h>
#include "iec61850_model.h"
extern IedModel iedModel;
extern LogicalDevice iedModel_GenericIO;
extern LogicalNode iedModel_GenericIO_LLN0;
extern DataObject iedModel_GenericIO_LLN0_Mod;
extern DataAttribute iedModel_GenericIO_LLN0_Mod_stVal;
extern DataAttribute iedModel_GenericIO_LLN0_Mod_q;
extern DataAttribute iedModel_GenericIO_LLN0_Mod_t;
extern DataAttribute iedModel_GenericIO_LLN0_Mod_ctlModel;
extern DataObject iedModel_GenericIO_LLN0_Beh;
extern DataAttribute iedModel_GenericIO_LLN0_Beh_stVal;
extern DataAttribute iedModel_GenericIO_LLN0_Beh_q;
extern DataAttribute iedModel_GenericIO_LLN0_Beh_t;
extern DataObject iedModel_GenericIO_LLN0_Health;
extern DataAttribute iedModel_GenericIO_LLN0_Health_stVal;
extern DataAttribute iedModel_GenericIO_LLN0_Health_q;
extern DataAttribute iedModel_GenericIO_LLN0_Health_t;
extern DataObject iedModel_GenericIO_LLN0_NamPlt;
extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_vendor;
extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_swRev;
extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_d;
extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_configRev;
extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs;
extern LogicalNode iedModel_GenericIO_LPHD1;
extern DataObject iedModel_GenericIO_LPHD1_PhyNam;
extern DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor;
extern DataObject iedModel_GenericIO_LPHD1_PhyHealth;
extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal;
extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_q;
extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_t;
extern DataObject iedModel_GenericIO_LPHD1_Proxy;
extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_stVal;
extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_q;
extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_t;
extern LogicalNode iedModel_GenericIO_GGIO1;
extern DataObject iedModel_GenericIO_GGIO1_Mod;
extern DataAttribute iedModel_GenericIO_GGIO1_Mod_q;
extern DataAttribute iedModel_GenericIO_GGIO1_Mod_t;
extern DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel;
extern DataObject iedModel_GenericIO_GGIO1_Beh;
extern DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_Beh_q;
extern DataAttribute iedModel_GenericIO_GGIO1_Beh_t;
extern DataObject iedModel_GenericIO_GGIO1_Health;
extern DataAttribute iedModel_GenericIO_GGIO1_Health_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_Health_q;
extern DataAttribute iedModel_GenericIO_GGIO1_Health_t;
extern DataObject iedModel_GenericIO_GGIO1_NamPlt;
extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_vendor;
extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_swRev;
extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_d;
extern DataObject iedModel_GenericIO_GGIO1_AnIn1;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag_f;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_q;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_t;
extern DataObject iedModel_GenericIO_GGIO1_AnIn2;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag_f;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_q;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_t;
extern DataObject iedModel_GenericIO_GGIO1_AnIn3;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag_f;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_q;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_t;
extern DataObject iedModel_GenericIO_GGIO1_AnIn4;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag_f;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_q;
extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_t;
extern DataObject iedModel_GenericIO_GGIO1_SPCSO1;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_T;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t;
extern DataObject iedModel_GenericIO_GGIO1_SPCSO2;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_T;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t;
extern DataObject iedModel_GenericIO_GGIO1_SPCSO3;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_T;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t;
extern DataObject iedModel_GenericIO_GGIO1_SPCSO4;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel;
extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t;
extern DataObject iedModel_GenericIO_GGIO1_Ind1;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_q;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_t;
extern DataObject iedModel_GenericIO_GGIO1_Ind2;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_q;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_t;
extern DataObject iedModel_GenericIO_GGIO1_Ind3;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_q;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_t;
extern DataObject iedModel_GenericIO_GGIO1_Ind4;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_q;
extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t;
#define IEDMODEL_GenericIO (&iedModel_GenericIO)
#define IEDMODEL_GenericIO_LLN0 (&iedModel_GenericIO_LLN0)
#define IEDMODEL_GenericIO_LLN0_Mod (&iedModel_GenericIO_LLN0_Mod)
#define IEDMODEL_GenericIO_LLN0_Mod_stVal (&iedModel_GenericIO_LLN0_Mod_stVal)
#define IEDMODEL_GenericIO_LLN0_Mod_q (&iedModel_GenericIO_LLN0_Mod_q)
#define IEDMODEL_GenericIO_LLN0_Mod_t (&iedModel_GenericIO_LLN0_Mod_t)
#define IEDMODEL_GenericIO_LLN0_Mod_ctlModel (&iedModel_GenericIO_LLN0_Mod_ctlModel)
#define IEDMODEL_GenericIO_LLN0_Beh (&iedModel_GenericIO_LLN0_Beh)
#define IEDMODEL_GenericIO_LLN0_Beh_stVal (&iedModel_GenericIO_LLN0_Beh_stVal)
#define IEDMODEL_GenericIO_LLN0_Beh_q (&iedModel_GenericIO_LLN0_Beh_q)
#define IEDMODEL_GenericIO_LLN0_Beh_t (&iedModel_GenericIO_LLN0_Beh_t)
#define IEDMODEL_GenericIO_LLN0_Health (&iedModel_GenericIO_LLN0_Health)
#define IEDMODEL_GenericIO_LLN0_Health_stVal (&iedModel_GenericIO_LLN0_Health_stVal)
#define IEDMODEL_GenericIO_LLN0_Health_q (&iedModel_GenericIO_LLN0_Health_q)
#define IEDMODEL_GenericIO_LLN0_Health_t (&iedModel_GenericIO_LLN0_Health_t)
#define IEDMODEL_GenericIO_LLN0_NamPlt (&iedModel_GenericIO_LLN0_NamPlt)
#define IEDMODEL_GenericIO_LLN0_NamPlt_vendor (&iedModel_GenericIO_LLN0_NamPlt_vendor)
#define IEDMODEL_GenericIO_LLN0_NamPlt_swRev (&iedModel_GenericIO_LLN0_NamPlt_swRev)
#define IEDMODEL_GenericIO_LLN0_NamPlt_d (&iedModel_GenericIO_LLN0_NamPlt_d)
#define IEDMODEL_GenericIO_LLN0_NamPlt_configRev (&iedModel_GenericIO_LLN0_NamPlt_configRev)
#define IEDMODEL_GenericIO_LLN0_NamPlt_ldNs (&iedModel_GenericIO_LLN0_NamPlt_ldNs)
#define IEDMODEL_GenericIO_LPHD1 (&iedModel_GenericIO_LPHD1)
#define IEDMODEL_GenericIO_LPHD1_PhyNam (&iedModel_GenericIO_LPHD1_PhyNam)
#define IEDMODEL_GenericIO_LPHD1_PhyNam_vendor (&iedModel_GenericIO_LPHD1_PhyNam_vendor)
#define IEDMODEL_GenericIO_LPHD1_PhyHealth (&iedModel_GenericIO_LPHD1_PhyHealth)
#define IEDMODEL_GenericIO_LPHD1_PhyHealth_stVal (&iedModel_GenericIO_LPHD1_PhyHealth_stVal)
#define IEDMODEL_GenericIO_LPHD1_PhyHealth_q (&iedModel_GenericIO_LPHD1_PhyHealth_q)
#define IEDMODEL_GenericIO_LPHD1_PhyHealth_t (&iedModel_GenericIO_LPHD1_PhyHealth_t)
#define IEDMODEL_GenericIO_LPHD1_Proxy (&iedModel_GenericIO_LPHD1_Proxy)
#define IEDMODEL_GenericIO_LPHD1_Proxy_stVal (&iedModel_GenericIO_LPHD1_Proxy_stVal)
#define IEDMODEL_GenericIO_LPHD1_Proxy_q (&iedModel_GenericIO_LPHD1_Proxy_q)
#define IEDMODEL_GenericIO_LPHD1_Proxy_t (&iedModel_GenericIO_LPHD1_Proxy_t)
#define IEDMODEL_GenericIO_GGIO1 (&iedModel_GenericIO_GGIO1)
#define IEDMODEL_GenericIO_GGIO1_Mod (&iedModel_GenericIO_GGIO1_Mod)
#define IEDMODEL_GenericIO_GGIO1_Mod_q (&iedModel_GenericIO_GGIO1_Mod_q)
#define IEDMODEL_GenericIO_GGIO1_Mod_t (&iedModel_GenericIO_GGIO1_Mod_t)
#define IEDMODEL_GenericIO_GGIO1_Mod_ctlModel (&iedModel_GenericIO_GGIO1_Mod_ctlModel)
#define IEDMODEL_GenericIO_GGIO1_Beh (&iedModel_GenericIO_GGIO1_Beh)
#define IEDMODEL_GenericIO_GGIO1_Beh_stVal (&iedModel_GenericIO_GGIO1_Beh_stVal)
#define IEDMODEL_GenericIO_GGIO1_Beh_q (&iedModel_GenericIO_GGIO1_Beh_q)
#define IEDMODEL_GenericIO_GGIO1_Beh_t (&iedModel_GenericIO_GGIO1_Beh_t)
#define IEDMODEL_GenericIO_GGIO1_Health (&iedModel_GenericIO_GGIO1_Health)
#define IEDMODEL_GenericIO_GGIO1_Health_stVal (&iedModel_GenericIO_GGIO1_Health_stVal)
#define IEDMODEL_GenericIO_GGIO1_Health_q (&iedModel_GenericIO_GGIO1_Health_q)
#define IEDMODEL_GenericIO_GGIO1_Health_t (&iedModel_GenericIO_GGIO1_Health_t)
#define IEDMODEL_GenericIO_GGIO1_NamPlt (&iedModel_GenericIO_GGIO1_NamPlt)
#define IEDMODEL_GenericIO_GGIO1_NamPlt_vendor (&iedModel_GenericIO_GGIO1_NamPlt_vendor)
#define IEDMODEL_GenericIO_GGIO1_NamPlt_swRev (&iedModel_GenericIO_GGIO1_NamPlt_swRev)
#define IEDMODEL_GenericIO_GGIO1_NamPlt_d (&iedModel_GenericIO_GGIO1_NamPlt_d)
#define IEDMODEL_GenericIO_GGIO1_AnIn1 (&iedModel_GenericIO_GGIO1_AnIn1)
#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag (&iedModel_GenericIO_GGIO1_AnIn1_mag)
#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f (&iedModel_GenericIO_GGIO1_AnIn1_mag_f)
#define IEDMODEL_GenericIO_GGIO1_AnIn1_q (&iedModel_GenericIO_GGIO1_AnIn1_q)
#define IEDMODEL_GenericIO_GGIO1_AnIn1_t (&iedModel_GenericIO_GGIO1_AnIn1_t)
#define IEDMODEL_GenericIO_GGIO1_AnIn2 (&iedModel_GenericIO_GGIO1_AnIn2)
#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag (&iedModel_GenericIO_GGIO1_AnIn2_mag)
#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f (&iedModel_GenericIO_GGIO1_AnIn2_mag_f)
#define IEDMODEL_GenericIO_GGIO1_AnIn2_q (&iedModel_GenericIO_GGIO1_AnIn2_q)
#define IEDMODEL_GenericIO_GGIO1_AnIn2_t (&iedModel_GenericIO_GGIO1_AnIn2_t)
#define IEDMODEL_GenericIO_GGIO1_AnIn3 (&iedModel_GenericIO_GGIO1_AnIn3)
#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag (&iedModel_GenericIO_GGIO1_AnIn3_mag)
#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f (&iedModel_GenericIO_GGIO1_AnIn3_mag_f)
#define IEDMODEL_GenericIO_GGIO1_AnIn3_q (&iedModel_GenericIO_GGIO1_AnIn3_q)
#define IEDMODEL_GenericIO_GGIO1_AnIn3_t (&iedModel_GenericIO_GGIO1_AnIn3_t)
#define IEDMODEL_GenericIO_GGIO1_AnIn4 (&iedModel_GenericIO_GGIO1_AnIn4)
#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag (&iedModel_GenericIO_GGIO1_AnIn4_mag)
#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f (&iedModel_GenericIO_GGIO1_AnIn4_mag_f)
#define IEDMODEL_GenericIO_GGIO1_AnIn4_q (&iedModel_GenericIO_GGIO1_AnIn4_q)
#define IEDMODEL_GenericIO_GGIO1_AnIn4_t (&iedModel_GenericIO_GGIO1_AnIn4_t)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1 (&iedModel_GenericIO_GGIO1_SPCSO1)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal (&iedModel_GenericIO_GGIO1_SPCSO1_stVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_q (&iedModel_GenericIO_GGIO1_SPCSO1_q)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper (&iedModel_GenericIO_GGIO1_SPCSO1_Oper)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_T)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO1_ctlModel)
#define IEDMODEL_GenericIO_GGIO1_SPCSO1_t (&iedModel_GenericIO_GGIO1_SPCSO1_t)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2 (&iedModel_GenericIO_GGIO1_SPCSO2)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal (&iedModel_GenericIO_GGIO1_SPCSO2_stVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_q (&iedModel_GenericIO_GGIO1_SPCSO2_q)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper (&iedModel_GenericIO_GGIO1_SPCSO2_Oper)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_T)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO2_ctlModel)
#define IEDMODEL_GenericIO_GGIO1_SPCSO2_t (&iedModel_GenericIO_GGIO1_SPCSO2_t)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3 (&iedModel_GenericIO_GGIO1_SPCSO3)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal (&iedModel_GenericIO_GGIO1_SPCSO3_stVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_q (&iedModel_GenericIO_GGIO1_SPCSO3_q)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper (&iedModel_GenericIO_GGIO1_SPCSO3_Oper)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_T)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO3_ctlModel)
#define IEDMODEL_GenericIO_GGIO1_SPCSO3_t (&iedModel_GenericIO_GGIO1_SPCSO3_t)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4 (&iedModel_GenericIO_GGIO1_SPCSO4)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal (&iedModel_GenericIO_GGIO1_SPCSO4_stVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_q (&iedModel_GenericIO_GGIO1_SPCSO4_q)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper (&iedModel_GenericIO_GGIO1_SPCSO4_Oper)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_T)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel)
#define IEDMODEL_GenericIO_GGIO1_SPCSO4_t (&iedModel_GenericIO_GGIO1_SPCSO4_t)
#define IEDMODEL_GenericIO_GGIO1_Ind1 (&iedModel_GenericIO_GGIO1_Ind1)
#define IEDMODEL_GenericIO_GGIO1_Ind1_stVal (&iedModel_GenericIO_GGIO1_Ind1_stVal)
#define IEDMODEL_GenericIO_GGIO1_Ind1_q (&iedModel_GenericIO_GGIO1_Ind1_q)
#define IEDMODEL_GenericIO_GGIO1_Ind1_t (&iedModel_GenericIO_GGIO1_Ind1_t)
#define IEDMODEL_GenericIO_GGIO1_Ind2 (&iedModel_GenericIO_GGIO1_Ind2)
#define IEDMODEL_GenericIO_GGIO1_Ind2_stVal (&iedModel_GenericIO_GGIO1_Ind2_stVal)
#define IEDMODEL_GenericIO_GGIO1_Ind2_q (&iedModel_GenericIO_GGIO1_Ind2_q)
#define IEDMODEL_GenericIO_GGIO1_Ind2_t (&iedModel_GenericIO_GGIO1_Ind2_t)
#define IEDMODEL_GenericIO_GGIO1_Ind3 (&iedModel_GenericIO_GGIO1_Ind3)
#define IEDMODEL_GenericIO_GGIO1_Ind3_stVal (&iedModel_GenericIO_GGIO1_Ind3_stVal)
#define IEDMODEL_GenericIO_GGIO1_Ind3_q (&iedModel_GenericIO_GGIO1_Ind3_q)
#define IEDMODEL_GenericIO_GGIO1_Ind3_t (&iedModel_GenericIO_GGIO1_Ind3_t)
#define IEDMODEL_GenericIO_GGIO1_Ind4 (&iedModel_GenericIO_GGIO1_Ind4)
#define IEDMODEL_GenericIO_GGIO1_Ind4_stVal (&iedModel_GenericIO_GGIO1_Ind4_stVal)
#define IEDMODEL_GenericIO_GGIO1_Ind4_q (&iedModel_GenericIO_GGIO1_Ind4_q)
#define IEDMODEL_GenericIO_GGIO1_Ind4_t (&iedModel_GenericIO_GGIO1_Ind4_t)
#endif /* STATIC_MODEL_H_ */

@ -1108,6 +1108,8 @@ extern SettingGroupControlBlock iedModel_PROT_LLN0_sgcb;
SettingGroupControlBlock iedModel_PROT_LLN0_sgcb = {&iedModel_PROT_LLN0, 1, 5, 0, false, 0, 0, NULL};
IedModel iedModel = {
"DEMO",
&iedModel_PROT,
@ -1116,6 +1118,8 @@ IedModel iedModel = {
NULL,
NULL,
&iedModel_PROT_LLN0_sgcb,
NULL,
NULL,
initializeValues
};

@ -1851,6 +1851,10 @@ ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0,
extern LogControlBlock iedModel_GenericIO_LLN0_lcb0;
LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, NULL};
IedModel iedModel = {
"simpleIO",
@ -1860,6 +1864,8 @@ IedModel iedModel = {
NULL,
NULL,
NULL,
&iedModel_GenericIO_LLN0_lcb0,
NULL,
initializeValues
};

@ -8,3 +8,4 @@ INCLUDES += -I$(LIBIEC_HOME)/src/iec61850/inc_private
INCLUDES += -I$(LIBIEC_HOME)/src/hal/inc
INCLUDES += -I$(LIBIEC_HOME)/src/goose
INCLUDES += -I$(LIBIEC_HOME)/src/sampled_values
INCLUDES += -I$(LIBIEC_HOME)/src/logging

@ -28,6 +28,7 @@ set (lib_common_SRCS
./mms/iso_mms/client/mms_client_get_var_access.c
./mms/iso_mms/client/mms_client_common.c
./mms/iso_mms/client/mms_client_read.c
./mms/iso_mms/client/mms_client_journals.c
./mms/iso_mms/server/mms_read_service.c
./mms/iso_mms/server/mms_file_service.c
./mms/iso_mms/server/mms_association_service.c
@ -43,6 +44,8 @@ set (lib_common_SRCS
./mms/iso_mms/server/mms_domain.c
./mms/iso_mms/server/mms_device.c
./mms/iso_mms/server/mms_information_report.c
./mms/iso_mms/server/mms_journal.c
./mms/iso_mms/server/mms_journal_service.c
./mms/iso_mms/server/mms_server_connection.c
./mms/iso_mms/server/mms_write_service.c
./mms/iso_mms/server/mms_get_var_access_service.c
@ -73,6 +76,8 @@ set (lib_common_SRCS
./iec61850/server/mms_mapping/reporting.c
./iec61850/server/mms_mapping/mms_goose.c
./iec61850/server/mms_mapping/mms_sv.c
./iec61850/server/mms_mapping/logging.c
./logging/log_storage.c
)
set (lib_asn1c_SRCS
@ -198,7 +203,7 @@ set (lib_windows_SRCS
set (lib_bsd_SRCS
./hal/socket/bsd/socket_bsd.c
./hal/ethernet/bsd/ethernet_bsd.c
./hal/thread/linux/thread_linux.c
./hal/thread/bsd/thread_bsd.c
./hal/filesystem/linux/file_provider_linux.c
./hal/time/unix/time.c
)
@ -284,6 +289,24 @@ ENDIF(WIN32)
include (GenerateExportHeader)
set(RES_FILES "")
if ( WIN32 )
# Adding RC resource file for adding information to the archive
set(RES_FILES "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
message(STATUS "Generating RC file : ${RES_FILES}")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in
${RES_FILES}
@ONLY)
if( MINGW )
set(CMAKE_RC_COMPILER_INIT windres)
ENABLE_LANGUAGE(RC)
SET(CMAKE_RC_COMPILE_OBJECT
"<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
endif(MINGW)
set(library_SRCS ${library_SRCS} ${RES_FILES})
endif( WIN32 )
add_library (iec61850-shared SHARED ${library_SRCS} )
set_target_properties(iec61850-shared PROPERTIES
@ -345,10 +368,9 @@ endif()
ENDIF(WITH_WPCAP)
install (TARGETS iec61850 iec61850-shared
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin COMPONENT Applications
ARCHIVE DESTINATION lib COMPONENT Libraries
LIBRARY DESTINATION lib COMPONENT Libraries
)

@ -15,7 +15,7 @@
#include "platform_endian.h"
#define LIBIEC61850_VERSION "0.9.1"
#define LIBIEC61850_VERSION "0.9.2"
#ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME
#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com"

@ -18,7 +18,7 @@ DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "libIEC61850"
PROJECT_NUMBER = 0.9.1
PROJECT_NUMBER = 0.9.2
PROJECT_BRIEF = "Open-source IEC 61850 MMS/GOOSE/SV server and client library"
@ -237,6 +237,7 @@ INPUT += "hal/inc/hal_thread.h"
INPUT += "hal/inc/hal_ethernet.h"
INPUT += "hal/inc/hal_filesystem.h"
INPUT += "hal/inc/hal_time.h"
INPUT += "logging/logging_api.h"
INPUT_ENCODING = UTF-8

@ -233,7 +233,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr)
long interfaceIndex = strtol(interfaceId, &endPtr, 10);
if (endPtr != NULL) {
if ((*interfaceId != '\0') && (*endPtr != '\0')) {
printf("Ethernet_getInterfaceMACAddress: invalid interface number %s\n", interfaceId);
return;
}

@ -34,52 +34,20 @@
#include "stack_config.h"
#ifndef CONFIG_VIRTUAL_FILESTORE_BASEPATH
#define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/"
#endif
static char fileBasePath[256];
static bool fileBasePathInitialized = false;
struct sDirectoryHandle {
DIR* handle;
};
static void
createFullPathFromFileName(char* fullPath, char* filename)
{
if (!fileBasePathInitialized) {
strcpy(fileBasePath, CONFIG_VIRTUAL_FILESTORE_BASEPATH);
fileBasePathInitialized = true;
}
strcpy(fullPath, fileBasePath);
if (filename != NULL)
strcat(fullPath, filename);
}
void
FileSystem_setBasePath(char* basePath)
{
strcpy(fileBasePath, basePath);
fileBasePathInitialized = true;
}
FileHandle
FileSystem_openFile(char* fileName, bool readWrite)
{
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(fullPath, fileName);
FileHandle newHandle = NULL;
if (readWrite)
newHandle = (FileHandle) fopen(fullPath, "w");
newHandle = (FileHandle) fopen(fileName, "w");
else
newHandle = (FileHandle) fopen(fullPath, "r");
newHandle = (FileHandle) fopen(fileName, "r");
return newHandle;
}
@ -99,11 +67,7 @@ FileSystem_closeFile(FileHandle handle)
bool
FileSystem_deleteFile(char* filename)
{
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(fullPath, filename);
if (remove(fullPath) == 0)
if (remove(filename) == 0)
return true;
else
return false;
@ -112,13 +76,7 @@ FileSystem_deleteFile(char* filename)
bool
FileSystem_renameFile(char* oldFilename, char* newFilename)
{
char oldFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
char newFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(oldFullPath, oldFilename);
createFullPathFromFileName(newFullPath, newFilename);
if (rename(oldFullPath, newFullPath) == 0)
if (rename(oldFilename, newFilename) == 0)
return true;
else
return false;
@ -130,11 +88,7 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific
{
struct stat fileStats;
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
createFullPathFromFileName(fullPath, filename);
if (stat(fullPath, &fileStats) == -1)
if (stat(filename, &fileStats) == -1)
return false;
if (lastModificationTimestamp != NULL)
@ -150,11 +104,7 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific
DirectoryHandle
FileSystem_openDirectory(char* directoryName)
{
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(fullPath, directoryName);
DIR* dirHandle = opendir(fullPath);
DIR* dirHandle = opendir(directoryName);
DirectoryHandle handle = NULL;

@ -37,15 +37,6 @@
#include <windows.h>
#ifndef CONFIG_VIRTUAL_FILESTORE_BASEPATH
#define CONFIG_VIRTUAL_FILESTORE_BASEPATH ".\\vmd-filestore\\"
#endif
//static char* fileBasePath = CONFIG_VIRTUAL_FILESTORE_BASEPATH;
static char fileBasePath[256];
static bool fileBasePathInitialized = false;
struct sDirectoryHandle {
HANDLE handle;
WIN32_FIND_DATAW findData;
@ -53,40 +44,16 @@ struct sDirectoryHandle {
bool available;
};
static void
createFullPathFromFileName(char* fullPath, char* filename)
{
if (!fileBasePathInitialized) {
strcpy(fileBasePath, CONFIG_VIRTUAL_FILESTORE_BASEPATH);
fileBasePathInitialized = true;
}
strcpy(fullPath, fileBasePath);
if (filename != NULL)
strcat(fullPath, filename);
}
void
FileSystem_setBasePath(char* basePath)
{
strcpy(fileBasePath, basePath);
fileBasePathInitialized = true;
}
FileHandle
FileSystem_openFile(char* fileName, bool readWrite)
{
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(fullPath, fileName);
FileHandle newHandle = NULL;
if (readWrite)
newHandle = (FileHandle) fopen(fullPath, "wb");
newHandle = (FileHandle) fopen(fileName, "wb");
else
newHandle = (FileHandle) fopen(fullPath, "rb");
newHandle = (FileHandle) fopen(fileName, "rb");
return newHandle;
}
@ -106,13 +73,9 @@ FileSystem_closeFile(FileHandle handle)
bool
FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModificationTimestamp)
{
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
createFullPathFromFileName(fullPath, filename);
WIN32_FILE_ATTRIBUTE_DATA fad;
if (GetFileAttributesEx(fullPath, GetFileExInfoStandard, &fad) == 0)
if (GetFileAttributesEx(filename, GetFileExInfoStandard, &fad) == 0)
return false;
if (lastModificationTimestamp != NULL) {
@ -136,13 +99,12 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific
DirectoryHandle
FileSystem_openDirectory(char* directoryName)
{
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(fullPath, directoryName);
DirectoryHandle dirHandle = (DirectoryHandle) GLOBAL_CALLOC(1, sizeof(struct sDirectoryHandle));
strcat(fullPath, "\\*");
char fullPath[MAX_PATH + 1];
strncpy(fullPath, directoryName, MAX_PATH - 3);
strncat(fullPath, "\\*", MAX_PATH);
/* convert UTF-8 path name to WCHAR */
WCHAR unicodeFullPath[MAX_PATH + 1];
@ -201,11 +163,7 @@ getNextDirectoryEntry(DirectoryHandle directory, bool* isDirectory)
bool
FileSystem_deleteFile(char* filename)
{
char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(fullPath, filename);
if (remove(fullPath) == 0)
if (remove(filename) == 0)
return true;
else
return false;
@ -214,13 +172,7 @@ FileSystem_deleteFile(char* filename)
bool
FileSystem_renameFile(char* oldFilename, char* newFilename)
{
char oldFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
char newFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255];
createFullPathFromFileName(oldFullPath, oldFilename);
createFullPathFromFileName(newFullPath, newFilename);
if (rename(oldFullPath, newFullPath) == 0)
if (rename(oldFilename, newFilename) == 0)
return true;
else
return false;

@ -0,0 +1,122 @@
/**
* thread_bsd.c
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include "hal_thread.h"
#include "libiec61850_platform_includes.h"
struct sThread {
ThreadExecutionFunction function;
void* parameter;
pthread_t pthread;
int state;
bool autodestroy;
};
Semaphore
Semaphore_create(int initialValue)
{
char tmpname[] = {"/tmp/libiec61850.XXXXXX"};
mktemp(tmpname);
Semaphore self = sem_open(tmpname, O_CREAT, 0666, initialValue);
return self;
}
/* Wait until semaphore value is more than zero. Then decrease the semaphore value. */
void
Semaphore_wait(Semaphore self)
{
sem_wait((sem_t*) self);
}
void
Semaphore_post(Semaphore self)
{
sem_post((sem_t*) self);
}
void
Semaphore_destroy(Semaphore self)
{
sem_close(self);
}
Thread
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
{
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
if (thread != NULL) {
thread->parameter = parameter;
thread->function = function;
thread->state = 0;
thread->autodestroy = autodestroy;
}
return thread;
}
static void*
destroyAutomaticThread(void* parameter)
{
Thread thread = (Thread) parameter;
thread->function(thread->parameter);
GLOBAL_FREEMEM(thread);
pthread_exit(NULL);
}
void
Thread_start(Thread thread)
{
if (thread->autodestroy == true) {
pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread);
pthread_detach(thread->pthread);
}
else
pthread_create(&thread->pthread, NULL, thread->function, thread->parameter);
thread->state = 1;
}
void
Thread_destroy(Thread thread)
{
if (thread->state == 1) {
pthread_join(thread->pthread, NULL);
}
GLOBAL_FREEMEM(thread);
}
void
Thread_sleep(int millies)
{
usleep(millies * 1000);
}

@ -31,6 +31,8 @@
#include "libiec61850_platform_includes.h"
#include <inttypes.h>
struct sClientReport
{
ReportCallbackFunction callback;
@ -405,7 +407,7 @@ private_IedConnection_handleReport(IedConnection self, MmsValue* value)
matchingReport->timestamp = MmsValue_getBinaryTimeAsUtcMs(timeStampValue);
if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: report has timestamp %llu\n", matchingReport->timestamp);
printf("DEBUG_IED_CLIENT: report has timestamp %" PRIu64 "\n", matchingReport->timestamp);
}
inclusionIndex++;

@ -39,7 +39,6 @@ typedef struct sICLogicalDevice
{
char* name;
LinkedList variables;
LinkedList dataSets;
} ICLogicalDevice;
struct sClientDataSet
@ -165,12 +164,6 @@ ICLogicalDevice_setVariableList(ICLogicalDevice* self, LinkedList variables)
self->variables = variables;
}
static void
ICLogicalDevice_setDataSetList(ICLogicalDevice* self, LinkedList dataSets)
{
self->dataSets = dataSets;
}
static void
ICLogicalDevice_destroy(ICLogicalDevice* self)
{
@ -179,9 +172,6 @@ ICLogicalDevice_destroy(ICLogicalDevice* self)
if (self->variables != NULL)
LinkedList_destroy(self->variables);
if (self->dataSets != NULL)
LinkedList_destroy(self->dataSets);
GLOBAL_FREEMEM(self);
}
@ -1015,16 +1005,6 @@ IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error
break;
}
LinkedList dataSets = MmsConnection_getDomainVariableListNames(self->connection,
&mmsError, name);
if (dataSets != NULL)
ICLogicalDevice_setDataSetList(icLogicalDevice, dataSets);
else {
*error = iedConnection_mapMmsErrorToIedError(mmsError);
break;
}
LinkedList_add(logicalDevices, icLogicalDevice);
logicalDevice = LinkedList_getNext(logicalDevice);
@ -1306,6 +1286,91 @@ addVariablesWithFc(char* fc, char* lnName, LinkedList variables, LinkedList lnDi
}
}
static LinkedList
getLogicalNodeDirectoryLogs(IedConnection self, IedClientError* error, const char* logicalDeviceName,
const char* logicalNodeName)
{
MmsConnection mmsCon = self->connection;
MmsError mmsError;
LinkedList journals = MmsConnection_getDomainJournals(mmsCon, &mmsError, logicalDeviceName);
if (mmsError != MMS_ERROR_NONE) {
*error = iedConnection_mapMmsErrorToIedError(mmsError);
return NULL;
}
LinkedList logs = LinkedList_create();
LinkedList journal = LinkedList_getNext(journals);
while (journal != NULL) {
char* journalName = (char*) LinkedList_getData(journal);
char* logName = strchr(journalName, '$');
if (logName != NULL) {
logName[0] = 0;
logName += 1;
if (strcmp(journalName, logicalNodeName) == 0) {
char* log = copyString(logName);
LinkedList_add(logs, (void*) log);
}
}
journal = LinkedList_getNext(journal);
}
LinkedList_destroy(journals);
return logs;
}
static LinkedList
getLogicalNodeDirectoryDataSets(IedConnection self, IedClientError* error, const char* logicalDeviceName,
const char* logicalNodeName)
{
MmsConnection mmsCon = self->connection;
MmsError mmsError;
LinkedList dataSets = MmsConnection_getDomainVariableListNames(mmsCon, &mmsError, logicalDeviceName);
if (mmsError != MMS_ERROR_NONE) {
*error = iedConnection_mapMmsErrorToIedError(mmsError);
return NULL;
}
LinkedList lnDataSets = LinkedList_create();
LinkedList dataSet = LinkedList_getNext(dataSets);
while (dataSet != NULL) {
char* dataSetName = (char*) LinkedList_getData(dataSet);
char* lnDataSetName = strchr(dataSetName, '$');
if (lnDataSetName != NULL) {
lnDataSetName[0] = 0;
lnDataSetName += 1;
if (strcmp(dataSetName, logicalNodeName) == 0) {
char* lnDataSet = copyString(lnDataSetName);
LinkedList_add(lnDataSets, (void*) lnDataSet);
}
}
dataSet = LinkedList_getNext(dataSet);
}
LinkedList_destroy(dataSets);
return lnDataSets;
}
LinkedList /*<char*>*/
IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error,
const char* logicalNodeReference, ACSIClass acsiClass)
@ -1317,12 +1382,6 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error,
return NULL;
}
if (self->logicalDevices == NULL)
IedConnection_getDeviceModelFromServer(self, error);
if (*error != IED_ERROR_OK)
return NULL;
char lnRefCopy[130];
strncpy(lnRefCopy, logicalNodeReference, 129);
@ -1341,6 +1400,18 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error,
char* logicalNodeName = ldSep + 1;
if (acsiClass == ACSI_CLASS_LOG)
return getLogicalNodeDirectoryLogs(self, error, logicalDeviceName, logicalNodeName);
if (acsiClass == ACSI_CLASS_DATA_SET)
return getLogicalNodeDirectoryDataSets(self, error, logicalDeviceName, logicalNodeName);
if (self->logicalDevices == NULL)
IedConnection_getDeviceModelFromServer(self, error);
if (*error != IED_ERROR_OK)
return NULL;
/* search for logical device */
LinkedList device = LinkedList_getNext(self->logicalDevices);
@ -1440,33 +1511,8 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error,
addVariablesWithFc("GO", logicalNodeName, ld->variables, lnDirectory);
break;
case ACSI_CLASS_DATA_SET:
{
LinkedList dataSet = LinkedList_getNext(ld->dataSets);
while (dataSet != NULL) {
char* dataSetName = (char*) dataSet->data;
char* fcPos = strchr(dataSetName, '$');
if (fcPos == NULL)
goto next_data_set_element;
size_t lnNameLen = fcPos - dataSetName;
if (strlen(logicalNodeName) != lnNameLen)
goto next_data_set_element;
if (memcmp(dataSetName, logicalNodeName, lnNameLen) != 0)
goto next_data_set_element;
LinkedList_add(lnDirectory, copyString(fcPos + 1));
next_data_set_element:
dataSet = LinkedList_getNext(dataSet);
}
}
case ACSI_CLASS_LCB:
addVariablesWithFc("LG", logicalNodeName, ld->variables, lnDirectory);
break;
default:
@ -2204,6 +2250,91 @@ exit_function:
return dataSet;
}
LinkedList /* <MmsJournalEntry> */
IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference,
uint64_t startTime, uint64_t endTime, bool* moreFollows)
{
MmsError mmsError;
char logRef[130];
strncpy(logRef, logReference, 129);
char* logDomain = logRef;
char* logName = strchr(logRef, '/');
if (logName != NULL) {
logName[0] = 0;
logName++;
MmsValue* startTimeMms = MmsValue_newBinaryTime(false);
MmsValue_setBinaryTime(startTimeMms, startTime);
MmsValue* endTimeMms = MmsValue_newBinaryTime(false);
MmsValue_setBinaryTime(endTimeMms, endTime);
LinkedList journalEntries = MmsConnection_readJournalTimeRange(self->connection, &mmsError, logDomain, logName,
startTimeMms, endTimeMms, moreFollows);
MmsValue_delete(startTimeMms);
MmsValue_delete(endTimeMms);
if (mmsError != MMS_ERROR_NONE) {
*error = iedConnection_mapMmsErrorToIedError(mmsError);
return NULL;
}
else
return journalEntries;
}
else {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return NULL;
}
}
LinkedList /* <MmsJournalEntry> */
IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference,
MmsValue* entryID, uint64_t timeStamp, bool* moreFollows)
{
MmsError mmsError;
char logRef[130];
strncpy(logRef, logReference, 129);
char* logDomain = logRef;
char* logName = strchr(logRef, '/');
if (logName != NULL) {
logName[0] = 0;
logName++;
MmsValue* timeStampMms = MmsValue_newBinaryTime(false);
MmsValue_setBinaryTime(timeStampMms, timeStamp);
LinkedList journalEntries = MmsConnection_readJournalStartAfter(self->connection, &mmsError, logDomain, logName,
timeStampMms, entryID, moreFollows);
MmsValue_delete(timeStampMms);
if (mmsError != MMS_ERROR_NONE) {
*error = iedConnection_mapMmsErrorToIedError(mmsError);
return NULL;
}
else
return journalEntries;
}
else {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID;
return NULL;
}
}
MmsConnection
IedConnection_getMmsConnection(IedConnection self)

@ -133,6 +133,8 @@ FunctionalConstraint_toString(FunctionalConstraint fc) {
return "RP";
case IEC61850_FC_BR:
return "BR";
case IEC61850_FC_LG:
return "LG";
default:
return NULL;
}
@ -212,6 +214,12 @@ FunctionalConstraint_fromString(const char* fcString)
return IEC61850_FC_NONE;
}
if (fcString[0] == 'L') {
if (fcString[1] == 'G')
return IEC61850_FC_LG;
return IEC61850_FC_NONE;
}
return IEC61850_FC_NONE;
}

@ -46,12 +46,20 @@ extern "C" {
*/
#define CDC_OPTION_PICS_SUBST (1 << 0)
#define CDC_OPTION_BLK_ENA (1 << 1)
/** Add d (description) data attribute */
#define CDC_OPTION_DESC (1 << 2)
/** Add dU (unicode description) data attribute */
#define CDC_OPTION_DESC_UNICODE (1 << 3)
/** Add cdcNs and cdcName required when a CDC is an extension to the standard */
#define CDC_OPTION_AC_DLNDA (1 << 4)
/** Add dataNs (data namespace) required for extended CDCs */
#define CDC_OPTION_AC_DLN (1 << 5)
/** Add the unit data attribute */
#define CDC_OPTION_UNIT (1 << 6)
#define CDC_OPTION_FROZEN_VALUE (1 << 7)
@ -87,6 +95,17 @@ extern "C" {
#define CDC_OPTION_ANGLE_REF (1 << 23)
/** Options that are only valid for DPL CDC */
#define CDC_OPTION_DPL_HWREV (1 << 17)
#define CDC_OPTION_DPL_SWREV (1 << 18)
#define CDC_OPTION_DPL_SERNUM (1 << 19)
#define CDC_OPTION_DPL_MODEL (1 << 20)
#define CDC_OPTION_DPL_LOCATION (1 << 21)
/** Add mandatory data attributes for LLN0 (e.g. LBL configRef) */
#define CDC_OPTION_AC_LN0_M (1 << 24)
#define CDC_OPTION_AC_LN0_EX (1 << 25)
#define CDC_OPTION_AC_DLD_M (1 << 26)
/**
* \brief Control model types
@ -233,10 +252,54 @@ CDC_CMV_create(const char* dataObjectName, ModelNode* parent, uint32_t options);
DataObject*
CDC_SAV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat);
/**
* \brief create a new LPL (Logical node name plate) CDC instance (data object)
*
* Allowed parent type is LogicalNode
*
* possible options:
* CDC_OPTION_AC_LN0_M (includes "configRev")
* CDC_OPTION_AC_LN0_EX (includes "ldNs")
* CDC_OPTION_AC_DLD_M (includes "lnNs")
* standard options:
* CDC_OPTION_DESC (includes "d")
* CDC_OPTION_DESC_UNICODE (include "du")
* CDC_OPTION_AC_DLNDA (include "cdcNs" and "cdcName")
* CDC_OPTION_AC_DLN (includes "dataNs")
*
* \param dataObjectName the name of the new object
* \param parent the parent of the new data object (either a LogicalNode or another DataObject)
* \param options bit mask to encode required optional elements
*
* \return new DataObject instance
*/
DataObject*
CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options);
/**
* \brief create a new DPL (Device name plate) CDC instance (data object)
*
* Allowed parent type is LogicalNode
*
* possible options:
* CDC_OPTION_DPL_HWREV (includes "hwRev")
* CDC_OPTION_DPL_SWREV (includes "swRev")
* CDC_OPTION_DPL_SERNUM (includes "serNum")
* CDC_OPTION_DPL_MODEL (includes "model")
* CDC_OPTION_DPL_LOCATION (includes "location")
* standard options:
* CDC_OPTION_AC_DLNDA (include "cdcNs" and "cdcName")
* CDC_OPTION_AC_DLN (includes "dataNs")
*
* \param dataObjectName the name of the new object
* \param parent the parent of the new data object (either a LogicalNode or another DataObject)
* \param options bit mask to encode required optional elements
*
* \return new DataObject instance
*/
DataObject*
CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options);
DataObject*
CDC_HST_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint16_t maxPts);

@ -1661,7 +1661,10 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error,
/**
* \brief returns the directory of the given logical node (LN) containing elements of the specified ACSI class
*
* Implementation of the GetLogicalNodeDirectory ACSI service.
* Implementation of the GetLogicalNodeDirectory ACSI service. In contrast to the ACSI description this
* function does not always creates a request to the server. For most ACSI classes it simply accesses the
* data model that was retrieved before. An exception to this rule are the ACSI classes ACSI_CLASS_DATASET and
* ACSI_CLASS_LOG. Both always perform a request to the server.
*
* \param self the connection object
* \param error the error code if an error occurs
@ -1744,6 +1747,58 @@ MmsVariableSpecification*
IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* dataAttributeReference,
FunctionalConstraint fc);
/** @} */
/**
* @defgroup IEC61850_CLIENT_LOG_SERVICE Log service related functions, data types, and definitions
*
* @{
*/
/**
* \brief Implementation of the QueryLogByTime ACSI service
*
* Read log entries from a log at the server. The log entries to read are specified by
* a starting time and an end time. If the complete range does not fit in a single MMS message
* the moreFollows flag will be set to true, to indicate that more entries are available for the
* specified time range.
*
* \param self the connection object
* \param error the error code if an error occurs
* \param logReference log object reference in the form <LD name>/<LN name>$<log name>
* \param startTime as millisecond UTC timestamp
* \param endTime as millisecond UTC timestamp
* \param moreFollows (output value) indicates that more entries are available that match the specification.
*
* \return list of MmsJournalEntry objects matching the specification
*/
LinkedList /* <MmsJournalEntry> */
IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference,
uint64_t startTime, uint64_t endTime, bool* moreFollows);
/**
* \brief Implementation of the QueryLogAfter ACSI service
*
* Read log entries from a log at the server following the entry with the specified entryID and timestamp.
* If the complete range does not fit in a single MMS message
* the moreFollows flag will be set to true, to indicate that more entries are available for the
* specified time range.
*
* \param self the connection object
* \param error the error code if an error occurs
* \param logReference log object reference in the form <LD name>/<LN name>$<log name>
* \param entryID usually the entryID of the last received entry
* \param timeStamp as millisecond UTC timestamp
* \param moreFollows (output value) indicates that more entries are available that match the specification.
*
* \return list of MmsJournalEntry objects matching the specification
*/
LinkedList /* <MmsJournalEntry> */
IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference,
MmsValue* entryID, uint64_t timeStamp, bool* moreFollows);
/** @} */
/**

@ -30,6 +30,7 @@ extern "C" {
#include "libiec61850_common_api.h"
#include "logging_api.h"
/**
* @defgroup iec61850_common_api_group IEC 61850 API common parts
@ -233,6 +234,8 @@ typedef enum eFunctionalConstraint {
IEC61850_FC_RP = 15,
/** Buffered report */
IEC61850_FC_BR = 16,
/** Log control blocks */
IEC61850_FC_LG = 17,
/** All FCs - wildcard value */
IEC61850_FC_ALL = 99,

@ -146,7 +146,7 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type
* \param parent the parent LN.
* \param rptId of the report. If NULL the default report ID (object reference) is used.
* \param isBuffered true for a buffered RCB - false for unbuffered RCB
* \param dataSetName name (object reference) of the default data set or NULL if no data
* \param dataSetName name (object reference) of the default data set or NULL if no data set
* is set by default
* \param confRef the configuration revision
* \param trgOps the trigger options supported by this RCB (bit set)
@ -160,6 +160,38 @@ ReportControlBlock*
ReportControlBlock_create(const char* name, LogicalNode* parent, char* rptId, bool isBuffered, char*
dataSetName, uint32_t confRef, uint8_t trgOps, uint8_t options, uint32_t bufTm, uint32_t intgPd);
/**
* \brief create a new log control block (LCB)
*
* Create a new log control block (LCB) and add it to the given logical node (LN).
*
* \param name name of the LCB relative to the parent LN
* \param parent the parent LN.
* \param dataSetName name (object reference) of the default data set or NULL if no data set
* is set by default
* \param logRef name (object reference) of the default log or NULL if no log is set by default. THe LDname doesn't contain the IED name!
* \param trgOps the trigger options supported by this LCB (bit set)
* \param intgPd integrity period in milliseconds
* \param logEna if true the log will be enabled by default, false otherwise
* \param reasonCode if true the reasonCode will be included in the log (this is always true in MMS mapping)
*
* \return the new LCB instance
*/
LogControlBlock*
LogControlBlock_create(const char* name, LogicalNode* parent, char* dataSetName, char* logRef, uint8_t trgOps,
uint32_t intgPd, bool logEna, bool reasonCode);
/**
* \brief create a log (used by the IEC 61850 log service)
*
* \param name name of the LOG relative to the parent LN
* \param parent the parent LN
*
* \return the new LOG instance
*/
Log*
Log_create(const char* name, LogicalNode* parent);
/**
* \brief create a setting group control block (SGCB)
*

@ -1,7 +1,7 @@
/*
* model.h
*
* Copyright 2013, 2014, 2015 Michael Zillgith
* Copyright 2013-2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -84,6 +84,8 @@ typedef struct sSVControlBlock SVControlBlock;
typedef struct sLogControlBlock LogControlBlock;
typedef struct sLog Log;
typedef enum {
IEC61850_BOOLEAN = 0,/* int */
IEC61850_INT8 = 1, /* int8_t */
@ -167,6 +169,8 @@ struct sIedModel {
GSEControlBlock* gseCBs;
SVControlBlock* svCBs;
SettingGroupControlBlock* sgcbs;
LogControlBlock* lcbs;
Log* logs;
void (*initializer) (void);
};
@ -266,8 +270,20 @@ struct sLogControlBlock {
char* logRef; /* object reference to the journal. Defaults to <MMS DomainName>/<LNName>$GeneralLog */
uint8_t trgOps; /* TrgOps - trigger conditions */
uint8_t options; /* OptFlds */
uint32_t intPeriod; /* IntgPd - integrity period */
bool logEna; /* enable log by default */
bool reasonCode; /* include reason code in log */
LogControlBlock* sibling; /* next control block in list or NULL if this is the last entry */
};
struct sLog {
LogicalNode* parent;
char* name;
Log* sibling; /* next log instance in list or NULL if this is the last entry */
};
struct sSettingGroupControlBlock {

@ -650,6 +650,9 @@ IedServer_updateQuality(IedServer self, DataAttribute* dataAttribute, Quality qu
/**@}*/
void
IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorage);
/**
* @defgroup IEC61850_SERVER_SETTING_GROUPS Server side setting group handling
*

@ -42,6 +42,7 @@ struct sIedServer
MmsMapping* mmsMapping;
LinkedList clientConnections;
uint8_t writeAccessPolicies;
bool running;
};

@ -0,0 +1,123 @@
/*
* logging.h
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#ifndef LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_
#define LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_
typedef struct {
char* name;
LogicalNode* parentLN;
bool locked;
LogStorage logStorage;
uint64_t newEntryId;
uint64_t newEntryTime;
uint64_t oldEntryId;
uint64_t oldEntryTime;
} LogInstance;
typedef struct {
char* name;
LogControlBlock* logControlBlock;
MmsMapping* mmsMapping;
DataSet* dataSet;
char* dataSetRef;
bool isDynamicDataSet;
LogicalNode* logicalNode;
MmsDomain* domain;
MmsValue* mmsValue;
MmsVariableSpecification* mmsType;
MmsValue* oldEntr;
MmsValue* oldEntrTm;
MmsValue* newEntr;
MmsValue* newEntrTm;
LogInstance* logInstance;
bool enabled;
uint64_t nextIntegrityScan;
int triggerOps;
uint32_t intgPd;
} LogControl;
LogInstance*
LogInstance_create(LogicalNode* parentLN, const char* name);
void
LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage);
void
LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag);
uint64_t
LogInstance_logEntryStart(LogInstance* self);
void
LogInstance_logEntryData(LogInstance* self, uint64_t entryID, const char* dataRef, MmsValue* value, uint8_t flag);
void
LogInstance_logEntryFinished(LogInstance* self, uint64_t entryID);
void
LogInstance_destroy(LogInstance* self);
LogControl*
LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping);
void
LogControl_setLog(LogControl* self, LogInstance* logInstance);
void
LogControl_destroy(LogControl* self);
void
LogControl_setLogStorage(LogControl* self, LogStorage logStorage);
MmsVariableSpecification*
Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode,
int lcbCount);
void
Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs);
MmsValue*
LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig);
MmsDataAccessError
LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
MmsValue* value, MmsServerConnection connection);
#endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ */

@ -1,7 +1,7 @@
/*
* mms_mapping.h
*
* Copyright 2013, 2014 Michael Zillgith
* Copyright 2013-2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -25,7 +25,6 @@
#define MMS_MAPPING_H_
#include "iec61850_model.h"
//#include "mms_server_connection.h"
#include "mms_device_model.h"
#include "control.h"
@ -36,6 +35,13 @@ typedef enum {
REPORT_CONTROL_QUALITY_CHANGED
} ReportInclusionFlag;
typedef enum {
LOG_CONTROL_NONE,
LOG_CONTROL_VALUE_UPDATE,
LOG_CONTROL_VALUE_CHANGED,
LOG_CONTROL_QUALITY_CHANGED
} LogInclusionFlag;
typedef struct sMmsMapping MmsMapping;
MmsMapping*
@ -86,6 +92,9 @@ MmsMapping_createDataSetByNamedVariableList(MmsMapping* self, MmsNamedVariableLi
void
MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag);
void
MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag);
void
MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value);
@ -138,6 +147,9 @@ MmsMapping_setIedServer(MmsMapping* self, IedServer iedServer);
void
MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicationHandler handler, void* parameter);
void
MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logStorage);
void
MmsMapping_installWriteAccessHandler(MmsMapping* self, DataAttribute* dataAttribute, WriteAccessHandler handler, void* parameter);

@ -1,7 +1,7 @@
/*
* mms_mapping_internal.h
*
* Copyright 2013, 2015 Michael Zillgith
* Copyright 2013-2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -35,6 +35,11 @@ struct sMmsMapping {
MmsServer mmsServer;
LinkedList reportControls;
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
LinkedList logControls;
LinkedList logInstances;
#endif
#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)
LinkedList gseControls;
const char* gooseInterfaceId;

@ -198,6 +198,10 @@ createMmsServerCache(IedServer self)
&& (strcmp(fcName, "MS") != 0) && (strcmp(fcName, "US") != 0)
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
&& (strcmp(fcName, "LG") != 0)
#endif
)
{
char* variableName = createString(3, lnName, "$", fcName);
@ -400,6 +404,8 @@ IedServer_create(IedModel* iedModel)
self->model = iedModel;
// self->running = false; /* not required due to CALLOC */
self->mmsMapping = MmsMapping_create(iedModel);
self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping);
@ -441,8 +447,31 @@ IedServer_create(IedModel* iedModel)
void
IedServer_destroy(IedServer self)
{
/* Stop server if running */
if (self->running) {
#if (CONFIG_MMS_THREADLESS_STACK == 1)
IedServer_stopThreadless(self);
#else
IedServer_stop(self);
#endif
}
MmsServer_destroy(self->mmsServer);
IsoServer_destroy(self->isoServer);
#if (CONFIG_MMS_SINGLE_THREADED == 1)
/* trigger stopping background task thread */
self->mmsMapping->reportThreadRunning = false;
/* waiting for thread to finish */
while (self->mmsMapping->reportThreadFinished == false) {
Thread_sleep(10);
}
#endif
MmsMapping_destroy(self->mmsMapping);
LinkedList_destroyDeep(self->clientConnections, (LinkedListValueDeleteFunction) private_ClientConnection_destroy);
@ -508,17 +537,22 @@ singleThreadedServerThread(void* parameter)
void
IedServer_start(IedServer self, int tcpPort)
{
if (self->running == false) {
#if (CONFIG_MMS_SINGLE_THREADED == 1)
MmsServer_startListeningThreadless(self->mmsServer, tcpPort);
MmsServer_startListeningThreadless(self->mmsServer, tcpPort);
Thread serverThread = Thread_create((ThreadExecutionFunction) singleThreadedServerThread, (void*) self, true);
Thread serverThread = Thread_create((ThreadExecutionFunction) singleThreadedServerThread, (void*) self, true);
Thread_start(serverThread);
Thread_start(serverThread);
#else
MmsServer_startListening(self->mmsServer, tcpPort);
MmsMapping_startEventWorkerThread(self->mmsMapping);
MmsServer_startListening(self->mmsServer, tcpPort);
MmsMapping_startEventWorkerThread(self->mmsMapping);
#endif
self->running = true;
}
}
#endif
@ -541,13 +575,17 @@ IedServer_getDataModel(IedServer self)
void
IedServer_stop(IedServer self)
{
MmsMapping_stopEventWorkerThread(self->mmsMapping);
if (self->running) {
self->running = false;
MmsMapping_stopEventWorkerThread(self->mmsMapping);
#if (CONFIG_MMS_SINGLE_THREADED == 1)
MmsServer_stopListeningThreadless(self->mmsServer);
MmsServer_stopListeningThreadless(self->mmsServer);
#else
MmsServer_stopListening(self->mmsServer);
MmsServer_stopListening(self->mmsServer);
#endif
}
}
#endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */
@ -555,7 +593,10 @@ IedServer_stop(IedServer self)
void
IedServer_startThreadless(IedServer self, int tcpPort)
{
MmsServer_startListeningThreadless(self->mmsServer, tcpPort);
if (self->running == false) {
MmsServer_startListeningThreadless(self->mmsServer, tcpPort);
self->running = true;
}
}
int
@ -573,7 +614,11 @@ IedServer_processIncomingData(IedServer self)
void
IedServer_stopThreadless(IedServer self)
{
MmsServer_stopListeningThreadless(self->mmsServer);
if (self->running) {
self->running = false;
MmsServer_stopListeningThreadless(self->mmsServer);
}
}
void
@ -773,12 +818,22 @@ IedServer_getStringAttributeValue(IedServer self, const DataAttribute* dataAttri
static inline void
checkForUpdateTrigger(IedServer self, DataAttribute* dataAttribute)
{
#if (CONFIG_IEC61850_REPORT_SERVICE== 1)
#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_IEC61850_LOG_SERVICE == 1))
if (dataAttribute->triggerOptions & TRG_OPT_DATA_UPDATE) {
#if (CONFIG_IEC61850_REPORT_SERVICE == 1)
MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue,
REPORT_CONTROL_VALUE_UPDATE);
}
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue,
LOG_CONTROL_VALUE_UPDATE);
#endif
}
#endif /* ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_IEC61850_LOG_SERVICE == 1)) */
}
static inline void
@ -795,6 +850,11 @@ checkForChangedTriggers(IedServer self, DataAttribute* dataAttribute)
MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue,
REPORT_CONTROL_VALUE_CHANGED);
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue,
LOG_CONTROL_VALUE_CHANGED);
#endif
}
else if (dataAttribute->triggerOptions & TRG_OPT_QUALITY_CHANGED) {
@ -807,6 +867,12 @@ checkForChangedTriggers(IedServer self, DataAttribute* dataAttribute)
MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue,
REPORT_CONTROL_QUALITY_CHANGED);
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue,
LOG_CONTROL_QUALITY_CHANGED);
#endif
}
#endif /* (CONFIG_IEC61850_REPORT_SERVICE== 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */
@ -1020,6 +1086,13 @@ IedServer_updateQuality(IedServer self, DataAttribute* dataAttribute, Quality qu
MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue,
REPORT_CONTROL_QUALITY_CHANGED);
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
if (dataAttribute->triggerOptions & TRG_OPT_QUALITY_CHANGED)
MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue,
LOG_CONTROL_QUALITY_CHANGED);
#endif
}
@ -1212,6 +1285,14 @@ IedServer_setEditSettingGroupConfirmationHandler(IedServer self, SettingGroupCon
#endif
}
void
IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorage)
{
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
MmsMapping_setLogStorage(self->mmsMapping, logRef, logStorage);
#endif
}
ClientConnection
private_IedServer_getClientConnectionByHandle(IedServer self, void* serverConnectionHandle)
{

@ -0,0 +1,947 @@
/*
* logging.c
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "libiec61850_platform_includes.h"
#include "stack_config.h"
#include "mms_mapping.h"
#include "logging.h"
#include "linked_list.h"
#include "array_list.h"
#include "hal_thread.h"
#include "simple_allocator.h"
#include "mem_alloc_linked_list.h"
#include "mms_mapping_internal.h"
#include "mms_value_internal.h"
#include "logging_api.h"
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
LogInstance*
LogInstance_create(LogicalNode* parentLN, const char* name)
{
LogInstance* self = (LogInstance*) GLOBAL_MALLOC(sizeof(LogInstance));
self->name = copyString(name);
self->parentLN = parentLN;
self->logStorage = NULL;
self->locked = false;
self->oldEntryId = 0;
self->oldEntryTime = 0;
self->newEntryId = 0;
self->newEntryTime = 0;
return self;
}
void
LogInstance_destroy(LogInstance* self)
{
GLOBAL_FREEMEM(self->name);
GLOBAL_FREEMEM(self);
}
void
LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag)
{
LogStorage logStorage = self->logStorage;
if (logStorage != NULL) {
while (self->locked)
Thread_sleep(1);
self->locked = true;
if (DEBUG_IED_SERVER)
printf("IED_SERVER: Log value - dataRef: %s flag: %i\n", dataRef, flag);
uint64_t timestamp = Hal_getTimeInMs();
uint64_t entryID = LogStorage_addEntry(logStorage, timestamp);
int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false);
uint8_t* data = (uint8_t*) GLOBAL_MALLOC(dataSize);
MmsValue_encodeMmsData(value, data, 0, true);
LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag);
self->locked = false;
GLOBAL_FREEMEM(data);
self->newEntryId = entryID;
self->newEntryTime = timestamp;
}
else
if (DEBUG_IED_SERVER)
printf("IED_SERVER: no log storage available for logging!\n");
}
uint64_t
LogInstance_logEntryStart(LogInstance* self)
{
LogStorage logStorage = self->logStorage;
if (logStorage != NULL) {
while (self->locked)
Thread_sleep(1);
self->locked = true;
uint64_t timestamp = Hal_getTimeInMs();
uint64_t entryID = LogStorage_addEntry(logStorage, timestamp);
return entryID;
}
else {
if (DEBUG_IED_SERVER)
printf("IED_SERVER: no log storage available for logging!\n");
return 0;
}
}
void
LogInstance_logEntryData(LogInstance* self, uint64_t entryID, const char* dataRef, MmsValue* value, uint8_t flag)
{
LogStorage logStorage = self->logStorage;
if (logStorage != NULL) {
int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false);
uint8_t* data = (uint8_t*) GLOBAL_MALLOC(dataSize);
MmsValue_encodeMmsData(value, data, 0, true);
LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag);
self->locked = false;
GLOBAL_FREEMEM(data);
}
}
void
LogInstance_logEntryFinished(LogInstance* self, uint64_t entryID)
{
self->locked = false;
}
void
LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage)
{
self->logStorage = logStorage;
LogStorage_getOldestAndNewestEntries(logStorage, &(self->newEntryId), &(self->newEntryTime),
&(self->oldEntryId), &(self->oldEntryTime));
}
LogControl*
LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping)
{
LogControl* self = (LogControl*) GLOBAL_MALLOC(sizeof(LogControl));
self->enabled = false;
self->dataSet = NULL;
self->isDynamicDataSet = false;
self->triggerOps = 0;
self->logicalNode = parentLN;
self->mmsMapping = mmsMapping;
self->dataSetRef = NULL;
self->logInstance = NULL;
self->intgPd = 0;
self->nextIntegrityScan = 0;
return self;
}
void
LogControl_destroy(LogControl* self)
{
if (self != NULL) {
MmsValue_delete(self->mmsValue);
GLOBAL_FREEMEM(self->name);
if (self->dataSetRef != NULL)
GLOBAL_FREEMEM(self->dataSetRef);
GLOBAL_FREEMEM(self);
}
}
void
LogControl_setLog(LogControl* self, LogInstance* logInstance)
{
self->logInstance = logInstance;
}
static void
prepareLogControl(LogControl* logControl)
{
if (logControl->dataSetRef == NULL) {
logControl->enabled = false;
return;
}
DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, logControl->dataSetRef);
if (dataSet == NULL)
return;
else
logControl->dataSet = dataSet;
}
static bool
enableLogging(LogControl* self)
{
if ((self->dataSet != NULL) && (self->logInstance != NULL)) {
self->enabled = true;
if ((self->triggerOps & TRG_OPT_INTEGRITY) && (self->intgPd != 0))
self->nextIntegrityScan = Hal_getTimeInMs();
else
self->nextIntegrityScan = 0;
MmsValue* enabled = MmsValue_getSubElement(self->mmsValue, self->mmsType, "LogEna");
MmsValue_setBoolean(enabled, true);
return true;
}
else
return false;
}
static LogControlBlock*
getLCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index)
{
int lcbCount = 0;
LogControlBlock* nextLcb = self->model->lcbs;
while (nextLcb != NULL ) {
if (nextLcb->parent == logicalNode) {
if (lcbCount == index)
return nextLcb;
lcbCount++;
}
nextLcb = nextLcb->sibling;
}
return NULL ;
}
static LogControl*
lookupLogControl(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName)
{
LinkedList element = LinkedList_getNext(self->logControls);
while (element != NULL) {
LogControl* logControl = (LogControl*) element->data;
if (logControl->domain == domain) {
if (strcmp(logControl->logicalNode->name, lnName) == 0) {
if (strcmp(logControl->logControlBlock->name, objectName) == 0) {
return logControl;
}
}
}
element = LinkedList_getNext(element);
}
return NULL;
}
static LogInstance*
getLogInstanceByLogRef(MmsMapping* self, const char* logRef)
{
char refStr[130];
char* domainName;
char* lnName;
char* logName;
strncpy(refStr, logRef, 129);
domainName = refStr;
lnName = strchr(refStr, '/');
if (lnName == NULL)
return NULL;
if ((lnName - domainName) > 64)
return NULL;
lnName[0] = 0;
lnName++;
logName = strchr(lnName, '$');
if (logName == NULL)
return NULL;
logName[0] = 0;
logName++;
LinkedList instance = LinkedList_getNext(self->logInstances);
while (instance != NULL) {
LogInstance* logInstance = (LogInstance*) LinkedList_getData(instance);
if (strcmp(logInstance->name, logName) == 0) {
if (strcmp(lnName, logInstance->parentLN->name) == 0) {
LogicalDevice* ld = (LogicalDevice*) logInstance->parentLN->parent;
if (strcmp(ld->name, domainName) == 0)
return logInstance;
}
}
instance = LinkedList_getNext(instance);
}
return NULL;
}
static void
updateLogStatusInLCB(LogControl* self)
{
LogInstance* logInstance = self->logInstance;
if (logInstance != NULL) {
MmsValue_setBinaryTime(self->oldEntrTm, logInstance->oldEntryTime);
MmsValue_setBinaryTime(self->newEntrTm, logInstance->newEntryTime);
MmsValue_setOctetString(self->oldEntr, (uint8_t*) &(logInstance->oldEntryId), 8);
MmsValue_setOctetString(self->newEntr, (uint8_t*) &(logInstance->newEntryId), 8);
}
}
static void
freeDynamicDataSet(LogControl* self)
{
if (self->isDynamicDataSet) {
if (self->dataSet != NULL) {
MmsMapping_freeDynamicallyCreatedDataSet(self->dataSet);
self->isDynamicDataSet = false;
self->dataSet = NULL;
}
}
}
MmsDataAccessError
LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig,
MmsValue* value, MmsServerConnection connection)
{
bool updateValue = false;
char variableId[130];
strncpy(variableId, variableIdOrig, 129);
char* separator = strchr(variableId, '$');
*separator = 0;
char* lnName = variableId;
if (lnName == NULL)
return DATA_ACCESS_ERROR_INVALID_ADDRESS;
char* objectName = MmsMapping_getNextNameElement(separator + 1);
if (objectName == NULL)
return DATA_ACCESS_ERROR_INVALID_ADDRESS;
char* varName = MmsMapping_getNextNameElement(objectName);
if (varName != NULL)
*(varName - 1) = 0;
LogControl* logControl = lookupLogControl(self, domain, lnName, objectName);
if (logControl == NULL) {
return DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT;
}
if (strcmp(varName, "LogEna") == 0) {
bool logEna = MmsValue_getBoolean(value);
if (logEna == false) {
logControl->enabled = false;
}
else {
if (enableLogging(logControl)) {
logControl->enabled = true;
if (DEBUG_IED_SERVER)
printf("IED_SERVER: enabled log control %s\n", logControl->name);
}
else
return DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT;
}
updateValue = true;
}
else if (strcmp(varName, "LogRef") == 0) {
if (logControl->enabled == false) {
/* check if logRef is valid or NULL */
const char* logRef = MmsValue_toString(value);
if (logRef == NULL) {
logControl->logInstance = NULL;
updateValue = true;
}
else {
if (strcmp(logRef, "") == 0) {
logControl->logInstance = NULL;
updateValue = true;
}
else {
/* remove IED name from logRef */
char* iedName = self->mmsDevice->deviceName;
uint32_t iedNameLen = strlen(iedName);
if (iedNameLen < strlen(logRef)) {
if (memcmp(iedName, logRef, iedNameLen) == 0) {
logRef = logRef + iedNameLen;
}
}
LogInstance* logInstance = getLogInstanceByLogRef(self, logRef);
if (logInstance != NULL) {
logControl->logInstance = logInstance;
updateValue = true;
}
else
return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
}
}
}
else
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
}
else if (strcmp(varName, "DatSet") == 0) {
if (logControl->enabled == false) {
/* check if datSet is valid or NULL/empty */
const char* dataSetRef = MmsValue_toString(value);
if (strlen(dataSetRef) == 0) {
logControl->dataSet = NULL;
updateValue = true;
}
else {
DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, dataSetRef);
if (dataSet != NULL) {
freeDynamicDataSet(logControl);
logControl->dataSet = dataSet;
updateValue = true;
}
#if (MMS_DYNAMIC_DATA_SETS == 1)
if (dataSet == NULL) {
dataSet = MmsMapping_getDomainSpecificDataSet(self, dataSetRef);
if (dataSet == NULL) {
if (dataSetRef[0] == '/') { /* check for VMD specific data set */
MmsNamedVariableList mmsVariableList =
MmsDevice_getNamedVariableListWithName(self->mmsDevice, dataSetRef + 1);
if (mmsVariableList != NULL)
dataSet = MmsMapping_createDataSetByNamedVariableList(self, mmsVariableList);
}
}
if (dataSet != NULL) {
freeDynamicDataSet(logControl);
logControl->dataSet = dataSet;
logControl->isDynamicDataSet = true;
updateValue = true;
}
}
#endif /*(MMS_DYNAMIC_DATA_SETS == 1) */
if (dataSet == NULL) {
if (DEBUG_IED_SERVER)
printf("IED_SERVER: data set (%s) not found!\n", logControl->dataSetRef);
return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID;
}
}
}
else
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
}
else if (strcmp(varName, "IntgPd") == 0) {
if (logControl->enabled == false) {
logControl->intgPd = MmsValue_toUint32(value);
updateValue = true;
}
else
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
}
else if (strcmp(varName, "TrgOps") == 0) {
if (logControl->enabled == false) {
logControl->triggerOps = (MmsValue_getBitStringAsInteger(value) / 2);
updateValue = true;
}
else
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
}
if (updateValue) {
MmsValue* element = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName);
MmsValue_update(element, value);
return DATA_ACCESS_ERROR_SUCCESS;
}
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
}
MmsValue*
LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig)
{
MmsValue* value = NULL;
char variableId[130];
strncpy(variableId, variableIdOrig, 129);
char* separator = strchr(variableId, '$');
*separator = 0;
char* lnName = variableId;
if (lnName == NULL)
return NULL;
char* objectName = MmsMapping_getNextNameElement(separator + 1);
if (objectName == NULL)
return NULL;
char* varName = MmsMapping_getNextNameElement(objectName);
if (varName != NULL)
*(varName - 1) = 0;
LogControl* logControl = lookupLogControl(self, domain, lnName, objectName);
if (logControl != NULL) {
updateLogStatusInLCB(logControl);
if (varName != NULL) {
value = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName);
}
else {
value = logControl->mmsValue;
}
}
return value;
}
static char*
createDataSetReferenceForDefaultDataSet(LogControlBlock* lcb, LogControl* logControl)
{
char* dataSetReference;
char* domainName = MmsDomain_getName(logControl->domain);
char* lnName = lcb->parent->name;
dataSetReference = createString(5, domainName, "/", lnName, "$", lcb->dataSetName);
return dataSetReference;
}
static MmsValue*
createTrgOps(LogControlBlock* logControlBlock) {
MmsValue* trgOps = MmsValue_newBitString(-6);
uint8_t triggerOps = logControlBlock->trgOps;
if (triggerOps & TRG_OPT_DATA_CHANGED)
MmsValue_setBitStringBit(trgOps, 1, true);
if (triggerOps & TRG_OPT_QUALITY_CHANGED)
MmsValue_setBitStringBit(trgOps, 2, true);
if (triggerOps & TRG_OPT_DATA_UPDATE)
MmsValue_setBitStringBit(trgOps, 3, true);
if (triggerOps & TRG_OPT_INTEGRITY)
MmsValue_setBitStringBit(trgOps, 4, true);
return trgOps;
}
static void
LogControl_updateLogEna(LogControl* self)
{
MmsValue_setBoolean(MmsValue_getElement(self->mmsValue, 0), self->enabled);
}
static MmsVariableSpecification*
createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock,
LogControl* logControl)
{
MmsVariableSpecification* lcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
lcb->name = copyString(logControlBlock->name);
lcb->type = MMS_STRUCTURE;
MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
mmsValue->deleteValue = false;
mmsValue->type = MMS_STRUCTURE;
int structSize = 9;
mmsValue->value.structure.size = structSize;
mmsValue->value.structure.components = (MmsValue**) GLOBAL_CALLOC(structSize, sizeof(MmsValue*));
lcb->typeSpec.structure.elementCount = structSize;
lcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(structSize,
sizeof(MmsVariableSpecification*));
/* LogEna */
MmsVariableSpecification* namedVariable =
(MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("LogEna");
namedVariable->type = MMS_BOOLEAN;
lcb->typeSpec.structure.elements[0] = namedVariable;
mmsValue->value.structure.components[0] = MmsValue_newBoolean(logControlBlock->logEna);
/* LogRef */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("LogRef");
namedVariable->typeSpec.visibleString = -129;
namedVariable->type = MMS_VISIBLE_STRING;
lcb->typeSpec.structure.elements[1] = namedVariable;
if (logControlBlock->logRef != NULL) {
char logRef[130];
int maxLogRefLength = 129 - strlen(self->model->name);
strcpy(logRef, self->model->name);
strncat(logRef, logControlBlock->logRef, maxLogRefLength);
mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logRef);
}
else {
char* logRef = createString(4, logControl->domain->domainName, "/", logControlBlock->parent->name,
"$GeneralLog");
mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logRef);
GLOBAL_FREEMEM(logRef);
}
/* DatSet */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("DatSet");
namedVariable->typeSpec.visibleString = -129;
namedVariable->type = MMS_VISIBLE_STRING;
lcb->typeSpec.structure.elements[2] = namedVariable;
if (logControlBlock->dataSetName != NULL) {
char* dataSetReference = createDataSetReferenceForDefaultDataSet(logControlBlock, logControl);
logControl->dataSetRef = dataSetReference;
mmsValue->value.structure.components[2] = MmsValue_newVisibleString(dataSetReference);
}
else
mmsValue->value.structure.components[2] = MmsValue_newVisibleString("");
/* OldEntrTm */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("OldEntrTm");
namedVariable->type = MMS_BINARY_TIME;
namedVariable->typeSpec.binaryTime = 6;
lcb->typeSpec.structure.elements[3] = namedVariable;
mmsValue->value.structure.components[3] = MmsValue_newBinaryTime(false);
logControl->oldEntrTm = mmsValue->value.structure.components[3];
/* NewEntrTm */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("NewEntrTm");
namedVariable->type = MMS_BINARY_TIME;
namedVariable->typeSpec.binaryTime = 6;
lcb->typeSpec.structure.elements[4] = namedVariable;
mmsValue->value.structure.components[4] = MmsValue_newBinaryTime(false);
logControl->newEntrTm = mmsValue->value.structure.components[4];
/* OldEntr */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("OldEntr");
namedVariable->type = MMS_OCTET_STRING;
namedVariable->typeSpec.octetString = 8;
lcb->typeSpec.structure.elements[5] = namedVariable;
mmsValue->value.structure.components[5] = MmsValue_newOctetString(8, 8);
logControl->oldEntr = mmsValue->value.structure.components[5];
/* NewEntr */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("NewEntr");
namedVariable->type = MMS_OCTET_STRING;
namedVariable->typeSpec.octetString = 8;
lcb->typeSpec.structure.elements[6] = namedVariable;
mmsValue->value.structure.components[6] = MmsValue_newOctetString(8, 8);
logControl->newEntr = mmsValue->value.structure.components[6];
/* TrgOps */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("TrgOps");
namedVariable->type = MMS_BIT_STRING;
namedVariable->typeSpec.bitString = -6;
lcb->typeSpec.structure.elements[7] = namedVariable;
mmsValue->value.structure.components[7] = createTrgOps(logControlBlock);
/* IntgPd */
namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
namedVariable->name = copyString("IntgPd");
namedVariable->type = MMS_UNSIGNED;
namedVariable->typeSpec.unsignedInteger = 32;
lcb->typeSpec.structure.elements[8] = namedVariable;
mmsValue->value.structure.components[8] =
MmsValue_newUnsignedFromUint32(logControlBlock->intPeriod);
logControl->intgPd = logControlBlock->intPeriod;
logControl->mmsType = lcb;
logControl->mmsValue = mmsValue;
logControl->logControlBlock = logControlBlock;
logControl->triggerOps = logControlBlock->trgOps;
logControl->enabled = logControlBlock->logEna;
prepareLogControl(logControl);
if (logControl->enabled)
enableLogging(logControl);
LogControl_updateLogEna(logControl);
return lcb;
}
MmsVariableSpecification*
Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode,
int lcbCount)
{
MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1,
sizeof(MmsVariableSpecification));
namedVariable->name = copyString("LG");
namedVariable->type = MMS_STRUCTURE;
namedVariable->typeSpec.structure.elementCount = lcbCount;
namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(lcbCount,
sizeof(MmsVariableSpecification*));
int currentLcb = 0;
while (currentLcb < lcbCount) {
LogControl* logControl = LogControl_create(logicalNode, self);
LogControlBlock* logControlBlock = getLCBForLogicalNodeWithIndex(self, logicalNode, currentLcb);
logControl->name = createString(3, logicalNode->name, "$LG$", logControlBlock->name);
logControl->domain = domain;
namedVariable->typeSpec.structure.elements[currentLcb] =
createLogControlBlock(self, logControlBlock, logControl);
if (logControlBlock->logRef != NULL)
logControl->logInstance = getLogInstanceByLogRef(self, logControlBlock->logRef);
LinkedList_add(self->logControls, logControl);
currentLcb++;
}
return namedVariable;
}
static void
LogControl_logAllDatasetEntries(LogControl* self, const char* iedName)
{
if (self->dataSet == NULL)
return;
if (self->logInstance != NULL) {
char dataRef[130];
LogInstance* log = self->logInstance;
uint64_t entryID = LogInstance_logEntryStart(log);
DataSetEntry* dataSetEntry = self->dataSet->fcdas;
while (dataSetEntry != NULL) {
sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName);
LogInstance_logEntryData(log, entryID, dataRef, dataSetEntry->value, TRG_OPT_INTEGRITY * 2);
dataSetEntry = dataSetEntry->sibling;
}
LogInstance_logEntryFinished(log, entryID);
}
}
void
Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs)
{
LinkedList logControlElem = LinkedList_getNext(self->logControls);
while (logControlElem != NULL) {
LogControl* logControl = (LogControl*) LinkedList_getData(logControlElem);
if (logControl->enabled) {
if (logControl->nextIntegrityScan != 0) {
if (currentTimeInMs >= logControl->nextIntegrityScan) {
//if (DEBUG_IED_SERVER)
printf("IED_SERVER: INTEGRITY SCAN for log %s\n", logControl->name);
LogControl_logAllDatasetEntries(logControl, self->mmsDevice->deviceName);
logControl->nextIntegrityScan += logControl->intgPd;
}
}
}
logControlElem = LinkedList_getNext(logControlElem);
}
}
void
MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logStorage)
{
LogInstance* logInstance = getLogInstanceByLogRef(self, logRef);
if (logInstance != NULL) {
LogInstance_setLogStorage(logInstance, logStorage);
char domainName[65];
MmsMapping_getMmsDomainFromObjectReference(logRef, domainName);
char domainNameWithIEDName[65];
strcpy(domainNameWithIEDName, self->model->name);
strcat(domainNameWithIEDName, domainName);
MmsDomain* mmsDomain = MmsDevice_getDomain(self->mmsDevice, domainNameWithIEDName);
if (mmsDomain == NULL) {
if (DEBUG_IED_SERVER)
printf("IED_SERVER: MmsMapping_setLogStorage: domain %s not found!\n", domainNameWithIEDName);
return;
}
MmsJournal mmsJournal = NULL;
const char* logName = strchr(logRef, '/');
if (logName != NULL) {
logName += 1;
mmsJournal = MmsDomain_getJournal(mmsDomain, logName);
}
if (mmsJournal != NULL)
mmsJournal->logStorage = logStorage;
else
if (DEBUG_IED_SERVER)
printf("IED_SERVER: Failed to retrieve MMS journal for log!\n");
}
if (DEBUG_IED_SERVER)
if (logInstance == NULL)
printf("IED_SERVER: MmsMapping_setLogStorage no matching log for %s found!\n", logRef);
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */

@ -1,7 +1,7 @@
/*
* mms_mapping.c
*
* Copyright 2013, 2014, 2015 Michael Zillgith
* Copyright 2013-2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -30,6 +30,7 @@
#include "mms_goose.h"
#include "mms_sv.h"
#include "reporting.h"
#include "logging.h"
#include "control.h"
#include "ied_server_private.h"
@ -756,6 +757,25 @@ countReportControlBlocksForLogicalNode(MmsMapping* self, LogicalNode* logicalNod
}
#endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
static int
countLogControlBlocksForLogicalNode (MmsMapping* self, LogicalNode* logicalNode)
{
int lcbCount = 0;
LogControlBlock* lcb = self->model->lcbs;
while (lcb != NULL) {
if (lcb->parent == logicalNode)
lcbCount++;
lcb = lcb->sibling;
}
return lcbCount;
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)
@ -870,6 +890,19 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain,
}
#endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
int lcbCount = countLogControlBlocksForLogicalNode(self, logicalNode);
if (lcbCount > 0) {
if (DEBUG_IED_SERVER)
printf(" and %i LOG control blocks\n", lcbCount);
componentCount++;
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)
int gseCount = countGSEControlBlocksForLogicalNode(self, logicalNode);
@ -969,7 +1002,15 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain,
}
#endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */
/* TODO create LCBs here */
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
if (lcbCount > 0) {
namedVariable->typeSpec.structure.elements[currentComponent] =
Logging_createLCBs(self, domain, logicalNode, lcbCount);
currentComponent++;
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
#if (CONFIG_IEC61850_REPORT_SERVICE == 1)
if (brcbCount > 0) {
@ -1069,6 +1110,36 @@ createMmsDomainFromIedDevice(MmsMapping* self, LogicalDevice* logicalDevice)
if (domain == NULL)
goto exit_function;
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
/* add logs (journals) */
Log* log = self->model->logs;
while (log != NULL) {
char journalName[65];
int nameLength = strlen(log->parent->name) + strlen(log->name);
if (nameLength > 63) {
if (DEBUG_IED_SERVER)
printf("IED_SERVER: Log name %s invalid! Resulting journal name too long! Skip log\n", log->name);
}
else {
strcpy(journalName, log->parent->name);
strcat(journalName, "$");
strcat(journalName, log->name);
MmsDomain_addJournal(domain, journalName);
LogInstance* logInstance = LogInstance_create(log->parent, log->name);
LinkedList_add(self->logInstances, (void*) logInstance);
}
log = log->sibling;
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
int nodesCount = LogicalDevice_getLogicalNodeCount(logicalDevice);
/* Logical nodes are first level named variables */
@ -1193,6 +1264,11 @@ MmsMapping_create(IedModel* model)
self->reportControls = LinkedList_create();
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
self->logControls = LinkedList_create();
self->logInstances = LinkedList_create();
#endif
#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)
self->gseControls = LinkedList_create();
self->gooseInterfaceId = NULL;
@ -1254,6 +1330,11 @@ MmsMapping_destroy(MmsMapping* self)
LinkedList_destroy(self->settingGroups);
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
LinkedList_destroyDeep(self->logControls, (LinkedListValueDeleteFunction) LogControl_destroy);
LinkedList_destroyDeep(self->logInstances, (LinkedListValueDeleteFunction) LogInstance_destroy);
#endif
LinkedList_destroy(self->observedObjects);
LinkedList_destroy(self->attributeAccessHandlers);
@ -1370,6 +1451,19 @@ isSampledValueControlBlock(char* separator)
#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
static bool
isLogControlBlock(char* separator)
{
if (strncmp(separator + 1, "LG", 2) == 0)
return true;
return false;
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
char*
MmsMapping_getNextNameElement(char* name)
{
@ -1705,6 +1799,13 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
#endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
/* Log control block - LG */
if (isLogControlBlock(separator))
return LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(self, domain, variableId, value, connection);
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
#if (CONFIG_IEC61850_REPORT_SERVICE == 1)
/* Report control blocks - BR, RP */
@ -1894,7 +1995,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain,
SettingGroup* sg = getSettingGroupByMmsDomain(self, domain);
if (sg->editingClient != (ClientConnection) connection)
return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED;
return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE;
}
#endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */
@ -2145,6 +2246,14 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo
}
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
/* LOG control block - LG */
if (isLogControlBlock(separator)) {
retValue = LIBIEC61850_LOG_SVC_readAccessControlBlock(self, domain, variableId);
goto exit_function;
}
#endif
#if (CONFIG_IEC61850_REPORT_SERVICE == 1)
/* Report control blocks - BR, RP */
if (isReportControlBlock(separator)) {
@ -2374,10 +2483,10 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li
else {
/* Check if data set is referenced in a report */
LinkedList element = self->reportControls;
LinkedList rcElement = self->reportControls;
while ((element = LinkedList_getNext(element)) != NULL) {
ReportControl* rc = (ReportControl*) element->data;
while ((rcElement = LinkedList_getNext(rcElement)) != NULL) {
ReportControl* rc = (ReportControl*) rcElement->data;
if (rc->isDynamicDataSet) {
if (rc->dataSet != NULL) {
@ -2386,16 +2495,24 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li
if (rc->dataSet->logicalDeviceName != NULL) {
if (strcmp(rc->dataSet->name, listName) == 0) {
if (strcmp(rc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) {
allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED;
allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT;
break;
}
}
}
}
else if (listType == MMS_VMD_SPECIFIC) {
if (rc->dataSet->logicalDeviceName == NULL) {
if (strcmp(rc->dataSet->name, listName) == 0) {
allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT;
break;
}
}
}
else if (listType == MMS_ASSOCIATION_SPECIFIC) {
if (rc->dataSet->logicalDeviceName == NULL) {
if (strcmp(rc->dataSet->name, listName) == 0) {
allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED;
allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT;
break;
}
}
@ -2404,6 +2521,42 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li
}
}
}
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
/* check if data set is referenced in a log control block*/
LinkedList logElement = self->logControls;
while ((logElement = LinkedList_getNext(logElement)) != NULL) {
LogControl* lc = (LogControl*) logElement->data;
if (lc->isDynamicDataSet) {
if (lc->dataSet != NULL) {
if (listType == MMS_DOMAIN_SPECIFIC) {
if (lc->dataSet->logicalDeviceName != NULL) {
if (strcmp(lc->dataSet->name, listName) == 0) {
if (strcmp(lc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) {
allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT;
break;
}
}
}
}
else if (listType == MMS_VMD_SPECIFIC) {
if (lc->dataSet->logicalDeviceName == NULL) {
if (strcmp(lc->dataSet->name, listName) == 0) {
allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT;
break;
}
}
}
}
}
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
}
return allow;
@ -2432,7 +2585,7 @@ MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicat
self->connectionIndicationHandlerParameter = parameter;
}
#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1))
static bool
isMemberValueRecursive(MmsValue* container, MmsValue* value)
@ -2460,6 +2613,8 @@ isMemberValueRecursive(MmsValue* container, MmsValue* value)
return isMemberValue;
}
#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1))
static bool
DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index)
{
@ -2489,6 +2644,97 @@ DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index)
}
#endif /* ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT)) */
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
static bool
DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef, const char* iedName)
{
int i = 0;
DataSetEntry* dataSetEntry = dataSet->fcdas;
while (dataSetEntry != NULL) {
MmsValue* dataSetValue = dataSetEntry->value;
if (dataSetValue != NULL) { /* prevent invalid data set members */
if (isMemberValueRecursive(dataSetValue, value)) {
if (dataRef != NULL)
sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName);
return true;
}
}
i++;
dataSetEntry = dataSetEntry->sibling;
}
return false;
}
void
MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag)
{
LinkedList element = self->logControls;
while ((element = LinkedList_getNext(element)) != NULL) {
LogControl* lc = (LogControl*) element->data;
if ((lc->enabled) && (lc->dataSet != NULL)) {
uint8_t reasonCode;
switch (flag) {
case LOG_CONTROL_VALUE_UPDATE:
if ((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)
continue;
reasonCode = TRG_OPT_DATA_UPDATE * 2;
break;
case LOG_CONTROL_VALUE_CHANGED:
if (((lc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) &&
((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0))
continue;
reasonCode = TRG_OPT_DATA_CHANGED * 2;
break;
case LOG_CONTROL_QUALITY_CHANGED:
if ((lc->triggerOps & TRG_OPT_QUALITY_CHANGED) == 0)
continue;
reasonCode = TRG_OPT_QUALITY_CHANGED * 2;
break;
default:
continue;
}
char dataRef[130];
if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef, self->model->name)) {
if (lc->logInstance != NULL) {
LogInstance_logSingleData(lc->logInstance, dataRef, value, reasonCode);
}
else
printf("No log instance available!\n");
}
}
}
}
#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */
#if (CONFIG_IEC61850_REPORT_SERVICE == 1)
void
MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag)
@ -2655,10 +2901,27 @@ MmsMapping_createMmsVariableNameFromObjectReference(const char* objectReference,
return NULL;
if (i == objRefLength)
i = 0;
i = 0; /* for the case when no LD name is present */
else
i++;
if (fc == IEC61850_FC_NONE) {
int len = objRefLength - i;
char* mmsVariableName;
if (buffer == NULL)
mmsVariableName = (char*) GLOBAL_MALLOC(len);
else
mmsVariableName = buffer;
strcpy(mmsVariableName, objectReference + i);
return mmsVariableName;
}
char* fcString = FunctionalConstraint_toString(fc);
if (fcString == NULL)
@ -2756,6 +3019,10 @@ processPeriodicTasks(MmsMapping* self)
#if (CONFIG_IEC61850_SETTING_GROUPS == 1)
MmsMapping_checkForSettingGroupReservationTimeouts(self, currentTimeInMs);
#endif
#if (CONFIG_IEC61850_LOG_SERVICE == 1)
Logging_processIntegrityLogs(self, currentTimeInMs);
#endif
}
void

@ -249,7 +249,6 @@ LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* d
return value;
}
static SVControlBlock*
getSVCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index, bool isUnicast)
{

@ -774,11 +774,47 @@ CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
DataAttribute_create("vendor", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
DataAttribute_create("swRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
if (options & CDC_OPTION_AC_LN0_M)
DataAttribute_create("configRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
if (options & CDC_OPTION_AC_LN0_EX)
DataAttribute_create("ldNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0);
if (options & CDC_OPTION_AC_DLD_M)
DataAttribute_create("lnNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0);
CDC_addStandardOptions(newLPL, options);
return newLPL;
}
DataObject*
CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
DataObject* newDPL = DataObject_create(dataObjectName, parent, 0);
DataAttribute_create("vendor", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
if (options & CDC_OPTION_DPL_HWREV)
DataAttribute_create("hwRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
if (options & CDC_OPTION_DPL_SWREV)
DataAttribute_create("swRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
if (options & CDC_OPTION_DPL_SERNUM)
DataAttribute_create("serNum", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
if (options & CDC_OPTION_DPL_MODEL)
DataAttribute_create("model", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
if (options & CDC_OPTION_DPL_LOCATION)
DataAttribute_create("location", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
CDC_addStandardOptions(newDPL, options);
return newDPL;
}
/* Directional protection activation information (ACD) */
DataObject*
CDC_ACD_create(const char* dataObjectName, ModelNode* parent, uint32_t options)

@ -215,6 +215,38 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
ReportControlBlock_create(nameString, currentLN, rptId,
(bool) isBuffered, dataSetName, confRef, trgOps, options, bufTm, intgPd);
}
else if (StringUtils_startsWith((char*) lineBuffer, "LC")) {
uint32_t trgOps;
uint32_t intgPd;
int logEna;
int withReasonCode;
int matchedItems = sscanf((char*) lineBuffer, "LC(%s %s %s %u %u %i %i)",
nameString, nameString2, nameString3, &trgOps, &intgPd, &logEna, &withReasonCode);
if (matchedItems < 7) goto exit_error;
char* dataSet = NULL;
if (strcmp(nameString2, "-") != 0)
dataSet = nameString2;
char* logRef = NULL;
if (strcmp(nameString3, "-") != 0)
logRef = nameString3;
LogControlBlock_create(nameString, currentLN, dataSet, logRef, trgOps, intgPd, logEna, withReasonCode);
}
else if (StringUtils_startsWith((char*) lineBuffer, "LOG")) {
int matchedItems = sscanf((char*) lineBuffer, "LOG(%s)", nameString);
if (matchedItems < 1) goto exit_error;
/* remove trailing ')' character */
int nameLen = strlen(nameString);
nameString[nameLen - 1] = 0;
Log_create(nameString, currentLN);
}
else if (StringUtils_startsWith((char*) lineBuffer, "GC")) {
uint32_t confRef;
int fixedOffs;

@ -58,6 +58,10 @@ IedModel_create(const char* name/*, MemoryAllocator allocator*/)
self->sgcbs = NULL;
self->lcbs = NULL;
self->logs = NULL;
self->initializer = iedModel_emptyVariableInitializer;
return self;
@ -97,6 +101,36 @@ IedModel_addLogicalDevice(IedModel* self, LogicalDevice* lDevice)
}
}
static void
IedModel_addLog(IedModel* self, Log* log)
{
if (self->logs == NULL)
self->logs = log;
else {
Log* lastLog = self->logs;
while (lastLog->sibling != NULL)
lastLog = lastLog->sibling;
lastLog->sibling = log;
}
}
static void
IedModel_addLogControlBlock(IedModel* self, LogControlBlock* lcb)
{
if (self->lcbs == NULL)
self->lcbs = lcb;
else {
LogControlBlock* lastLcb = self->lcbs;
while (lastLcb->sibling != NULL)
lastLcb = lastLcb->sibling;
lastLcb->sibling = lcb;
}
}
static void
IedModel_addReportControlBlock(IedModel* self, ReportControlBlock* rcb)
{
@ -231,6 +265,65 @@ LogicalNode_addDataObject(LogicalNode* self, DataObject* dataObject)
}
}
static void
LogicalNode_addLog(LogicalNode* self, Log* log)
{
IedModel* model = (IedModel*) self->parent->parent;
IedModel_addLog(model, log);
}
Log*
Log_create(const char* name, LogicalNode* parent)
{
Log* self = (Log*) GLOBAL_MALLOC(sizeof(Log));
self->name = copyString(name);
self->parent = parent;
self->sibling = NULL;
LogicalNode_addLog(parent, self);
return self;
}
static void
LogicalNode_addLogControlBlock(LogicalNode* self, LogControlBlock* lcb)
{
IedModel* model = (IedModel*) self->parent->parent;
IedModel_addLogControlBlock(model, lcb);
}
LogControlBlock*
LogControlBlock_create(const char* name, LogicalNode* parent, char* dataSetName, char* logRef, uint8_t trgOps,
uint32_t intPeriod, bool logEna, bool reasonCode)
{
LogControlBlock* self = (LogControlBlock*) GLOBAL_MALLOC(sizeof(LogControlBlock));
self->name = copyString(name);
self->parent = parent;
if (dataSetName)
self->dataSetName = copyString(dataSetName);
else
dataSetName = NULL;
if (logRef)
self->logRef = copyString(logRef);
else
logRef = NULL;
self->trgOps = trgOps;
self->intPeriod = intPeriod;
self->logEna = logEna;
self->reasonCode = reasonCode;
LogicalNode_addLogControlBlock(parent, self);
return self;
}
static void
LogicalNode_addReportControlBlock(LogicalNode* self, ReportControlBlock* rcb)
{
@ -743,6 +836,28 @@ IedModel_destroy(IedModel* model)
sgcb = nextSgcb;
}
/* delete all LCBs */
LogControlBlock* lcb = model->lcbs;
while (lcb != NULL) {
LogControlBlock* nextLcb = lcb->sibling;
GLOBAL_FREEMEM(lcb);
lcb = nextLcb;
}
/* delete all LOGs */
Log* log = model->logs;
while (log != NULL) {
Log* nextLog = log->sibling;
GLOBAL_FREEMEM(log);
log = nextLog;
}
/* delete generic model parts */

@ -372,7 +372,7 @@ LogicalNode_hasFCData(LogicalNode* node, FunctionalConstraint fc)
DataSet*
LogicalNode_getDataSet(LogicalNode* self, const char* dataSetName)
{
assert(self->modelType == LogicalNodeModelType);
assert(self->modelType == LogicalNodeModelType);
assert(dataSetName != NULL);
char dsName[66];

@ -0,0 +1,3 @@
This directory contains the log driver implementation that implements the logging API. The logging API is a service provider interface that has to be implemented by the log driver.
An application can use different log drivers at the same time.
Each log driver provides a public constructor for the driver specific implementation of the LogStorage class. The constructor has to fill the virtual function table of the LogStorage instance with its own implementation functions.

@ -0,0 +1,486 @@
/*
* log_storage_sqlite.c
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "logging_api.h"
#include "libiec61850_platform_includes.h"
#include <sqlite3.h>
#ifndef DEBUG_LOG_STORAGE_DRIVER
#define DEBUG_LOG_STORAGE_DRIVER 0
#endif
static uint64_t
SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp);
static bool
SqliteLogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode);
static bool
SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter);
static bool
SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter);
static bool
SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime,
uint64_t* oldEntry, uint64_t* oldEntryTime);
static void
SqliteLogStorage_destroy(LogStorage self);
typedef struct sSqliteLogStorage {
char* filename;
sqlite3* db;
sqlite3_stmt* insertEntryStmt;
sqlite3_stmt* insertEntryDataStmt;
sqlite3_stmt* getEntriesWithRange;
sqlite3_stmt* getEntriesAfter;
sqlite3_stmt* getEntryData;
sqlite3_stmt* getOldEntry;
sqlite3_stmt* getNewEntry;
} SqliteLogStorage;
static const char* CREATE_TABLE_ENTRYS = "create table if not exists Entries (entryID integer primary key, timeOfEntry integer)";
static const char* CREATE_TABLE_ENTRY_DATA = "create table if not exists EntryData (entryID integer, dataRef text, value blob, reasonCode integer)";
static const char* INSERT_ENTRY = "insert into Entries (timeOfEntry) values (?)";
static const char* INSERT_ENTRY_DATA = "insert into EntryData (entryID, dataRef, value, reasonCode) values (?,?,?,?)";
static const char* GET_ENTRIES_WITH_RANGE = "select entryID, timeOfEntry from Entries where timeOfEntry >= ? and timeOfEntry <= ?";
static const char* GET_ENTRIES_AFTER = "select entryID, timeOfEntry from Entries where entryID > ?";
static const char* GET_ENTRY_DATA = "select dataRef, value, reasonCode from EntryData where entryID = ?";
static const char* GET_OLD_ENTRY = "select * from Entries where entryID = (select min(entryID) from Entries where timeOfEntry = (select min(timeOfEntry) from Entries))";
static const char* GET_NEW_ENTRY = "select * from Entries where entryID = (select max(entryID) from Entries where timeOfEntry = (select max(timeOfEntry) from Entries))";
static char*
copyStringInternal(const char* string)
{
int newStringLength = strlen(string) + 1;
char* newString = (char*) malloc(newStringLength);
memcpy(newString, string, newStringLength);
return newString;
}
LogStorage
SqliteLogStorage_createInstance(const char* filename)
{
sqlite3* db = NULL;
sqlite3_stmt* insertEntryStmt = NULL;
sqlite3_stmt* insertEntryDataStmt = NULL;
sqlite3_stmt* getEntriesWithRange = NULL;
sqlite3_stmt* getEntriesAfter = NULL;
sqlite3_stmt* getEntryData = NULL;
sqlite3_stmt* getOldEntry = NULL;
sqlite3_stmt* getNewEntry = NULL;
char *zErrMsg = 0;
int rc = sqlite3_open(filename, &db);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_exec(db, CREATE_TABLE_ENTRYS, NULL, 0, &zErrMsg);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_exec(db, CREATE_TABLE_ENTRY_DATA, NULL, 0, &zErrMsg);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_prepare(db, INSERT_ENTRY, -1, &insertEntryStmt, NULL);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_prepare(db, INSERT_ENTRY_DATA, -1, &insertEntryDataStmt, NULL);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_prepare_v2(db, GET_ENTRIES_WITH_RANGE, -1, &getEntriesWithRange, NULL);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_prepare_v2(db, GET_ENTRIES_AFTER, -1, &getEntriesAfter, NULL);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_prepare_v2(db, GET_ENTRY_DATA, -1, &getEntryData, NULL);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_prepare_v2(db, GET_OLD_ENTRY, -1, &getOldEntry, NULL);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_prepare_v2(db, GET_NEW_ENTRY, -1, &getNewEntry, NULL);
if (rc != SQLITE_OK)
goto exit_with_error;
LogStorage self = (LogStorage) calloc(1, sizeof(struct sLogStorage));
SqliteLogStorage* instanceData = (SqliteLogStorage*) calloc(1, sizeof(struct sSqliteLogStorage));
instanceData->filename = copyStringInternal(filename);
instanceData->db = db;
instanceData->insertEntryStmt = insertEntryStmt;
instanceData->insertEntryDataStmt = insertEntryDataStmt;
instanceData->getEntriesWithRange = getEntriesWithRange;
instanceData->getEntriesAfter = getEntriesAfter;
instanceData->getEntryData = getEntryData;
instanceData->getOldEntry = getOldEntry;
instanceData->getNewEntry = getNewEntry;
self->instanceData = (void*) instanceData;
self->addEntry = SqliteLogStorage_addEntry;
self->addEntryData = SqliteLogStorage_addEntryData;
self->getEntries = SqliteLogStorage_getEntries;
self->getEntriesAfter = SqliteLogStorage_getEntriesAfter;
self->getOldestAndNewestEntries = SqliteLogStorage_getOldestAndNewestEntries;
self->destroy = SqliteLogStorage_destroy;
return self;
exit_with_error:
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - failed to create LogStorage instance!\n");
return NULL;
}
static uint64_t
SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp)
{
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - add entry\n");
SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData);
sqlite3* db = instanceData->db;
int rc;
char *zErrMsg = 0;
rc = sqlite3_bind_int64(instanceData->insertEntryStmt, 1, (sqlite_int64) timestamp);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_step(instanceData->insertEntryStmt);
if (rc != SQLITE_DONE)
goto exit_with_error;
uint64_t id = sqlite3_last_insert_rowid(db);
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - new entry with ID = %lu\n", id);
rc = sqlite3_reset(instanceData->insertEntryStmt);
if (rc != SQLITE_OK)
goto exit_with_error;
return id;
exit_with_error:
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - failed to add entry to log!\n");
return 0;
}
static bool
SqliteLogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode)
{
SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData);
sqlite3* db = instanceData->db;
int rc;
char *zErrMsg = 0;
rc = sqlite3_bind_int64(instanceData->insertEntryDataStmt, 1, (sqlite_int64) entryID);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_bind_text(instanceData->insertEntryDataStmt, 2, dataRef, -1, SQLITE_STATIC);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_bind_blob(instanceData->insertEntryDataStmt, 3, data, dataSize, SQLITE_STATIC);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_bind_int(instanceData->insertEntryDataStmt, 4, reasonCode);
if (rc != SQLITE_OK)
goto exit_with_error;
rc = sqlite3_step(instanceData->insertEntryDataStmt);
if (rc != SQLITE_DONE)
goto exit_with_error;
rc = sqlite3_reset(instanceData->insertEntryDataStmt);
if (rc != SQLITE_OK)
goto exit_with_error;
return true;
exit_with_error:
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - failed to add entry data!\n");
return false;
}
static void
getEntryData(LogStorage self, uint64_t entryID, LogEntryDataCallback entryDataCallback, void* parameter)
{
SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData);
int rc;
rc = sqlite3_bind_int64(instanceData->getEntryData, 1, entryID);
if (rc != SQLITE_OK) {
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - getEntryData 1 rc:%i\n", rc);
}
bool sendFinalEvent = true;
while ((rc = sqlite3_step(instanceData->getEntryData)) == SQLITE_ROW) {
const char* dataRef = (const char*) sqlite3_column_text(instanceData->getEntryData, 0);
uint8_t* data = (uint8_t*) sqlite3_column_blob(instanceData->getEntryData, 1);
int dataSize = sqlite3_column_bytes(instanceData->getEntryData, 1);
int reasonCode = sqlite3_column_int(instanceData->getEntryData, 2);
if (entryDataCallback != NULL) {
if (entryDataCallback(parameter, dataRef, data, dataSize, (uint8_t) reasonCode, true) == false) {
sendFinalEvent = false;
break;
}
}
}
if (sendFinalEvent) {
if (entryDataCallback != NULL)
entryDataCallback(parameter, NULL, NULL, 0, (uint8_t) 0, false);
}
rc = sqlite3_reset(instanceData->getEntryData);
if (rc != SQLITE_OK)
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - getEntryData reset rc:%i\n", rc);
}
static bool
SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter)
{
SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData);
sqlite3* db = instanceData->db;
int rc;
rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 1, startingTime);
if (rc != SQLITE_OK)
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries 1 rc:%i\n", rc);
rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 2, endingTime);
if (rc != SQLITE_OK)
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries 2 rc:%i\n", rc);
bool sendFinalEvent = true;
while((rc = sqlite3_step(instanceData->getEntriesWithRange)) == SQLITE_ROW) {
uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesWithRange, 0);
uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesWithRange, 1);
if (entryCallback != NULL) {
if (entryCallback(parameter, timestamp, entryID, true) == false) {
sendFinalEvent = false;
break;
}
}
getEntryData(self, entryID, entryDataCallback, parameter);
}
if (sendFinalEvent)
if (entryCallback != NULL)
entryCallback(parameter, 0, 0, false);
rc = sqlite3_reset(instanceData->getEntriesWithRange);
if (rc != SQLITE_OK)
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries reset rc:%i\n", rc);
return true;
}
static bool
SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime,
uint64_t* oldEntry, uint64_t* oldEntryTime)
{
bool validOldEntry = false;
bool validNewEntry = false;
SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData);
sqlite3* db = instanceData->db;
int rc;
/* Get oldest entry */
rc = sqlite3_step(instanceData->getOldEntry);
if (rc == SQLITE_ROW) {
*oldEntry = sqlite3_column_int64(instanceData->getOldEntry, 0);
*oldEntryTime = sqlite3_column_int64(instanceData->getOldEntry, 1);
validNewEntry = true;
}
else {
*oldEntry = 0;
*oldEntryTime = 0;
}
sqlite3_reset(instanceData->getOldEntry);
/* Get newest entry */
rc = sqlite3_step(instanceData->getNewEntry);
if (rc == SQLITE_ROW) {
*newEntry = sqlite3_column_int64(instanceData->getNewEntry, 0);
*newEntryTime = sqlite3_column_int64(instanceData->getNewEntry, 1);
validOldEntry = true;
}
else {
*newEntry = 0;
*newEntryTime = 0;
}
sqlite3_reset(instanceData->getNewEntry);
return (validOldEntry && validNewEntry);
}
static bool
SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter)
{
SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData);
sqlite3* db = instanceData->db;
int rc;
rc = sqlite3_bind_int64(instanceData->getEntriesAfter, 1, entryID);
if (rc != SQLITE_OK)
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntriesAfter 1 rc:%i\n", rc);
bool sendFinalEvent = true;
while ((rc = sqlite3_step(instanceData->getEntriesAfter)) == SQLITE_ROW) {
uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesAfter, 0);
uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesAfter, 1);
if (entryCallback != NULL) {
if (entryCallback(parameter, timestamp, entryID, true) == false) {
sendFinalEvent = false;
break;
}
}
getEntryData(self, entryID, entryDataCallback, parameter);
}
if (sendFinalEvent)
if (entryCallback != NULL)
entryCallback(parameter, 0, 0, false);
rc = sqlite3_reset(instanceData->getEntriesAfter);
if (rc != SQLITE_OK)
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntriesAfter reset rc:%i\n", rc);
return true;
}
static void
SqliteLogStorage_destroy(LogStorage self)
{
SqliteLogStorage* instanceData = (SqliteLogStorage*) self->instanceData;
sqlite3_finalize(instanceData->insertEntryStmt);
sqlite3_finalize(instanceData->insertEntryDataStmt);
sqlite3_finalize(instanceData->getEntriesWithRange);
sqlite3_finalize(instanceData->getEntriesAfter);
sqlite3_finalize(instanceData->getEntryData);
sqlite3_finalize(instanceData->getOldEntry);
sqlite3_finalize(instanceData->getNewEntry);
if (sqlite3_close(instanceData->db) != SQLITE_OK)
if (DEBUG_LOG_STORAGE_DRIVER)
printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage: failed to close database %s!\n", instanceData->filename);
free(instanceData->filename);
free(instanceData);
free(self);
}

@ -0,0 +1,65 @@
/*
* log_storage.c
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "logging_api.h"
uint64_t
LogStorage_addEntry(LogStorage self, uint64_t timestamp)
{
return self->addEntry(self, timestamp);
}
bool
LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode)
{
return self->addEntryData(self, entryID, dataRef, data, dataSize, reasonCode);
}
bool
LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter)
{
return self->getEntries(self, startingTime, endingTime, entryCallback, entryDataCallback, parameter);
}
bool
LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter)
{
return self->getEntriesAfter(self, startingTime, entryID, entryCallback, entryDataCallback, parameter);
}
void
LogStorage_destroy(LogStorage self)
{
self->destroy(self);
}
bool
LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime,
uint64_t* oldEntry, uint64_t* oldEntryTime)
{
return self->getOldestAndNewestEntries(self, newEntry, newEntryTime, oldEntry, oldEntryTime);
}

@ -0,0 +1,193 @@
/*
* logging_api.h
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#ifndef LIBIEC61850_SRC_LOGGING_LOGGING_API_H_
#define LIBIEC61850_SRC_LOGGING_LOGGING_API_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
/** \addtogroup server_api_group
* @{
*/
/**
* @defgroup LOGGING_SPI Service provider interface (SPI) for log storage implementations
*
* This interface has to be implemented by the log storage provider. The Log storage provider
* has to provide a specific constructor that creates an instance of LogStorage by allocating
* the required memory fot the struct sLogStorage data structure and populate the function
* pointers with provider specific implementation functions.
*
* @{
*/
/** The LogStorage object handle */
typedef struct sLogStorage* LogStorage;
/**
* \brief Will be called for each new LogEntry by the getEntries and getEntriesAfter functions
*
* \param parameter - a user provided parameter that is passed to the callback handler
* \param timestamp - the entry timestamp of the LogEntry
* \param entryID - the entryID of the LogEntry
* \param moreFollow - more data will follow - if false, the data is not valid and no more data will follow
*
* \return true ready to receive new data, false abort operation
*/
typedef bool (*LogEntryCallback) (void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow);
/**
* \brief Will be called for each new LogEntryData by the getEntries and getEntriesAfter functions
*
* \param parameter - a user provided parameter that is passed to the callback handler
* \param dataRef - the data reference of the LogEntryData
* \param data - the data content as an unstructured binary data block
* \param dataSize - the size of the binary data block
* \param reasonCode - the reasonCode of the LogEntryData
* \param moreFollow - more data will follow - if false, the data is not valid and no more data will follow
*
* \return true ready to receive new data, false abort operation
*/
typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow);
struct sLogStorage {
void* instanceData;
uint64_t (*addEntry) (LogStorage self, uint64_t timestamp);
bool (*addEntryData) (LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode);
bool (*getEntries) (LogStorage self, uint64_t startingTime, uint64_t endingTime,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter);
bool (*getEntriesAfter) (LogStorage self, uint64_t startingTime, uint64_t entryID,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter);
bool (*getOldestAndNewestEntries) (LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime,
uint64_t* oldEntry, uint64_t* oldEntryTime);
void (*destroy) (LogStorage self);
};
/**
* \brief Add an entry to the log
*
* \param self the pointer of the LogStorage instance
* \param timestamp the entry time of the new entry
*
* \return the entryID of the new entry
*/
uint64_t
LogStorage_addEntry(LogStorage self, uint64_t timestamp);
/**
* \brief Add new entry data to an existing log entry
*
* \param self the pointer of the LogStorage instance
* \param entryID the ID of the log entry where the data will be added
* \param dataRef the data reference of the log entry data
* \param data the data content as an unstructured binary data block
* \param dataSize - the size of the binary data block
* \param reasonCode - the reasonCode of the LogEntryData
*
* \return true if the entry data was successfully added, false otherwise
*/
bool
LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode);
/**
* \brief Get log entries specified by a time range
*
* \param self the pointer of the LogStorage instance
* \param startingTime start time of the time range
* \param endingTime end time of the time range
* \param entryCallback callback function to be called for each new log entry
* \param entryDataCallback callback function to be called for each new log entry data
* \param parameter - a user provided parameter that is passed to the callback handler
*
* \return true if the request has been successful, false otherwise
*/
bool
LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter);
/**
* \brief Get log entries specified by a start log entry
*
* The request will return all log entries that where entered into the log after the
* log entry specified by startingTime and entryID.
*
* \param self the pointer of the LogStorage instance
* \param startingTime timestamp of the start log entry
* \param entryID entryID of the start log entry
* \param entryCallback callback function to be called for each new log entry
* \param entryDataCallback callback function to be called for each new log entry data
* \param parameter - a user provided parameter that is passed to the callback handler
*
* \return true if the request has been successful, false otherwise
*/
bool
LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID,
LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter);
/**
* \brief Get the entry time and entryID of the oldest and the newest log entries
*
* This function is used to update the log status information in the LCB
*
* \param self the pointer of the LogStorage instance
* \param newEntry pointer to store the entryID of the newest entry
* \param newEntryTime pointer to store the entry time of the newest entry
* \param oldEntry pointer to store the entryID of the oldest entry
* \param oldEntryTime pointer to store the entry time of the oldest entry
*
*/
bool
LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime,
uint64_t* oldEntry, uint64_t* oldEntryTime);
/**
* \brief Destroy the LogStorage instance and free all related resources
*
* \param self the pointer of the LogStorage instance
*/
void
LogStorage_destroy(LogStorage self);
/**@}*/
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ */

@ -279,6 +279,20 @@ MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, con
LinkedList /* <char*> */
MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, const char* domainId);
/**
* \brief Get the names of all journals present in a MMS domain of the server
*
* This will result in a domain specific GetNameList request.
*
* \param self MmsConnection instance to operate on
* \param mmsError user provided variable to store error code
* \param domainId the domain name for the domain specific request
*
* \return the domain specific journal names or NULL if the request failed.
*/
LinkedList /* <char*> */
MmsConnection_getDomainJournals(MmsConnection self, MmsError* mmsError, const char* domainId);
/**
* \brief Get the names of all named variable lists associated with this client connection.
*
@ -706,6 +720,60 @@ bool
MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const char* fileSpecification, const char* continueAfter,
MmsFileDirectoryHandler handler, void* handlerParameter);
typedef struct sMmsJournalEntry* MmsJournalEntry;
typedef struct sMmsJournalVariable* MmsJournalVariable;
struct sMmsJournalEntry {
MmsValue* entryID; /* type MMS_OCTET_STRING */
MmsValue* occurenceTime; /* type MMS_BINARY_TIME */
LinkedList journalVariables;
};
struct sMmsJournalVariable {
char* tag;
MmsValue* value;
};
/**
* \brief Destroy a single MmsJournalEntry instance.
*
* This function will destroy the whole MmsJournalEntry object including the attached list
* of MmsJournalVariable objects. It is intended to be used in conjunction with the
* LinkedList_destroyDeep function in order to free the result of MmsConnection_readJournalTimeRange
* or MmsConnection_readJournalStartAfter
*
* LinkedList_destroyDeep(journalEntries, (LinkedListValueDeleteFunction)
* MmsJournalEntry_destroy);
*
* \param self the MmsJournalEntry instance to destroy
*/
void
MmsJournalEntry_destroy(MmsJournalEntry self);
const MmsValue*
MmsJournalEntry_getEntryID(MmsJournalEntry self);
const MmsValue*
MmsJournalEntry_getOccurenceTime(MmsJournalEntry self);
const LinkedList /* <MmsJournalVariable> */
MmsJournalEntry_getJournalVariables(MmsJournalEntry self);
const char*
MmsJournalVariable_getTag(MmsJournalVariable self);
const MmsValue*
MmsJournalVariable_getValue(MmsJournalVariable self);
LinkedList
MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId,
MmsValue* startingTime, MmsValue* endingTime, bool* moreFollows);
LinkedList
MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId,
MmsValue* timeSpecification, MmsValue* entrySpecification, bool* moreFollows);
/**
* \brief Destroy (free) an MmsServerIdentity object

@ -45,6 +45,8 @@ typedef enum
MMS_ERROR_PARSING_RESPONSE = 4,
MMS_ERROR_HARDWARE_FAULT = 5,
MMS_ERROR_CONCLUDE_REJECTED = 6,
MMS_ERROR_INVALID_ARGUMENTS = 7,
MMS_ERROR_OTHER = 9,
/* confirmed error PDU codes */
@ -64,6 +66,7 @@ typedef enum
MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE = 41,
MMS_ERROR_SERVICE_OTHER = 50,
MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT = 55,
MMS_ERROR_SERVICE_PREEMPT_OTHER = 60,

@ -29,6 +29,7 @@
#include "mms_type_spec.h"
#include "mms_common.h"
#include "mms_named_variable_list.h"
#include "logging_api.h"
#ifdef __cplusplus
extern "C" {
@ -49,14 +50,23 @@ typedef struct {
MmsDomain** domains;
} MmsDevice;
struct sMmsJournal {
char* name;
LogStorage logStorage;
};
typedef struct sMmsJournal* MmsJournal;
/*
* Server side data structure to hold informations for a MMS domain (Logical Device)
* Server side data structure to hold information of an MMS domain (Logical Device)
*/
struct sMmsDomain {
char* domainName;
int namedVariablesCount;
MmsVariableSpecification** namedVariables;
LinkedList /*<MmsNamedVariableList>*/ namedVariableLists;
LinkedList /* <MmsJournal> */ journals;
};
/**
@ -80,6 +90,12 @@ MmsDomain_create(char* domainName);
char*
MmsDomain_getName(MmsDomain* self);
void
MmsDomain_addJournal(MmsDomain* self, const char* name);
MmsJournal
MmsDomain_getJournal(MmsDomain* self, const char* name);
/**
* Delete a MmsDomain instance
*
@ -185,6 +201,12 @@ MmsDevice_getNamedVariableLists(MmsDevice* self);
MmsNamedVariableList
MmsDevice_getNamedVariableListWithName(MmsDevice* self, const char* variableListName);
MmsJournal
MmsJournal_create(const char* name);
void
MmsJournal_destroy(MmsJournal self);
/**@}*/
#ifdef __cplusplus

@ -101,7 +101,7 @@ MmsValue_getArraySize(const MmsValue* self);
* \return the element object
*/
MmsValue*
MmsValue_getElement(MmsValue* array, int index);
MmsValue_getElement(const MmsValue* array, int index);
/**
* \brief Create an emtpy array.
@ -911,7 +911,7 @@ MmsValue_isDeletable(MmsValue* self);
* \param self the MmsValue instance
*/
MmsType
MmsValue_getType(MmsValue* self);
MmsValue_getType(const MmsValue* self);
/**
* \brief Get a sub-element of a MMS_STRUCTURE value specified by a path name.
@ -947,8 +947,8 @@ MmsValue_getTypeString(MmsValue* self);
*
* \return a pointer to the start of the buffer
*/
char*
MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize);
const char*
MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize);
/**
* \brief create a new MmsValue instance from a BER encoded MMS Data element (deserialize)

@ -94,9 +94,10 @@ struct sMmsConnection {
* MMS Object class enumeration type
*/
typedef enum {
MMS_NAMED_VARIABLE,
MMS_NAMED_VARIABLE_LIST,
MMS_DOMAIN_NAMES
MMS_OBJECT_CLASS_NAMED_VARIABLE = 0,
MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST = 2,
MMS_OBJECT_CLASS_JOURNAL = 8,
MMS_OBJECT_CLASS_DOMAIN = 9
} MmsObjectClass;
MmsValue*
@ -259,4 +260,15 @@ int
mmsClient_createMmsGetNameListRequestAssociationSpecific(long invokeId, ByteBuffer* writeBuffer,
const char* continueAfter);
void
mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId,
MmsValue* startingTime, MmsValue* endingTime);
void
mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId,
MmsValue* timeSpecification, MmsValue* entrySpecification);
bool
mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, LinkedList* result);
#endif /* MMS_MSG_INTERNAL_H_ */

@ -168,7 +168,11 @@ MmsPdu_t*
mmsServer_createConfirmedResponse(uint32_t invokeId);
void
mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType);
mmsServer_createServiceErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType);
void
mmsServer_createServiceErrorPduWithServiceSpecificInfo(uint32_t invokeId, ByteBuffer* response,
MmsError errorType, uint8_t* serviceSpecificInfo, int serviceSpecficInfoLength);
void
mmsServer_writeConcludeResponsePdu(ByteBuffer* response);
@ -221,6 +225,14 @@ mmsServer_handleStatusRequest(
int invokeId,
ByteBuffer* response);
void
mmsServer_handleReadJournalRequest(
MmsServerConnection connection,
uint8_t* requestBuffer,
int bufPos, int maxBufPos,
uint32_t invokeId,
ByteBuffer* response);
void
mmsServer_handleFileDirectoryRequest(
MmsServerConnection connection,

@ -1054,7 +1054,7 @@ mmsClient_getNameListSingleRequest(
payload, continueAfter);
else {
if (objectClass == MMS_DOMAIN_NAMES)
if (objectClass == MMS_OBJECT_CLASS_DOMAIN)
mmsClient_createMmsGetNameListRequestVMDspecific(invokeId,
payload, continueAfter);
else
@ -1110,31 +1110,37 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError,
LinkedList /* <char*> */
MmsConnection_getVMDVariableNames(MmsConnection self, MmsError* mmsError)
{
return mmsClient_getNameList(self, mmsError, NULL, MMS_NAMED_VARIABLE, false);
return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_NAMED_VARIABLE, false);
}
LinkedList /* <char*> */
MmsConnection_getDomainNames(MmsConnection self, MmsError* mmsError)
{
return mmsClient_getNameList(self, mmsError, NULL, MMS_DOMAIN_NAMES, false);
return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_DOMAIN, false);
}
LinkedList /* <char*> */
MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, const char* domainId)
{
return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE, false);
return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_NAMED_VARIABLE, false);
}
LinkedList /* <char*> */
MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, const char* domainId)
{
return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE_LIST, false);
return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST, false);
}
LinkedList /* <char*> */
MmsConnection_getDomainJournals(MmsConnection self, MmsError* mmsError, const char* domainId)
{
return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_JOURNAL, false);
}
LinkedList /* <char*> */
MmsConnection_getVariableListNamesAssociationSpecific(MmsConnection self, MmsError* mmsError)
{
return mmsClient_getNameList(self, mmsError, NULL, MMS_NAMED_VARIABLE_LIST, true);
return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST, true);
}
MmsValue*
@ -1636,6 +1642,124 @@ MmsConnection_getServerStatus(MmsConnection self, MmsError* mmsError, int* vmdLo
}
static LinkedList
readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuffer* payload, bool* moreFollows)
{
*mmsError = MMS_ERROR_NONE;
ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload);
LinkedList response = NULL;
if (self->lastResponseError != MMS_ERROR_NONE)
*mmsError = self->lastResponseError;
else if (responseMessage != NULL) {
if (mmsClient_parseReadJournalResponse(self, moreFollows, &response) == false)
*mmsError = MMS_ERROR_PARSING_RESPONSE;
}
releaseResponse(self);
if (self->associationState == MMS_STATE_CLOSED)
*mmsError = MMS_ERROR_CONNECTION_LOST;
return response;
}
static void
MmsJournalVariable_destroy(MmsJournalVariable self)
{
if (self != NULL) {
GLOBAL_FREEMEM(self->tag);
MmsValue_delete(self->value);
GLOBAL_FREEMEM(self);
}
}
void
MmsJournalEntry_destroy(MmsJournalEntry self)
{
if (self != NULL) {
MmsValue_delete(self->entryID);
MmsValue_delete(self->occurenceTime);
LinkedList_destroyDeep(self->journalVariables,
(LinkedListValueDeleteFunction) MmsJournalVariable_destroy);
GLOBAL_FREEMEM(self);
}
}
const MmsValue*
MmsJournalEntry_getEntryID(MmsJournalEntry self)
{
return self->entryID;
}
const MmsValue*
MmsJournalEntry_getOccurenceTime(MmsJournalEntry self)
{
return self->occurenceTime;
}
const LinkedList /* <MmsJournalVariable> */
MmsJournalEntry_getJournalVariables(MmsJournalEntry self)
{
return self->journalVariables;
}
const char*
MmsJournalVariable_getTag(MmsJournalVariable self)
{
return self->tag;
}
const MmsValue*
MmsJournalVariable_getValue(MmsJournalVariable self)
{
return self->value;
}
LinkedList
MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId,
MmsValue* startingTime, MmsValue* endingTime, bool* moreFollows)
{
if ((MmsValue_getType(startingTime) != MMS_BINARY_TIME) ||
(MmsValue_getType(endingTime) != MMS_BINARY_TIME)) {
*mmsError = MMS_ERROR_INVALID_ARGUMENTS;
return NULL;
}
ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient);
uint32_t invokeId = getNextInvokeId(self);
mmsClient_createReadJournalRequestWithTimeRange(invokeId, payload, domainId, itemId, startingTime, endingTime);
return readJournal(self, mmsError, invokeId, payload, moreFollows);
}
LinkedList
MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId,
MmsValue* timeSpecification, MmsValue* entrySpecification, bool* moreFollows)
{
if ((MmsValue_getType(timeSpecification) != MMS_BINARY_TIME) ||
(MmsValue_getType(entrySpecification) != MMS_OCTET_STRING)) {
*mmsError = MMS_ERROR_INVALID_ARGUMENTS;
return NULL;
}
ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient);
uint32_t invokeId = getNextInvokeId(self);
mmsClient_createReadJournalRequestStartAfter(invokeId, payload, domainId, itemId, timeSpecification, entrySpecification);
return readJournal(self, mmsError, invokeId, payload, moreFollows);
}
int32_t
MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filename, uint32_t initialPosition,

@ -229,12 +229,7 @@ mmsClient_createGetNameListRequestDomainOrVMDSpecific(long invokeId, const char*
request->objectClass.present = ObjectClass_PR_basicObjectClass;
if (objectClass == MMS_NAMED_VARIABLE)
asn_long2INTEGER(&request->objectClass.choice.basicObjectClass,
ObjectClass__basicObjectClass_namedVariable);
else if (objectClass == MMS_NAMED_VARIABLE_LIST)
asn_long2INTEGER(&request->objectClass.choice.basicObjectClass,
ObjectClass__basicObjectClass_namedVariableList);
asn_long2INTEGER(&request->objectClass.choice.basicObjectClass, objectClass);
asn_enc_rval_t rval;

@ -0,0 +1,511 @@
/*
* mms_client_journals.c
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "libiec61850_platform_includes.h"
#include "stack_config.h"
#include "mms_common.h"
#include "mms_client_connection.h"
#include "byte_buffer.h"
#include "mms_client_internal.h"
#include "ber_encoder.h"
#include "ber_decode.h"
#include "conversions.h"
#include "mms_value_internal.h"
//TODO add event-based API to parse journal entries
static bool
parseJournalVariable(uint8_t* buffer, int bufPos, int maxLength, MmsJournalVariable journalVariable)
{
int maxBufPos = bufPos + maxLength;
while (bufPos < maxBufPos) {
int length;
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
if ((bufPos + length) > maxBufPos) { /* check length field for validity */
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n");
return false;
}
switch (tag) {
case 0x80: /* variableTag */
if (journalVariable->tag == NULL) {
journalVariable->tag = (char*) GLOBAL_MALLOC(length + 1);
memcpy(journalVariable->tag, buffer + bufPos, length);
journalVariable->tag[length] = 0;
}
break;
case 0xa1: /* valueSpec */
if (journalVariable->value == NULL) {
journalVariable->value = MmsValue_decodeMmsData(buffer, bufPos, length);
}
break;
default:
break;
}
bufPos += length;
}
return true;
}
static bool
parseJournalVariables(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry)
{
int maxBufPos = bufPos + maxLength;
while (bufPos < maxBufPos) {
int length;
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
if ((bufPos + length) > maxBufPos) { /* check length field for validity */
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n");
return false;
}
MmsJournalVariable journalVariable;
switch (tag) {
case 0x30: /* journalVariable */
journalVariable = (MmsJournalVariable)
GLOBAL_CALLOC(1, sizeof(struct sMmsJournalVariable));
parseJournalVariable(buffer, bufPos, length, journalVariable);
LinkedList_add(journalEntry->journalVariables, (void*) journalVariable);
break;
default:
break;
}
bufPos += length;
}
return true;
}
static bool
parseData(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry)
{
int maxBufPos = bufPos + maxLength;
while (bufPos < maxBufPos) {
int length;
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
if ((bufPos + length) > maxBufPos) { /* check length field for validity */
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n");
return false;
}
switch (tag) {
case 0xa1: /* journalVariables */
journalEntry->journalVariables = LinkedList_create();
parseJournalVariables(buffer, bufPos, length, journalEntry);
break;
default:
break;
}
bufPos += length;
}
return true;
}
static bool
parseEntryContent(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry)
{
int maxBufPos = bufPos + maxLength;
while (bufPos < maxBufPos) {
int length;
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
if ((bufPos + length) > maxBufPos) { /* check length field for validity */
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n");
return false;
}
switch (tag) {
case 0x80: /* occurenceTime */
if (length == 6)
journalEntry->occurenceTime = MmsValue_newBinaryTime(false);
else if (length == 4)
journalEntry->occurenceTime = MmsValue_newBinaryTime(true);
else
break;
memcpy(journalEntry->occurenceTime->value.binaryTime.buf, buffer + bufPos, length);
break;
case 0xa2: /* data */
parseData(buffer, bufPos, length, journalEntry);
break;
default:
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: ignore unknown tag %02x\n", tag);
break;
}
bufPos += length;
}
return true;
}
static bool
parseJournalEntry(uint8_t* buffer, int bufPos, int maxLength, LinkedList journalEntries)
{
int maxBufPos = bufPos + maxLength;
MmsJournalEntry journalEntry = (MmsJournalEntry) GLOBAL_CALLOC(1, sizeof(struct sMmsJournalEntry));
LinkedList_add(journalEntries, (void*) journalEntry);
while (bufPos < maxBufPos) {
int length;
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
if ((bufPos + length) > maxBufPos) { /* check length field for validity */
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n");
return false;
}
switch (tag) {
case 0x80: /* entryID */
journalEntry->entryID = MmsValue_newOctetString(length, length);
MmsValue_setOctetString(journalEntry->entryID, buffer + bufPos, length);
break;
case 0xa1: /* originatingApplication */
/* ignore */
break;
case 0xa2: /* entryContent */
if (parseEntryContent(buffer, bufPos, length, journalEntry) == false)
return false;
break;
default:
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: unknown tag %02x\n", tag);
return false;
}
bufPos += length;
}
return true;
}
static bool
parseListOfJournalEntries(uint8_t* buffer, int bufPos, int maxLength, LinkedList journalEntries)
{
int maxBufPos = bufPos + maxLength;
while (bufPos < maxBufPos) {
int length;
uint8_t tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
if ((bufPos + length) > maxBufPos) { /* check length field for validity */
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n");
return false;
}
switch (tag) {
case 0x30:
if (parseJournalEntry(buffer, bufPos, length, journalEntries) == false)
return false;
break;
default:
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: parseReadJournalResponse: unknown tag %02x\n", tag);
return false;
}
bufPos += length;
}
return true;
}
bool
mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, LinkedList* result)
{
uint8_t* buffer = self->lastResponse->buffer;
int maxBufPos = self->lastResponse->size;
int bufPos = self->lastResponseBufPos;
int length;
uint8_t tag = buffer[bufPos++];
if (tag != 0xbf) {
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: unknown tag %02x\n", tag);
return false;
}
tag = buffer[bufPos++];
if (moreFollows)
*moreFollows = false;
if (tag != 0x41) {
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: unknown tag %02x\n", tag);
return false;
}
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
if (bufPos < 0)
return false;
int endPos = bufPos + length;
if (endPos > maxBufPos) {
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: message to short (length:%i maxBufPos:%i)!\n", length, maxBufPos);
return false;
}
LinkedList journalEntries = NULL;
while (bufPos < endPos) {
tag = buffer[bufPos++];
bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos);
switch (tag) {
case 0xa0: /* listOfJournalEntry */
journalEntries = LinkedList_create();
if (!parseListOfJournalEntries(buffer, bufPos, length, journalEntries))
return false;
break;
case 0x81: /* moreFollows */
if (moreFollows)
*moreFollows = BerDecoder_decodeBoolean(buffer, bufPos);
break;
default:
if (DEBUG_MMS_CLIENT)
printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: message contains unknown tag %02x!\n", tag);
return false;
}
bufPos += length;
}
*result = journalEntries;
return true;
}
void
mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId,
MmsValue* startingTime, MmsValue* endingTime)
{
/* calculate sizes */
uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId);
uint32_t domainIdStringSize = strlen(domainId);
uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize;
uint32_t itemIdStringSize = strlen(itemId);
uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize;
uint32_t objectIdSize = domainIdSize + itemIdSize;
uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize);
uint32_t startingTimeSize = 2 + startingTime->value.binaryTime.size;
uint32_t rangeStartSpecSize = 2 + startingTimeSize;
uint32_t endingTimeSize = 2 + endingTime->value.binaryTime.size;
uint32_t rangeEndSpecSize = 2 + endingTimeSize;
uint32_t journalReadContentSize = journalNameSize + rangeStartSpecSize + rangeEndSpecSize;
uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalReadContentSize) + (journalReadContentSize);
uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize;
/* encode to buffer */
int bufPos = 0;
uint8_t* buffer = request->buffer;
bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos);
/* encode read journal tag (context | structured ) [65 = 41h] */
buffer[bufPos++] = 0xbf;
buffer[bufPos++] = 0x41;
bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos);
/* encode journalName */
bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos);
bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos);
bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos);
/* encode rangeStartSpecification|startingTime */
bufPos = BerEncoder_encodeTL(0xa1, startingTimeSize, buffer, bufPos);
bufPos = BerEncoder_encodeOctetString(0x80, startingTime->value.binaryTime.buf,
startingTime->value.binaryTime.size, buffer, bufPos);
/* encode rangeStopSpecification|endingTime */
bufPos = BerEncoder_encodeTL(0xa2, endingTimeSize, buffer, bufPos);
bufPos = BerEncoder_encodeOctetString(0x80, endingTime->value.binaryTime.buf,
endingTime->value.binaryTime.size, buffer, bufPos);
request->size = bufPos;
}
void
mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId,
MmsValue* timeSpecification, MmsValue* entrySpecification)
{
/* calculate sizes */
uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId);
uint32_t domainIdStringSize = strlen(domainId);
uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize;
uint32_t itemIdStringSize = strlen(itemId);
uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize;
uint32_t objectIdSize = domainIdSize + itemIdSize;
uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize);
uint32_t timeSpecificationSize = 2 + timeSpecification->value.binaryTime.size;
uint32_t entrySpecificationSize = 2 + entrySpecification->value.octetString.size;
uint32_t entryToStartAfterContentSize = timeSpecificationSize + entrySpecificationSize;
uint32_t entryToStartAfterSize = 1 + BerEncoder_determineLengthSize(entryToStartAfterContentSize)
+ entryToStartAfterContentSize;
uint32_t journalReadContentSize = journalNameSize + entryToStartAfterSize;
uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalReadContentSize) + (journalReadContentSize);
uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize;
/* encode to buffer */
int bufPos = 0;
uint8_t* buffer = request->buffer;
bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos);
/* encode read journal tag (context | structured ) [65 = 41h] */
buffer[bufPos++] = 0xbf;
buffer[bufPos++] = 0x41;
bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos);
/* encode journalName */
bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos);
bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos);
bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos);
/* encode entryToStartAfter */
bufPos = BerEncoder_encodeTL(0xa5, entryToStartAfterContentSize, buffer, bufPos);
/* encode entryToStartAfter|timeSpecification */
bufPos = BerEncoder_encodeOctetString(0x80, timeSpecification->value.binaryTime.buf,
timeSpecification->value.binaryTime.size, buffer, bufPos);
/* encode entryToStartAfter|entrySpecification */
bufPos = BerEncoder_encodeOctetString(0x81, entrySpecification->value.octetString.buf,
entrySpecification->value.octetString.size, buffer, bufPos);
request->size = bufPos;
}

@ -1600,7 +1600,12 @@ MmsValue_newBinaryTime(bool timeOfDay)
void
MmsValue_setBinaryTime(MmsValue* self, uint64_t timestamp)
{
uint64_t mmsTime = timestamp - (441763200000LL);
uint64_t mmsTime;
if (timestamp > 441763200000LL)
mmsTime = timestamp - (441763200000LL);
else
mmsTime = 0;
uint8_t* binaryTimeBuf = self->value.binaryTime.buf;
@ -1877,7 +1882,7 @@ MmsValue_setElement(MmsValue* complexValue, int index, MmsValue* elementValue)
}
MmsValue*
MmsValue_getElement(MmsValue* complexValue, int index)
MmsValue_getElement(const MmsValue* complexValue, int index)
{
if ((complexValue->type != MMS_ARRAY) && (complexValue->type != MMS_STRUCTURE))
return NULL;
@ -1918,7 +1923,7 @@ MmsValue_isDeletable(MmsValue* self)
}
MmsType
MmsValue_getType(MmsValue* self)
MmsValue_getType(const MmsValue* self)
{
return self->type;
}
@ -1970,8 +1975,8 @@ MmsValue_getTypeString(MmsValue* self)
}
}
char*
MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize)
const char*
MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
{
switch (MmsValue_getType(self)) {
case MMS_STRUCTURE:
@ -1985,7 +1990,7 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize)
int i;
for (i = 0; i < arraySize; i++) {
char* currentStr = MmsValue_printToBuffer(MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos);
const char* currentStr = MmsValue_printToBuffer((const MmsValue*) MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos);
bufPos += strlen(currentStr);
@ -2100,7 +2105,7 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize)
case MMS_STRING:
case MMS_VISIBLE_STRING:
strncpy(buffer, MmsValue_toString(self), bufferSize);
strncpy(buffer, MmsValue_toString((MmsValue*) self), bufferSize);
/* Ensure buffer is always 0 terminated */
if (bufferSize > 0)

@ -41,6 +41,7 @@ MmsDomain_create(char* domainName)
self->domainName = copyString(domainName);
self->namedVariableLists = LinkedList_create();
self->journals = NULL;
return self;
}
@ -57,6 +58,10 @@ MmsDomain_destroy(MmsDomain* self)
GLOBAL_FREEMEM(self->namedVariables);
}
if (self->journals != NULL) {
LinkedList_destroyDeep(self->journals, (LinkedListValueDeleteFunction) MmsJournal_destroy);
}
LinkedList_destroyDeep(self->namedVariableLists, (LinkedListValueDeleteFunction) MmsNamedVariableList_destroy);
GLOBAL_FREEMEM(self);
@ -68,6 +73,39 @@ MmsDomain_getName(MmsDomain* self)
return self->domainName;
}
void
MmsDomain_addJournal(MmsDomain* self, const char* name)
{
if (self->journals == NULL)
self->journals = LinkedList_create();
MmsJournal journal = MmsJournal_create(name);
LinkedList_add(self->journals, (void*) journal);
}
MmsJournal
MmsDomain_getJournal(MmsDomain* self, const char* name)
{
if (self->journals != NULL) {
LinkedList journal = LinkedList_getNext(self->journals);
while (journal != NULL) {
MmsJournal mmsJournal = (MmsJournal) LinkedList_getData(journal);
if (strcmp(mmsJournal->name, name) == 0)
return mmsJournal;
journal = LinkedList_getNext(journal);
}
}
return NULL;
}
bool
MmsDomain_addNamedVariableList(MmsDomain* self, MmsNamedVariableList variableList)
{

@ -123,13 +123,69 @@ encodeFileAttributes(uint8_t tag, uint32_t fileSize, char* gtString, uint8_t* bu
}
}
static void
createExtendedFilename(char* extendedFileName, char* fileName)
{
strcpy(extendedFileName, CONFIG_VIRTUAL_FILESTORE_BASEPATH);
strncat(extendedFileName, fileName, sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256);
}
static bool
getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModificationTimestamp)
{
char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
createExtendedFilename(extendedFileName, filename);
return FileSystem_getFileInfo(extendedFileName, fileSize, lastModificationTimestamp);
}
static FileHandle
openFile(char* fileName, bool readWrite)
{
char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
createExtendedFilename(extendedFileName, fileName);
return FileSystem_openFile(extendedFileName, readWrite);
}
static DirectoryHandle
openDirectory(char* directoryName)
{
char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
createExtendedFilename(extendedFileName, directoryName);
return FileSystem_openDirectory(extendedFileName);
}
static bool
renameFile(char* oldFilename, char* newFilename) {
char extendedOldFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
char extendedNewFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
createExtendedFilename(extendedOldFileName, oldFilename);
createExtendedFilename(extendedNewFileName, newFilename);
return FileSystem_renameFile(extendedOldFileName, extendedNewFileName);
}
static bool
deleteFile(char* fileName) {
char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256];
createExtendedFilename(extendedFileName, fileName);
return FileSystem_deleteFile(extendedFileName);
}
static void
createFileOpenResponse(uint32_t invokeId, ByteBuffer* response, char* fullPath, MmsFileReadStateMachine* frsm)
{
uint64_t msTime;
FileSystem_getFileInfo(fullPath, &(frsm->fileSize), &msTime);
getFileInfo(fullPath, &(frsm->fileSize), &msTime);
char gtString[30];
@ -216,19 +272,19 @@ mmsServer_handleFileDeleteRequest(
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: mms_file_service.c: Delete file (%s)\n", filename);
if (!FileSystem_getFileInfo(filename, NULL, NULL)) {
if (!getFileInfo(filename, NULL, NULL)) {
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: mms_file_service.c: File (%s) not found\n", filename);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
return;
}
if (!FileSystem_deleteFile(filename)) {
if (!deleteFile(filename)) {
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: mms_file_service.c: Delete file (%s) failed\n", filename);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_ACCESS_DENIED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_ACCESS_DENIED);
return;
}
@ -284,7 +340,7 @@ mmsServer_handleFileOpenRequest(
MmsFileReadStateMachine* frsm = getFreeFrsm(connection);
if (frsm != NULL) {
FileHandle fileHandle = FileSystem_openFile(filename, false);
FileHandle fileHandle = openFile(filename, false);
if (fileHandle != NULL) {
frsm->fileHandle = fileHandle;
@ -294,12 +350,12 @@ mmsServer_handleFileOpenRequest(
createFileOpenResponse(invokeId, response, filename, frsm);
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER);
}
else
goto exit_invalid_parameter;
@ -388,7 +444,7 @@ mmsServer_handleFileReadRequest(
if (frsm != NULL)
createFileReadResponse(connection, invokeId, response, frsm);
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER);
}
static void
@ -441,9 +497,10 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire
{
int directoryNameLength = strlen(directoryName);
DirectoryHandle directory = FileSystem_openDirectory(directoryName);
DirectoryHandle directory = openDirectory(directoryName);
if (directory != NULL) {
bool isDirectory;
char* fileName = FileSystem_readDirectory(directory, &isDirectory);
@ -457,13 +514,7 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire
strcat(directoryName, fileName);
if (isDirectory) {
bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows);
}
else {
bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows);
}
bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows);
if (*moreFollows == true)
break;
@ -485,8 +536,7 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire
uint32_t fileSize;
if (FileSystem_getFileInfo(directoryName, &fileSize, &msTime)) {
if (getFileInfo(directoryName, &fileSize, &msTime)) {
char gtString[30];
Conversions_msTimeToGeneralizedTime(msTime, (uint8_t*) gtString);
@ -547,7 +597,7 @@ createFileDirectoryResponse(uint32_t invokeId, ByteBuffer* response, int maxPduS
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: Error opening directory!\n");
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT);
return;
}
@ -642,7 +692,7 @@ mmsServer_handleFileRenameRequest(
}
if ((strlen(currentFileName) != 0) && (strlen(newFileName) != 0)) {
if (FileSystem_renameFile(currentFileName, newFileName)){
if (renameFile(currentFileName, newFileName)){
/* send positive response */
createNullResponseExtendedTag(invokeId, response, 0x4b);
}
@ -651,7 +701,7 @@ mmsServer_handleFileRenameRequest(
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: rename file failed!\n");
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER);
}
}
else

@ -173,6 +173,38 @@ addSubNamedVaribleNamesToList(LinkedList nameList, char* prefix, MmsVariableSpec
#endif /* (CONFIG_MMS_SUPPORT_FLATTED_NAME_SPACE == 1) */
static LinkedList
getJournalListDomainSpecific(MmsServerConnection connection, char* domainName)
{
MmsDevice* device = MmsServer_getDevice(connection->server);
LinkedList nameList = NULL;
MmsDomain* domain = MmsDevice_getDomain(device, domainName);
if (domain != NULL) {
nameList = LinkedList_create();
if (domain->journals != NULL) {
LinkedList journalList = domain->journals;
while ((journalList = LinkedList_getNext(journalList)) != NULL) {
MmsJournal journal = (MmsJournal) LinkedList_getData(journalList);
LinkedList_add(nameList, (void*) journal->name);
}
}
}
return nameList;
}
static LinkedList
getNameListDomainSpecific(MmsServerConnection connection, char* domainName)
{
@ -316,7 +348,7 @@ createNameListResponse(
}
if (startElement == NULL) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
return;
}
}
@ -497,18 +529,32 @@ mmsServer_handleGetNameListRequest(
LinkedList nameList = getNameListDomainSpecific(connection, domainSpecificName);
if (nameList == NULL)
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
else {
createNameListResponse(connection, invokeId, nameList, response, continueAfterId);
LinkedList_destroy(nameList);
}
}
else if (objectClass == OBJECT_CLASS_JOURNAL) {
LinkedList nameList = getJournalListDomainSpecific(connection, domainSpecificName);
if (nameList == NULL)
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
else {
#if (CONFIG_MMS_SORT_NAME_LIST == 1)
StringUtils_sortList(nameList);
#endif
createNameListResponse(connection, invokeId, nameList, response, continueAfterId);
LinkedList_destroyStatic(nameList);
}
}
#if (MMS_DATA_SET_SERVICE == 1)
else if (objectClass == OBJECT_CLASS_NAMED_VARIABLE_LIST) {
LinkedList nameList = getNamedVariableListsDomainSpecific(connection, domainSpecificName);
if (nameList == NULL)
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
else {
#if (CONFIG_MMS_SORT_NAME_LIST == 1)
@ -524,7 +570,7 @@ mmsServer_handleGetNameListRequest(
else {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList domain specific objectClass %i not supported!\n", objectClass);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
}
@ -582,7 +628,7 @@ mmsServer_handleGetNameListRequest(
else {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList VMD specific objectClass %i not supported!\n", objectClass);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
}
@ -602,14 +648,14 @@ mmsServer_handleGetNameListRequest(
LinkedList_destroy(nameList);
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
#endif /* (MMS_DYNAMIC_DATA_SETS == 1) */
#endif /* (MMS_DATA_SET_SERVICE == 1) */
else {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList(%i) not supported!\n", objectScope);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
}

@ -221,7 +221,7 @@ createVariableAccessAttributesResponse(
if (domain == NULL) {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: domain %s not known\n", domainId);
mmsServer_createConfirmedErrorPdu(invokeId, response,
mmsServer_createServiceErrorPdu(invokeId, response,
MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_function;
}
@ -237,7 +237,7 @@ createVariableAccessAttributesResponse(
if (namedVariable == NULL) {
if (DEBUG_MMS_SERVER) printf("MMS_SERVER: named variable %s not known\n", nameId);
mmsServer_createConfirmedErrorPdu(invokeId, response,
mmsServer_createServiceErrorPdu(invokeId, response,
MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_function;
@ -266,7 +266,7 @@ createVariableAccessAttributesResponse(
if (DEBUG_MMS_SERVER)
printf("MMS getVariableAccessAttributes: message to large! send error PDU!\n");
mmsServer_createConfirmedErrorPdu(invokeId, response,
mmsServer_createServiceErrorPdu(invokeId, response,
MMS_ERROR_SERVICE_OTHER);
goto exit_function;

@ -0,0 +1,46 @@
/*
* mms_journal.c
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "libiec61850_platform_includes.h"
#include "mms_device_model.h"
#include "mms_server_internal.h"
MmsJournal
MmsJournal_create(const char* name)
{
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: create new journal %s\n", name);
MmsJournal self = (MmsJournal) GLOBAL_MALLOC(sizeof(struct sMmsJournal));
self->name = copyString(name);
return self;
}
void
MmsJournal_destroy(MmsJournal self)
{
GLOBAL_FREEMEM(self->name);
GLOBAL_FREEMEM(self);
}

@ -0,0 +1,533 @@
/*
* mms_journal_service.c
*
* Copyright 2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "libiec61850_platform_includes.h"
#include "mms_server_internal.h"
#include "mms_value_internal.h"
#if (MMS_JOURNAL_SERVICE == 1)
typedef struct sJournalVariable* JournalVariable;
struct sJournalVariable {
char* tag; /* UTF8(1..255) */
int tagSize;
uint8_t* data;
int dataSize;
JournalVariable next;
};
typedef struct {
uint8_t* entryID;
int entryIDSize;
uint64_t timestamp;
JournalVariable listOfVariables;
} JournalEntry;
typedef struct sJournalEncoder* JournalEncoder;
struct sJournalEncoder {
uint8_t* buffer;
int maxSize;
int bufPos;
int currentEntryBufPos; /* store start buffer position of current entry in case the whole JournalEntry will become too long */
uint64_t currentEntryId; //TODO use a byte array for the generic MMS case!
uint64_t currentTimestamp;
bool moreFollows;
};
static bool
entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow)
{
JournalEncoder encoder = (JournalEncoder) parameter;
if (moreFollow) {
if (encoder->moreFollows)
return false;
encoder->currentEntryBufPos = encoder->bufPos;
encoder->bufPos += 48; /* reserve space for common entry parts */
encoder->currentEntryId = entryID;
encoder->currentTimestamp = timestamp;
}
return true;
}
static const char* REASON_CODE_STR = "ReasonCode";
static bool
entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow)
{
JournalEncoder encoder = (JournalEncoder) parameter;
uint8_t* buffer = encoder->buffer;
//TODO check if entry is too long for buffer!
if (moreFollow) {
int bufPos = encoder->bufPos;
uint32_t dataRefStrLen = strlen(dataRef);
uint32_t dataRefLen = 1 + BerEncoder_determineLengthSize(dataRefStrLen) + dataRefStrLen;
uint32_t valueSpecLen = 1 + BerEncoder_determineLengthSize(dataSize) + dataSize;
uint32_t firstVariableLen = 1 + BerEncoder_determineLengthSize(valueSpecLen + dataRefLen)
+ valueSpecLen + dataRefLen;
uint8_t reasonCodeNBuf[2];
MmsValue reasonCodeValue;
reasonCodeValue.type = MMS_BIT_STRING;
reasonCodeValue.value.bitString.size = 7;
reasonCodeValue.value.bitString.buf = reasonCodeNBuf;
MmsValue_setBitStringFromInteger(&reasonCodeValue, reasonCode);
uint32_t reasonCodeValueLen = MmsValue_encodeMmsData(&reasonCodeValue, NULL, 0, false);
uint32_t reasonCodeContentLen = reasonCodeValueLen + 2 + 12;
uint32_t secondVariableLen = 1 + BerEncoder_determineLengthSize(reasonCodeContentLen)
+ reasonCodeContentLen;
uint32_t totalLen = firstVariableLen + secondVariableLen;
if ((int) (bufPos + totalLen) > encoder->maxSize) {
encoder->moreFollows = true;
encoder->bufPos = encoder->currentEntryBufPos; /* remove last entry */
return false;
}
bufPos = BerEncoder_encodeTL(0x30, valueSpecLen + dataRefLen, buffer, bufPos);
/* encode dataRef */
bufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) dataRef, dataRefStrLen, buffer, bufPos);
/* encode valueSpec */
bufPos = BerEncoder_encodeOctetString(0xa1, data, dataSize, buffer, bufPos);
/* encode reasonCode */
bufPos = BerEncoder_encodeTL(0x30, reasonCodeContentLen , buffer, bufPos);
bufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) REASON_CODE_STR, 10, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0xa1, reasonCodeValueLen, buffer, bufPos);
bufPos = MmsValue_encodeMmsData(&reasonCodeValue, buffer, bufPos, true);
encoder->bufPos = bufPos;
}
else {
int dataContentLen = encoder->bufPos - (encoder->currentEntryBufPos + 48);
int journalVariablesLen = 1 + BerEncoder_determineLengthSize(dataContentLen) + dataContentLen;
int dataLen = 1 + BerEncoder_determineLengthSize(journalVariablesLen) + journalVariablesLen;
int dataAndTimeLen = dataLen + 8;
int entryContentLen = 1 + BerEncoder_determineLengthSize(dataAndTimeLen) + dataAndTimeLen;
int journalEntryContentLen = 10 /* entryIdentifier */
+ 4 /* originatingApplication */
+ entryContentLen;
int headerBufPos = encoder->currentEntryBufPos;
headerBufPos = BerEncoder_encodeTL(0x30, journalEntryContentLen, buffer, headerBufPos);
headerBufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) &(encoder->currentEntryId), 8, buffer, headerBufPos);
headerBufPos = BerEncoder_encodeTL(0xa1, 2, buffer, headerBufPos);
headerBufPos = BerEncoder_encodeTL(0x30, 0, buffer, headerBufPos);
headerBufPos = BerEncoder_encodeTL(0xa2, dataAndTimeLen, buffer, headerBufPos);
MmsValue occurenceTime;
occurenceTime.type = MMS_BINARY_TIME;
occurenceTime.value.binaryTime.size = 6;
MmsValue_setBinaryTime(&occurenceTime, encoder->currentTimestamp);
headerBufPos = BerEncoder_encodeOctetString(0x80, occurenceTime.value.binaryTime.buf, 6, buffer, headerBufPos);
headerBufPos = BerEncoder_encodeTL(0xa2, journalVariablesLen, buffer, headerBufPos);
headerBufPos = BerEncoder_encodeTL(0xa1, dataContentLen, buffer, headerBufPos);
int entryHeaderLength = headerBufPos - encoder->currentEntryBufPos;
/* move data to entry header */
memmove(buffer + (encoder->currentEntryBufPos + entryHeaderLength), buffer + (encoder->currentEntryBufPos + 48),
dataContentLen);
/* prepare buffer for next entry. */
encoder->bufPos = encoder->currentEntryBufPos + entryHeaderLength + dataContentLen;
}
return true;
}
static bool
parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bufPos, int maxBufPos , uint32_t invokeId, ByteBuffer* response)
{
uint8_t tag = buffer[(*bufPos)++];
int length;
if (tag != 0x1a) {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
return false;
}
*bufPos = BerDecoder_decodeLength(buffer, &length, *bufPos, maxBufPos);
if (*bufPos < 0) {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
return false;
}
if (length > maxLength) {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return false;
}
memcpy(filename, buffer + *bufPos, length);
filename[length] = 0;
*bufPos += length;
return true;
}
#define RESERVED_SPACE_FOR_HEADER 22
void
mmsServer_handleReadJournalRequest(
MmsServerConnection connection,
uint8_t* requestBuffer,
int bufPos, int maxBufPos,
uint32_t invokeId,
ByteBuffer* response)
{
char domainId[65];
char logName[65];
uint8_t entryIdBuf[64]; /* maximum size of entry id is 64 bytes! */
MmsValue entrySpec;
entrySpec.type = MMS_OCTET_STRING;
entrySpec.value.octetString.buf = entryIdBuf;
entrySpec.value.octetString.maxSize = 64;
MmsValue rangeStart;
MmsValue rangeStop;
bool hasNames = false;
bool hasRangeStartSpec = false;
bool hasRangeStopSpec = false;
bool hasTimeSpec = false;
bool hasEntrySpec = false;
while (bufPos < maxBufPos) {
uint8_t tag = requestBuffer[bufPos++];
int length;
bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos);
if (bufPos < 0) {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
return;
}
switch (tag) {
case 0xa0: /* journalName */
{
uint8_t objectIdTag = requestBuffer[bufPos++];
bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos);
switch (objectIdTag) {
case 0xa1: /* domain-specific */
if (!parseStringWithMaxLength(domainId, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) {
return;
}
if (!parseStringWithMaxLength(logName, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) {
return;
}
hasNames = true;
break;
default:
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response);
return;
}
}
break;
case 0xa1: /* rangeStartSpecification */
{
uint8_t subTag = requestBuffer[bufPos++];
bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos);
if (subTag != 0x80) {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response);
return;
}
if ((length == 4) || (length == 6)) {
rangeStart.type = MMS_BINARY_TIME;
rangeStart.value.binaryTime.size = length;
memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length);
hasRangeStartSpec = true;
}
else {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return; // forward request to implementation class
}
bufPos += length;
}
break;
case 0xa2: /* rangeStopSpecification */
{
uint8_t subTag = requestBuffer[bufPos++];
bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos);
if (subTag != 0x80) {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response);
return;
}
if ((length == 4) || (length == 6)) {
rangeStop.type = MMS_BINARY_TIME;
rangeStop.value.binaryTime.size = length;
memcpy(rangeStop.value.binaryTime.buf, requestBuffer + bufPos, length);
hasRangeStopSpec = true;
}
else {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return; // forward request to implementation class
}
bufPos += length;
}
break;
case 0xa5: /* entryToStartAfter */
{
int maxSubBufPos = bufPos + length;
while (bufPos < maxSubBufPos) {
uint8_t subTag = requestBuffer[bufPos++];
bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos);
switch (subTag) {
case 0x80: /* timeSpecification */
if ((length == 4) || (length == 6)) {
rangeStart.type = MMS_BINARY_TIME;
rangeStart.value.binaryTime.size = length;
memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length);
hasTimeSpec = true;
}
else {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return;
}
break;
case 0x81: /* entrySpecification */
if (length <= entrySpec.value.octetString.maxSize) {
memcpy(entrySpec.value.octetString.buf, requestBuffer + bufPos, length);
entrySpec.value.octetString.size = length;
hasEntrySpec = true;
}
else {
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return;
}
break;
default:
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response);
return;
}
bufPos += length;
}
}
break;
default:
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response);
return;
}
}
//TODO check if required fields are present
if (hasNames == false) {
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: readJournal missing journal name\n");
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return;
}
//TODO check valid field combinations
/* lookup journal */
MmsDevice* mmsDevice = MmsServer_getDevice(connection->server);
MmsDomain* mmsDomain = MmsDevice_getDomain(mmsDevice, domainId);
if (mmsDomain == NULL) {
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: readJournal domain %s not found\n", domainId);
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return;
}
MmsJournal mmsJournal = MmsDomain_getJournal(mmsDomain, logName);
if (mmsJournal == NULL) {
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: readJournal journal %s not found\n", logName);
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return;
}
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: readJournal - read journal %s ...\n", mmsJournal->name);
struct sJournalEncoder encoder;
encoder.buffer = response->buffer;
encoder.moreFollows = false;
encoder.maxSize = connection->maxPduSize - 3; /* reserve three bytes for moreFollows */
encoder.bufPos = RESERVED_SPACE_FOR_HEADER; /* reserve space for header */
LogStorage logStorage = mmsJournal->logStorage;
if (logStorage != NULL) {
if (hasRangeStartSpec && hasRangeStopSpec) {
LogStorage_getEntries(logStorage, MmsValue_getBinaryTimeAsUtcMs(&rangeStart), MmsValue_getBinaryTimeAsUtcMs(&rangeStop),
entryCallback, entryDataCallback, &encoder);
}
else if (hasEntrySpec && hasTimeSpec) {
LogStorage_getEntriesAfter(logStorage, MmsValue_getBinaryTimeAsUtcMs(&rangeStart), *((uint64_t*) entryIdBuf),
entryCallback, entryDataCallback, &encoder);
}
else {
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: readJournal missing valid argument combination\n");
mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response);
return;
}
}
/* actual encoding will happen in callback handler. When getEntries returns the data is
* already encoded in the buffer.
*/
/* encode message header in response buffer */
uint8_t* buffer = encoder.buffer;
bufPos = 0;
int dataSize = encoder.bufPos - RESERVED_SPACE_FOR_HEADER;
uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize((uint32_t) invokeId) + 2;
uint32_t listOfEntriesLen = 1 + BerEncoder_determineLengthSize(dataSize) + dataSize;
uint32_t moreFollowsLen;
if (encoder.moreFollows)
moreFollowsLen = 3;
else
moreFollowsLen = 0;
uint32_t readJournalLen = 2 + BerEncoder_determineLengthSize(listOfEntriesLen + moreFollowsLen) +
(listOfEntriesLen + moreFollowsLen);
uint32_t confirmedResponsePDUSize = readJournalLen + invokeIdSize;
bufPos = BerEncoder_encodeTL(0xa1, confirmedResponsePDUSize, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x02, invokeIdSize - 2, buffer, bufPos);
bufPos = BerEncoder_encodeUInt32((uint32_t) invokeId, buffer, bufPos);
buffer[bufPos++] = 0xbf;
buffer[bufPos++] = 0x41;
bufPos = BerEncoder_encodeLength(listOfEntriesLen + moreFollowsLen, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0xa0, dataSize, buffer, bufPos);
/* move encoded JournalEntry data to continue the buffer header */
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: readJournal: Encoded message header with %i bytes\n", bufPos);
memmove(buffer + bufPos, buffer + RESERVED_SPACE_FOR_HEADER, dataSize);
bufPos = bufPos + dataSize;
/* encode morefollows */
if (encoder.moreFollows)
bufPos = BerEncoder_encodeBoolean(0x81, true, buffer, bufPos);
response->size = bufPos;
}
#endif /* (MMS_JOURNAL_SERVICE == 1) */

@ -99,6 +99,18 @@ createDeleteNamedVariableListResponse(uint32_t invokeId, ByteBuffer* response,
response->size = bufPos;
}
static void /* Confirmed service error (ServiceError) */
createServiceErrorDeleteVariableLists(uint32_t invokeId, ByteBuffer* response,
MmsError errorType, uint32_t numberDeleted)
{
uint8_t buffer[8];
int size = BerEncoder_encodeUInt32WithTL(0x86, numberDeleted, buffer, 0);
mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType,
buffer, size);
}
void
mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection,
uint8_t* buffer, int bufPos, int maxBufPos,
@ -125,6 +137,8 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection,
MmsDevice* device = MmsServer_getDevice(connection->server);
if (scopeOfDelete == DeleteNamedVariableListRequest__scopeOfDelete_specific) {
MmsError serviceError = MMS_ERROR_NONE;
int numberMatched = 0;
int numberDeleted = 0;
@ -154,10 +168,14 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection,
if (MmsNamedVariableList_isDeletable(variableList)) {
if (mmsServer_callVariableListChangedHandler(false, MMS_DOMAIN_SPECIFIC, domain, listName, connection) == MMS_ERROR_NONE) {
MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_DOMAIN_SPECIFIC, domain, listName, connection);
if (deleteError == MMS_ERROR_NONE) {
MmsDomain_deleteNamedVariableList(domain, listName);
numberDeleted++;
}
else
serviceError = deleteError;
}
}
}
@ -173,10 +191,14 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection,
if (variableList != NULL) {
numberMatched++;
if (mmsServer_callVariableListChangedHandler(false, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection) == MMS_ERROR_NONE) {
MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection);
if (deleteError == MMS_ERROR_NONE) {
numberDeleted++;
MmsServerConnection_deleteNamedVariableList(connection, listName);
}
else
serviceError = deleteError;
}
}
else if (request->listOfVariableListName->list.array[i]->present == ObjectName_PR_vmdspecific) {
@ -190,19 +212,25 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection,
if (variableList != NULL) {
numberMatched++;
if (mmsServer_callVariableListChangedHandler(false, MMS_VMD_SPECIFIC, NULL, listName, connection)
== MMS_ERROR_NONE) {
MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_VMD_SPECIFIC, NULL, listName, connection);
if (deleteError == MMS_ERROR_NONE) {
numberDeleted++;
mmsServer_deleteVariableList(device->namedVariableLists, listName);
}
else
serviceError = deleteError;
}
}
}
createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted);
if (serviceError == MMS_ERROR_NONE)
createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted);
else
createServiceErrorDeleteVariableLists(invokeId, response, serviceError, numberDeleted);
}
else {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
@ -404,7 +432,7 @@ mmsServer_handleDefineNamedVariableListRequest(
char domainName[65];
if (request->variableListName.choice.domainspecific.domainId.size > 64) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_free_struct;
}
@ -415,7 +443,7 @@ mmsServer_handleDefineNamedVariableListRequest(
MmsDomain* domain = MmsDevice_getDomain(device, domainName);
if (domain == NULL) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_free_struct;
}
@ -423,7 +451,7 @@ mmsServer_handleDefineNamedVariableListRequest(
char variableListName[65];
if (request->variableListName.choice.domainspecific.itemId.size > 64) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_free_struct;
}
@ -432,7 +460,7 @@ mmsServer_handleDefineNamedVariableListRequest(
request->variableListName.choice.domainspecific.itemId.size);
if (MmsDomain_getNamedVariableList(domain, variableListName) != NULL) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS);
}
else {
MmsError mmsError;
@ -450,15 +478,15 @@ mmsServer_handleDefineNamedVariableListRequest(
}
else {
MmsNamedVariableList_destroy(namedVariableList);
mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError);
mmsServer_createServiceErrorPdu(invokeId, response, mmsError);
}
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError);
mmsServer_createServiceErrorPdu(invokeId, response, mmsError);
}
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE);
}
@ -470,7 +498,7 @@ mmsServer_handleDefineNamedVariableListRequest(
if (request->variableListName.choice.aaspecific.size > 64) {
//TODO send reject PDU instead?
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_free_struct;
}
@ -479,7 +507,7 @@ mmsServer_handleDefineNamedVariableListRequest(
request->variableListName.choice.aaspecific.size);
if (MmsServerConnection_getNamedVariableList(connection, variableListName) != NULL) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS);
}
else {
MmsError mmsError;
@ -495,16 +523,16 @@ mmsServer_handleDefineNamedVariableListRequest(
}
else {
MmsNamedVariableList_destroy(namedVariableList);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED);
}
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError);
mmsServer_createServiceErrorPdu(invokeId, response, mmsError);
}
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE);
}
else if (request->variableListName.present == ObjectName_PR_vmdspecific) {
LinkedList vmdScopeNVLs = MmsDevice_getNamedVariableLists(connection->server->device);
@ -515,7 +543,7 @@ mmsServer_handleDefineNamedVariableListRequest(
if (request->variableListName.choice.vmdspecific.size > 64) {
//TODO send reject PDU instead?
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
goto exit_free_struct;
}
@ -524,7 +552,7 @@ mmsServer_handleDefineNamedVariableListRequest(
request->variableListName.choice.vmdspecific.size);
if (mmsServer_getNamedVariableListWithName(MmsDevice_getNamedVariableLists(connection->server->device), variableListName) != NULL) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS);
}
else {
MmsError mmsError;
@ -541,7 +569,7 @@ mmsServer_handleDefineNamedVariableListRequest(
}
else {
MmsNamedVariableList_destroy(namedVariableList);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED);
}
}
@ -549,7 +577,7 @@ mmsServer_handleDefineNamedVariableListRequest(
}
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED);
exit_free_struct:
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
@ -648,7 +676,7 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
if ((request->choice.domainspecific.domainId.size > 64) ||
(request->choice.domainspecific.itemId.size > 64)) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER);
goto exit_function;
}
@ -669,10 +697,10 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
if (variableList != NULL)
createGetNamedVariableListAttributesResponse(invokeId, response, variableList);
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
#if (MMS_DYNAMIC_DATA_SETS == 1)
@ -681,7 +709,7 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
char listName[65];
if (request->choice.aaspecific.size > 64) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER);
goto exit_function;
}
@ -693,14 +721,14 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
if (varList != NULL)
createGetNamedVariableListAttributesResponse(invokeId, response, varList);
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
#endif /* (MMS_DYNAMIC_DATA_SETS == 1) */
else if (request->present == ObjectName_PR_vmdspecific) {
char listName[65];
if (request->choice.vmdspecific.size > 64) {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER);
goto exit_function;
}
@ -714,10 +742,10 @@ mmsServer_handleGetNamedVariableListAttributesRequest(
if (varList != NULL)
createGetNamedVariableListAttributesResponse(invokeId, response, varList);
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
else {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
exit_function:

@ -431,7 +431,7 @@ encodeReadResponse(MmsServerConnection connection,
if (DEBUG_MMS_SERVER)
printf("MMS read: message to large! send error PDU!\n");
mmsServer_createConfirmedErrorPdu(invokeId, response,
mmsServer_createServiceErrorPdu(invokeId, response,
MMS_ERROR_SERVICE_OTHER);
goto exit_function;
@ -647,7 +647,7 @@ handleReadNamedVariableListRequest(
if (domain == NULL) {
if (DEBUG_MMS_SERVER) printf("MMS read: domain %s not found!\n", domainIdStr);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
else {
MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr);
@ -658,7 +658,7 @@ handleReadNamedVariableListRequest(
}
else {
if (DEBUG_MMS_SERVER) printf("MMS read: named variable list %s not found!\n", nameIdStr);
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
}
}
}
@ -672,7 +672,7 @@ handleReadNamedVariableListRequest(
MmsNamedVariableList namedList = mmsServer_getNamedVariableListWithName(connection->server->device->namedVariableLists, listName);
if (namedList == NULL)
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
else {
VarAccessSpec accessSpec;
@ -697,7 +697,7 @@ handleReadNamedVariableListRequest(
MmsNamedVariableList namedList = MmsServerConnection_getNamedVariableList(connection, listName);
if (namedList == NULL)
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT);
else {
VarAccessSpec accessSpec;
@ -711,7 +711,7 @@ handleReadNamedVariableListRequest(
}
#endif /* (MMS_DYNAMIC_DATA_SETS == 1) */
else
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
#endif /* MMS_DATA_SET_SERVICE == 1 */
@ -745,7 +745,7 @@ mmsServer_handleReadRequest(
}
#endif
else {
mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED);
}
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);

@ -1,7 +1,7 @@
/*
* mms_server_common.c
*
* Copyright 2013 Michael Zillgith
* Copyright 2013-2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -45,96 +45,142 @@ mmsServer_createConfirmedResponse(uint32_t invokeId)
return mmsPdu;
}
static void
mapErrorTypeToErrorClass(MmsError errorType, uint8_t* tag, uint8_t* value)
{
switch (errorType) {
case MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED:
*tag = 0x87; /* access */
*value = 1;
break;
case MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT:
*tag = 0x87; /* access */
*value = 2;
break;
case MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED:
*tag = 0x87; /* access */
*value = 3;
break;
case MMS_ERROR_SERVICE_OTHER:
*tag = 0x84; /* service */
*value = 0;
break;
case MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT:
*tag = 0x84; /* service */
*value = 5;
break;
case MMS_ERROR_DEFINITION_OTHER:
*tag = 0x82; /* definition */
*value = 0;
break;
case MMS_ERROR_DEFINITION_OBJECT_UNDEFINED:
*tag = 0x82; /* definition */
*value = 1;
break;
case MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED:
*tag = 0x82; /* definition */
*value = 3;
break;
case MMS_ERROR_DEFINITION_OBJECT_EXISTS:
*tag = 0x82; /* definition */
*value = 5;
break;
case MMS_ERROR_FILE_OTHER:
*tag = 0x8b; /* file */
*value = 0;
break;
case MMS_ERROR_FILE_FILE_NON_EXISTENT:
*tag = 0x8b; /* file */
*value = 7;
break;
case MMS_ERROR_RESOURCE_OTHER:
*tag = 0x83; /* resource */
*value = 0;
break;
case MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE:
*tag = 0x83; /* resource */
*value = 4;
break;
default:
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: unknown errorType!\n");
*tag = 0x8c; /* others */
*value = 0;
break;
}
}
void
mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType)
mmsServer_createServiceErrorPduWithServiceSpecificInfo(uint32_t invokeId, ByteBuffer* response,
MmsError errorType, uint8_t* serviceSpecificInfo, int serviceSpecficInfoLength)
{
MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t));
mmsPdu->present = MmsPdu_PR_confirmedErrorPDU;
/* determine encoded size */
asn_long2INTEGER(&(mmsPdu->choice.confirmedErrorPDU.invokeID),
invokeId);
uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId) + 2;
if (errorType == MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_access;
uint32_t specificInfoSize = 0;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__access_objectnonexistent);
}
else if (errorType == MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_access;
if (serviceSpecificInfo != NULL)
specificInfoSize = 1 + BerEncoder_determineLengthSize(serviceSpecficInfoLength)
+ serviceSpecficInfoLength;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__access_objectaccessdenied);
}
else if (errorType == MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_access;
uint32_t serviceErrorContentSize = 5 /* errorClass */ + specificInfoSize;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__access_objectaccessunsupported);
}
else if (errorType == MMS_ERROR_SERVICE_OTHER) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_service;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__service_other);
}
else if (errorType == MMS_ERROR_DEFINITION_OTHER) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_definition;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__definition_other);
}
else if (errorType == MMS_ERROR_DEFINITION_OBJECT_EXISTS) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_definition;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__definition_objectexists);
}
else if (errorType == MMS_ERROR_DEFINITION_OBJECT_UNDEFINED) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_definition;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__definition_objectundefined);
}
else if (errorType == MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_definition;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__definition_typeunsupported);
}
else if (errorType == MMS_ERROR_FILE_FILE_NON_EXISTENT) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_file;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__file_filenonexistent);
}
else if (errorType == MMS_ERROR_FILE_OTHER) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_file;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__file_other);
}
else if (errorType == MMS_ERROR_RESOURCE_OTHER) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_resource;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__resource_other);
}
else if (errorType == MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE) {
mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present =
ServiceError__errorClass_PR_resource;
asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access,
ServiceError__errorClass__resource_capabilityunavailable);
}
uint32_t serviceErrorSize = 1 + BerEncoder_determineLengthSize(serviceErrorContentSize) +
serviceErrorContentSize;
uint32_t confirmedErrorContentSize = serviceErrorSize + invokeIdSize;
/* encode */
uint8_t* buffer = response->buffer;
int bufPos = response->size;
bufPos = BerEncoder_encodeTL(0xa2, confirmedErrorContentSize, buffer, bufPos);
bufPos = BerEncoder_encodeTL(0x80, invokeIdSize - 2, buffer, bufPos); /* invokeID */
bufPos = BerEncoder_encodeUInt32((uint32_t) invokeId, buffer, bufPos);
der_encode(&asn_DEF_MmsPdu, mmsPdu,
mmsServer_write_out, (void*) response);
bufPos = BerEncoder_encodeTL(0xa2, serviceErrorContentSize, buffer, bufPos); /* serviceError */
bufPos = BerEncoder_encodeTL(0xa0, 3, buffer, bufPos); /* serviceError */
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
uint8_t errorCodeTag;
uint8_t errorCodeValue;
mapErrorTypeToErrorClass(errorType, &errorCodeTag, &errorCodeValue);
buffer[bufPos++] = errorCodeTag;
buffer[bufPos++] = 1;
buffer[bufPos++] = errorCodeValue;
if (serviceSpecificInfo != NULL)
bufPos = BerEncoder_encodeOctetString(0xa3, serviceSpecificInfo, serviceSpecficInfoLength,
buffer, bufPos);
response->size = bufPos;
}
void /* Confirmed service error (ServiceError) */
mmsServer_createServiceErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType)
{
mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, NULL, 0);
}
int

@ -1,7 +1,7 @@
/*
* mms_server_connection.c
*
* Copyright 2013 Michael Zillgith
* Copyright 2013-2016 Michael Zillgith
*
* This file is part of libIEC61850.
*
@ -64,8 +64,8 @@ mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response
}
else if (reason == MMS_ERROR_REJECT_INVALID_PDU) {
mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_pduError;
mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU =
RejectPDU__rejectReason__pduError_invalidPdu;
asn_long2INTEGER(&mmsPdu->choice.rejectPDU.rejectReason.choice.pduError,
RejectPDU__rejectReason__pduError_invalidPdu);
}
else {
mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU;
@ -108,11 +108,15 @@ handleConfirmedRequestPdu(
return;
}
if (DEBUG_MMS_SERVER) printf("tag %02x extended tag: %i size: %i\n", tag, extendedTag, length);
if (extendedTag) {
switch(tag) {
#if (MMS_JOURNAL_SERVICE == 1)
case 0x41: /* read-journal */
mmsServer_handleReadJournalRequest(self, buffer, bufPos, bufPos + length, invokeId, response);
break;
#endif /* (MMS_JOURNAL_SERVICE == 1) */
#if (MMS_FILE_SERVICE == 1)
case 0x48: /* file-open-request */
mmsServer_handleFileOpenRequest(self, buffer, bufPos, bufPos + length, invokeId, response);
@ -149,7 +153,8 @@ handleConfirmedRequestPdu(
switch(tag) {
case 0x02: /* invoke Id */
invokeId = BerDecoder_decodeUint32(buffer, length, bufPos);
if (DEBUG_MMS_SERVER) printf("invokeId: %i\n", invokeId);
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: received request with invokeId: %i\n", invokeId);
self->lastInvokeId = invokeId;
break;
@ -250,7 +255,7 @@ MmsServerConnection_parseMessage(MmsServerConnection self, ByteBuffer* message,
return MMS_ERROR;
if (DEBUG_MMS_SERVER)
printf("mms_server: recvd MMS-PDU type: %02x size: %i\n", pduType, pduLength);
printf("MMS_SERVER: recvd MMS-PDU type: %02x size: %i\n", pduType, pduLength);
switch (pduType) {
case 0xa8: /* Initiate request PDU */
@ -266,7 +271,8 @@ MmsServerConnection_parseMessage(MmsServerConnection self, ByteBuffer* message,
retVal = MMS_CONCLUDE;
break;
case 0xa4: /* Reject PDU - silently ignore */
if (DEBUG) printf("received reject PDU!\n");
if (DEBUG_MMS_SERVER)
printf("MMS_SERVER: received reject PDU!\n");
retVal = MMS_OK;
break;
default:

@ -24,7 +24,7 @@
#include "libiec61850_platform_includes.h"
#include "mms_server_internal.h"
#if MMS_STATUS_SERVICE == 1
#if (MMS_STATUS_SERVICE == 1)
void
mmsServer_handleStatusRequest(

@ -67,24 +67,23 @@ struct sIsoServer {
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
LinkedList openClientConnections;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore openClientConnectionsMutex; /* mutex for openClientConnections list */
#endif
#else
IsoConnection* openClientConnections;
#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore userLock;
#endif
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore openClientConnectionsMutex; /* mutex for openClientConnections list */
Semaphore connectionCounterMutex;
#endif
int connectionCounter;
};
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
static inline void
lockClientConnections(IsoServer self)
{
@ -102,6 +101,18 @@ static void
addClientConnection(IsoServer self, IsoConnection connection)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore_wait(self->connectionCounterMutex);
#endif
self->connectionCounter++;
if (DEBUG_ISO_SERVER)
printf("IsoServer: increase connection counter to %i!\n", self->connectionCounter);
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
lockClientConnections(self);
#endif
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
LinkedList_add(self->openClientConnections, connection);
#else
@ -118,30 +129,37 @@ addClientConnection(IsoServer self, IsoConnection connection)
}
}
#endif
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
unlockClientConnections(self);
#endif
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore_post(self->connectionCounterMutex);
#endif
}
static void
removeClientConnection(IsoServer self, IsoConnection connection)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
lockClientConnections(self);
#endif
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
#if (CONFIG_MMS_SINGLE_THREADED == 0)
#if (CONFIG_MMS_THREADLESS_STACK != 1)
lockClientConnections(self);
#endif
LinkedList_remove(self->openClientConnections, connection);
#if (CONFIG_MMS_THREADLESS_STACK != 1)
unlockClientConnections(self);
#endif
#endif /* (CONFIG_MMS_SINGLE_THREADED == 0) */
#else
int i;
for (i = 0; i < CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS; i++) {
@ -155,18 +173,22 @@ removeClientConnection(IsoServer self, IsoConnection connection)
}
}
#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
unlockClientConnections(self);
#endif
}
static void
closeAllOpenClientConnections(IsoServer self)
{
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
lockClientConnections(self);
#endif
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
LinkedList openConnection = LinkedList_getNext(self->openClientConnections);
while (openConnection != NULL) {
IsoConnection isoConnection = (IsoConnection) openConnection->data;
@ -186,11 +208,6 @@ closeAllOpenClientConnections(IsoServer self)
self->openClientConnections = NULL;
#endif
#if (CONFIG_MMS_THREADLESS_STACK != 1)
unlockClientConnections(self);
#endif
#else
int i;
@ -206,7 +223,12 @@ closeAllOpenClientConnections(IsoServer self)
}
}
#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
unlockClientConnections(self);
#endif
}
static void
@ -214,7 +236,7 @@ handleClientConnections(IsoServer self)
{
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
lockClientConnections(self);
#endif
@ -237,12 +259,15 @@ handleClientConnections(IsoServer self)
openConnection = LinkedList_getNext(openConnection);
}
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
unlockClientConnections(self);
#endif
#else
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
lockClientConnections(self);
#endif
int i;
@ -260,6 +285,11 @@ handleClientConnections(IsoServer self)
}
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
unlockClientConnections(self);
#endif
#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */
}
@ -319,8 +349,6 @@ handleIsoConnections(IsoServer self)
IsoConnection isoConnection = IsoConnection_create(connectionSocket, self);
private_IsoServer_increaseConnectionCounter(self);
addClientConnection(self, isoConnection);
self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter,
@ -357,8 +385,6 @@ handleIsoConnectionsThreadless(IsoServer self)
IsoConnection isoConnection = IsoConnection_create(connectionSocket, self);
private_IsoServer_increaseConnectionCounter(self);
addClientConnection(self, isoConnection);
self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter,
@ -419,12 +445,9 @@ IsoServer_create()
GLOBAL_CALLOC(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS, sizeof(IsoConnection));
#endif
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
self->connectionCounterMutex = Semaphore_create(1);
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
self->openClientConnectionsMutex = Semaphore_create(1);
#endif
#endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */
self->connectionCounter = 0;
@ -525,12 +548,13 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs)
handles = Handleset_new();
if (handles != NULL) {
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
lockClientConnections(self);
#endif
#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1)
LinkedList openConnection = LinkedList_getNext(self->openClientConnections);
LinkedList lastConnection = self->openClientConnections;
@ -550,10 +574,6 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs)
lastConnection = lastConnection->next;
}
#if (CONFIG_MMS_THREADLESS_STACK != 1)
unlockClientConnections(self);
#endif
#else
int i;
@ -561,12 +581,20 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs)
if (self->openClientConnections[i] != NULL) {
if (IsoConnection_isRunning(self->openClientConnections[i])) {
IsoConnection_addHandleSet(self->openClientConnections[i], handles);
} else {
}
else {
#if ((CONFIG_MMS_SINGLE_THREADED == 1) || (CONFIG_MMS_THREADLESS_STACK == 1))
IsoConnection_destroy(self->openClientConnections[i]);
#endif
self->openClientConnections[i] = NULL;
}
}
}
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
unlockClientConnections(self);
#endif
#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */
Handleset_addSocket(handles, self->serverSocket);
result = Handleset_waitReady(handles, timeoutMs);
@ -668,42 +696,26 @@ IsoServer_destroy(IsoServer self)
LinkedList_destroyStatic(self->openClientConnections);
#endif /* (CONFIG_MMS_SINGLE_THREADED == 1) */
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
lockClientConnections(self);
Semaphore_destroy(self->openClientConnectionsMutex);
#endif
#else
GLOBAL_FREEMEM(self->openClientConnections);
#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore_destroy(self->connectionCounterMutex);
Semaphore_destroy(self->openClientConnectionsMutex);
#endif
GLOBAL_FREEMEM(self);
}
void
private_IsoServer_increaseConnectionCounter(IsoServer self)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_wait(self->connectionCounterMutex);
#endif
self->connectionCounter++;
if (DEBUG_ISO_SERVER)
printf("IsoServer: increase connection counter to %i!\n", self->connectionCounter);
#if (CONFIG_MMS_THREADLESS_STACK != 1)
Semaphore_post(self->connectionCounterMutex);
#endif
}
void
private_IsoServer_decreaseConnectionCounter(IsoServer self)
{
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore_wait(self->connectionCounterMutex);
#endif
@ -712,7 +724,7 @@ private_IsoServer_decreaseConnectionCounter(IsoServer self)
if (DEBUG_ISO_SERVER)
printf("IsoServer: decrease connection counter to %i!\n", self->connectionCounter);
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore_post(self->connectionCounterMutex);
#endif
}
@ -722,13 +734,13 @@ private_IsoServer_getConnectionCounter(IsoServer self)
{
int connectionCounter;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore_wait(self->connectionCounterMutex);
#endif
connectionCounter = self->connectionCounter;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0)
Semaphore_post(self->connectionCounterMutex);
#endif

@ -0,0 +1,20 @@
1 VERSIONINFO
FILEVERSION @LIB_VERSION_MAJOR@,@LIB_VERSION_MINOR@,@LIB_VERSION_PATCH@,0
PRODUCTVERSION @LIB_VERSION_MAJOR@,@LIB_VERSION_MINOR@,@LIB_VERSION_PATCH@,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileVersion", "@LIB_VERSION_MAJOR@.@LIB_VERSION_MINOR@.@LIB_VERSION_PATCH@.0"
VALUE "ProductVersion", "@LIB_VERSION_MAJOR@.@LIB_VERSION_MINOR@.@LIB_VERSION_PATCH@.0"
VALUE "ProductName", "libIEC61850"
VALUE "FileDescription", "libIEC61850 - open source library for IEC 61850"
VALUE "LegalCopyright", "Dual license : Commercial or GPLv3"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x400, 1252
END
END

@ -113,7 +113,6 @@ EXPORTS
FileSystem_readDirectory @302
FileSystem_readFile @303
FileSystem_renameFile @304
FileSystem_setBasePath @305
FunctionalConstraint_fromString @313
FunctionalConstraint_toString @314
GSEControlBlock_create @316
@ -528,4 +527,23 @@ EXPORTS
MmsValue_encodeMmsData
ControlObjectClient_setInterlockCheck
ControlObjectClient_setSynchroCheck
LogStorage_addEntry
LogStorage_addEntryData
LogStorage_getEntries
LogStorage_getEntriesAfter
LogStorage_getOldestAndNewestEntries
LogStorage_destroy
IedServer_setLogStorage
MmsJournalEntry_destroy
MmsJournalEntry_getEntryID
MmsJournalEntry_getOccurenceTime
MmsJournalEntry_getJournalVariables
MmsJournalVariable_getTag
MmsJournalVariable_getValue
MmsConnection_readJournalTimeRange
MmsConnection_readJournalStartAfter
LogControlBlock_create
Log_create
IedConnection_queryLogByTime
IedConnection_queryLogAfter
CDC_DPL_create

@ -113,7 +113,6 @@ EXPORTS
FileSystem_readDirectory @302
FileSystem_readFile @303
FileSystem_renameFile @304
FileSystem_setBasePath @305
FunctionalConstraint_fromString @313
FunctionalConstraint_toString @314
GSEControlBlock_create @316
@ -604,3 +603,23 @@ EXPORTS
IedServer_updateVisibleStringAttributeValue
ControlObjectClient_setInterlockCheck
ControlObjectClient_setSynchroCheck
LogStorage_addEntry
LogStorage_addEntryData
LogStorage_getEntries
LogStorage_getEntriesAfter
LogStorage_getOldestAndNewestEntries
LogStorage_destroy
IedServer_setLogStorage
MmsJournalEntry_destroy
MmsJournalEntry_getEntryID
MmsJournalEntry_getOccurenceTime
MmsJournalEntry_getJournalVariables
MmsJournalVariable_getTag
MmsJournalVariable_getValue
MmsConnection_readJournalTimeRange
MmsConnection_readJournalStartAfter
LogControlBlock_create
Log_create
IedConnection_queryLogByTime
IedConnection_queryLogAfter
CDC_DPL_create

@ -0,0 +1,48 @@
# - Try to find the sqlite library
# Once done this will define
#
# SQLITE_FOUND - system has sqlite
# SQLITE_INCLUDE_DIRS - the sqlite include directory
# SQLITE_LIBRARIES - Link these to use sqlite
#
# Define SQLITE_MIN_VERSION for which version desired.
#
INCLUDE(FindPkgConfig)
IF(Sqlite_FIND_REQUIRED)
SET(_pkgconfig_REQUIRED "REQUIRED")
ELSE(Sqlite_FIND_REQUIRED)
SET(_pkgconfig_REQUIRED "")
ENDIF(Sqlite_FIND_REQUIRED)
IF(SQLITE_MIN_VERSION)
PKG_SEARCH_MODULE(SQLITE ${_pkgconfig_REQUIRED} sqlite>=${SQLITE_MIN_VERSION} sqlite${SQLITE_MIN_VERSION})
ELSE(SQLITE_MIN_VERSION)
PKG_SEARCH_MODULE(SQLITE ${_pkgconfig_REQUIRED} sqlite)
ENDIF(SQLITE_MIN_VERSION)
IF(NOT SQLITE_FOUND AND NOT PKG_CONFIG_FOUND)
FIND_PATH(SQLITE_INCLUDE_DIRS sqlite${SQLITE_MIN_VERSION}.h)
FIND_LIBRARY(SQLITE_LIBRARIES sqlite${SQLITE_MIN_VERSION})
# Report results
IF(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS)
SET(SQLITE_FOUND 1)
IF(NOT Sqlite_FIND_QUIETLY)
MESSAGE(STATUS "Found Sqlite: ${SQLITE_LIBRARIES}")
ENDIF(NOT Sqlite_FIND_QUIETLY)
ELSE(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS)
IF(Sqlite_FIND_REQUIRED)
MESSAGE(SEND_ERROR "Could not find Sqlite")
ELSE(Sqlite_FIND_REQUIRED)
IF(NOT Sqlite_FIND_QUIETLY)
MESSAGE(STATUS "Could not find Sqlite")
ENDIF(NOT Sqlite_FIND_QUIETLY)
ENDIF(Sqlite_FIND_REQUIRED)
ENDIF(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS)
ENDIF(NOT SQLITE_FOUND AND NOT PKG_CONFIG_FOUND)
# Hide advanced variables from CMake GUIs
MARK_AS_ADVANCED(SQLITE_LIBRARIES SQLITE_INCLUDE_DIRS)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save