From 0dff4286aa2a0da2c342705d1b6f3ae622da90b5 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 23 Mar 2019 15:13:35 +0100 Subject: [PATCH] - merged latest changes to the server reporting module from 1.3 branch - merged with 1.3 branch --- config/stack_config.h | 5 +- config/stack_config.h.cmake | 5 +- examples/mms_utility/mms_utility.c | 2 +- .../server_example_basic_io.c | 2 +- .../simpleIO_direct_control.cid | 59 +- .../simpleIO_direct_control.icd | 44 +- .../server_example_basic_io/static_model.c | 306 +--- .../server_example_basic_io/static_model.h | 48 +- .../simpleIO_control_tests.icd | 9 +- examples/server_example_files/static_model.c | 14 +- examples/server_example_goose/static_model.c | 4 +- .../server_example_logging/static_model.c | 14 +- .../static_model.c | 2 +- examples/server_example_simple/static_model.c | 4 +- .../static_model.c | 4 +- .../server_example_threadless/static_model.c | 8 +- .../static_model.c | 4 +- examples/tls_server_example/static_model.c | 189 ++- examples/tls_server_example/static_model.h | 20 +- hal/socket/bsd/socket_bsd.c | 8 +- hal/socket/linux/socket_linux.c | 7 +- hal/thread/bsd/thread_bsd.c | 6 +- src/common/inc/string_utilities.h | 2 +- src/goose/goose_receiver.c | 16 +- src/iec61850/inc/iec61850_dynamic_model.h | 12 + src/iec61850/inc/iec61850_model.h | 4 + src/iec61850/inc_private/reporting.h | 19 +- src/iec61850/server/impl/ied_server.c | 1 + src/iec61850/server/mms_mapping/control.c | 3 + src/iec61850/server/mms_mapping/reporting.c | 1314 ++++++++++++----- src/iec61850/server/model/dynamic_model.c | 17 + src/mms/inc_private/mms_client_internal.h | 9 +- src/mms/inc_private/mms_common_internal.h | 2 +- src/mms/inc_private/mms_server_connection.h | 6 + .../iso_mms/client/mms_client_connection.c | 172 ++- src/mms/iso_mms/client/mms_client_files.c | 19 +- src/mms/iso_mms/common/mms_common_msg.c | 240 +-- src/mms/iso_mms/server/mms_file_service.c | 6 + .../iso_mms/server/mms_information_report.c | 1 - .../iso_mms/server/mms_server_connection.c | 22 +- src/mms/iso_mms/server/mms_write_service.c | 1 - tools/model_generator/genmodel.jar | Bin 89615 -> 93826 bytes .../src/com/libiec61850/scl/SclParser.java | 28 + .../scl/communication/ConnectedAP.java | 13 +- .../com/libiec61850/scl/model/RptEnabled.java | 18 + .../com/libiec61850/tools/ModelViewer.java | 15 + .../tools/StaticModelGenerator.java | 134 +- 47 files changed, 1907 insertions(+), 931 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index a3c419df..5512c57f 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -147,7 +147,10 @@ #define CONFIG_IEC61850_REPORT_SERVICE 1 /* support buffered report control blocks with ResvTms field */ -#define CONFIG_IEC61850_BRCB_WITH_RESVTMS 0 +#define CONFIG_IEC61850_BRCB_WITH_RESVTMS 1 + +/* allow only configured clients (when pre-configured by ClientLN) - note behavior in PIXIT Rp13 */ +#define CONFIG_IEC61850_RCB_ALLOW_ONLY_PRECONFIGURED_CLIENT 0 /* The default buffer size of buffered RCBs in bytes */ #define CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE 65536 diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index b3fcadf7..9365b492 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -137,7 +137,10 @@ #cmakedefine01 CONFIG_IEC61850_REPORT_SERVICE /* support buffered report control blocks with ResvTms field */ -#define CONFIG_IEC61850_BRCB_WITH_RESVTMS 0 +#define CONFIG_IEC61850_BRCB_WITH_RESVTMS 1 + +/* allow only configured clients (when pre-configured by ClientLN) - note behavior in PIXIT Rp13 */ +#define CONFIG_IEC61850_RCB_ALLOW_ONLY_PRECONFIGURED_CLIENT 0 /* The default buffer size of buffered RCBs in bytes */ #cmakedefine CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE @CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE@ diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index cb21460c..56ca03bd 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -104,7 +104,7 @@ int main(int argc, char** argv) { char* hostname = StringUtils_copyString("localhost"); int tcpPort = 102; int maxPduSize = 65000; - int arrayIndex = -1; + int arrayIndex = -1; char* domainName = NULL; char* variableName = NULL; diff --git a/examples/server_example_basic_io/server_example_basic_io.c b/examples/server_example_basic_io/server_example_basic_io.c index cc73e912..98bcf49a 100644 --- a/examples/server_example_basic_io/server_example_basic_io.c +++ b/examples/server_example_basic_io/server_example_basic_io.c @@ -99,7 +99,7 @@ main(int argc, char** argv) /* disable MMS file service */ IedServerConfig_enableFileService(config, false); - /* disable dynamic data set service */ + /* enable dynamic data set service */ IedServerConfig_enableDynamicDataSetService(config, true); /* disable log service */ diff --git a/examples/server_example_basic_io/simpleIO_direct_control.cid b/examples/server_example_basic_io/simpleIO_direct_control.cid index 554ed708..1bc0fabc 100644 --- a/examples/server_example_basic_io/simpleIO_direct_control.cid +++ b/examples/server_example_basic_io/simpleIO_direct_control.cid @@ -5,7 +5,6 @@ Station bus - 10

0.0.0.0

@@ -19,6 +18,19 @@

102

+ +
+

192.168.2.9

+

255.255.255.0

+

192.168.2.1

+

1,3,9999,33

+

33

+

00000001

+

0001

+

0001

+

102

+
+
@@ -31,6 +43,7 @@ + @@ -68,19 +81,35 @@ - + - - + + - + + + - + + + + + + + + + + + + + + + @@ -198,10 +227,10 @@ - - - - + + + + @@ -257,7 +286,7 @@ - + @@ -266,6 +295,14 @@ + + + + + + + + diff --git a/examples/server_example_basic_io/simpleIO_direct_control.icd b/examples/server_example_basic_io/simpleIO_direct_control.icd index a32282b9..258e9ae6 100644 --- a/examples/server_example_basic_io/simpleIO_direct_control.icd +++ b/examples/server_example_basic_io/simpleIO_direct_control.icd @@ -2,25 +2,7 @@
- - - Station bus - 10 - -
-

0.0.0.0

-

255.255.255.0

-

192.168.2.1

-

1,3,9999,33

-

33

-

00000001

-

0001

-

0001

-

102

-
-
-
-
+ @@ -79,12 +61,6 @@
- - - - - - @@ -198,10 +174,10 @@ - - - - + + + + @@ -257,7 +233,7 @@ - + @@ -266,6 +242,14 @@ + + + + + + + + diff --git a/examples/server_example_basic_io/static_model.c b/examples/server_example_basic_io/static_model.c index f1d43ff8..276d60da 100644 --- a/examples/server_example_basic_io/static_model.c +++ b/examples/server_example_basic_io/static_model.c @@ -1280,62 +1280,10 @@ DataObject iedModel_GenericIO_GGIO1_SPCSO2 = { "SPCSO2", (ModelNode*) &iedModel_GenericIO_GGIO1, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_stVal, 0 }; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_origin = { - DataAttributeModelType, - "origin", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_ctlNum, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_origin_orCat, - 0, - IEC61850_FC_ST, - IEC61850_CONSTRUCTED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_origin_orCat = { - DataAttributeModelType, - "orCat", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_origin, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_origin_orIdent, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_origin_orIdent = { - DataAttributeModelType, - "orIdent", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_origin, - NULL, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_OCTET_STRING_64, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlNum = { - DataAttributeModelType, - "ctlNum", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_stVal, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_INT8U, - 0, - NULL, - 0}; - DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal = { DataAttributeModelType, "stVal", @@ -1353,7 +1301,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q = { DataAttributeModelType, "q", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_t, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, NULL, 0, IEC61850_FC_ST, @@ -1362,37 +1310,11 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q = { NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t = { - DataAttributeModelType, - "t", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_ctlModel, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_TIMESTAMP, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel = { - DataAttributeModelType, - "ctlModel", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, - NULL, - 0, - IEC61850_FC_CF, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper = { DataAttributeModelType, "Oper", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_ctlModel, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal, 0, IEC61850_FC_CO, @@ -1505,66 +1427,40 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check = { NULL, 0}; -DataObject iedModel_GenericIO_GGIO1_SPCSO3 = { - DataObjectModelType, - "SPCSO3", - (ModelNode*) &iedModel_GenericIO_GGIO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_origin, - 0 -}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_origin = { - DataAttributeModelType, - "origin", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_ctlNum, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_origin_orCat, - 0, - IEC61850_FC_ST, - IEC61850_CONSTRUCTED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_origin_orCat = { +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel = { DataAttributeModelType, - "orCat", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_origin, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_origin_orIdent, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_t, NULL, 0, - IEC61850_FC_ST, + IEC61850_FC_CF, IEC61850_ENUMERATED, 0, NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_origin_orIdent = { +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t = { DataAttributeModelType, - "orIdent", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_origin, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, NULL, NULL, 0, IEC61850_FC_ST, - IEC61850_OCTET_STRING_64, + IEC61850_TIMESTAMP, 0, NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlNum = { - DataAttributeModelType, - "ctlNum", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, +DataObject iedModel_GenericIO_GGIO1_SPCSO3 = { + DataObjectModelType, + "SPCSO3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_stVal, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_INT8U, - 0, - NULL, - 0}; + 0 +}; DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal = { DataAttributeModelType, @@ -1583,7 +1479,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q = { DataAttributeModelType, "q", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_t, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, NULL, 0, IEC61850_FC_ST, @@ -1592,37 +1488,11 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q = { NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t = { - DataAttributeModelType, - "t", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_ctlModel, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_TIMESTAMP, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel = { - DataAttributeModelType, - "ctlModel", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, - NULL, - 0, - IEC61850_FC_CF, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper = { DataAttributeModelType, "Oper", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, - NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_ctlModel, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal, 0, IEC61850_FC_CO, @@ -1735,66 +1605,40 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check = { NULL, 0}; -DataObject iedModel_GenericIO_GGIO1_SPCSO4 = { - DataObjectModelType, - "SPCSO4", - (ModelNode*) &iedModel_GenericIO_GGIO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, - 0 -}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin = { - DataAttributeModelType, - "origin", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlNum, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat, - 0, - IEC61850_FC_ST, - IEC61850_CONSTRUCTED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat = { +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel = { DataAttributeModelType, - "orCat", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_t, NULL, 0, - IEC61850_FC_ST, + IEC61850_FC_CF, IEC61850_ENUMERATED, 0, NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent = { +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t = { DataAttributeModelType, - "orIdent", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_origin, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, NULL, NULL, 0, IEC61850_FC_ST, - IEC61850_OCTET_STRING_64, + IEC61850_TIMESTAMP, 0, NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlNum = { - DataAttributeModelType, - "ctlNum", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, +DataObject iedModel_GenericIO_GGIO1_SPCSO4 = { + DataObjectModelType, + "SPCSO4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_INT8U, - 0, - NULL, - 0}; + 0 +}; DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal = { DataAttributeModelType, @@ -1813,7 +1657,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { DataAttributeModelType, "q", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, NULL, 0, IEC61850_FC_ST, @@ -1822,37 +1666,11 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { - DataAttributeModelType, - "t", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_TIMESTAMP, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { - DataAttributeModelType, - "ctlModel", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, - NULL, - 0, - IEC61850_FC_CF, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper = { DataAttributeModelType, "Oper", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, - NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal, 0, IEC61850_FC_CO, @@ -1965,6 +1783,32 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check = { NULL, 0}; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + DataObject iedModel_GenericIO_GGIO1_Ind1 = { DataObjectModelType, "Ind1", @@ -2168,16 +2012,16 @@ extern ReportControlBlock iedModel_GenericIO_LLN0_report7; extern ReportControlBlock iedModel_GenericIO_LLN0_report8; extern ReportControlBlock iedModel_GenericIO_LLN0_report9; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report2}; -ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report3}; -ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report4}; -ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Events201", "Events2", false, "Events2", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report5}; -ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Events202", "Events2", false, "Events2", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report6}; -ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Events203", "Events2", false, "Events2", 1, 24, 175, 50, 1000, &iedModel_GenericIO_LLN0_report7}; -ReportControlBlock iedModel_GenericIO_LLN0_report7 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report8}; -ReportControlBlock iedModel_GenericIO_LLN0_report8 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report9}; -ReportControlBlock iedModel_GenericIO_LLN0_report9 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsRCBPreConf01", "Events1", false, "Events", 1, 24, 175, 50, 1000, {0x4, 0xc0, 0xa8, 0x2, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsBRCB01", "Events2", true, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsBRCBPreConf01", "Events2", true, "Events", 1, 24, 175, 50, 1000, {0x4, 0xc0, 0xa8, 0x2, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report5}; +ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report6}; +ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report7}; +ReportControlBlock iedModel_GenericIO_LLN0_report7 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report8}; +ReportControlBlock iedModel_GenericIO_LLN0_report8 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report9}; +ReportControlBlock iedModel_GenericIO_LLN0_report9 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/server_example_basic_io/static_model.h b/examples/server_example_basic_io/static_model.h index c03ed491..b6030e51 100644 --- a/examples/server_example_basic_io/static_model.h +++ b/examples/server_example_basic_io/static_model.h @@ -100,14 +100,8 @@ 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 DataObject iedModel_GenericIO_GGIO1_SPCSO2; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_origin; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_origin_orCat; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_origin_orIdent; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin; @@ -117,15 +111,11 @@ 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_origin; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_origin_orCat; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_origin_orIdent; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin; @@ -135,15 +125,11 @@ 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_origin; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t; -extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin; @@ -153,6 +139,8 @@ 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; @@ -261,14 +249,8 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_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_SPCSO2 (&iedModel_GenericIO_GGIO1_SPCSO2) -#define IEDMODEL_GenericIO_GGIO1_SPCSO2_origin (&iedModel_GenericIO_GGIO1_SPCSO2_origin) -#define IEDMODEL_GenericIO_GGIO1_SPCSO2_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO2_origin_orCat) -#define IEDMODEL_GenericIO_GGIO1_SPCSO2_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO2_origin_orIdent) -#define IEDMODEL_GenericIO_GGIO1_SPCSO2_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO2_ctlNum) #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_t (&iedModel_GenericIO_GGIO1_SPCSO2_t) -#define IEDMODEL_GenericIO_GGIO1_SPCSO2_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO2_ctlModel) #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) @@ -278,15 +260,11 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #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_origin (&iedModel_GenericIO_GGIO1_SPCSO3_origin) -#define IEDMODEL_GenericIO_GGIO1_SPCSO3_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO3_origin_orCat) -#define IEDMODEL_GenericIO_GGIO1_SPCSO3_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO3_origin_orIdent) -#define IEDMODEL_GenericIO_GGIO1_SPCSO3_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO3_ctlNum) #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_t (&iedModel_GenericIO_GGIO1_SPCSO3_t) -#define IEDMODEL_GenericIO_GGIO1_SPCSO3_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO3_ctlModel) #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) @@ -296,15 +274,11 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #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_origin (&iedModel_GenericIO_GGIO1_SPCSO4_origin) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO4_origin_orCat) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO4_origin_orIdent) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO4_ctlNum) #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_t (&iedModel_GenericIO_GGIO1_SPCSO4_t) -#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel) #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) @@ -314,6 +288,8 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #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) diff --git a/examples/server_example_control/simpleIO_control_tests.icd b/examples/server_example_control/simpleIO_control_tests.icd index 6bfd88cd..4ff831ec 100644 --- a/examples/server_example_control/simpleIO_control_tests.icd +++ b/examples/server_example_control/simpleIO_control_tests.icd @@ -1,15 +1,13 @@ - -
+
Station bus - 10
-

10.0.0.2

+

192.168.2.223

255.255.255.0

10.0.0.1

0001

@@ -29,10 +27,7 @@ - - - diff --git a/examples/server_example_files/static_model.c b/examples/server_example_files/static_model.c index 05926022..aaa714d3 100644 --- a/examples/server_example_files/static_model.c +++ b/examples/server_example_files/static_model.c @@ -1944,13 +1944,13 @@ extern ReportControlBlock iedModel_GenericIO_LLN0_report4; extern ReportControlBlock iedModel_GenericIO_LLN0_report5; extern ReportControlBlock iedModel_GenericIO_LLN0_report6; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report2}; -ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report3}; -ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report4}; -ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report5}; -ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report6}; -ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report5}; +ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report6}; +ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/server_example_goose/static_model.c b/examples/server_example_goose/static_model.c index 4c3d6c97..bcc77426 100644 --- a/examples/server_example_goose/static_model.c +++ b/examples/server_example_goose/static_model.c @@ -1882,8 +1882,8 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { extern ReportControlBlock iedModel_GenericIO_LLN0_report0; extern ReportControlBlock iedModel_GenericIO_LLN0_report1; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "AnalogValuesRCB01", "AnalogValues", false, "AnalogValues", 1, 24, 111, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "AnalogValuesRCB01", "AnalogValues", false, "AnalogValues", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; extern GSEControlBlock iedModel_GenericIO_LLN0_gse0; diff --git a/examples/server_example_logging/static_model.c b/examples/server_example_logging/static_model.c index 07b5b4a4..aaa714d3 100644 --- a/examples/server_example_logging/static_model.c +++ b/examples/server_example_logging/static_model.c @@ -1944,13 +1944,13 @@ extern ReportControlBlock iedModel_GenericIO_LLN0_report4; extern ReportControlBlock iedModel_GenericIO_LLN0_report5; extern ReportControlBlock iedModel_GenericIO_LLN0_report6; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report2}; -ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report3}; -ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report4}; -ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, &iedModel_GenericIO_LLN0_report5}; -ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, &iedModel_GenericIO_LLN0_report6}; -ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report5}; +ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report6}; +ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/server_example_password_auth/static_model.c b/examples/server_example_password_auth/static_model.c index 4cc943c7..2763f623 100644 --- a/examples/server_example_password_auth/static_model.c +++ b/examples/server_example_password_auth/static_model.c @@ -1795,7 +1795,7 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { extern ReportControlBlock iedModel_GenericIO_LLN0_report0; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 24, 239, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/server_example_simple/static_model.c b/examples/server_example_simple/static_model.c index 922e1c25..9e91626d 100644 --- a/examples/server_example_simple/static_model.c +++ b/examples/server_example_simple/static_model.c @@ -3,7 +3,7 @@ * * automatically generated from sampleModel_with_dataset.icd */ -#include "../server_example_simple/static_model.h" +#include "static_model.h" static void initializeValues(); @@ -1590,7 +1590,7 @@ DataAttribute iedModel_Device1_MMXU2_TotW_t = { extern ReportControlBlock iedModel_Device1_LLN0_report0; -ReportControlBlock iedModel_Device1_LLN0_report0 = {&iedModel_Device1_LLN0, "LLN0_Events_BuffRep01", "LLN0$RP$brcbEV1", true, "dataset1", 1, 25, 239, 50, 900000, NULL}; +ReportControlBlock iedModel_Device1_LLN0_report0 = {&iedModel_Device1_LLN0, "LLN0_Events_BuffRep01", "LLN0$RP$brcbEV1", true, "dataset1", 1, 25, 239, 50, 900000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/server_example_substitution/static_model.c b/examples/server_example_substitution/static_model.c index bf53c61a..0f8bff5d 100644 --- a/examples/server_example_substitution/static_model.c +++ b/examples/server_example_substitution/static_model.c @@ -970,8 +970,8 @@ DataAttribute iedModel_LD1_GGIO1_AnIn1_subID = { extern ReportControlBlock iedModel_LD1_LLN0_report0; extern ReportControlBlock iedModel_LD1_LLN0_report1; -ReportControlBlock iedModel_LD1_LLN0_report0 = {&iedModel_LD1_LLN0, "urcb01", "13e08c78", false, "", 1, 23, 247, 3000, 5000, &iedModel_LD1_LLN0_report1}; -ReportControlBlock iedModel_LD1_LLN0_report1 = {&iedModel_LD1_LLN0, "urcb02", "13e08c78", false, "", 1, 23, 247, 3000, 5000, NULL}; +ReportControlBlock iedModel_LD1_LLN0_report0 = {&iedModel_LD1_LLN0, "urcb01", "13e08c78", false, "", 1, 23, 247, 3000, 5000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_LD1_LLN0_report1}; +ReportControlBlock iedModel_LD1_LLN0_report1 = {&iedModel_LD1_LLN0, "urcb02", "13e08c78", false, "", 1, 23, 247, 3000, 5000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/server_example_threadless/static_model.c b/examples/server_example_threadless/static_model.c index 4f37bdea..ae71aa57 100644 --- a/examples/server_example_threadless/static_model.c +++ b/examples/server_example_threadless/static_model.c @@ -1843,10 +1843,10 @@ extern ReportControlBlock iedModel_GenericIO_LLN0_report1; extern ReportControlBlock iedModel_GenericIO_LLN0_report2; extern ReportControlBlock iedModel_GenericIO_LLN0_report3; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report2}; -ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report3}; -ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 111, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/server_example_write_handler/static_model.c b/examples/server_example_write_handler/static_model.c index c0dba265..0cf385e4 100644 --- a/examples/server_example_write_handler/static_model.c +++ b/examples/server_example_write_handler/static_model.c @@ -3,7 +3,7 @@ * * automatically generated from complexModel.icd */ -#include "../server_example_write_handler/static_model.h" +#include "static_model.h" static void initializeValues(); @@ -3580,7 +3580,7 @@ DataAttribute iedModel_Physical_Measurements_LPHD1_Proxy_t = { extern ReportControlBlock iedModel_Inverter_LLN0_report0; -ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "rcb101", "ID", false, "dataset1", 0, 19, 32, 0, 0, NULL}; +ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "rcb101", "ID", false, "dataset1", 0, 19, 32, 0, 0, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; diff --git a/examples/tls_server_example/static_model.c b/examples/tls_server_example/static_model.c index ddc8dd64..276d60da 100644 --- a/examples/tls_server_example/static_model.c +++ b/examples/tls_server_example/static_model.c @@ -1,9 +1,9 @@ /* * static_model.c * - * automatically generated from simpleIO_direct_control.icd + * automatically generated from simpleIO_direct_control.cid */ -#include "../server_example_basic_io/static_model.h" +#include "static_model.h" static void initializeValues(); @@ -248,7 +248,7 @@ DataAttribute iedModel_GenericIO_LLN0_Mod_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -309,7 +309,7 @@ DataAttribute iedModel_GenericIO_LLN0_Beh_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -357,7 +357,7 @@ DataAttribute iedModel_GenericIO_LLN0_Health_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -509,7 +509,7 @@ DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -601,10 +601,23 @@ DataObject iedModel_GenericIO_GGIO1_Mod = { "Mod", (ModelNode*) &iedModel_GenericIO_GGIO1, (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, - (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_stVal, 0 }; +DataAttribute iedModel_GenericIO_GGIO1_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_Mod_q = { DataAttributeModelType, "q", @@ -661,7 +674,7 @@ DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -709,7 +722,7 @@ DataAttribute iedModel_GenericIO_GGIO1_Health_stVal = { NULL, 0, IEC61850_FC_ST, - IEC61850_INT32, + IEC61850_ENUMERATED, 0 + TRG_OPT_DATA_CHANGED, NULL, 0}; @@ -1037,10 +1050,62 @@ DataObject iedModel_GenericIO_GGIO1_SPCSO1 = { "SPCSO1", (ModelNode*) &iedModel_GenericIO_GGIO1, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_stVal, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin, 0 }; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat, + 0, + IEC61850_FC_ST, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_origin, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_stVal, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT8U, + 0, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal = { DataAttributeModelType, "stVal", @@ -1058,7 +1123,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q = { DataAttributeModelType, "q", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_t, NULL, 0, IEC61850_FC_ST, @@ -1067,11 +1132,37 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q = { NULL, 0}; +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper = { DataAttributeModelType, "Oper", (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlModel, + NULL, (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal, 0, IEC61850_FC_CO, @@ -1184,32 +1275,6 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check = { NULL, 0}; -DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel = { - DataAttributeModelType, - "ctlModel", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_t, - NULL, - 0, - IEC61850_FC_CF, - IEC61850_ENUMERATED, - 0, - NULL, - 0}; - -DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t = { - DataAttributeModelType, - "t", - (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, - NULL, - NULL, - 0, - IEC61850_FC_ST, - IEC61850_TIMESTAMP, - 0, - NULL, - 0}; - DataObject iedModel_GenericIO_GGIO1_SPCSO2 = { DataObjectModelType, "SPCSO2", @@ -1943,27 +2008,25 @@ extern ReportControlBlock iedModel_GenericIO_LLN0_report3; extern ReportControlBlock iedModel_GenericIO_LLN0_report4; extern ReportControlBlock iedModel_GenericIO_LLN0_report5; extern ReportControlBlock iedModel_GenericIO_LLN0_report6; +extern ReportControlBlock iedModel_GenericIO_LLN0_report7; +extern ReportControlBlock iedModel_GenericIO_LLN0_report8; +extern ReportControlBlock iedModel_GenericIO_LLN0_report9; -ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report1}; -ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report2}; -ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report3}; -ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 239, 50, 1000, &iedModel_GenericIO_LLN0_report4}; -ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report5}; -ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, &iedModel_GenericIO_LLN0_report6}; -ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, NULL}; +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsRCBPreConf01", "Events1", false, "Events", 1, 24, 175, 50, 1000, {0x4, 0xc0, 0xa8, 0x2, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsBRCB01", "Events2", true, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsBRCBPreConf01", "Events2", true, "Events", 1, 24, 175, 50, 1000, {0x4, 0xc0, 0xa8, 0x2, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report5}; +ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report6}; +ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 175, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report7}; +ReportControlBlock iedModel_GenericIO_LLN0_report7 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report8}; +ReportControlBlock iedModel_GenericIO_LLN0_report8 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, &iedModel_GenericIO_LLN0_report9}; +ReportControlBlock iedModel_GenericIO_LLN0_report9 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 239, 50, 1000, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, NULL}; -extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; -extern LogControlBlock iedModel_GenericIO_LLN0_lcb1; -LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, &iedModel_GenericIO_LLN0_lcb1}; -LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", NULL, NULL, 3, 0, true, true, NULL}; -extern Log iedModel_GenericIO_LLN0_log0; -extern Log iedModel_GenericIO_LLN0_log1; -Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1}; -Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL}; IedModel iedModel = { @@ -1974,8 +2037,8 @@ IedModel iedModel = { NULL, NULL, NULL, - &iedModel_GenericIO_LLN0_lcb0, - &iedModel_GenericIO_LLN0_log0, + NULL, + NULL, initializeValues }; @@ -1983,16 +2046,30 @@ static void initializeValues() { +iedModel_GenericIO_LLN0_Mod_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_LLN0_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); +iedModel_GenericIO_LLN0_Beh_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_LLN0_Health_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_LLN0_NamPlt_vendor.mmsValue = MmsValue_newVisibleString("MZ Automation"); -iedModel_GenericIO_LLN0_NamPlt_swRev.mmsValue = MmsValue_newVisibleString("0.7.3"); +iedModel_GenericIO_LLN0_NamPlt_swRev.mmsValue = MmsValue_newVisibleString("1.3.0"); iedModel_GenericIO_LLN0_NamPlt_d.mmsValue = MmsValue_newVisibleString("libiec61850 server example"); +iedModel_GenericIO_LPHD1_PhyHealth_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_Mod_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); +iedModel_GenericIO_GGIO1_Beh_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_Health_stVal.mmsValue = MmsValue_newIntegerFromInt32(1); + iedModel_GenericIO_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); diff --git a/examples/tls_server_example/static_model.h b/examples/tls_server_example/static_model.h index b5670e9f..b6030e51 100644 --- a/examples/tls_server_example/static_model.h +++ b/examples/tls_server_example/static_model.h @@ -1,7 +1,7 @@ /* * static_model.h * - * automatically generated from simpleIO_direct_control.icd + * automatically generated from simpleIO_direct_control.cid */ #ifndef STATIC_MODEL_H_ @@ -45,6 +45,7 @@ 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_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_Mod_q; extern DataAttribute iedModel_GenericIO_GGIO1_Mod_t; extern DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel; @@ -81,8 +82,14 @@ 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_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlNum; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal; extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin; @@ -92,8 +99,6 @@ 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; @@ -189,6 +194,7 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #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_stVal (&iedModel_GenericIO_GGIO1_Mod_stVal) #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) @@ -225,8 +231,14 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #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_origin (&iedModel_GenericIO_GGIO1_SPCSO1_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO1_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO1_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO1_ctlNum) #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_t (&iedModel_GenericIO_GGIO1_SPCSO1_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO1_ctlModel) #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) @@ -236,8 +248,6 @@ extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; #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) diff --git a/hal/socket/bsd/socket_bsd.c b/hal/socket/bsd/socket_bsd.c index d53df7ac..c1dd339e 100644 --- a/hal/socket/bsd/socket_bsd.c +++ b/hal/socket/bsd/socket_bsd.c @@ -442,8 +442,12 @@ Socket_write(Socket self, uint8_t* buf, int size) if (self->fd == -1) return -1; - // MSG_NOSIGNAL - prevent send to signal SIGPIPE when peer unexpectedly closed the socket - return send(self->fd, buf, size, 0); + int retVal = send(self->fd, buf, size, 0); + + if ((retVal == -1) && (errno == EAGAIN)) + return 0; + else + return retVal; } void diff --git a/hal/socket/linux/socket_linux.c b/hal/socket/linux/socket_linux.c index 2a45294a..01c497cc 100644 --- a/hal/socket/linux/socket_linux.c +++ b/hal/socket/linux/socket_linux.c @@ -532,7 +532,12 @@ Socket_write(Socket self, uint8_t* buf, int size) return -1; /* MSG_NOSIGNAL - prevent send to signal SIGPIPE when peer unexpectedly closed the socket */ - return send(self->fd, buf, size, MSG_NOSIGNAL); + int retVal = send(self->fd, buf, size, MSG_NOSIGNAL); + + if ((retVal == -1) && (errno == EAGAIN)) + return 0; + else + return retVal; } void diff --git a/hal/thread/bsd/thread_bsd.c b/hal/thread/bsd/thread_bsd.c index 7d882d22..23ed9512 100644 --- a/hal/thread/bsd/thread_bsd.c +++ b/hal/thread/bsd/thread_bsd.c @@ -39,9 +39,9 @@ struct sThread { Semaphore Semaphore_create(int initialValue) { - char tmpname[] = {"/tmp/libiec61850.XXXXXX"}; - mktemp(tmpname); - Semaphore self = sem_open(tmpname, O_CREAT, 0666, initialValue); + Semaphore self = GLOBAL_MALLOC(sizeof(sem_t)); + + sem_init((sem_t*) self, 0, initialValue); return self; } diff --git a/src/common/inc/string_utilities.h b/src/common/inc/string_utilities.h index 428a5bab..ed0ffe6c 100644 --- a/src/common/inc/string_utilities.h +++ b/src/common/inc/string_utilities.h @@ -122,7 +122,7 @@ StringUtils_sortList(LinkedList list); * * \return true when parsing has been successful, false otherwise */ -bool +LIB61850_INTERNAL bool StringUtils_convertIPv6AdddressStringToByteArray(const char* addressString, uint8_t ipV6Addr[]); #ifdef __cplusplus diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index b833e04e..1985ee8c 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -48,7 +48,7 @@ struct sGooseReceiver { bool running; - bool stopped; + bool stop; char* interfaceId; uint8_t* buffer; EthernetSocket ethSocket; @@ -65,6 +65,7 @@ GooseReceiver_create() if (self != NULL) { self->running = false; + self->stop = false; self->interfaceId = NULL; self->buffer = (uint8_t*) GLOBAL_MALLOC(ETH_BUFFER_LENGTH); self->ethSocket = NULL; @@ -772,9 +773,6 @@ gooseReceiverLoop(void* threadParameter) { GooseReceiver self = (GooseReceiver) threadParameter; - self->running = true; - self->stopped = false; - GooseReceiver_startThreadless(self); if (self->running) { @@ -783,12 +781,13 @@ gooseReceiverLoop(void* threadParameter) if (GooseReceiver_tick(self) == false) Thread_sleep(1); + + if (self->stop) + break; } GooseReceiver_stopThreadless(self); } - - self->stopped = true; } #endif @@ -822,12 +821,11 @@ void GooseReceiver_stop(GooseReceiver self) { #if (CONFIG_MMS_THREADLESS_STACK == 0) + self->stop = true; self->running = false; Thread_destroy(self->thread); - - while (self->stopped == false) - Thread_sleep(1); + self->stop = false; #endif } diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index 72c69ba6..13599698 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -160,6 +160,18 @@ LIB61850_API 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 Set a pre-configured client for the RCB + * + * If set only the pre configured client should use this RCB instance + * + * \param self the RCB instance + * \param clientType the type of the client (0 = no client, 4 = IPv4 client, 6 = IPv6 client) + * \param clientAddress buffer containing the client address (4 byte in case of an IPv4 address, 16 byte in case of an IPv6 address, NULL for no client) + */ +LIB61850_API void +ReportControlBlock_setPreconfiguredClient(ReportControlBlock* self, uint8_t clientType, uint8_t* clientAddress); + /** * \brief create a new log control block (LCB) * diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 6bcff86f..18de227c 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -264,6 +264,10 @@ struct sReportControlBlock { uint32_t bufferTime; /* BufTm - time to buffer events until a report is generated */ uint32_t intPeriod; /* IntgPd - integrity period */ + /* type (first byte) and address of the pre-configured client + type can be one of (0 - no reservation, 4 - IPv4 client, 6 - IPv6 client) */ + uint8_t clientReservation[17]; + ReportControlBlock* sibling; /* next control block in list or NULL if this is the last entry */ }; diff --git a/src/iec61850/inc_private/reporting.h b/src/iec61850/inc_private/reporting.h index 125f6cde..1b471a81 100644 --- a/src/iec61850/inc_private/reporting.h +++ b/src/iec61850/inc_private/reporting.h @@ -1,7 +1,7 @@ /* * reporting.h * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2019 Michael Zillgith * * This file is part of libIEC61850. * @@ -28,9 +28,9 @@ typedef struct sReportBufferEntry ReportBufferEntry; struct sReportBufferEntry { uint8_t entryId[8]; - uint8_t flags; /* bit 0 (1 = isIntegrityReport), bit 1 (1 = isGiReport) */ uint64_t timeOfEntry; - int entryLength; + int entryLength:30; + unsigned int flags:2; /* bit 0 (1 = isIntegrityReport), bit 1 (1 = isGiReport) */ ReportBufferEntry* next; }; @@ -42,6 +42,8 @@ typedef struct { ReportBufferEntry* lastEnqueuedReport; ReportBufferEntry* nextToTransmit; bool isOverflow; /* true if overflow condition is active */ + + Semaphore lock; /* protect access to report buffer */ } ReportBuffer; typedef struct { @@ -53,6 +55,7 @@ typedef struct { MmsValue* rcbValues; MmsValue* inclusionField; MmsValue* confRev; + DataSet* dataSet; bool isDynamicDataSet; bool enabled; @@ -76,6 +79,12 @@ typedef struct { int triggerOps; + /* information for segmented reporting */ + bool segmented; /* indicates that a segmented report is in process */ + int startIndexForNextSegment; /* start data set index for the next report segment */ + MmsValue* subSeqVal; /* sub sequence value for segmented reporting */ + uint64_t segmentedReportTimestamp; /* time stamp used for all report segments */ + #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore createNotificationsMutex; /* { covered by mutex } */ #endif @@ -91,6 +100,7 @@ typedef struct { bool isBuffering; /* true if buffered RCB is buffering (datSet is set to a valid value) */ bool isResync; /* true if buffered RCB is in resync state */ + int resvTms; /* -1 for preconfigured client, 0 - not reserved, > 0 reserved by client */ ReportBuffer* reportBuffer; MmsValue* timeOfEntry; @@ -122,6 +132,9 @@ LIB61850_INTERNAL MmsDataAccessError Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value, MmsServerConnection connection); +LIB61850_INTERNAL void +ReportControl_readAccess(ReportControl* rc, char* elementName); + LIB61850_INTERNAL void Reporting_activateBufferedReports(MmsMapping* self); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index b45248df..072733fb 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -99,6 +99,7 @@ createControlObjects(IedServer self, MmsDomain* domain, char* lnName, MmsVariabl if (DEBUG_IED_SERVER) printf("IED_SERVER: createControlObjects: Unknown element in CO: %s\n", coElementSpec->name); + break; } } diff --git a/src/iec61850/server/mms_mapping/control.c b/src/iec61850/server/mms_mapping/control.c index 9a4548ea..442cc8e5 100644 --- a/src/iec61850/server/mms_mapping/control.c +++ b/src/iec61850/server/mms_mapping/control.c @@ -417,6 +417,9 @@ ControlObject_destroy(ControlObject* self) if (self->mmsValue != NULL) MmsValue_delete(self->mmsValue); + if (self->sbo != NULL) + MmsValue_delete(self->sbo); + if (self->emptyString != NULL) MmsValue_delete(self->emptyString); diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 38bfd9bc..9c2b2a96 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1,7 +1,7 @@ /* * reporting.c * - * Copyright 2013-2018 Michael Zillgith + * Copyright 2013-2019 Michael Zillgith * * This file is part of libIEC61850. * @@ -29,14 +29,19 @@ #include "simple_allocator.h" #include "mem_alloc_linked_list.h" +#include "ber_encoder.h" #include "mms_mapping_internal.h" #include "mms_value_internal.h" +#include "mms_server_internal.h" #include "conversions.h" #include "reporting.h" #include "ied_server_private.h" #include +/* if not explicitly set by client "ResvTms" will be set to this value */ +#define RESV_TMS_IMPLICIT_VALUE 30 + #ifndef DEBUG_IED_SERVER #define DEBUG_IED_SERVER 0 #endif @@ -66,6 +71,10 @@ ReportBuffer_create(int bufferSize) GLOBAL_FREEMEM(self); self = NULL; } + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + self->lock = Semaphore_create(1); +#endif } return self; @@ -75,6 +84,11 @@ static void ReportBuffer_destroy(ReportBuffer* self) { GLOBAL_FREEMEM(self->memoryBlock); + +#if (CONFIG_MMS_THREADLESS_STACK != 1) + Semaphore_destroy(self->lock); +#endif + GLOBAL_FREEMEM(self); } @@ -87,6 +101,9 @@ ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize, self->parentLN = parentLN; self->rcbValues = NULL; self->confRev = NULL; + self->subSeqVal = MmsValue_newUnsigned(16); + self->segmented = false; + self->startIndexForNextSegment = 0; self->enabled = false; self->reserved = false; self->buffered = buffered; @@ -113,6 +130,7 @@ ReportControl_create(bool buffered, LogicalNode* parentLN, int reportBufferSize, self->bufferedDataSetValues = NULL; self->valueReferences = NULL; self->lastEntryId = 0; + self->resvTms = 0; self->server = iedServer; @@ -196,6 +214,8 @@ ReportControl_destroy(ReportControl* self) if (self->buffered == false) MmsValue_delete(self->timeOfEntry); + MmsValue_delete(self->subSeqVal); + deleteDataSetValuesShadowBuffer(self); if (self->isDynamicDataSet) { @@ -308,63 +328,238 @@ updateTimeOfEntry(ReportControl* self, uint64_t currentTime) MmsValue_setBinaryTime(timeOfEntry, currentTime); } -static void -sendReport(ReportControl* self, bool isIntegrity, bool isGI) +static DataSetEntry* +getDataSetEntryWithIndex(DataSetEntry* dataSet, int index) +{ + int i = 0; + + while (dataSet) { + if (i == index) + return dataSet; + + i++; + + dataSet = dataSet->sibling; + } + + return NULL; +} + +static bool +sendReportSegment(ReportControl* self, bool isIntegrity, bool isGI) { - updateTimeOfEntry(self, Hal_getTimeInMs()); + if (self->clientConnection == NULL) + return false; - LinkedList reportElements = LinkedList_create(); + int maxMmsPduSize = MmsServerConnection_getMaxMmsPduSize(self->clientConnection); + int estimatedSegmentSize = 19; /* maximum size of header information (header can have 13-19 byte) */ + estimatedSegmentSize += 8; /* reserve space for more-segments-follow (3 byte) and sub-seq-num (3-5 byte) */ - LinkedList deletableElements = LinkedList_create(); + bool segmented = self->segmented; + bool moreFollows = false; + + bool hasSeqNum = false; + bool hasReportTimestamp = false; + bool hasDataSetReference = false; + bool hasConfRev = false; + + uint32_t accessResultSize = 0; MmsValue* rptId = ReportControl_getRCBValue(self, "RptID"); MmsValue* optFlds = ReportControl_getRCBValue(self, "OptFlds"); MmsValue* datSet = ReportControl_getRCBValue(self, "DatSet"); - LinkedList_add(reportElements, rptId); - LinkedList_add(reportElements, optFlds); + MmsValue timeOfEntry; + timeOfEntry.type = MMS_BINARY_TIME; + timeOfEntry.value.binaryTime.size = 6; - /* delete option fields for unsupported options */ + accessResultSize += MmsValue_encodeMmsData(rptId, NULL, 0, false); + accessResultSize += 5; /* size of OptFlds */ + + /* delete option fields for unrelated options (not present in unbuffered report) */ MmsValue_setBitStringBit(optFlds, 6, false); /* bufOvfl */ MmsValue_setBitStringBit(optFlds, 7, false); /* entryID */ - MmsValue_setBitStringBit(optFlds, 9, false); /* segmentation */ MmsValue* sqNum = ReportControl_getRCBValue(self, "SqNum"); - if (MmsValue_getBitStringBit(optFlds, 1)) /* sequence number */ - LinkedList_add(reportElements, sqNum); + if (MmsValue_getBitStringBit(optFlds, 1)) { /* sequence number */ + hasSeqNum = true; + accessResultSize += MmsValue_encodeMmsData(sqNum, NULL, 0, false); + } + + if (MmsValue_getBitStringBit(optFlds, 2)) { /* report time stamp */ + hasReportTimestamp = true; + MmsValue_setBinaryTime(&timeOfEntry, self->segmentedReportTimestamp); + accessResultSize += MmsValue_encodeMmsData(&timeOfEntry, NULL, 0, false); + } + + if (MmsValue_getBitStringBit(optFlds, 4)) { /* data set reference */ + hasDataSetReference = true; + accessResultSize += MmsValue_encodeMmsData(datSet, NULL, 0, false); + } - if (MmsValue_getBitStringBit(optFlds, 2)) /* report time stamp */ - LinkedList_add(reportElements, self->timeOfEntry); + if (MmsValue_getBitStringBit(optFlds, 8)) { /* configuration revision */ + hasConfRev = true; + accessResultSize += MmsValue_encodeMmsData(self->confRev, NULL, 0, false); + } - if (MmsValue_getBitStringBit(optFlds, 4)) /* data set reference */ - LinkedList_add(reportElements, datSet); + MmsValue_deleteAllBitStringBits(self->inclusionField); + accessResultSize += MmsValue_encodeMmsData(self->inclusionField, NULL, 0, false); - if (MmsValue_getBitStringBit(optFlds, 8)) - LinkedList_add(reportElements, self->confRev); + /* here ends the base part that is equal for all sub reports and independent of the + * number of included data points + */ + estimatedSegmentSize += accessResultSize; - if (isGI || isIntegrity) - MmsValue_setAllBitStringBits(self->inclusionField); - else - MmsValue_deleteAllBitStringBits(self->inclusionField); + int startElementIndex = self->startIndexForNextSegment; /* get value from segmented report control info */ - LinkedList_add(reportElements, self->inclusionField); + bool withDataReference = MmsValue_getBitStringBit(optFlds, 5); + bool withReasonCode = MmsValue_getBitStringBit(optFlds, 3); - /* add data references if selected */ - if (MmsValue_getBitStringBit(optFlds, 5)) { /* data-reference */ - DataSetEntry* dataSetEntry = self->dataSet->fcdas; + LogicalDevice* ld = (LogicalDevice*) self->parentLN->parent; + + IedModel* iedModel = (IedModel*) ld->parent; + + int maxIndex = startElementIndex; + + char* iedName = iedModel->name; + int iedNameLength = strlen(iedName); + + int i; + + MmsValue _moreFollows; + _moreFollows.type = MMS_BOOLEAN; + _moreFollows.value.boolean = false; + + MmsValue* subSeqNum = self->subSeqVal; + + for (i = startElementIndex; i < self->dataSet->elementCount; i++) { + + DataSetEntry* dataSetEntry = getDataSetEntryWithIndex(self->dataSet->fcdas, i); + + bool includeElementInReport = false; + + if (isGI || isIntegrity) + includeElementInReport = true; + else if (self->inclusionFlags[i] != REPORT_CONTROL_NONE) + includeElementInReport = true; + + if (includeElementInReport) { + + int elementSize = 0; + + if (withDataReference) { + int currentPos = 0; + + currentPos += iedNameLength; + currentPos += (int) strlen(dataSetEntry->logicalDeviceName); + currentPos++; + currentPos += (int) strlen(dataSetEntry->variableName); + + elementSize += (1 + BerEncoder_determineLengthSize(currentPos) + currentPos); + } + + if (self->inclusionFlags[i] != REPORT_CONTROL_NONE) { + elementSize += MmsValue_encodeMmsData(self->bufferedDataSetValues[i], NULL, 0, false); + } + else { + elementSize += MmsValue_encodeMmsData(dataSetEntry->value, NULL, 0, false); + } + + if (withReasonCode) { + elementSize += 4; /* reason code size is always 4 byte */ + } + + if ((estimatedSegmentSize + elementSize) > maxMmsPduSize) { + + segmented = true; + moreFollows = true; + _moreFollows.value.boolean = true; + + if (startElementIndex == 0) + MmsValue_setUint32(subSeqNum, 0); + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: element doesn't fit into MMS PDU --> another report segment is required!\n"); + break; + } + + MmsValue_setBitStringBit(self->inclusionField, i, true); + + accessResultSize += elementSize; + estimatedSegmentSize += elementSize; + } + + maxIndex++; + } + + MmsValue_setBitStringBit(optFlds, 9, segmented); /* set segmentation flag */ + + /* now calculate the exact information report segment size */ + + if (segmented) { + int segmentedSize = MmsValue_encodeMmsData(&_moreFollows, NULL, 0, false) + MmsValue_encodeMmsData(subSeqNum, NULL, 0, false); + accessResultSize += segmentedSize; + } + + uint32_t variableAccessSpecSize = 7; /* T L "RPT" */ + uint32_t listOfAccessResultSize = accessResultSize + BerEncoder_determineLengthSize(accessResultSize) + 1; + uint32_t informationReportContentSize = variableAccessSpecSize + listOfAccessResultSize; + uint32_t informationReportSize = 1 + informationReportContentSize + BerEncoder_determineLengthSize(informationReportContentSize); + uint32_t completeMessageSize = 1 + informationReportSize + BerEncoder_determineLengthSize(informationReportSize); + + if ((int) completeMessageSize > maxMmsPduSize) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: report message too large %i (max = %i) -> skip message!\n", completeMessageSize, maxMmsPduSize); + + goto exit_function; + } + + /* encode the report message */ + + ByteBuffer* reportBuffer = MmsServer_reserveTransmitBuffer(self->server->mmsServer); + + uint8_t* buffer = reportBuffer->buffer; + int bufPos = 0; + + /* encode header */ + bufPos = BerEncoder_encodeTL(0xa3, informationReportSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0xa0, informationReportContentSize, buffer, bufPos); - LogicalDevice* ld = (LogicalDevice*) self->parentLN->parent; + bufPos = BerEncoder_encodeTL(0xa1, 5, buffer, bufPos); + bufPos = BerEncoder_encodeStringWithTag(0x80, "RPT", buffer, bufPos); - IedModel* iedModel = (IedModel*) ld->parent; + bufPos = BerEncoder_encodeTL(0xa0, accessResultSize, buffer, bufPos); - char* iedName = iedModel->name; + /* encode access-results */ - int iedNameLength = strlen(iedName); + bufPos = MmsValue_encodeMmsData(rptId, buffer, bufPos, true); + bufPos = MmsValue_encodeMmsData(optFlds, buffer, bufPos, true); - int i = 0; + if (hasSeqNum) + bufPos = MmsValue_encodeMmsData(sqNum, buffer, bufPos, true); - for (i = 0; i < self->dataSet->elementCount; i++) { + if (hasReportTimestamp) + bufPos = MmsValue_encodeMmsData(&timeOfEntry, buffer, bufPos, true); + + if (hasDataSetReference) + bufPos = MmsValue_encodeMmsData(datSet, buffer, bufPos, true); + + if (hasConfRev) + bufPos = MmsValue_encodeMmsData(self->confRev, buffer, bufPos, true); + + if (segmented) { + bufPos = MmsValue_encodeMmsData(subSeqNum, buffer, bufPos, true); + bufPos = MmsValue_encodeMmsData(&_moreFollows, buffer, bufPos, true); + } + + bufPos = MmsValue_encodeMmsData(self->inclusionField, buffer, bufPos, true); + + /* encode data references if selected */ + if (MmsValue_getBitStringBit(optFlds, 5)) { /* data-reference */ + DataSetEntry* dataSetEntry = getDataSetEntryWithIndex(self->dataSet->fcdas, startElementIndex); + + for (i = startElementIndex; i < maxIndex; i++) { assert(dataSetEntry->value != NULL); bool addReferenceForEntry = false; @@ -400,33 +595,31 @@ sendReport(ReportControl* self, bool isIntegrity, bool isGI) dataReference[currentPos] = 0; - MmsValue* dataRef = MmsValue_newVisibleString(dataReference); + MmsValue _dataRef; + _dataRef.type = MMS_VISIBLE_STRING; + _dataRef.value.visibleString.buf = dataReference; + _dataRef.value.visibleString.size = currentPos; - LinkedList_add(reportElements, dataRef); - LinkedList_add(deletableElements, dataRef); + bufPos = MmsValue_encodeMmsData(&_dataRef, buffer, bufPos, true); } dataSetEntry = dataSetEntry->sibling; - } } - /* add data set value elements */ - DataSetEntry* dataSetEntry = self->dataSet->fcdas; + /* encode data set value elements */ + DataSetEntry* dataSetEntry = getDataSetEntryWithIndex(self->dataSet->fcdas, startElementIndex); - int i = 0; - for (i = 0; i < self->dataSet->elementCount; i++) { - assert(dataSetEntry->value != NULL); + for (i = startElementIndex; i < maxIndex; i++) { if (isGI || isIntegrity) { - LinkedList_add(reportElements, dataSetEntry->value); + /* encode value from data set */ + bufPos = MmsValue_encodeMmsData(dataSetEntry->value, buffer, bufPos, true); } else { if (self->inclusionFlags[i] != REPORT_CONTROL_NONE) { - assert(self->bufferedDataSetValues[i] != NULL); - - LinkedList_add(reportElements, self->bufferedDataSetValues[i]); - MmsValue_setBitStringBit(self->inclusionField, i, true); + /* encode value from the event buffer */ + bufPos = MmsValue_encodeMmsData(self->bufferedDataSetValues[i], buffer, bufPos, true); } } @@ -434,58 +627,90 @@ sendReport(ReportControl* self, bool isIntegrity, bool isGI) } /* add reason code to report if requested */ - if (MmsValue_getBitStringBit(optFlds, 3)) { - for (i = 0; i < self->dataSet->elementCount; i++) { + if (withReasonCode) { + + uint8_t bsBuf[1]; + + MmsValue _reason; + _reason.type = MMS_BIT_STRING; + _reason.value.bitString.size = 6; + _reason.value.bitString.buf = bsBuf; + + for (i = startElementIndex; i < maxIndex; i++) { if (isGI || isIntegrity) { - MmsValue* reason = MmsValue_newBitString(6); + bsBuf[0] = 0; /* clear all bits */ if (isGI) - MmsValue_setBitStringBit(reason, 5, true); + MmsValue_setBitStringBit(&_reason, 5, true); if (isIntegrity) - MmsValue_setBitStringBit(reason, 4, true); + MmsValue_setBitStringBit(&_reason, 4, true); - LinkedList_add(reportElements, reason); - LinkedList_add(deletableElements, reason); + bufPos = MmsValue_encodeMmsData(&_reason, buffer, bufPos, true); } else if (self->inclusionFlags[i] != REPORT_CONTROL_NONE) { - MmsValue* reason = MmsValue_newBitString(6); + bsBuf[0] = 0; /* clear all bits */ if (self->inclusionFlags[i] == REPORT_CONTROL_QUALITY_CHANGED) - MmsValue_setBitStringBit(reason, 2, true); + MmsValue_setBitStringBit(&_reason, 2, true); else if (self->inclusionFlags[i] == REPORT_CONTROL_VALUE_CHANGED) - MmsValue_setBitStringBit(reason, 1, true); + MmsValue_setBitStringBit(&_reason, 1, true); else if (self->inclusionFlags[i] == REPORT_CONTROL_VALUE_UPDATE) - MmsValue_setBitStringBit(reason, 3, true); + MmsValue_setBitStringBit(&_reason, 3, true); - LinkedList_add(reportElements, reason); - LinkedList_add(deletableElements, reason); + bufPos = MmsValue_encodeMmsData(&_reason, buffer, bufPos, true); } } } /* clear inclusion flags */ - for (i = 0; i < self->dataSet->elementCount; i++) + for (i = startElementIndex; i < maxIndex; i++) self->inclusionFlags[i] = REPORT_CONTROL_NONE; - ReportControl_unlockNotify(self); + reportBuffer->size = bufPos; - MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", reportElements, false); + MmsServerConnection_sendMessage(self->clientConnection, reportBuffer, false); - ReportControl_lockNotify(self); + MmsServer_releaseTransmitBuffer(self->server->mmsServer); + + if (moreFollows == false) { + /* reset sub sequence number */ + segmented = false; + self->startIndexForNextSegment = 0; + } + else { + /* increase sub sequence number */ + uint32_t subSeqNumVal = MmsValue_toUint32(subSeqNum); + subSeqNumVal++; + MmsValue_setUint32(subSeqNum, subSeqNumVal); + + self->startIndexForNextSegment = maxIndex; + } + + if (segmented == false) { + /* Increase sequence number */ + self->sqNum++; - /* Increase sequence number */ - self->sqNum++; + /* Unbuffered reporting --> sqNum is 8 bit only!!! */ + if (self->sqNum == 256) + self->sqNum = 0; - /* Unbuffered reporting --> sqNum is 8 bit only!!! */ - if (self->sqNum == 256) - self->sqNum = 0; + MmsValue_setUint16(sqNum, self->sqNum); + } - MmsValue_setUint16(sqNum, self->sqNum); +exit_function: + self->segmented = segmented; + return moreFollows; +} + +static void +sendReport(ReportControl* self, bool isIntegrity, bool isGI, uint64_t currentTime) +{ + updateTimeOfEntry(self, currentTime); + self->segmentedReportTimestamp = currentTime; - LinkedList_destroyDeep(deletableElements, (LinkedListValueDeleteFunction) MmsValue_delete); - LinkedList_destroyStatic(reportElements); + while (sendReportSegment(self, isIntegrity, isGI)); } static void @@ -936,7 +1161,17 @@ createUnbufferedReportControlBlock(ReportControlBlock* reportControlBlock, namedVariable->type = MMS_OCTET_STRING; namedVariable->typeSpec.octetString = -64; rcb->typeSpec.structure.elements[11] = namedVariable; - mmsValue->value.structure.components[11] = MmsValue_newOctetString(0, 128); + mmsValue->value.structure.components[11] = MmsValue_newOctetString(0, 16); /* size 16 is enough to store client IPv6 address */ + + /* initialize pre configured owner */ + if (reportControlBlock->clientReservation[0] == 4) { + reportControl->resvTms = -1; + MmsValue_setOctetString(mmsValue->value.structure.components[11], reportControlBlock->clientReservation + 1, 4); + } + else if (reportControlBlock->clientReservation[0] == 6) { + reportControl->resvTms = -1; + MmsValue_setOctetString(mmsValue->value.structure.components[11], reportControlBlock->clientReservation + 1, 16); + } } reportControl->rcbValues = mmsValue; @@ -1107,7 +1342,21 @@ createBufferedReportControlBlock(ReportControlBlock* reportControlBlock, namedVariable->type = MMS_OCTET_STRING; namedVariable->typeSpec.octetString = -64; rcb->typeSpec.structure.elements[currentIndex] = namedVariable; - mmsValue->value.structure.components[currentIndex] = MmsValue_newOctetString(0, 128); /* size 4 is enough to store client IPv4 address */ + mmsValue->value.structure.components[currentIndex] = MmsValue_newOctetString(0, 16); /* size 16 is enough to store client IPv6 address */ + + /* initialize pre configured owner */ + if (reportControlBlock->clientReservation[0] == 4) { + reportControl->resvTms = -1; + MmsValue_setOctetString(mmsValue->value.structure.components[currentIndex], reportControlBlock->clientReservation + 1, 4); + } + else if (reportControlBlock->clientReservation[0] == 6) { + reportControl->resvTms = -1; + MmsValue_setOctetString(mmsValue->value.structure.components[currentIndex], reportControlBlock->clientReservation + 1, 16); + } + +#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) + MmsValue_setInt16(mmsValue->value.structure.components[13], reportControl->resvTms); +#endif } reportControl->rcbValues = mmsValue; @@ -1220,6 +1469,32 @@ Reporting_createMmsUnbufferedRCBs(MmsMapping* self, MmsDomain* domain, return namedVariable; } +static bool +convertIPv4AddressStringToByteArray(const char* clientAddressString, uint8_t ipV4Addr[]) +{ + int addrElementCount = 0; + + char* separator = (char*) clientAddressString; + + while (separator != NULL && addrElementCount < 4) { + int intVal = atoi(separator); + + ipV4Addr[addrElementCount] = intVal; + + separator = strchr(separator, '.'); + + if (separator != NULL) + separator++; /* skip '.' character */ + + addrElementCount ++; + } + + if (addrElementCount == 4) + return true; + else + return false; +} + static void updateOwner(ReportControl* rc, MmsServerConnection connection) { @@ -1238,28 +1513,13 @@ updateOwner(ReportControl* rc, MmsServerConnection connection) if (strchr(clientAddressString, '.') != NULL) { if (DEBUG_IED_SERVER) - printf("IED_SERVER: reporting.c: client address is IPv4 address\n"); + printf("IED_SERVER: reporting.c: client address is IPv4 address\n"); uint8_t ipV4Addr[4]; - int addrElementCount = 0; - - char* separator = clientAddressString; - - while (separator != NULL && addrElementCount < 4) { - int intVal = atoi(separator); - - ipV4Addr[addrElementCount] = intVal; - - separator = strchr(separator, '.'); - - if (separator != NULL) - separator++; /* skip '.' character */ + bool valid = convertIPv4AddressStringToByteArray(clientAddressString, ipV4Addr); - addrElementCount ++; - } - - if (addrElementCount == 4) + if (valid) MmsValue_setOctetString(owner, ipV4Addr, 4); else MmsValue_setOctetString(owner, ipV4Addr, 0); @@ -1267,8 +1527,21 @@ updateOwner(ReportControl* rc, MmsServerConnection connection) } else { uint8_t ipV6Addr[16]; - MmsValue_setOctetString(owner, ipV6Addr, 0); - if (DEBUG_IED_SERVER) printf("IED_SERVER: reporting.c: client address is IPv6 address or unknown\n"); + + bool valid = StringUtils_convertIPv6AdddressStringToByteArray(clientAddressString, ipV6Addr); + + if (valid) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: reporting.c: client address is IPv6 address\n"); + + MmsValue_setOctetString(owner, ipV6Addr, 16); + } + else { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: reporting.c: not a valid IPv6 address\n"); + + MmsValue_setOctetString(owner, ipV6Addr, 0); + } } } else { @@ -1276,11 +1549,9 @@ updateOwner(ReportControl* rc, MmsServerConnection connection) MmsValue_setOctetString(owner, emptyAddr, 0); } } - } } - static bool checkForZeroEntryID(MmsValue* value) { @@ -1335,6 +1606,83 @@ increaseConfRev(ReportControl* self) MmsValue_setUint32(self->confRev, confRev); } +static void +checkReservationTimeout(ReportControl* rc) +{ + if (rc->enabled == false) { + if (rc->resvTms > 0) { + if (Hal_getTimeInMs() > rc->reservationTimeout) { + rc->resvTms = 0; + +#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) + MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); + if (resvTmsVal) + MmsValue_setInt16(resvTmsVal, rc->resvTms); +#endif + + rc->reservationTimeout = 0; + updateOwner(rc, NULL); + rc->reserved = false; + } + } + } +} + +void +ReportControl_readAccess(ReportControl* rc, char* elementName) +{ + /* check reservation timeout */ + if (rc->buffered) { + checkReservationTimeout(rc); + } +} + +static bool +isIpAddressMatchingWithOwner(ReportControl* rc, const char* ipAddress) +{ + MmsValue* owner = ReportControl_getRCBValue(rc, "Owner"); + + if (owner != NULL) { + + if (strchr(ipAddress, '.') != NULL) { + uint8_t ipV4Addr[4]; + + if (convertIPv4AddressStringToByteArray(ipAddress, ipV4Addr)) { + if (memcmp(ipV4Addr, MmsValue_getOctetStringBuffer(owner), 4) == 0) + return true; + } + } + else { + uint8_t ipV6Addr[16]; + + if (StringUtils_convertIPv6AdddressStringToByteArray(ipAddress, ipV6Addr)) { + if (memcmp(ipV6Addr, MmsValue_getOctetStringBuffer(owner), 16) == 0) + return true; + } + else + return false; + } + } + + return false; +} + +static void +reserveRcb(ReportControl* rc, MmsServerConnection connection) +{ + rc->reserved = true; + rc->clientConnection = connection; + +#if (CONFIG_IEC61850_BRCB_WITH_RESVTMS == 1) + MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); + if (resvTmsVal) + MmsValue_setInt16(resvTmsVal, rc->resvTms); +#endif + + rc->reservationTimeout = Hal_getTimeInMs() + (RESV_TMS_IMPLICIT_VALUE * 1000); + updateOwner(rc, connection); +} + MmsDataAccessError Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* elementName, MmsValue* value, MmsServerConnection connection) @@ -1343,6 +1691,53 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme ReportControl_lockNotify(rc); + bool resvTmsAccess = false; + + /* check reservation timeout for buffered RCBs */ + if (rc->buffered) { + + checkReservationTimeout(rc); + + if (rc->resvTms == 0) { + /* nothing to to */ + } + else if (rc->resvTms == -1) { + + if (rc->reserved == false) { + +#if (CONFIG_IEC61850_RCB_ALLOW_ONLY_PRECONFIGURED_CLIENT == 1) + if (isIpAddressMatchingWithOwner(rc, MmsServerConnection_getClientAddress(connection))) { + rc->reserved = true; + rc->clientConnection = connection; + } +#else + rc->reserved = true; + rc->clientConnection = connection; +#endif + } + } + else if (rc->resvTms > 0) { + if (rc->reserved == false) { + + if (isIpAddressMatchingWithOwner(rc, MmsServerConnection_getClientAddress(connection))) { + rc->reserved = true; + rc->clientConnection = connection; + rc->reservationTimeout = Hal_getTimeInMs() + (rc->resvTms * 1000); + } + else { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: client IP not matching with owner\n"); + } + + } + else { + if (rc->clientConnection == connection) { + rc->reservationTimeout = Hal_getTimeInMs() + (rc->resvTms * 1000); + } + } + } + } + if (strcmp(elementName, "RptEna") == 0) { if (value->value.boolean == true) { @@ -1358,7 +1753,8 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (updateReportDataset(self, rc, NULL, connection)) { - updateOwner(rc, connection); + if (rc->resvTms != -1) + updateOwner(rc, connection); MmsValue* rptEna = ReportControl_getRCBValue(rc, "RptEna"); @@ -1422,9 +1818,10 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme rc->triggered = false; rc->reserved = false; - } - updateOwner(rc, NULL); + if (rc->resvTms != -1) + updateOwner(rc, NULL); + } rc->enabled = false; } @@ -1581,6 +1978,44 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme goto exit_function; } + else if (strcmp(elementName, "ResvTms") == 0) { + if (rc->buffered) { + + resvTmsAccess = true; + + if (rc->resvTms != -1) { + + int resvTms = MmsValue_toInt32(value); + + if (resvTms >= 0) { + rc->resvTms = resvTms; + + if (rc->resvTms == 0) { + rc->reservationTimeout = 0; + rc->reserved = false; + updateOwner(rc, NULL); + } + else { + rc->reservationTimeout = Hal_getTimeInMs() + (rc->resvTms * 1000); + + reserveRcb(rc, connection); + } + + MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); + + if (resvTmsVal != NULL) + MmsValue_update(resvTmsVal, value); + } + else + retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + } + else { + retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + } + + goto exit_function; + } + } else if (strcmp(elementName, "ConfRev") == 0) { retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; goto exit_function; @@ -1608,10 +2043,21 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } } - else + else { retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } exit_function: + + /* every successful write access reserves the RCB */ + if ((rc->buffered) && (retVal == DATA_ACCESS_ERROR_SUCCESS) && (resvTmsAccess == false)) { + if (rc->resvTms == 0) { + rc->resvTms = RESV_TMS_IMPLICIT_VALUE; + + reserveRcb(rc, connection); + } + } + ReportControl_unlockNotify(rc); return retVal; @@ -1633,15 +2079,23 @@ Reporting_deactivateReportsForConnection(MmsMapping* self, MmsServerConnection c MmsValue* rptEna = ReportControl_getRCBValue(rc, "RptEna"); MmsValue_setBoolean(rptEna, false); + rc->reserved = false; + if (rc->buffered == false) { MmsValue* resv = ReportControl_getRCBValue(rc, "Resv"); MmsValue_setBoolean(resv, false); - rc->reserved = false; + if (rc->resvTms != -1) + updateOwner(rc, NULL); + } + else { + if (rc->resvTms == 0) + updateOwner(rc, NULL); + else if (rc->resvTms > 0) { + rc->reservationTimeout = Hal_getTimeInMs() + (rc->resvTms * 1000); + } } - - updateOwner(rc, NULL); } } } @@ -1713,7 +2167,6 @@ removeAllGIReportsFromReportBuffer(ReportBuffer* reportBuffer) } else { lastReport->next = currentReport->next; - } #if (DEBUG_IED_SERVER == 1) @@ -1725,12 +2178,18 @@ removeAllGIReportsFromReportBuffer(ReportBuffer* reportBuffer) if (reportBuffer->nextToTransmit == currentReport) reportBuffer->nextToTransmit = currentReport->next; - currentReport = currentReport->next; + if (reportBuffer->lastEnqueuedReport == currentReport) { + if (lastReport != NULL) + reportBuffer->lastEnqueuedReport = lastReport; + else + reportBuffer->lastEnqueuedReport = reportBuffer->oldestReport; + } } else { lastReport = currentReport; - currentReport = currentReport->next; } + + currentReport = currentReport->next; } if (reportBuffer->oldestReport == NULL) @@ -1747,8 +2206,12 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ updateTimeOfEntry(reportControl, Hal_getTimeInMs()); + int inclusionBitStringSize = MmsValue_getBitStringSize(reportControl->inclusionField); + ReportBuffer* buffer = reportControl->reportBuffer; + Semaphore_wait(buffer->lock); + /* calculate size of complete buffer entry */ int bufferEntrySize = MemoryAllocator_getAlignedSize(sizeof(ReportBufferEntry)); @@ -1757,41 +2220,54 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ MmsValue inclusionFieldStatic; inclusionFieldStatic.type = MMS_BIT_STRING; - inclusionFieldStatic.value.bitString.size = MmsValue_getBitStringSize(reportControl->inclusionField); + inclusionFieldStatic.value.bitString.size = inclusionBitStringSize; MmsValue* inclusionField = &inclusionFieldStatic; + int dataBlockSize = 0; + if (isIntegrity || isGI) { DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas; int i; - for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) { + for (i = 0; i < inclusionBitStringSize; i++) { assert(dataSetEntry != NULL); - bufferEntrySize += MemoryAllocator_getAlignedSize(1); /* reason-for-inclusion */ + /* don't need reason for inclusion in GI or integrity report */ - bufferEntrySize += MmsValue_getSizeInMemory(dataSetEntry->value); + int encodedSize = MmsValue_encodeMmsData(dataSetEntry->value, NULL, 0, false); + + dataBlockSize += encodedSize; dataSetEntry = dataSetEntry->sibling; } + + bufferEntrySize += MemoryAllocator_getAlignedSize(sizeof(int) + dataBlockSize); /* add aligned_size(LEN + DATA) */ } else { /* other trigger reason */ bufferEntrySize += inclusionFieldSize; + int reasonForInclusionSize = 0; + int i; - for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) { + for (i = 0; i < inclusionBitStringSize; i++) { if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) { - bufferEntrySize += MemoryAllocator_getAlignedSize(1); /* reason-for-inclusion */ + + reasonForInclusionSize++; assert(reportControl->bufferedDataSetValues[i] != NULL); - bufferEntrySize += MmsValue_getSizeInMemory(reportControl->bufferedDataSetValues[i]); + int encodedSize = MmsValue_encodeMmsData(reportControl->bufferedDataSetValues[i], NULL, 0, false); + + dataBlockSize += encodedSize; } } + + bufferEntrySize += MemoryAllocator_getAlignedSize(sizeof(int) + dataBlockSize + reasonForInclusionSize); /* add aligned_size (LEN + DATA + REASON) */ } if (DEBUG_IED_SERVER) @@ -2008,49 +2484,64 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ else entry->flags = 0; - entry->entryLength = MemoryAllocator_getAlignedSize(bufferEntrySize); + entry->entryLength = bufferEntrySize; entryBufPos += MemoryAllocator_getAlignedSize(sizeof(ReportBufferEntry)); if (isIntegrity || isGI) { DataSetEntry* dataSetEntry = reportControl->dataSet->fcdas; + /* encode LEN */ + memcpy(entryBufPos, (uint8_t*)(&dataBlockSize), sizeof(int)); + entryBufPos += sizeof(int); + + /* encode DATA */ int i; - for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) { + for (i = 0; i < inclusionBitStringSize; i++) { assert(dataSetEntry != NULL); - *entryBufPos = (uint8_t) reportControl->inclusionFlags[i]; - entryBufPos += MemoryAllocator_getAlignedSize(1); - - entryBufPos = MmsValue_cloneToBuffer(dataSetEntry->value, entryBufPos); + entryBufPos += MmsValue_encodeMmsData(dataSetEntry->value, entryBufPos, 0, true); dataSetEntry = dataSetEntry->sibling; } } else { + /* encode inclusion bit string */ inclusionFieldStatic.value.bitString.buf = entryBufPos; memset(entryBufPos, 0, inclusionFieldSize); entryBufPos += inclusionFieldSize; + /* encode LEN */ + memcpy(entryBufPos, (uint8_t*)(&dataBlockSize), sizeof(int)); + entryBufPos += sizeof(int); + + /* encode DATA */ int i; - for (i = 0; i < MmsValue_getBitStringSize(reportControl->inclusionField); i++) { + for (i = 0; i < inclusionBitStringSize; i++) { if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) { + /* update inclusion bit string for report entry */ + MmsValue_setBitStringBit(inclusionField, i, true); + assert(reportControl->bufferedDataSetValues[i] != NULL); - *entryBufPos = (uint8_t) reportControl->inclusionFlags[i]; - entryBufPos += MemoryAllocator_getAlignedSize(1); + entryBufPos += MmsValue_encodeMmsData(reportControl->bufferedDataSetValues[i], entryBufPos, 0, true); + } - entryBufPos = MmsValue_cloneToBuffer(reportControl->bufferedDataSetValues[i], entryBufPos); + } - MmsValue_setBitStringBit(inclusionField, i, true); - } + /* encode REASON */ + for (i = 0; i < inclusionBitStringSize; i++) { + if (reportControl->inclusionFlags[i] != REPORT_CONTROL_NONE) { + *entryBufPos = (uint8_t) reportControl->inclusionFlags[i]; + entryBufPos ++; + } } } @@ -2073,26 +2564,41 @@ enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_ reportControl->lastEntryId = entryId; exit_function: + + Semaphore_post(buffer->lock); + return; } /* enqueuReport() */ -static void -sendNextReportEntry(ReportControl* self) +static bool +sendNextReportEntrySegment(ReportControl* self) { -#define LOCAL_STORAGE_MEMORY_SIZE 65536 + if (self->clientConnection == NULL) + return false; - if (self->reportBuffer->nextToTransmit == NULL) - return; + int maxMmsPduSize = MmsServerConnection_getMaxMmsPduSize(self->clientConnection); - char* localStorage = (char*) GLOBAL_MALLOC(LOCAL_STORAGE_MEMORY_SIZE); /* reserve 64k for dynamic memory allocation - - this can be optimized - maybe there is a good guess for the - required memory size */ + Semaphore_wait(self->reportBuffer->lock); - if (localStorage == NULL) /* out-of-memory */ - goto return_out_of_memory; + if (self->reportBuffer->nextToTransmit == NULL) { + Semaphore_post(self->reportBuffer->lock); + return false; + } + + int estimatedSegmentSize = 19; /* maximum size of header information (header can have 13-19 byte) */ + estimatedSegmentSize += 8; /* reserve space for more-segments-follow (3 byte) and sub-seq-num (3-5 byte) */ + + bool segmented = self->segmented; + bool moreFollows = false; + + bool hasSeqNum = false; + bool hasReportTimestamp = false; + bool hasDataSetReference = false; + bool hasBufOvfl = false; + bool hasEntryId = false; + bool hasConfRev = false; - MemoryAllocator ma; - MemoryAllocator_init(&ma, localStorage, LOCAL_STORAGE_MEMORY_SIZE); + uint32_t accessResultSize = 0; ReportBufferEntry* report = self->reportBuffer->nextToTransmit; @@ -2105,329 +2611,473 @@ sendNextReportEntry(ReportControl* self) MmsValue* entryIdValue = MmsValue_getElement(self->rcbValues, 11); MmsValue_setOctetString(entryIdValue, (uint8_t*) report->entryId, 8); - MemAllocLinkedList reportElements = MemAllocLinkedList_create(&ma); - - assert(reportElements != NULL); - - if (reportElements == NULL) - goto return_out_of_memory; - MmsValue* rptId = ReportControl_getRCBValue(self, "RptID"); MmsValue* optFlds = ReportControl_getRCBValue(self, "OptFlds"); - if (MemAllocLinkedList_add(reportElements, rptId) == NULL) - goto return_out_of_memory; - - if (MemAllocLinkedList_add(reportElements, optFlds) == NULL) - goto return_out_of_memory; + accessResultSize += MmsValue_encodeMmsData(rptId, NULL, 0, false); + accessResultSize += 5; /* add size of OptFlds */ MmsValue inclusionFieldStack; - inclusionFieldStack.type = MMS_BIT_STRING; - inclusionFieldStack.value.bitString.size = MmsValue_getBitStringSize(self->inclusionField); - uint8_t* currentReportBufferPos = (uint8_t*) report + sizeof(ReportBufferEntry); - inclusionFieldStack.value.bitString.buf = currentReportBufferPos; + MmsValue* inclusionField = NULL; - MmsValue* inclusionField = &inclusionFieldStack; + if (report->flags == 0) { - if (report->flags == 0) - currentReportBufferPos += MemoryAllocator_getAlignedSize(MmsValue_getBitStringByteSize(inclusionField)); - else { - inclusionFieldStack.value.bitString.buf = - (uint8_t*) MemoryAllocator_allocate(&ma, MmsValue_getBitStringByteSize(inclusionField)); + inclusionField = &inclusionFieldStack; - if (inclusionFieldStack.value.bitString.buf == NULL) - goto return_out_of_memory; + inclusionFieldStack.type = MMS_BIT_STRING; + inclusionFieldStack.value.bitString.size = MmsValue_getBitStringSize(self->inclusionField); + inclusionFieldStack.value.bitString.buf = currentReportBufferPos; + + currentReportBufferPos += MemoryAllocator_getAlignedSize(MmsValue_getBitStringByteSize(inclusionField)); } + MmsValue_deleteAllBitStringBits(self->inclusionField); + + int dataLen; + + /* get LEN (length of encoded data) from report buffer */ + memcpy((uint8_t*)(&dataLen), currentReportBufferPos, sizeof(int)); + currentReportBufferPos += sizeof(int); + uint8_t* valuesInReportBuffer = currentReportBufferPos; MmsValue_setBitStringBit(optFlds, 9, false); /* segmentation */ MmsValue* sqNum = ReportControl_getRCBValue(self, "SqNum"); - if (MmsValue_getBitStringBit(optFlds, 1)) /* sequence number */ - if (MemAllocLinkedList_add(reportElements, sqNum) == NULL) - goto return_out_of_memory; - - if (MmsValue_getBitStringBit(optFlds, 2)) { /* report time stamp */ - MmsValue* timeOfEntry = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue)); + if (MmsValue_getBitStringBit(optFlds, 1)) { /* sequence number */ + hasSeqNum = true; + accessResultSize += MmsValue_encodeMmsData(sqNum, NULL, 0, false); + } - if (timeOfEntry == NULL) goto return_out_of_memory; + MmsValue _timeOfEntry; + MmsValue* timeOfEntry = NULL; - timeOfEntry->deleteValue = 0; - timeOfEntry->type = MMS_BINARY_TIME; - timeOfEntry->value.binaryTime.size = 6; + if (MmsValue_getBitStringBit(optFlds, 2)) { /* report time stamp */ + hasReportTimestamp = true; + _timeOfEntry.type = MMS_BINARY_TIME; + _timeOfEntry.value.binaryTime.size = 6; + timeOfEntry = &_timeOfEntry; MmsValue_setBinaryTime(timeOfEntry, report->timeOfEntry); - if (MemAllocLinkedList_add(reportElements, timeOfEntry) == NULL) - goto return_out_of_memory; + accessResultSize += MmsValue_encodeMmsData(timeOfEntry, NULL, 0, false); } + MmsValue* datSet = ReportControl_getRCBValue(self, "DatSet"); + if (MmsValue_getBitStringBit(optFlds, 4)) {/* data set reference */ - MmsValue* datSet = ReportControl_getRCBValue(self, "DatSet"); - if (MemAllocLinkedList_add(reportElements, datSet) == NULL) - goto return_out_of_memory; + hasDataSetReference = true; + accessResultSize += MmsValue_encodeMmsData(datSet, NULL, 0, false); } - if (MmsValue_getBitStringBit(optFlds, 6)) { /* bufOvfl */ + MmsValue _bufOvfl; + MmsValue* bufOvfl = NULL; - MmsValue* bufOvfl = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue)); + if (MmsValue_getBitStringBit(optFlds, 6)) { /* bufOvfl */ + hasBufOvfl = true; - if (bufOvfl == NULL) goto return_out_of_memory; + bufOvfl = &_bufOvfl; - bufOvfl->deleteValue = 0; - bufOvfl->type = MMS_BOOLEAN; - bufOvfl->value.boolean = self->reportBuffer->isOverflow; + _bufOvfl.type = MMS_BOOLEAN; + _bufOvfl.value.boolean = self->reportBuffer->isOverflow; - if (self->reportBuffer->isOverflow) - self->reportBuffer->isOverflow = false; - - if (MemAllocLinkedList_add(reportElements, bufOvfl) == NULL) - goto return_out_of_memory; + accessResultSize += MmsValue_encodeMmsData(bufOvfl, NULL, 0, false); } - if (MmsValue_getBitStringBit(optFlds, 7)) { /* entryID */ - MmsValue* entryId = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue)); + MmsValue _entryId; + MmsValue* entryId = NULL; - if (entryId == NULL) goto return_out_of_memory; + if (MmsValue_getBitStringBit(optFlds, 7)) { /* entryID */ + hasEntryId = true; + entryId = &_entryId; - entryId->deleteValue = 0; entryId->type = MMS_OCTET_STRING; entryId->value.octetString.buf = report->entryId; entryId->value.octetString.size = 8; entryId->value.octetString.maxSize = 8; - if (MemAllocLinkedList_add(reportElements, entryId) == NULL) - goto return_out_of_memory; + accessResultSize += MmsValue_encodeMmsData(entryId, NULL, 0, false); + } + + if (MmsValue_getBitStringBit(optFlds, 8)) { + hasConfRev = true; + accessResultSize += MmsValue_encodeMmsData(self->confRev, NULL, 0, false); } - if (MmsValue_getBitStringBit(optFlds, 8)) - if (MemAllocLinkedList_add(reportElements, self->confRev) == NULL) - goto return_out_of_memory; + accessResultSize += MmsValue_encodeMmsData(self->inclusionField, NULL, 0, false); - if (report->flags > 0) - MmsValue_setAllBitStringBits(inclusionField); + /* here ends the base part that is equal for all sub reports and independent of the + * number of included data points + */ - if (MemAllocLinkedList_add(reportElements, inclusionField) == NULL) - goto return_out_of_memory; + estimatedSegmentSize += accessResultSize; + int startElementIndex = self->startIndexForNextSegment; /* get value from segmented report control info */ - /* add data references if selected */ - if (MmsValue_getBitStringBit(optFlds, 5)) { /* data-reference */ - DataSetEntry* dataSetEntry = self->dataSet->fcdas; + bool withDataReference = MmsValue_getBitStringBit(optFlds, 5); + bool withReasonCode = MmsValue_getBitStringBit(optFlds, 3); + + LogicalDevice* ld = (LogicalDevice*) self->parentLN->parent; + + IedModel* iedModel = (IedModel*) ld->parent; + + int maxIndex = startElementIndex; - LogicalDevice* ld = (LogicalDevice*) self->parentLN->parent; + char* iedName = iedModel->name; + int iedNameLength = strlen(iedName); - IedModel* iedModel = (IedModel*) ld->parent; + int i; + + MmsValue _moreFollows; + _moreFollows.type = MMS_BOOLEAN; + _moreFollows.value.boolean = false; - char* iedName = iedModel->name; + MmsValue* subSeqNum = self->subSeqVal; - int iedNameLength = strlen(iedName); + for (i = startElementIndex; i < self->dataSet->elementCount; i++) { - int i = 0; + DataSetEntry* dataSetEntry = getDataSetEntryWithIndex(self->dataSet->fcdas, i); - for (i = 0; i < self->dataSet->elementCount; i++) { - assert(dataSetEntry->value != NULL); + if ((report->flags > 0) || MmsValue_getBitStringBit(inclusionField, i)) { - bool addReferenceForEntry = false; + int elementSize = 0; - if (report->flags > 0) - addReferenceForEntry = true; - else - if (MmsValue_getBitStringBit(inclusionField, i)) - addReferenceForEntry = true; + if (withDataReference) { - if (addReferenceForEntry) { + char dataReference[130]; + int currentPos = 0; - int ldNameLength = strlen(dataSetEntry->logicalDeviceName); - int variableNameLength = strlen(dataSetEntry->variableName); + int j; + + for (j = 0; j < iedNameLength; j++) { + dataReference[currentPos++] = iedName[j]; + } - int refLen = iedNameLength - + ldNameLength - + variableNameLength + 1; + int ldNameLength = strlen(dataSetEntry->logicalDeviceName); + for (j = 0; j < ldNameLength; j++) { + dataReference[currentPos] = dataSetEntry->logicalDeviceName[j]; + currentPos++; + } - char* dataReference = (char*) MemoryAllocator_allocate(&ma, refLen + 1); + dataReference[currentPos++] = '/'; - if (dataReference == NULL) goto return_out_of_memory; + for (j = 0; j < (int) strlen(dataSetEntry->variableName); j++) { + dataReference[currentPos++] = dataSetEntry->variableName[j]; + } - int currentPos = 0; + dataReference[currentPos] = 0; - int j; + MmsValue _dataRef; + _dataRef.type = MMS_VISIBLE_STRING; + _dataRef.value.visibleString.buf = dataReference; + _dataRef.value.visibleString.size = currentPos; - for (j = 0; j < iedNameLength; j++) { - dataReference[currentPos++] = iedName[j]; - } + elementSize += MmsValue_encodeMmsData(&_dataRef, NULL, 0, false); + } - for (j = 0; j < ldNameLength; j++) { - dataReference[currentPos] = dataSetEntry->logicalDeviceName[j]; - currentPos++; - } + /* get size of data */ + if ((report->flags > 0) || MmsValue_getBitStringBit(inclusionField, i)) { + int length; - dataReference[currentPos++] = '/'; + int lenSize = BerDecoder_decodeLength(currentReportBufferPos + 1, &length, 0, report->entryLength); - for (j = 0; j < variableNameLength; j++) { - dataReference[currentPos++] = dataSetEntry->variableName[j]; - } + int dataElementSize = 1 + lenSize + length; - dataReference[currentPos] = 0; + elementSize += dataElementSize; + currentReportBufferPos += dataElementSize; + } - MmsValue* dataRef = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue)); + if (withReasonCode) { + elementSize += 4; /* reason code size is always 4 byte */ + } - if (dataRef == NULL) goto return_out_of_memory; - dataRef->deleteValue = 0; - dataRef->type = MMS_VISIBLE_STRING; - dataRef->value.visibleString.buf = dataReference; - dataRef->value.visibleString.size = refLen; + if ((estimatedSegmentSize + elementSize) > maxMmsPduSize) { + segmented = true; + moreFollows = true; + _moreFollows.value.boolean = true; - if (MemAllocLinkedList_add(reportElements, dataRef) == NULL) - goto return_out_of_memory; + if (startElementIndex == 0) + MmsValue_setUint32(subSeqNum, 0); - } + break; + } - dataSetEntry = dataSetEntry->sibling; + MmsValue_setBitStringBit(self->inclusionField, i, true); + accessResultSize += elementSize; + estimatedSegmentSize += elementSize; } + + maxIndex++; } - /* add data set value elements */ - int i = 0; - for (i = 0; i < self->dataSet->elementCount; i++) { + MmsValue_setBitStringBit(optFlds, 9, segmented); /* set segmentation flag */ - if (report->flags > 0) { - currentReportBufferPos += MemoryAllocator_getAlignedSize(1);; - if (MemAllocLinkedList_add(reportElements, currentReportBufferPos) == NULL) - goto return_out_of_memory; + /* now calculate the exact information report segment size */ - currentReportBufferPos += MmsValue_getSizeInMemory((MmsValue*) currentReportBufferPos); - } - else { - if (MmsValue_getBitStringBit(inclusionField, i)) { - currentReportBufferPos += MemoryAllocator_getAlignedSize(1);; - if (MemAllocLinkedList_add(reportElements, currentReportBufferPos) == NULL) - goto return_out_of_memory; - currentReportBufferPos += MmsValue_getSizeInMemory((MmsValue*) currentReportBufferPos); + if (segmented) { + int segmentedSize = MmsValue_encodeMmsData(&_moreFollows, NULL, 0, false) + MmsValue_encodeMmsData(subSeqNum, NULL, 0, false); + accessResultSize += segmentedSize; + } + + uint32_t variableAccessSpecSize = 7; /* T L "RPT" */ + uint32_t listOfAccessResultSize = accessResultSize + BerEncoder_determineLengthSize(accessResultSize) + 1; + uint32_t informationReportContentSize = variableAccessSpecSize + listOfAccessResultSize; + uint32_t informationReportSize = 1 + informationReportContentSize + BerEncoder_determineLengthSize(informationReportContentSize); + uint32_t completeMessageSize = 1 + informationReportSize + BerEncoder_determineLengthSize(informationReportSize); + + if ((int) completeMessageSize > maxMmsPduSize) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: report message too large %i (max = %i) -> skip message!\n", completeMessageSize, maxMmsPduSize); + + goto exit_function; + } + + /* encode the report message */ + + ReportControl_unlockNotify(self); + + ByteBuffer* reportBuffer = MmsServer_reserveTransmitBuffer(self->server->mmsServer); + + uint8_t* buffer = reportBuffer->buffer; + int bufPos = 0; + + /* encode header */ + bufPos = BerEncoder_encodeTL(0xa3, informationReportSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0xa0, informationReportContentSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, 5, buffer, bufPos); + bufPos = BerEncoder_encodeStringWithTag(0x80, "RPT", buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa0, accessResultSize, buffer, bufPos); + + /* encode access-results */ + + bufPos = MmsValue_encodeMmsData(rptId, buffer, bufPos, true); + bufPos = MmsValue_encodeMmsData(optFlds, buffer, bufPos, true); + + if (hasSeqNum) + bufPos = MmsValue_encodeMmsData(sqNum, buffer, bufPos, true); + + if (hasReportTimestamp) + bufPos = MmsValue_encodeMmsData(timeOfEntry, buffer, bufPos, true); + + if (hasDataSetReference) + bufPos = MmsValue_encodeMmsData(datSet, buffer, bufPos, true); + + if (hasBufOvfl) + bufPos = MmsValue_encodeMmsData(bufOvfl, buffer, bufPos, true); + + if (hasEntryId) + bufPos = MmsValue_encodeMmsData(entryId, buffer, bufPos, true); + + if (hasConfRev) + bufPos = MmsValue_encodeMmsData(self->confRev, buffer, bufPos, true); + + if (segmented) { + bufPos = MmsValue_encodeMmsData(subSeqNum, buffer, bufPos, true); + bufPos = MmsValue_encodeMmsData(&_moreFollows, buffer, bufPos, true); + } + + bufPos = MmsValue_encodeMmsData(self->inclusionField, buffer, bufPos, true); + + /* encode data references if selected */ + if (MmsValue_getBitStringBit(optFlds, 5)) { /* data-reference */ + DataSetEntry* dataSetEntry = getDataSetEntryWithIndex(self->dataSet->fcdas, startElementIndex); + + for (i = startElementIndex; i < maxIndex; i++) { + assert(dataSetEntry->value != NULL); + + bool addReferenceForEntry = false; + + if (report->flags > 0) + addReferenceForEntry = true; + else if (MmsValue_getBitStringBit(self->inclusionField, i)) + addReferenceForEntry = true; + + if (addReferenceForEntry) { + + char dataReference[130]; + int currentPos = 0; + + int j; + + for (j = 0; j < iedNameLength; j++) { + dataReference[currentPos++] = iedName[j]; + } + + int ldNameLength = strlen(dataSetEntry->logicalDeviceName); + for (j = 0; j < ldNameLength; j++) { + dataReference[currentPos] = dataSetEntry->logicalDeviceName[j]; + currentPos++; + } + + dataReference[currentPos++] = '/'; + + for (j = 0; j < (int) strlen(dataSetEntry->variableName); j++) { + dataReference[currentPos++] = dataSetEntry->variableName[j]; + } + + dataReference[currentPos] = 0; + + MmsValue _dataRef; + _dataRef.type = MMS_VISIBLE_STRING; + _dataRef.value.visibleString.buf = dataReference; + _dataRef.value.visibleString.size = currentPos; + + bufPos = MmsValue_encodeMmsData(&_dataRef, buffer, bufPos, true); } + + dataSetEntry = dataSetEntry->sibling; } } - /* add reason code to report if requested */ - if (MmsValue_getBitStringBit(optFlds, 3)) { - currentReportBufferPos = valuesInReportBuffer; + /* move to start position in report buffer */ + currentReportBufferPos = valuesInReportBuffer; - for (i = 0; i < self->dataSet->elementCount; i++) { + /* encode data set value elements */ + for (i = 0; i < maxIndex; i++) { - if (report->flags > 0) { - MmsValue* reason = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue)); + bool isInBuffer = false; - if (reason == NULL) goto return_out_of_memory; + if (report->flags > 0) + isInBuffer = true; + else { + if (MmsValue_getBitStringBit(inclusionField, i)) + isInBuffer = true; + } - reason->deleteValue = 0; - reason->type = MMS_BIT_STRING; - reason->value.bitString.size = 6; - reason->value.bitString.buf = (uint8_t*) MemoryAllocator_allocate(&ma, 1); + if (isInBuffer) + { + int length; - if (reason->value.bitString.buf == NULL) goto return_out_of_memory; + int lenSize = BerDecoder_decodeLength(currentReportBufferPos + 1, &length, 0, report->entryLength); - MmsValue_deleteAllBitStringBits(reason); + int dataElementSize = 1 + lenSize + length; - if (report->flags & 0x02) /* GI */ - MmsValue_setBitStringBit(reason, 5, true); + if (i >= startElementIndex) { + /* copy value from report entry to message buffer */ + memcpy(buffer + bufPos, currentReportBufferPos, dataElementSize); + bufPos += dataElementSize; + } - if (report->flags & 0x01) /* Integrity */ - MmsValue_setBitStringBit(reason, 4, true); + currentReportBufferPos += dataElementSize; + } + } + + /* add reason code to report if requested */ + if (withReasonCode) { - if (MemAllocLinkedList_add(reportElements, reason) == NULL) - goto return_out_of_memory; + /* move to start position in report buffer */ + currentReportBufferPos = valuesInReportBuffer + dataLen; - currentReportBufferPos += MemoryAllocator_getAlignedSize(1); + uint8_t bsBuf[1]; - MmsValue* dataSetElement = (MmsValue*) currentReportBufferPos; + MmsValue _reason; + _reason.type = MMS_BIT_STRING; + _reason.value.bitString.size = 6; + _reason.value.bitString.buf = bsBuf; - currentReportBufferPos += MmsValue_getSizeInMemory(dataSetElement); - } - else if (MmsValue_getBitStringBit(inclusionField, i)) { - MmsValue* reason = (MmsValue*) MemoryAllocator_allocate(&ma, sizeof(MmsValue)); + for (i = 0; i < maxIndex; i++) { - if (reason == NULL) goto return_out_of_memory; + bool isIncluded = false; - reason->deleteValue = 0; - reason->type = MMS_BIT_STRING; - reason->value.bitString.size = 6; - reason->value.bitString.buf = (uint8_t*) MemoryAllocator_allocate(&ma, 1); + if (report->flags > 0) { + bsBuf[0] = 0; /* clear all bits */ + + if (report->flags & 0x02) /* GI */ + MmsValue_setBitStringBit(&_reason, 5, true); - if (reason->value.bitString.buf == NULL) - goto return_out_of_memory; + if (report->flags & 0x01) /* Integrity */ + MmsValue_setBitStringBit(&_reason, 4, true); - MmsValue_deleteAllBitStringBits(reason); + isIncluded = true; + } + else if (MmsValue_getBitStringBit(self->inclusionField, i)) { + bsBuf[0] = 0; /* clear all bits */ switch((int) *currentReportBufferPos) { case REPORT_CONTROL_QUALITY_CHANGED: - MmsValue_setBitStringBit(reason, 2, true); + MmsValue_setBitStringBit(&_reason, 2, true); break; case REPORT_CONTROL_VALUE_CHANGED: - MmsValue_setBitStringBit(reason, 1, true); + MmsValue_setBitStringBit(&_reason, 1, true); break; case REPORT_CONTROL_VALUE_UPDATE: - MmsValue_setBitStringBit(reason, 3, true); + MmsValue_setBitStringBit(&_reason, 3, true); break; default: break; } - currentReportBufferPos += MemoryAllocator_getAlignedSize(1); + isIncluded = true; + } - MmsValue* dataSetElement = (MmsValue*) currentReportBufferPos; + if (isIncluded) { - currentReportBufferPos += MmsValue_getSizeInMemory(dataSetElement); + if (i >= startElementIndex) + bufPos = MmsValue_encodeMmsData(&_reason, buffer, bufPos, true); - if (MemAllocLinkedList_add(reportElements, reason) == NULL) - goto return_out_of_memory; + currentReportBufferPos++; } } } - ReportControl_unlockNotify(self); + reportBuffer->size = bufPos; - MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", (LinkedList) reportElements, false); + MmsServerConnection_sendMessage(self->clientConnection, reportBuffer, false); - ReportControl_lockNotify(self); - - /* Increase sequence number */ - self->sqNum++; - MmsValue_setUint32(sqNum, self->sqNum); + MmsServer_releaseTransmitBuffer(self->server->mmsServer); - assert(self->reportBuffer->nextToTransmit != self->reportBuffer->nextToTransmit->next); + if (moreFollows == false) { + /* reset sub sequence number */ + segmented = false; + self->startIndexForNextSegment = 0; + } + else { + /* increase sub sequence number */ + uint32_t subSeqNumVal = MmsValue_toUint32(subSeqNum); + subSeqNumVal++; + MmsValue_setUint32(subSeqNum, subSeqNumVal); - self->reportBuffer->nextToTransmit = self->reportBuffer->nextToTransmit->next; + self->startIndexForNextSegment = maxIndex; + } - if (DEBUG_IED_SERVER) - printf("IED_SERVER: sendNextReportEntry: memory(used/size): %i/%i\n", - (int) (ma.currentPtr - ma.memoryBlock), ma.size); + if (segmented == false) { -#if (DEBUG_IED_SERVER == 1) - printf("IED_SERVER: reporting.c nextToTransmit: %p\n", self->reportBuffer->nextToTransmit); - printEnqueuedReports(self); -#endif + assert(self->reportBuffer->nextToTransmit != self->reportBuffer->nextToTransmit->next); - goto cleanup_and_return; + self->reportBuffer->nextToTransmit = self->reportBuffer->nextToTransmit->next; -return_out_of_memory: + #if (DEBUG_IED_SERVER == 1) + printf("IED_SERVER: reporting.c nextToTransmit: %p\n", self->reportBuffer->nextToTransmit); + printEnqueuedReports(self); + #endif - if (DEBUG_IED_SERVER) - printf("IED_SERVER: sendNextReportEntry failed - memory allocation problem!\n"); + /* Increase sequence number */ + self->sqNum++; - /* TODO set some flag to notify application here */ + MmsValue_setUint16(sqNum, self->sqNum); -cleanup_and_return: + if (self->reportBuffer->isOverflow) + self->reportBuffer->isOverflow = false; + } - if (localStorage != NULL) - GLOBAL_FREEMEM(localStorage); +exit_function: + self->segmented = segmented; + Semaphore_post(self->reportBuffer->lock); + return moreFollows; +} +static void +sendNextReportEntry(ReportControl* self) +{ + while (sendNextReportEntrySegment(self)); } void @@ -2463,13 +3113,13 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) if (rc->buffered) enqueueReport(rc, false, false, currentTimeInMs); else - sendReport(rc, false, false); + sendReport(rc, false, false, currentTimeInMs); } if (rc->buffered) enqueueReport(rc, false, true, currentTimeInMs); else - sendReport(rc, false, true); + sendReport(rc, false, true, currentTimeInMs); rc->gi = false; @@ -2487,7 +3137,7 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) if (rc->buffered) enqueueReport(rc, false, false, currentTimeInMs); else - sendReport(rc, false, false); + sendReport(rc, false, false, currentTimeInMs); rc->triggered = false; } @@ -2497,7 +3147,7 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) if (rc->buffered) enqueueReport(rc, true, false, currentTimeInMs); else - sendReport(rc, true, false); + sendReport(rc, true, false, currentTimeInMs); rc->triggered = false; } @@ -2510,7 +3160,7 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs) if (rc->buffered) enqueueReport(rc, false, false, currentTimeInMs); else - sendReport(rc, false, false); + sendReport(rc, false, false, currentTimeInMs); rc->triggered = false; } diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 88fd7a0c..4377de8b 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -360,12 +360,29 @@ ReportControlBlock_create(const char* name, LogicalNode* parent, char* rptId, bo self->bufferTime = bufTm; self->intPeriod = intgPd; self->sibling = NULL; + self->clientReservation[0] = 0; /* no pre-configured client */ LogicalNode_addReportControlBlock(parent, self); return self; } +void +ReportControlBlock_setPreconfiguredClient(ReportControlBlock* self, uint8_t clientType, uint8_t* clientAddress) +{ + if (clientType == 4) { /* IPv4 address */ + self->clientReservation[0] = 4; + memcpy(self->clientReservation + 1, clientAddress, 4); + } + else if (clientType == 6) { /* IPv6 address */ + self->clientReservation[0] = 6; + memcpy(self->clientReservation + 1, clientAddress, 6); + } + else { /* no reservation or unknown type */ + self->clientReservation[0] = 0; + } +} + #if (CONFIG_IEC61850_SETTING_GROUPS == 1) static void LogicalNode_addSettingGroupControlBlock(LogicalNode* self, SettingGroupControlBlock* sgcb) diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index bab9bb7d..2c75a4a9 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -70,6 +70,13 @@ typedef enum { MMS_CALL_TYPE_GET_FILE_DIR } eMmsOutstandingCallType; +typedef union +{ + int32_t i32; + uint32_t u32; + void* ptr; +} MmsClientInternalParameter; + struct sMmsOutstandingCall { bool isUsed; @@ -77,7 +84,7 @@ struct sMmsOutstandingCall eMmsOutstandingCallType type; void* userCallback; void* userParameter; - void* internalParameter; + MmsClientInternalParameter internalParameter; uint64_t timeout; }; diff --git a/src/mms/inc_private/mms_common_internal.h b/src/mms/inc_private/mms_common_internal.h index 59e0b76e..53e6b678 100644 --- a/src/mms/inc_private/mms_common_internal.h +++ b/src/mms/inc_private/mms_common_internal.h @@ -56,7 +56,7 @@ LIB61850_INTERNAL bool mmsMsg_parseFileOpenResponse(uint8_t* buffer, int bufPos, int maxBufPos, int32_t* frsmId, uint32_t* fileSize, uint64_t* lastModified); LIB61850_INTERNAL bool -mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, bool* moreFollows, uint8_t** dataBuffer, int* dataLength); +mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t invokeId, int32_t frsmId, bool* moreFollows, MmsConnection_FileReadHandler handler, void* handlerParameter); LIB61850_INTERNAL void mmsMsg_createFileReadResponse(int maxPduSize, uint32_t invokeId, ByteBuffer* response, MmsFileReadStateMachine* frsm); diff --git a/src/mms/inc_private/mms_server_connection.h b/src/mms/inc_private/mms_server_connection.h index f78fc3fa..aa65e98b 100644 --- a/src/mms/inc_private/mms_server_connection.h +++ b/src/mms/inc_private/mms_server_connection.h @@ -48,6 +48,12 @@ MmsServerConnection_init(MmsServerConnection connection, MmsServer server, IsoCo LIB61850_INTERNAL void MmsServerConnection_destroy(MmsServerConnection connection); +LIB61850_INTERNAL int +MmsServerConnection_getMaxMmsPduSize(MmsServerConnection self); + +LIB61850_INTERNAL void +MmsServerConnection_sendMessage(MmsServerConnection self, ByteBuffer* message, bool handlerMode); + LIB61850_INTERNAL bool MmsServerConnection_addNamedVariableList(MmsServerConnection self, MmsNamedVariableList variableList); diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 7da2ee0b..13aae158 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -270,7 +270,7 @@ checkForOutstandingCall(MmsConnection self, uint32_t invokeId) } static bool -addToOutstandingCalls(MmsConnection self, uint32_t invokeId, eMmsOutstandingCallType type, void* userCallback, void* userParameter, void* internalParameter) +addToOutstandingCalls(MmsConnection self, uint32_t invokeId, eMmsOutstandingCallType type, void* userCallback, void* userParameter, MmsClientInternalParameter internalParameter) { int i = 0; @@ -333,7 +333,7 @@ sendMessage(MmsConnection self, ByteBuffer* message) static MmsError sendAsyncRequest(MmsConnection self, uint32_t invokeId, ByteBuffer* message, eMmsOutstandingCallType type, - void* userCallback, void* userParameter, void* internalParameter) + void* userCallback, void* userParameter, MmsClientInternalParameter internalParameter) { if (addToOutstandingCalls(self, invokeId, type, userCallback, userParameter, internalParameter) == false) { @@ -855,7 +855,7 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M handler(outstandingCall->invokeId, outstandingCall->userParameter, err, NULL, false); } else { - LinkedList nameList = (LinkedList) outstandingCall->internalParameter; + LinkedList nameList = (LinkedList) outstandingCall->internalParameter.ptr; bool moreFollows = mmsClient_parseGetNameListResponse(&nameList, response); @@ -880,7 +880,6 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M uint32_t fileSize; uint64_t lastModified; - if (mmsMsg_parseFileOpenResponse(ByteBuffer_getBuffer(response), bufPos, ByteBuffer_getSize(response), &frsmId, &fileSize, &lastModified) == false) { @@ -900,16 +899,14 @@ handleAsyncResponse(MmsConnection self, ByteBuffer* response, uint32_t bufPos, M } else { bool moreFollows; - uint8_t* dataBuffer; - int dataLength; - if (mmsMsg_parseFileReadResponse(ByteBuffer_getBuffer(response), bufPos, ByteBuffer_getSize(response), &moreFollows, &dataBuffer, &dataLength) == false) + int32_t frsmId = outstandingCall->internalParameter.i32; + + if (mmsMsg_parseFileReadResponse(ByteBuffer_getBuffer(response), bufPos, ByteBuffer_getSize(response), outstandingCall->invokeId, frsmId, &moreFollows, + handler, outstandingCall->userParameter) == false) { handler(outstandingCall->invokeId, outstandingCall->userParameter, MMS_ERROR_PARSING_RESPONSE, NULL, 0, false); } - else { - handler(outstandingCall->invokeId, outstandingCall->userParameter, err, dataBuffer, dataLength, moreFollows); - } } } else if ((outstandingCall->type == MMS_CALL_TYPE_FILE_CLOSE) || @@ -1860,7 +1857,10 @@ mmsClient_getNameListSingleRequestAsync( payload, objectClass, continueAfter); } - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_NAME_LIST, handler, parameter, nameList); + MmsClientInternalParameter intParam; + intParam.ptr = nameList; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_NAME_LIST, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2074,7 +2074,10 @@ MmsConnection_readVariableAsync(MmsConnection self, MmsError* mmsError, const ch mmsClient_createReadRequest(invokeId, domainId, itemId, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2135,7 +2138,10 @@ MmsConnection_readVariableComponentAsync(MmsConnection self, MmsError* mmsError, mmsClient_createReadRequestComponent(invokeId, domainId, itemId, componentId, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2230,7 +2236,10 @@ MmsConnection_readArrayElementsAsync(MmsConnection self, MmsError* mmsError, con mmsClient_createReadRequestAlternateAccessIndex(invokeId, domainId, itemId, startIndex, numberOfElements, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2293,7 +2302,10 @@ MmsConnection_readSingleArrayElementWithComponentAsync(MmsConnection self, MmsEr mmsClient_createReadRequestAlternateAccessSingleIndexComponent(invokeId, domainId, itemId, index, componentId, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_VARIABLE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2354,7 +2366,10 @@ MmsConnection_readMultipleVariablesAsync(MmsConnection self, MmsError* mmsError, mmsClient_createReadRequestMultipleValues(invokeId, domainId, items, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2417,7 +2432,10 @@ MmsConnection_readNamedVariableListValuesAsync(MmsConnection self, MmsError* mms mmsClient_createReadNamedVariableListRequest(invokeId, domainId, listName, payload, specWithResult); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2481,7 +2499,10 @@ MmsConnection_readNamedVariableListValuesAssociationSpecificAsync(MmsConnection mmsClient_createReadAssociationSpecificNamedVariableListRequest(invokeId, listName, payload, specWithResult); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_MULTIPLE_VARIABLES, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2567,7 +2588,10 @@ MmsConnection_readNamedVariableListDirectoryAsync(MmsConnection self, MmsError* mmsClient_createGetNamedVariableListAttributesRequest(invokeId, payload, domainId, listName); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_NVL_DIRECTORY, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_NVL_DIRECTORY, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2632,7 +2656,10 @@ MmsConnection_readNamedVariableListDirectoryAssociationSpecificAsync(MmsConnecti mmsClient_createGetNamedVariableListAttributesRequestAssociationSpecific(invokeId, payload, listName); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_NVL_DIRECTORY, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_NVL_DIRECTORY, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2707,7 +2734,10 @@ MmsConnection_defineNamedVariableListAsync(MmsConnection self, MmsError* mmsErro mmsClient_createDefineNamedVariableListRequest(invokeId, payload, domainId, listName, variableSpecs, false); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DEFINE_NVL, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DEFINE_NVL, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2762,7 +2792,10 @@ MmsConnection_defineNamedVariableListAssociationSpecificAsync(MmsConnection self mmsClient_createDefineNamedVariableListRequest(invokeId, payload, NULL, listName, variableSpecs, true); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DEFINE_NVL, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DEFINE_NVL, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2819,7 +2852,10 @@ MmsConnection_deleteNamedVariableListAsync(MmsConnection self, MmsError* mmsErro mmsClient_createDeleteNamedVariableListRequest(invokeId, payload, domainId, listName); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DELETE_NVL, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DELETE_NVL, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2877,7 +2913,10 @@ MmsConnection_deleteAssociationSpecificNamedVariableListAsync(MmsConnection self mmsClient_createDeleteAssociationSpecificNamedVariableListRequest( invokeId, payload, listName); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DELETE_NVL, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_DELETE_NVL, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -2954,7 +2993,10 @@ MmsConnection_getVariableAccessAttributesAsync(MmsConnection self, MmsError* mms mmsClient_createGetVariableAccessAttributesRequest(invokeId, domainId, itemId, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_VAR_ACCESS_ATTR, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_VAR_ACCESS_ATTR, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3036,7 +3078,10 @@ MmsConnection_identifyAsync(MmsConnection self, MmsError* mmsError, mmsClient_createIdentifyRequest(invokeId, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_IDENTIFY, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_IDENTIFY, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3119,7 +3164,10 @@ MmsConnection_getServerStatusAsync(MmsConnection self, MmsError* mmsError, bool mmsClient_createStatusRequest(invokeId, payload, extendedDerivation); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_SERVER_STATUS, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_SERVER_STATUS, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3261,7 +3309,10 @@ MmsConnection_readJournalTimeRangeAsync(MmsConnection self, MmsError* mmsError, mmsClient_createReadJournalRequestWithTimeRange(invokeId, payload, domainId, itemId, startTime, endTime); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_JOURNAL, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_JOURNAL, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3329,7 +3380,10 @@ MmsConnection_readJournalStartAfterAsync(MmsConnection self, MmsError* mmsError, mmsClient_createReadJournalRequestStartAfter(invokeId, payload, domainId, itemId, timeSpecification, entrySpecification); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_JOURNAL, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_READ_JOURNAL, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3428,7 +3482,10 @@ MmsConnection_fileOpenAsync(MmsConnection self, MmsError* mmsError, const char* mmsClient_createFileOpenRequest(invokeId, payload, filename, initialPosition); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_OPEN, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_OPEN, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3517,7 +3574,10 @@ MmsConnection_fileCloseAsync(MmsConnection self, MmsError* mmsError, uint32_t fr mmsClient_createFileCloseRequest(invokeId, payload, frsmId); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_CLOSE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_CLOSE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3588,7 +3648,10 @@ MmsConnection_fileDeleteAsync(MmsConnection self, MmsError* mmsError, const char mmsClient_createFileDeleteRequest(invokeId, payload, fileName); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_DELETE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_DELETE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3692,7 +3755,10 @@ MmsConnection_fileReadAsync(MmsConnection self, MmsError* mmsError, int32_t frsm mmsClient_createFileReadRequest(invokeId, payload, frsmId); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_READ, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.i32 = frsmId; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_READ, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3796,7 +3862,10 @@ MmsConnection_getFileDirectoryAsync(MmsConnection self, MmsError* mmsError, cons mmsClient_createFileDirectoryRequest(invokeId, payload, fileSpecification, continueAfter); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_FILE_DIR, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_GET_FILE_DIR, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3866,7 +3935,10 @@ MmsConnection_fileRenameAsync(MmsConnection self, MmsError* mmsError, const char mmsClient_createFileRenameRequest(invokeId, payload, currentFileName, newFileName); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_RENAME, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_FILE_RENAME, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -3944,7 +4016,10 @@ MmsConnection_obtainFileAsync(MmsConnection self, MmsError* mmsError, const char mmsClient_createObtainFileRequest(invokeId, payload, sourceFile, destinationFile); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_OBTAIN_FILE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_OBTAIN_FILE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -4022,7 +4097,10 @@ MmsConnection_writeVariableAsync(MmsConnection self, MmsError* mmsError, mmsClient_createWriteRequest(invokeId, domainId, itemId, value, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -4083,7 +4161,10 @@ MmsConnection_writeSingleArrayElementWithComponentAsync(MmsConnection self, MmsE mmsClient_createWriteRequestAlternateAccessSingleIndexComponent(invokeId, domainId, itemId, arrayIndex, componentId, value, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -4170,7 +4251,10 @@ MmsConnection_writeMultipleVariablesAsync(MmsConnection self, MmsError* mmsError mmsClient_createWriteMultipleItemsRequest(invokeId, domainId, items, values, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -4231,7 +4315,10 @@ MmsConnection_writeArrayElementsAsync(MmsConnection self, MmsError* mmsError, mmsClient_createWriteRequestArray(invokeId, domainId, itemId, index, numberOfElements, value, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_VARIABLE, handler, parameter, intParam); if (mmsError) *mmsError = err; @@ -4298,7 +4385,10 @@ MmsConnection_writeNamedVariableListAsync(MmsConnection self, MmsError* mmsError mmsClient_createWriteRequestNamedVariableList(invokeId, isAssociationSpecific, domainId, itemId, values, payload); - MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES, handler, parameter, NULL); + MmsClientInternalParameter intParam; + intParam.ptr = NULL; + + MmsError err = sendAsyncRequest(self, invokeId, payload, MMS_CALL_TYPE_WRITE_MULTIPLE_VARIABLES, handler, parameter, intParam); if (mmsError) *mmsError = err; diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index 776f058a..de799f52 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -689,12 +689,16 @@ mmsMsg_parseFileOpenResponse(uint8_t* buffer, int bufPos, int maxBufPos, int32_t } bool -mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, bool* moreFollows, uint8_t** dataBuffer, int* dataLength) +mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, uint32_t invokeId, int frsmId, bool* moreFollows, MmsConnection_FileReadHandler handler, void* handlerParameter) { int length; + uint8_t* data = NULL; + int dataLen = 0; + uint8_t tag = buffer[bufPos++]; + if (tag != 0xbf) { if (DEBUG_MMS_CLIENT) printf("MMS_CLIENT/SERVER: mmsClient_parseFileReadResponse: unknown tag %02x\n", tag); @@ -704,8 +708,6 @@ mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, bool* m tag = buffer[bufPos++]; *moreFollows = true; - *dataBuffer = NULL; - *dataLength = 0; if (tag != 0x49) { if (DEBUG_MMS_CLIENT) @@ -731,11 +733,11 @@ mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, bool* m if (bufPos < 0) return false; - switch (tag) - { + switch (tag) { case 0x80: /* fileData */ - *dataBuffer = buffer + bufPos; - *dataLength = length; + data = buffer + bufPos; + dataLen = length; + bufPos += length; break; @@ -753,8 +755,7 @@ mmsMsg_parseFileReadResponse(uint8_t* buffer, int bufPos, int maxBufPos, bool* m } } - if (*dataBuffer == NULL) - return false; + handler(invokeId, handlerParameter, frsmId, data, dataLen, *moreFollows); return true; } diff --git a/src/mms/iso_mms/common/mms_common_msg.c b/src/mms/iso_mms/common/mms_common_msg.c index bb6a90d9..3f25e5c2 100644 --- a/src/mms/iso_mms/common/mms_common_msg.c +++ b/src/mms/iso_mms/common/mms_common_msg.c @@ -244,163 +244,195 @@ mmsMsg_parseDataElement(Data_t* dataElement) printf("MMS CLIENT: error parsing data element (invalid structure size)!\n"); } } - else if (dataElement->present == Data_PR_integer) { + else if (dataElement->present == Data_PR_array) { - if (dataElement->choice.integer.size > 0) { - Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer( - dataElement->choice.integer.buf, dataElement->choice.integer.size); + int componentCount = dataElement->choice.array->list.count; + + if (componentCount > 0) { + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - value = MmsValue_newIntegerFromBerInteger(berInteger); + value->type = MMS_ARRAY; + value->value.structure.size = componentCount; + value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*)); + + int i; + + for (i = 0; i < componentCount; i++) { + value->value.structure.components[i] = + mmsMsg_parseDataElement(dataElement->choice.array->list.array[i]); + + if (value->value.structure.components[i] == NULL) { + MmsValue_delete(value); + value = NULL; + break; + } + } } else { if (DEBUG_MMS_CLIENT) - printf("MMS CLIENT: error parsing data element (invalid integer size)!\n"); + printf("MMS CLIENT: error parsing data element (invalid array size)!\n"); } + } - else if (dataElement->present == Data_PR_unsigned) { + else { + if (dataElement->present == Data_PR_integer) { - if (dataElement->choice.Unsigned.size > 0) { - Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer( - dataElement->choice.Unsigned.buf, dataElement->choice.Unsigned.size); + if (dataElement->choice.integer.size > 0) { + Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer( + dataElement->choice.integer.buf, dataElement->choice.integer.size); - value = MmsValue_newUnsignedFromBerInteger(berInteger); + value = MmsValue_newIntegerFromBerInteger(berInteger); + } + else { + if (DEBUG_MMS_CLIENT) + printf("MMS CLIENT: error parsing data element (invalid integer size)!\n"); + } } - else { - if (DEBUG_MMS_CLIENT) - printf("MMS CLIENT: error parsing data element (invalid unsigned size)!\n"); + else if (dataElement->present == Data_PR_unsigned) { + + if (dataElement->choice.Unsigned.size > 0) { + Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer( + dataElement->choice.Unsigned.buf, dataElement->choice.Unsigned.size); + + value = MmsValue_newUnsignedFromBerInteger(berInteger); + } + else { + if (DEBUG_MMS_CLIENT) + printf("MMS CLIENT: error parsing data element (invalid unsigned size)!\n"); + } } - } - else if (dataElement->present == Data_PR_visiblestring) { + else if (dataElement->present == Data_PR_visiblestring) { - if (dataElement->choice.visiblestring.size >= 0) { - value = MmsValue_newVisibleStringFromByteArray(dataElement->choice.visiblestring.buf, - dataElement->choice.visiblestring.size); + if (dataElement->choice.visiblestring.size >= 0) { + value = MmsValue_newVisibleStringFromByteArray(dataElement->choice.visiblestring.buf, + dataElement->choice.visiblestring.size); + } } - } - else if (dataElement->present == Data_PR_mMSString) { + else if (dataElement->present == Data_PR_mMSString) { - if ( dataElement->choice.mMSString.size >= 0) { - value = MmsValue_newMmsStringFromByteArray(dataElement->choice.mMSString.buf, - dataElement->choice.mMSString.size); + if ( dataElement->choice.mMSString.size >= 0) { + value = MmsValue_newMmsStringFromByteArray(dataElement->choice.mMSString.buf, + dataElement->choice.mMSString.size); + } } - } - else if (dataElement->present == Data_PR_bitstring) { + else if (dataElement->present == Data_PR_bitstring) { - int size = dataElement->choice.bitstring.size; + int size = dataElement->choice.bitstring.size; - if (size > 0) { + if (size > 0) { - int maxSize = (size * 8); - int bitSize = maxSize - dataElement->choice.bitstring.bits_unused; + int maxSize = (size * 8); + int bitSize = maxSize - dataElement->choice.bitstring.bits_unused; - if ((bitSize > 0) && (maxSize >= bitSize)) { - value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + if ((bitSize > 0) && (maxSize >= bitSize)) { + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - value->type = MMS_BIT_STRING; + value->type = MMS_BIT_STRING; - value->value.bitString.size = bitSize; + value->value.bitString.size = bitSize; - value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size); - memcpy(value->value.bitString.buf, - dataElement->choice.bitstring.buf, size); + value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size); + memcpy(value->value.bitString.buf, + dataElement->choice.bitstring.buf, size); + } + else { + if (DEBUG_MMS_CLIENT) + printf("MMS CLIENT: error parsing data element (bit string padding problem)!\n"); + } } else { if (DEBUG_MMS_CLIENT) - printf("MMS CLIENT: error parsing data element (bit string padding problem)!\n"); + printf("MMS CLIENT: error parsing data element (bit string size 0 or negative)!\n"); } } - else { - if (DEBUG_MMS_CLIENT) - printf("MMS CLIENT: error parsing data element (bit string size 0 or negative)!\n"); - } - } - else if (dataElement->present == Data_PR_floatingpoint) { + else if (dataElement->present == Data_PR_floatingpoint) { - int size = dataElement->choice.floatingpoint.size; + int size = dataElement->choice.floatingpoint.size; - if (size == 5) { /* FLOAT32 */ + if (size == 5) { /* FLOAT32 */ - value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - value->type = MMS_FLOAT; + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + value->type = MMS_FLOAT; - value->value.floatingPoint.formatWidth = 32; - value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0]; + value->value.floatingPoint.formatWidth = 32; + value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0]; - uint8_t* floatBuf = (dataElement->choice.floatingpoint.buf + 1); + uint8_t* floatBuf = (dataElement->choice.floatingpoint.buf + 1); - value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(4); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(4); #if (ORDER_LITTLE_ENDIAN == 1) - memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 4); + memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 4); #else - memcpy(value->value.floatingPoint.buf, floatBuf, 4); + memcpy(value->value.floatingPoint.buf, floatBuf, 4); #endif - } + } - if (size == 9) { /* FLOAT64 */ + if (size == 9) { /* FLOAT64 */ - value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - value->type = MMS_FLOAT; + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + value->type = MMS_FLOAT; - value->value.floatingPoint.formatWidth = 64; - value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0]; + value->value.floatingPoint.formatWidth = 64; + value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0]; - uint8_t* floatBuf = (dataElement->choice.floatingpoint.buf + 1); + uint8_t* floatBuf = (dataElement->choice.floatingpoint.buf + 1); - value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(8); + value->value.floatingPoint.buf = (uint8_t*) GLOBAL_MALLOC(8); #if (ORDER_LITTLE_ENDIAN == 1) - memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 8); + memcpyReverseByteOrder(value->value.floatingPoint.buf, floatBuf, 8); #else - memcpy(value->value.floatingPoint.buf, floatBuf, 8); + memcpy(value->value.floatingPoint.buf, floatBuf, 8); #endif + } } - } - else if (dataElement->present == Data_PR_utctime) { + else if (dataElement->present == Data_PR_utctime) { - int size = dataElement->choice.utctime.size; + int size = dataElement->choice.utctime.size; - if (size == 8) { - value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - value->type = MMS_UTC_TIME; - memcpy(value->value.utcTime, dataElement->choice.utctime.buf, 8); - } - else { - if (DEBUG_MMS_CLIENT) - printf("MMS CLIENT: error parsing UTC time (size is %i instead of 8\n", size); + if (size == 8) { + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + value->type = MMS_UTC_TIME; + memcpy(value->value.utcTime, dataElement->choice.utctime.buf, 8); + } + else { + if (DEBUG_MMS_CLIENT) + printf("MMS CLIENT: error parsing UTC time (size is %i instead of 8\n", size); + } } - } - else if (dataElement->present == Data_PR_octetstring) { + else if (dataElement->present == Data_PR_octetstring) { - if (dataElement->choice.octetstring.size >= 0) { - value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - value->type = MMS_OCTET_STRING; - int size = dataElement->choice.octetstring.size; - value->value.octetString.size = size; - value->value.octetString.maxSize = size; - value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); - memcpy(value->value.octetString.buf, dataElement->choice.octetstring.buf, size); - } + if (dataElement->choice.octetstring.size >= 0) { + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + value->type = MMS_OCTET_STRING; + int size = dataElement->choice.octetstring.size; + value->value.octetString.size = size; + value->value.octetString.maxSize = size; + value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); + memcpy(value->value.octetString.buf, dataElement->choice.octetstring.buf, size); + } - } - else if (dataElement->present == Data_PR_binarytime) { - int size = dataElement->choice.binarytime.size; + } + else if (dataElement->present == Data_PR_binarytime) { + int size = dataElement->choice.binarytime.size; - if ((size == 4) || (size == 6)) { - value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); - value->type = MMS_BINARY_TIME; - value->value.binaryTime.size = size; - memcpy(value->value.binaryTime.buf, dataElement->choice.binarytime.buf, size); + if ((size == 4) || (size == 6)) { + value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + value->type = MMS_BINARY_TIME; + value->value.binaryTime.size = size; + memcpy(value->value.binaryTime.buf, dataElement->choice.binarytime.buf, size); + } + else { + if (DEBUG_MMS_CLIENT) + printf("MMS CLIENT: error parsing binary time (size must be 4 or 6, is %i\n", size); + } } - else { - if (DEBUG_MMS_CLIENT) - printf("MMS CLIENT: error parsing binary time (size must be 4 or 6, is %i\n", size); + else if (dataElement->present == Data_PR_boolean) { + value = MmsValue_newBoolean(dataElement->choice.boolean); } - } - else if (dataElement->present == Data_PR_boolean) { - value = MmsValue_newBoolean(dataElement->choice.boolean); - } - else if (dataElement->present == Data_PR_booleanArray) { - if (DEBUG_MMS_CLIENT) - printf("MMS CLIENT: unsupported type - boolean-array\n"); + else if (dataElement->present == Data_PR_booleanArray) { + + } + } if (DEBUG_MMS_CLIENT) { diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index f93d44ea..7af7ba74 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -510,6 +510,9 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) IsoConnection_sendMessage(task->connection->isoConnection, response, false); + FileSystem_closeFile(task->fileHandle); + deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + MmsServer_releaseTransmitBuffer(self); if (DEBUG_MMS_SERVER) @@ -529,6 +532,9 @@ mmsServer_fileUploadTask(MmsServer self, MmsObtainFileTask task) IsoConnection_sendMessage(task->connection->isoConnection, response, false); + FileSystem_closeFile(task->fileHandle); + deleteFile(MmsServerConnection_getFilesystemBasepath(task->connection), task->destinationFilename); + MmsServer_releaseTransmitBuffer(self); if (DEBUG_MMS_SERVER) diff --git a/src/mms/iso_mms/server/mms_information_report.c b/src/mms/iso_mms/server/mms_information_report.c index 30b17d82..932cc473 100644 --- a/src/mms/iso_mms/server/mms_information_report.c +++ b/src/mms/iso_mms/server/mms_information_report.c @@ -220,7 +220,6 @@ exit_function: return; } - void /* send information report for a named variable list */ MmsServerConnection_sendInformationReportVMDSpecific(MmsServerConnection self, char* itemId, LinkedList values, bool handlerMode) diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index fb0a4118..8b55b45b 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -453,12 +453,12 @@ getUploadTaskByInvokeId(MmsServer mmsServer, uint32_t invokeId) } static void -mmsFileReadHandler(void* parameter, int32_t frsmId, uint8_t* buffer, uint32_t bytesReceived) +mmsFileReadHandler(uint32_t invokeId, void* parameter, MmsError mmsError, uint8_t* buffer, uint32_t bytesReceived, bool moreFollows) { MmsObtainFileTask task = (MmsObtainFileTask) parameter; if (DEBUG_MMS_SERVER) - printf("MMS_SERVER: FILE %i received %i bytes\n", frsmId, bytesReceived); + printf("MMS_SERVER: file %i received %i bytes\n", task->frmsId, bytesReceived); FileSystem_writeFile(task->fileHandle, buffer, bytesReceived); } @@ -538,12 +538,8 @@ handleConfirmedResponsePdu( if (fileTask != NULL) { bool moreFollows; - uint8_t* dataBuffer = NULL; - int dataLength = 0; - if (mmsMsg_parseFileReadResponse(buffer, startBufPos, maxBufPos, &moreFollows, &dataBuffer, &dataLength)) { - - mmsFileReadHandler((void*) fileTask, fileTask->frmsId, dataBuffer, dataLength); + if (mmsMsg_parseFileReadResponse(buffer, startBufPos, maxBufPos, invokeId, fileTask->frmsId, &moreFollows, mmsFileReadHandler, (void*) fileTask)) { if (moreFollows) { fileTask->state = MMS_FILE_UPLOAD_STATE_SEND_FILE_READ; @@ -743,6 +739,18 @@ MmsServerConnection_destroy(MmsServerConnection self) GLOBAL_FREEMEM(self); } +int +MmsServerConnection_getMaxMmsPduSize(MmsServerConnection self) +{ + return self->maxPduSize; +} + +void +MmsServerConnection_sendMessage(MmsServerConnection self, ByteBuffer* message, bool handlerMode) +{ + IsoConnection_sendMessage(self->isoConnection, message, false); +} + #if (MMS_DYNAMIC_DATA_SETS == 1) bool MmsServerConnection_addNamedVariableList(MmsServerConnection self, MmsNamedVariableList variableList) diff --git a/src/mms/iso_mms/server/mms_write_service.c b/src/mms/iso_mms/server/mms_write_service.c index acc24bad..ca4d8f95 100644 --- a/src/mms/iso_mms/server/mms_write_service.c +++ b/src/mms/iso_mms/server/mms_write_service.c @@ -80,7 +80,6 @@ mmsServer_createMmsWriteResponse(MmsServerConnection connection, response->size = bufPos; } - void MmsServerConnection_sendWriteResponse(MmsServerConnection self, uint32_t invokeId, MmsDataAccessError indication, bool handlerMode) { diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index 2da7859e584b8deaed1abca51a5fcab5397bc2de..34e4bfb289cbd29f796f983cb4587e58a2358bd6 100644 GIT binary patch delta 31398 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 Uj`9lP6+}2T1jL3WSkEB-50f7E^Z)<= delta 27410 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 diff --git a/tools/model_generator/src/com/libiec61850/scl/SclParser.java b/tools/model_generator/src/com/libiec61850/scl/SclParser.java index fc73748d..a606b27f 100644 --- a/tools/model_generator/src/com/libiec61850/scl/SclParser.java +++ b/tools/model_generator/src/com/libiec61850/scl/SclParser.java @@ -320,4 +320,32 @@ public class SclParser return null; } + public ConnectedAP getConnectedAP(String iedName, String accessPointName) { + communication = this.getCommunication(); + + if (communication != null) { + List subNetworks = communication.getSubNetworks(); + + for (SubNetwork subNetwork : subNetworks) { + List connectedAPs = subNetwork.getConnectedAPs(); + + for (ConnectedAP connectedAP : connectedAPs) { + if (connectedAP.getIedName().equals(iedName)) { + + if (connectedAP.getApName().equals(accessPointName)) { + + if (withOutput) + System.out.println("Found connectedAP " + accessPointName + " for IED " + iedName); + + return connectedAP; + } + + } + } + + } + } + + return null; + } } diff --git a/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java b/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java index bfa61d39..26abfbeb 100644 --- a/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java +++ b/tools/model_generator/src/com/libiec61850/scl/communication/ConnectedAP.java @@ -36,6 +36,8 @@ public class ConnectedAP { private List gses; private List smvs; + + private Address address = null; public ConnectedAP(Node node) throws SclParserException { iedName = ParserUtils.parseAttribute(node, "iedName"); @@ -56,7 +58,12 @@ public class ConnectedAP { List smvNodes = ParserUtils.getChildNodesWithTag(node, "SMV"); for (Node smvNode : smvNodes) - smvs.add(new SMV(smvNode)); + smvs.add(new SMV(smvNode)); + + Node addressNode = ParserUtils.getChildNodeWithTag(node, "Address"); + + if (addressNode != null) + address = new Address(addressNode); } public String getIedName() { @@ -66,6 +73,10 @@ public class ConnectedAP { public String getApName() { return apName; } + + public Address getAddress() { + return address; + } public List getGses() { return gses; diff --git a/tools/model_generator/src/com/libiec61850/scl/model/RptEnabled.java b/tools/model_generator/src/com/libiec61850/scl/model/RptEnabled.java index 6fb90d35..9282ebf3 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/RptEnabled.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/RptEnabled.java @@ -1,5 +1,8 @@ package com.libiec61850.scl.model; +import java.util.LinkedList; +import java.util.List; + import org.w3c.dom.Node; import com.libiec61850.scl.ParserUtils; @@ -8,6 +11,8 @@ public class RptEnabled { private int maxInstances = 1; private String desc = null; + + private List clientLNs = new LinkedList(); public RptEnabled(Node rptEnabledNode) { this.desc = ParserUtils.parseAttribute(rptEnabledNode, "desc"); @@ -16,6 +21,14 @@ public class RptEnabled { if (maxString != null) { maxInstances = new Integer(maxString); } + + List clientLNNodes = ParserUtils.getChildNodesWithTag(rptEnabledNode, "ClientLN"); + + for (Node clientLNNode : clientLNNodes) { + ClientLN clientLN = new ClientLN(clientLNNode); + + clientLNs.add(clientLN); + } } public int getMaxInstances() { @@ -25,4 +38,9 @@ public class RptEnabled { public String getDesc() { return desc; } + + public List getClientLNs() + { + return clientLNs; + } } diff --git a/tools/model_generator/src/com/libiec61850/tools/ModelViewer.java b/tools/model_generator/src/com/libiec61850/tools/ModelViewer.java index 82fde106..0997a4d9 100644 --- a/tools/model_generator/src/com/libiec61850/tools/ModelViewer.java +++ b/tools/model_generator/src/com/libiec61850/tools/ModelViewer.java @@ -184,6 +184,21 @@ public class ModelViewer { String lNodePrefix = devPrefix + lNode.getName(); printObjectList(lNode.getDataObjects(), output, lNodePrefix); + +// for (DataObject dObject : lNode.getDataObjects()) { +// +// String dOPrefix = lNodePrefix + "." + dObject.getName(); +// +// for (DataAttribute dAttribute : dObject.getDataAttributes()) { +// +// String daPrefix = dOPrefix + "." + dAttribute.getName(); +// +// output.println(daPrefix + " [" + dAttribute.getFc().toString() + "]"); +// +// printSubAttributeList(dAttribute, output, daPrefix); +// } +// +// } } } diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index c790ed6a..5bc26a10 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -29,17 +29,24 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.PrintStream; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.LinkedList; import java.util.List; import com.libiec61850.scl.SclParser; import com.libiec61850.scl.SclParserException; +import com.libiec61850.scl.communication.Address; import com.libiec61850.scl.communication.Communication; import com.libiec61850.scl.communication.ConnectedAP; import com.libiec61850.scl.communication.GSE; +import com.libiec61850.scl.communication.P; import com.libiec61850.scl.communication.PhyComAddress; import com.libiec61850.scl.communication.SubNetwork; import com.libiec61850.scl.model.AccessPoint; +import com.libiec61850.scl.model.ClientLN; import com.libiec61850.scl.model.DataAttribute; import com.libiec61850.scl.model.DataModelValue; import com.libiec61850.scl.model.DataObject; @@ -53,6 +60,7 @@ import com.libiec61850.scl.model.LogControl; import com.libiec61850.scl.model.LogicalDevice; import com.libiec61850.scl.model.LogicalNode; import com.libiec61850.scl.model.ReportControlBlock; +import com.libiec61850.scl.model.RptEnabled; import com.libiec61850.scl.model.SampledValueControl; import com.libiec61850.scl.model.Server; import com.libiec61850.scl.model.SettingControl; @@ -103,6 +111,8 @@ public class StaticModelGenerator { private String hDefineName; private String modelPrefix; private boolean initializeOnce; + + private SclParser sclParser; public StaticModelGenerator(InputStream stream, String icdFile, PrintStream cOut, PrintStream hOut, String outputFileName, String iedName, String accessPointName, String modelPrefix, @@ -131,7 +141,7 @@ public class StaticModelGenerator { this.logs = new StringBuffer(); this.logVariableNames = new LinkedList(); - SclParser sclParser = new SclParser(stream); + sclParser = new SclParser(stream); this.outputFileName = outputFileName; this.hDefineName = outputFileName.toUpperCase().replace( '.', '_' ).replace( '-', '_' ) + "_H_"; @@ -1078,6 +1088,51 @@ public class StaticModelGenerator { gseControlNumber++; } } + + private String getIpAddressByIedName(SclParser sclParser, String iedName, String apRef) + { + Communication com = sclParser.getCommunication(); + + if (com != null) { + + for (SubNetwork subNetWork : com.getSubNetworks()) { + + for (ConnectedAP ap : subNetWork.getConnectedAPs()) { + + if (apRef != null) { + boolean isMatching = false; + + String apName = ap.getApName(); + + if (apName != null) { + if (apName.equals(apRef)) + isMatching = true; + } + + if (isMatching == false) + continue; + } + + String apIedName = ap.getIedName(); + + if (apIedName != null) { + if (apIedName.equals(iedName)) { + Address address = ap.getAddress(); + + if (address != null) { + P ip = address.getAddressParameter("IP"); + + if (ip != null) + return ip.getText(); + } + } + } + } + } + } + + return null; + } private void printReportControlBlocks(String lnPrefix, LogicalNode logicalNode) { List reportControlBlocks = logicalNode.getReportControlBlocks(); @@ -1091,20 +1146,75 @@ public class StaticModelGenerator { if (rcb.isIndexed()) { int maxInstances = 1; + + List clientLNs = null; + + if (rcb.getRptEna() != null) { + RptEnabled rptEna = rcb.getRptEna(); + + maxInstances = rptEna.getMaxInstances(); - if (rcb.getRptEna() != null) - maxInstances = rcb.getRptEna().getMaxInstances(); + clientLNs = rptEna.getClientLNs(); + } + for (int i = 0; i < maxInstances; i++) { String index = String.format("%02d", (i + 1)); System.out.println("print report instance " + index); + + byte[] clientAddress = new byte[17]; + clientAddress[0] = 0; + + if (clientLNs != null) { + try { + ClientLN clientLN = clientLNs.get(i); + + if (clientLN != null) { + + String iedName = clientLN.getIedName(); + String apRef = clientLN.getApRef(); + + if (iedName != null) { + String ipAddress = getIpAddressByIedName(sclParser, iedName, apRef); + + try { + InetAddress inetAddr = InetAddress.getByName(ipAddress); + + if (inetAddr instanceof Inet4Address) { + clientAddress[0] = 4; + for (int j = 0; j < 4; j++) + clientAddress[j + 1] = inetAddr.getAddress()[j]; + } + else if (inetAddr instanceof Inet6Address) { + clientAddress[0] = 6; + for (int j = 0; j < 16; j++) + clientAddress[j + 1] = inetAddr.getAddress()[j]; + } + + inetAddr.getAddress(); + } catch (UnknownHostException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + } + catch (IndexOutOfBoundsException ex) { + /* no ClientLN defined for this report instance */ + } + } + - printReportControlBlockInstance(lnPrefix, rcb, index, reportNumber, reportsCount); + printReportControlBlockInstance(lnPrefix, rcb, index, reportNumber, reportsCount, clientAddress); reportNumber++; } } else { - printReportControlBlockInstance(lnPrefix, rcb, "", reportNumber, reportsCount); + byte[] clientAddress = new byte[17]; + clientAddress[0] = 0; + + printReportControlBlockInstance(lnPrefix, rcb, "", reportNumber, reportsCount, clientAddress); reportNumber++; } } @@ -1259,7 +1369,7 @@ public class StaticModelGenerator { this.logControlBlocks.append(lcbString); } - private void printReportControlBlockInstance(String lnPrefix, ReportControlBlock rcb, String index, int reportNumber, int reportsCount) + private void printReportControlBlockInstance(String lnPrefix, ReportControlBlock rcb, String index, int reportNumber, int reportsCount, byte[] clientIpAddr) { String rcbVariableName = lnPrefix + "_report" + reportNumber; @@ -1326,9 +1436,19 @@ public class StaticModelGenerator { rcbString += rcb.getIntegrityPeriod().toString() + ", "; else rcbString += "0, "; + + rcbString += "{"; + for (int i = 0; i < 17; i++) { + rcbString += "0x" + Integer.toHexString((int) (clientIpAddr[i] & 0xff)); + if (i == 16) + rcbString += "}, "; + else + rcbString += ", "; + } + currentRcbVariableNumber++; - + if (currentRcbVariableNumber < rcbVariableNames.size()) rcbString += "&" + rcbVariableNames.get(currentRcbVariableNumber); else