From 956deaf958a671e126545ee84092dcae2db93028 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 2 May 2016 12:32:07 +0200 Subject: [PATCH 01/36] - MMS client: added support to read domain specific journals from server: MmsConnection_getDomainJournals --- examples/mms_utility/mms_utility.c | 14 ++++++++++++++ src/mms/inc/mms_client_connection.h | 14 ++++++++++++++ src/mms/inc_private/mms_client_internal.h | 7 ++++--- src/mms/iso_mms/client/mms_client_connection.c | 18 ++++++++++++------ .../iso_mms/client/mms_client_get_namelist.c | 7 +------ .../tools/StaticModelGenerator.java | 6 +++++- 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index cdb9791f..792833be 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -156,6 +156,20 @@ int main(int argc, char** argv) { printf(" %s\n", name); } + variableList = MmsConnection_getDomainJournals(con, &error, domainName); + + if (variableList != NULL) { + + element = variableList; + + printf("\nMMS journals for domain %s\n", domainName); + + while ((element = LinkedList_getNext(element)) != NULL) { + char* name = (char*) element->data; + + printf(" %s\n", name); + } + } } if (readVariable) { diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index fc234e7e..85794d2a 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -269,6 +269,20 @@ MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, con LinkedList /* */ MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, const char* domainId); +/** + * \brief Get the names of all journals present in a MMS domain of the server + * + * This will result in a domain specific GetNameList request. + * + * \param self MmsConnection instance to operate on + * \param mmsError user provided variable to store error code + * \param domainId the domain name for the domain specific request + * + * \return the domain specific journal names or NULL if the request failed. + */ +LinkedList /* */ +MmsConnection_getDomainJournals(MmsConnection self, MmsError* mmsError, const char* domainId); + /** * \brief Get the names of all named variable lists associated with this client connection. * diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 8406f9c6..b31891b0 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -94,9 +94,10 @@ struct sMmsConnection { * MMS Object class enumeration type */ typedef enum { - MMS_NAMED_VARIABLE, - MMS_NAMED_VARIABLE_LIST, - MMS_DOMAIN_NAMES + MMS_OBJECT_CLASS_NAMED_VARIABLE = 0, + MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST = 2, + MMS_OBJECT_CLASS_JOURNAL = 8, + MMS_OBJECT_CLASS_DOMAIN = 9 } MmsObjectClass; MmsValue* diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 166ed6f4..f76e28ff 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1048,7 +1048,7 @@ mmsClient_getNameListSingleRequest( payload, continueAfter); else { - if (objectClass == MMS_DOMAIN_NAMES) + if (objectClass == MMS_OBJECT_CLASS_DOMAIN) mmsClient_createMmsGetNameListRequestVMDspecific(invokeId, payload, continueAfter); else @@ -1104,31 +1104,37 @@ mmsClient_getNameList(MmsConnection self, MmsError *mmsError, LinkedList /* */ MmsConnection_getVMDVariableNames(MmsConnection self, MmsError* mmsError) { - return mmsClient_getNameList(self, mmsError, NULL, MMS_NAMED_VARIABLE, false); + return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_NAMED_VARIABLE, false); } LinkedList /* */ MmsConnection_getDomainNames(MmsConnection self, MmsError* mmsError) { - return mmsClient_getNameList(self, mmsError, NULL, MMS_DOMAIN_NAMES, false); + return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_DOMAIN, false); } LinkedList /* */ MmsConnection_getDomainVariableNames(MmsConnection self, MmsError* mmsError, const char* domainId) { - return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE, false); + return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_NAMED_VARIABLE, false); } LinkedList /* */ MmsConnection_getDomainVariableListNames(MmsConnection self, MmsError* mmsError, const char* domainId) { - return mmsClient_getNameList(self, mmsError, domainId, MMS_NAMED_VARIABLE_LIST, false); + return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST, false); +} + +LinkedList /* */ +MmsConnection_getDomainJournals(MmsConnection self, MmsError* mmsError, const char* domainId) +{ + return mmsClient_getNameList(self, mmsError, domainId, MMS_OBJECT_CLASS_JOURNAL, false); } LinkedList /* */ MmsConnection_getVariableListNamesAssociationSpecific(MmsConnection self, MmsError* mmsError) { - return mmsClient_getNameList(self, mmsError, NULL, MMS_NAMED_VARIABLE_LIST, true); + return mmsClient_getNameList(self, mmsError, NULL, MMS_OBJECT_CLASS_NAMED_VARIABLE_LIST, true); } MmsValue* diff --git a/src/mms/iso_mms/client/mms_client_get_namelist.c b/src/mms/iso_mms/client/mms_client_get_namelist.c index b0b03b7c..8ca64b6d 100644 --- a/src/mms/iso_mms/client/mms_client_get_namelist.c +++ b/src/mms/iso_mms/client/mms_client_get_namelist.c @@ -229,12 +229,7 @@ mmsClient_createGetNameListRequestDomainOrVMDSpecific(long invokeId, const char* request->objectClass.present = ObjectClass_PR_basicObjectClass; - if (objectClass == MMS_NAMED_VARIABLE) - asn_long2INTEGER(&request->objectClass.choice.basicObjectClass, - ObjectClass__basicObjectClass_namedVariable); - else if (objectClass == MMS_NAMED_VARIABLE_LIST) - asn_long2INTEGER(&request->objectClass.choice.basicObjectClass, - ObjectClass__basicObjectClass_namedVariableList); + asn_long2INTEGER(&request->objectClass.choice.basicObjectClass, objectClass); asn_enc_rval_t rval; diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index a0b1ea6d..ea88eeee 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -3,7 +3,7 @@ package com.libiec61850.tools; /* * StaticModelGenerator.java * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -68,6 +68,10 @@ public class StaticModelGenerator { private List rcbVariableNames; private int currentRcbVariableNumber = 0; + private StringBuffer logControlBlocks; + private List lcbVariableNames; + private int currentLcbVariableNumber; + private StringBuffer gseControlBlocks; private List gseVariableNames; private int currentGseVariableNumber = 0; From 6d03f18748600f7298f2927f6484f1a9e9fd4682 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 2 May 2016 17:46:57 +0200 Subject: [PATCH 02/36] - started to implement MMS journals server side --- config/stack_config.h | 3 ++ examples/mms_utility/mms_utility.c | 3 ++ src/iec61850/inc_private/logging.h | 46 ++++++++++++++++++ src/iec61850/server/impl/ied_server.c | 14 +++++- src/iec61850/server/mms_mapping/logging.c | 47 +++++++++++++++++++ src/iec61850/server/mms_mapping/reporting.c | 2 +- src/mms/inc/mms_client_connection.h | 4 ++ src/mms/inc/mms_device_model.h | 19 +++++++- src/mms/inc_private/mms_client_internal.h | 3 ++ .../iso_mms/client/mms_client_connection.c | 28 +++++++++++ src/mms/iso_mms/client/mms_client_files.c | 45 ++++++++++++++++++ src/mms/iso_mms/server/mms_domain.c | 14 ++++++ src/mms/iso_mms/server/mms_journal.c | 41 ++++++++++++++++ 13 files changed, 265 insertions(+), 4 deletions(-) create mode 100644 src/iec61850/inc_private/logging.h create mode 100644 src/iec61850/server/mms_mapping/logging.c create mode 100644 src/mms/iso_mms/server/mms_journal.c diff --git a/config/stack_config.h b/config/stack_config.h index 3e900e11..a1a9170f 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -153,6 +153,9 @@ /* default reservation time of a setting group control block in s */ #define CONFIG_IEC61850_SG_RESVTMS 300 +/* include support for IEC 61850 log services */ +#define CONFIG_IEC61850_LOG_SERVICE 1 + /* default results for MMS identify service */ #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" #define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index 792833be..b56ea13b 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -168,6 +168,9 @@ int main(int argc, char** argv) { char* name = (char*) element->data; printf(" %s\n", name); + + printf(" read journal...\n"); + MmsConnection_readJournal(con, &error, domainName, name); } } } diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h new file mode 100644 index 00000000..33de8a89 --- /dev/null +++ b/src/iec61850/inc_private/logging.h @@ -0,0 +1,46 @@ +/* + * logging.h + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + + +#ifndef LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ +#define LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ + +typedef struct { + char* name; + + DataSet* dataSet; + + bool enabled; + + int triggerOps; + +} LogControl; + +LogControl* +LogControl_create(bool buffered, LogicalNode* parentLN); + +void +LogControl_destroy(LogControl* self); + + +#endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ */ diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index f18bebd8..e212db2d 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -773,12 +773,22 @@ IedServer_getStringAttributeValue(IedServer self, const DataAttribute* dataAttri static inline void checkForUpdateTrigger(IedServer self, DataAttribute* dataAttribute) { -#if (CONFIG_IEC61850_REPORT_SERVICE== 1) +#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_IEC61850_LOG_SERVICE == 1)) if (dataAttribute->triggerOptions & TRG_OPT_DATA_UPDATE) { + +#if (CONFIG_IEC61850_REPORT_SERVICE == 1) MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_VALUE_UPDATE); - } #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + //MmsMapping_triggerLogObserver(self->mmsMapping, dataAttribute->mmsValue,...) + +#endif + + + } +#endif /* ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_IEC61850_LOG_SERVICE == 1)) */ } static inline void diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c new file mode 100644 index 00000000..a7fddee5 --- /dev/null +++ b/src/iec61850/server/mms_mapping/logging.c @@ -0,0 +1,47 @@ +/* + * logging.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "libiec61850_platform_includes.h" +#include "stack_config.h" +#include "logging.h" + + +LogControl* +LogControl_create(bool buffered, LogicalNode* parentLN) +{ + LogControl* self = (LogControl*) GLOBAL_MALLOC(sizeof(LogControl)); + + self->enabled = false; + self->dataSet = NULL; + self->triggerOps = 0; + + return self; +} + +void +LogControl_destroy(LogControl* self) +{ + if (self != NULL) { + GLOBAL_FREEMEM(self); + } +} diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index b0e052dc..03d29fd3 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -31,7 +31,7 @@ #include "simple_allocator.h" #include "mem_alloc_linked_list.h" -#include "reporting.h" +#include "stack_config.h" #include "mms_mapping_internal.h" #include "mms_value_internal.h" #include "conversions.h" diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 85794d2a..9b895e65 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -711,6 +711,10 @@ MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const cha MmsFileDirectoryHandler handler, void* handlerParameter); +LinkedList +MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId); + + /** * \brief Destroy (free) an MmsServerIdentity object * diff --git a/src/mms/inc/mms_device_model.h b/src/mms/inc/mms_device_model.h index 218bdeff..6bed93ee 100644 --- a/src/mms/inc/mms_device_model.h +++ b/src/mms/inc/mms_device_model.h @@ -49,14 +49,22 @@ typedef struct { MmsDomain** domains; } MmsDevice; + +struct sMmsJournal { + char* name; +}; + +typedef struct sMmsJournal* MmsJournal; + /* - * Server side data structure to hold informations for a MMS domain (Logical Device) + * Server side data structure to hold information of an MMS domain (Logical Device) */ struct sMmsDomain { char* domainName; int namedVariablesCount; MmsVariableSpecification** namedVariables; LinkedList /**/ namedVariableLists; + LinkedList /* */ journals; }; /** @@ -80,6 +88,9 @@ MmsDomain_create(char* domainName); char* MmsDomain_getName(MmsDomain* self); +void +MmsDomain_addJournal(MmsDomain* self, MmsJournal journal); + /** * Delete a MmsDomain instance * @@ -185,6 +196,12 @@ MmsDevice_getNamedVariableLists(MmsDevice* self); MmsNamedVariableList MmsDevice_getNamedVariableListWithName(MmsDevice* self, const char* variableListName); +MmsJournal +MmsJournal_create(const char* name); + +void +MmsJournal_destroy(MmsJournal self); + /**@}*/ #ifdef __cplusplus diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index b31891b0..95f4a8b8 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -260,4 +260,7 @@ int mmsClient_createMmsGetNameListRequestAssociationSpecific(long invokeId, ByteBuffer* writeBuffer, const char* continueAfter); +void +mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId); + #endif /* MMS_MSG_INTERNAL_H_ */ diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index f76e28ff..dd4dbff7 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1637,6 +1637,34 @@ MmsConnection_getServerStatus(MmsConnection self, MmsError* mmsError, int* vmdLo } +LinkedList +MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId) +{ + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + *mmsError = MMS_ERROR_NONE; + + uint32_t invokeId = getNextInvokeId(self); + + mmsClient_createReadJournalRequest(invokeId, payload, domainId, itemId); + + ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload); + + if (self->lastResponseError != MMS_ERROR_NONE) + *mmsError = self->lastResponseError; + else if (responseMessage != NULL) { + // if (mmsClient_parseFileOpenResponse(self, &frsmId, fileSize, lastModified) == false) + // *mmsError = MMS_ERROR_PARSING_RESPONSE; + } + + releaseResponse(self); + + if (self->associationState == MMS_STATE_CLOSED) + *mmsError = MMS_ERROR_CONNECTION_LOST; + + return NULL; +} + int32_t MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filename, uint32_t initialPosition, uint32_t* fileSize, uint64_t* lastModified) diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index 81b18bb2..2ecf29a1 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -32,6 +32,51 @@ #include "ber_decode.h" #include "conversions.h" +void +mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId) +{ + /* calculate sizes */ + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); + + uint32_t domainIdStringSize = strlen(domainId); + uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; + + uint32_t itemIdStringSize = strlen(itemId); + uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; + + uint32_t objectIdSize = domainIdSize + itemIdSize; + + uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); + + uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalNameSize) + (journalNameSize); + + uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; + + /* encode to buffer */ + int bufPos = 0; + uint8_t* buffer = request->buffer; + + bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + + /* Encode FileOpen tag (context | structured ) [65 = 41h] */ + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); + +// bufPos = BerEncoder_encodeTL(0xa1, domainIdStringSize, buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); + + request->size = bufPos; +} + void mmsClient_createFileOpenRequest(uint32_t invokeId, ByteBuffer* request, const char* fileName, uint32_t initialPosition) { diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index 5e22fe29..b2ceb194 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -41,6 +41,7 @@ MmsDomain_create(char* domainName) self->domainName = copyString(domainName); self->namedVariableLists = LinkedList_create(); + self->journals = NULL; return self; } @@ -57,6 +58,10 @@ MmsDomain_destroy(MmsDomain* self) GLOBAL_FREEMEM(self->namedVariables); } + if (self->journals != NULL) { + LinkedList_destroyDeep(self->journals, (LinkedListValueDeleteFunction) MmsJournal_destroy); + } + LinkedList_destroyDeep(self->namedVariableLists, (LinkedListValueDeleteFunction) MmsNamedVariableList_destroy); GLOBAL_FREEMEM(self); @@ -68,6 +73,15 @@ MmsDomain_getName(MmsDomain* self) return self->domainName; } +void +MmsDomain_addJournal(MmsDomain* self, MmsJournal journal) +{ + if (self->journals == NULL) + self->journals = LinkedList_create(); + + LinkedList_add(self->journals, (void*) journal); +} + bool MmsDomain_addNamedVariableList(MmsDomain* self, MmsNamedVariableList variableList) { diff --git a/src/mms/iso_mms/server/mms_journal.c b/src/mms/iso_mms/server/mms_journal.c new file mode 100644 index 00000000..48758333 --- /dev/null +++ b/src/mms/iso_mms/server/mms_journal.c @@ -0,0 +1,41 @@ +/* + * mms_journal.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "mms_device_model.h" + +MmsJournal +MmsJournal_create(const char* name) +{ + MmsJournal self = (MmsJournal) GLOBAL_MALLOC(sizeof(struct sMmsJournal)); + + self->name = copyString(name); + + return self; +} + +void +MmsJournal_destroy(MmsJournal self) +{ + GLOBAL_FREEMEM(self->name); + GLOBAL_FREEMEM(self); +} From 379c2ac4450e5a77c6a2107f3d114667ada6d657 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 2 May 2016 18:59:26 +0200 Subject: [PATCH 03/36] - GeneralLog is default name for Logs --- .../model_generator/src/com/libiec61850/scl/model/Log.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/model_generator/src/com/libiec61850/scl/model/Log.java b/tools/model_generator/src/com/libiec61850/scl/model/Log.java index 3b39343d..da7f595e 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/Log.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/Log.java @@ -33,8 +33,10 @@ public class Log { public Log(Node logNode) throws SclParserException { name = ParserUtils.parseAttribute(logNode, "name"); - if (name == null) - throw new SclParserException(logNode, "Log is missing required attribute name!"); + if (name == null) { + /* Use default log name */ + name = "GeneralLog"; + } } public String getName() { From fb49549f660a721cd0e60ca8220b138466a45643 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 3 May 2016 17:51:21 +0200 Subject: [PATCH 04/36] - added Log data structure --- src/iec61850/inc/iec61850_model.h | 14 ++++++++++++++ src/iec61850/inc_private/mms_mapping_internal.h | 6 +++++- src/iec61850/server/mms_mapping/logging.c | 1 + src/iec61850/server/mms_mapping/mms_mapping.c | 4 ++++ src/iec61850/server/mms_mapping/reporting.c | 2 +- src/mms/iso_mms/server/mms_journal.c | 2 ++ 6 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 052ba95a..6e3c66a0 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -84,6 +84,8 @@ typedef struct sSVControlBlock SVControlBlock; typedef struct sLogControlBlock LogControlBlock; +typedef struct sLog Log; + typedef enum { IEC61850_BOOLEAN = 0,/* int */ IEC61850_INT8 = 1, /* int8_t */ @@ -167,6 +169,8 @@ struct sIedModel { GSEControlBlock* gseCBs; SVControlBlock* svCBs; SettingGroupControlBlock* sgcbs; + LogControlBlock* lcbs; + Log* logs; void (*initializer) (void); }; @@ -268,6 +272,16 @@ struct sLogControlBlock { uint8_t trgOps; /* TrgOps - trigger conditions */ uint8_t options; /* OptFlds */ uint32_t intPeriod; /* IntgPd - integrity period */ + + LogControlBlock* sibling; /* next control block in list or NULL if this is the last entry */ +}; + +struct sLog { + LogicalNode* parent; + + char* name; + + Log* sibling; /* next log instance in list or NULL if this is the last entry */ }; struct sSettingGroupControlBlock { diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index ef09bb24..367d0e4a 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -1,7 +1,7 @@ /* * mms_mapping_internal.h * - * Copyright 2013, 2015 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -35,6 +35,10 @@ struct sMmsMapping { MmsServer mmsServer; LinkedList reportControls; +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + LinkedList* logControls; +#endif + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) LinkedList gseControls; const char* gooseInterfaceId; diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index a7fddee5..46632d88 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -23,6 +23,7 @@ #include "libiec61850_platform_includes.h" #include "stack_config.h" +#include "mms_mapping.h" #include "logging.h" diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index fdb430a9..3b1b1425 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1193,6 +1193,10 @@ MmsMapping_create(IedModel* model) self->reportControls = LinkedList_create(); #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + self->logControls = LinkedList_create(); +#endif + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) self->gseControls = LinkedList_create(); self->gooseInterfaceId = NULL; diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 03d29fd3..22163fcd 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -31,10 +31,10 @@ #include "simple_allocator.h" #include "mem_alloc_linked_list.h" -#include "stack_config.h" #include "mms_mapping_internal.h" #include "mms_value_internal.h" #include "conversions.h" +#include "reporting.h" #include #ifndef DEBUG_IED_SERVER diff --git a/src/mms/iso_mms/server/mms_journal.c b/src/mms/iso_mms/server/mms_journal.c index 48758333..2f2f91df 100644 --- a/src/mms/iso_mms/server/mms_journal.c +++ b/src/mms/iso_mms/server/mms_journal.c @@ -21,7 +21,9 @@ * See COPYING file for the complete license text. */ +#include "libiec61850_platform_includes.h" #include "mms_device_model.h" +#include "mms_server_internal.h" MmsJournal MmsJournal_create(const char* name) From 2d753f3b24cbe97e8d1e0d325e39e20d7a7ed457 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 4 May 2016 17:44:30 +0200 Subject: [PATCH 05/36] - added static model generation for LogCBs and Logs - added MMS data model for LCBs - server: MMS getNameList now supports journals --- .../simpleIO_direct_control.icd | 10 +- examples/server_example3/static_model.c | 12 + src/iec61850/inc/iec61850_model.h | 4 +- src/iec61850/inc_private/logging.h | 7 +- .../inc_private/mms_mapping_internal.h | 2 +- src/iec61850/server/mms_mapping/logging.c | 236 +++++++++++++++++- src/iec61850/server/mms_mapping/mms_mapping.c | 57 ++++- src/iec61850/server/mms_mapping/reporting.c | 2 +- src/mms/inc/mms_device_model.h | 2 +- src/mms/iso_mms/server/mms_domain.c | 4 +- .../iso_mms/server/mms_get_namelist_service.c | 39 +++ tools/model_generator/genmodel.jar | Bin 79544 -> 83789 bytes .../com/libiec61850/scl/model/LogControl.java | 2 +- .../libiec61850/scl/model/LogicalNode.java | 8 + .../tools/StaticModelGenerator.java | 212 +++++++++++++++- 15 files changed, 574 insertions(+), 23 deletions(-) diff --git a/examples/server_example3/simpleIO_direct_control.icd b/examples/server_example3/simpleIO_direct_control.icd index df5636b9..b48a383e 100644 --- a/examples/server_example3/simpleIO_direct_control.icd +++ b/examples/server_example3/simpleIO_direct_control.icd @@ -85,7 +85,15 @@ - + + + + + + + + + diff --git a/examples/server_example3/static_model.c b/examples/server_example3/static_model.c index 84faec82..d28f3abd 100644 --- a/examples/server_example3/static_model.c +++ b/examples/server_example3/static_model.c @@ -1955,6 +1955,16 @@ ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, +extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; +extern LogControlBlock iedModel_GenericIO_LLN0_lcb1; +LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "EventLog", 19, 0, true, true, &iedModel_GenericIO_LLN0_lcb1}; +LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", "", "", 19, 0, true, true, NULL}; + +extern Log iedModel_GenericIO_LLN0_log0; +extern Log iedModel_GenericIO_LLN0_log1; +Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1}; +Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL}; + IedModel iedModel = { "simpleIO", @@ -1964,6 +1974,8 @@ IedModel iedModel = { NULL, NULL, NULL, + &iedModel_GenericIO_LLN0_lcb0, + &iedModel_GenericIO_LLN0_log0, initializeValues }; diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 6e3c66a0..961e640d 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -270,9 +270,11 @@ struct sLogControlBlock { char* logRef; /* object reference to the journal. Defaults to /$GeneralLog */ uint8_t trgOps; /* TrgOps - trigger conditions */ - uint8_t options; /* OptFlds */ uint32_t intPeriod; /* IntgPd - integrity period */ + bool logEna; /* enable log by default */ + bool reasonCode; /* include reason code in log */ + LogControlBlock* sibling; /* next control block in list or NULL if this is the last entry */ }; diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 33de8a89..9aedaadb 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -30,6 +30,8 @@ typedef struct { DataSet* dataSet; + MmsDomain* domain; + bool enabled; int triggerOps; @@ -37,10 +39,13 @@ typedef struct { } LogControl; LogControl* -LogControl_create(bool buffered, LogicalNode* parentLN); +LogControl_create(LogicalNode* parentLN); void LogControl_destroy(LogControl* self); +MmsVariableSpecification* +Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, + int lcbCount); #endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ */ diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index 367d0e4a..82660792 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -36,7 +36,7 @@ struct sMmsMapping { LinkedList reportControls; #if (CONFIG_IEC61850_LOG_SERVICE == 1) - LinkedList* logControls; + LinkedList logControls; #endif #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 46632d88..33ba25a9 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -25,10 +25,19 @@ #include "stack_config.h" #include "mms_mapping.h" #include "logging.h" +#include "linked_list.h" +#include "array_list.h" +#include "hal_thread.h" + +#include "simple_allocator.h" +#include "mem_alloc_linked_list.h" + +#include "mms_mapping_internal.h" +#include "mms_value_internal.h" LogControl* -LogControl_create(bool buffered, LogicalNode* parentLN) +LogControl_create(LogicalNode* parentLN) { LogControl* self = (LogControl*) GLOBAL_MALLOC(sizeof(LogControl)); @@ -46,3 +55,228 @@ LogControl_destroy(LogControl* self) GLOBAL_FREEMEM(self); } } + +static LogControlBlock* +getLCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index) +{ + int lcbCount = 0; + + LogControlBlock* nextLcb = self->model->lcbs; + + while (nextLcb != NULL ) { + if (nextLcb->parent == logicalNode) { + + if (lcbCount == index) + return nextLcb; + + lcbCount++; + + } + + nextLcb = nextLcb->sibling; + } + + return NULL ; +} + +static char* +createDataSetReferenceForDefaultDataSet(LogControlBlock* lcb, LogControl* logControl) +{ + char* dataSetReference; + + char* domainName = MmsDomain_getName(logControl->domain); + char* lnName = lcb->parent->name; + + dataSetReference = createString(5, domainName, "/", lnName, "$", lcb->dataSetName); + + return dataSetReference; +} + +static MmsValue* +createTrgOps(LogControlBlock* reportControlBlock) { + MmsValue* trgOps = MmsValue_newBitString(-6); + + uint8_t triggerOps = reportControlBlock->trgOps; + + if (triggerOps & TRG_OPT_DATA_CHANGED) + MmsValue_setBitStringBit(trgOps, 1, true); + if (triggerOps & TRG_OPT_QUALITY_CHANGED) + MmsValue_setBitStringBit(trgOps, 2, true); + if (triggerOps & TRG_OPT_DATA_UPDATE) + MmsValue_setBitStringBit(trgOps, 3, true); + if (triggerOps & TRG_OPT_INTEGRITY) + MmsValue_setBitStringBit(trgOps, 4, true); + + //TODO remove - GI doesn't exist here! + if (triggerOps & TRG_OPT_GI) + MmsValue_setBitStringBit(trgOps, 5, true); + + return trgOps; +} + +static MmsVariableSpecification* +createLogControlBlock(LogControlBlock* logControlBlock, + LogControl* logControl) +{ + MmsVariableSpecification* rcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + rcb->name = copyString(logControlBlock->name); + rcb->type = MMS_STRUCTURE; + + MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); + mmsValue->deleteValue = false; + mmsValue->type = MMS_STRUCTURE; + + int structSize = 9; + + mmsValue->value.structure.size = structSize; + mmsValue->value.structure.components = (MmsValue**) GLOBAL_CALLOC(structSize, sizeof(MmsValue*)); + + rcb->typeSpec.structure.elementCount = structSize; + + rcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(structSize, + sizeof(MmsVariableSpecification*)); + + /* LogEna */ + MmsVariableSpecification* namedVariable = + (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + + namedVariable->name = copyString("LogEna"); + namedVariable->type = MMS_BOOLEAN; + + rcb->typeSpec.structure.elements[0] = namedVariable; + mmsValue->value.structure.components[0] = MmsValue_newBoolean(logControlBlock->logEna); + + /* LogRef */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("LogRef"); + namedVariable->typeSpec.visibleString = -129; + namedVariable->type = MMS_VISIBLE_STRING; + rcb->typeSpec.structure.elements[1] = namedVariable; + + if (logControlBlock->logRef != NULL) { + mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logControlBlock->logRef); + } + else { + char* logRef = createString(4, logControl->domain->domainName, "/", logControlBlock->parent->name, + "$GeneralLog"); + + mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logRef); + + GLOBAL_FREEMEM(logRef); + } + + /* DatSet */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("DatSet"); + namedVariable->typeSpec.visibleString = -129; + namedVariable->type = MMS_VISIBLE_STRING; + rcb->typeSpec.structure.elements[2] = namedVariable; + + if (logControlBlock->dataSetName != NULL) { + char* dataSetReference = createDataSetReferenceForDefaultDataSet(logControlBlock, logControl); + + mmsValue->value.structure.components[2] = MmsValue_newVisibleString(dataSetReference); + GLOBAL_FREEMEM(dataSetReference); + } + else + mmsValue->value.structure.components[2] = MmsValue_newVisibleString(""); + + /* OldEntrTm */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("OldEntrTm"); + namedVariable->type = MMS_BINARY_TIME; + namedVariable->typeSpec.binaryTime = 6; + rcb->typeSpec.structure.elements[3] = namedVariable; + + mmsValue->value.structure.components[3] = MmsValue_newBinaryTime(false); + + /* NewEntrTm */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("NewEntrTm"); + namedVariable->type = MMS_BINARY_TIME; + namedVariable->typeSpec.binaryTime = 6; + rcb->typeSpec.structure.elements[4] = namedVariable; + + mmsValue->value.structure.components[4] = MmsValue_newBinaryTime(false); + + /* OldEntr */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("OldEntr"); + namedVariable->type = MMS_OCTET_STRING; + namedVariable->typeSpec.octetString = 8; + + rcb->typeSpec.structure.elements[5] = namedVariable; + + mmsValue->value.structure.components[5] = MmsValue_newOctetString(8, 8); + + /* NewEntr */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("NewEntr"); + namedVariable->type = MMS_OCTET_STRING; + namedVariable->typeSpec.octetString = 8; + + rcb->typeSpec.structure.elements[6] = namedVariable; + + mmsValue->value.structure.components[6] = MmsValue_newOctetString(8, 8); + + /* TrgOps */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("TrgOps"); + namedVariable->type = MMS_BIT_STRING; + namedVariable->typeSpec.bitString = -6; + rcb->typeSpec.structure.elements[7] = namedVariable; + mmsValue->value.structure.components[7] = createTrgOps(logControlBlock); + + /* IntgPd */ + namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("IntgPd"); + namedVariable->type = MMS_UNSIGNED; + namedVariable->typeSpec.unsignedInteger = 32; + rcb->typeSpec.structure.elements[8] = namedVariable; + mmsValue->value.structure.components[8] = + MmsValue_newUnsignedFromUint32(logControlBlock->intPeriod); + + + //TODO logControl->rcbValues = mmsValue; + + return rcb; +} + + + +MmsVariableSpecification* +Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, + int lcbCount) +{ + MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, + sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("LG"); + namedVariable->type = MMS_STRUCTURE; + + namedVariable->typeSpec.structure.elementCount = lcbCount; + namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(lcbCount, + sizeof(MmsVariableSpecification*)); + + int currentLcb = 0; + + while (currentLcb < lcbCount) { + + LogControl* logControl = LogControl_create(logicalNode); + + LogControlBlock* logControlBlock = getLCBForLogicalNodeWithIndex(self, logicalNode, currentLcb); + + logControl->name = createString(3, logicalNode->name, "$LG$", logControlBlock->name); + logControl->domain = domain; + + namedVariable->typeSpec.structure.elements[currentLcb] = + createLogControlBlock(logControlBlock, logControl); + + LinkedList_add(self->logControls, logControl); + + currentLcb++; + } + + return namedVariable; +} + + diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 3b1b1425..60d0767f 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1,7 +1,7 @@ /* * mms_mapping.c * - * Copyright 2013, 2014, 2015 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -30,6 +30,7 @@ #include "mms_goose.h" #include "mms_sv.h" #include "reporting.h" +#include "logging.h" #include "control.h" #include "ied_server_private.h" @@ -756,6 +757,25 @@ countReportControlBlocksForLogicalNode(MmsMapping* self, LogicalNode* logicalNod } #endif /* (CONFIG_IEC61850_CONTROL_SERVICE == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) +static int +countLogControlBlocksForLogicalNode (MmsMapping* self, LogicalNode* logicalNode) +{ + int lcbCount = 0; + + LogControlBlock* lcb = self->model->lcbs; + + while (lcb != NULL) { + if (lcb->parent == logicalNode) + lcbCount++; + + lcb = lcb->sibling; + } + + return lcbCount; +} +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) @@ -870,6 +890,19 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, } #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + + int lcbCount = countLogControlBlocksForLogicalNode(self, logicalNode); + + if (lcbCount > 0) { + if (DEBUG_IED_SERVER) + printf(" and %i LOG control blocks\n", lcbCount); + + componentCount++; + } + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) int gseCount = countGSEControlBlocksForLogicalNode(self, logicalNode); @@ -969,7 +1002,15 @@ createNamedVariableFromLogicalNode(MmsMapping* self, MmsDomain* domain, } #endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ - /* TODO create LCBs here */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + if (lcbCount > 0) { + namedVariable->typeSpec.structure.elements[currentComponent] = + Logging_createLCBs(self, domain, logicalNode, lcbCount); + + currentComponent++; + } +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_IEC61850_REPORT_SERVICE == 1) if (brcbCount > 0) { @@ -1069,6 +1110,18 @@ createMmsDomainFromIedDevice(MmsMapping* self, LogicalDevice* logicalDevice) if (domain == NULL) goto exit_function; +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + /* add logs (journals) */ + Log* log = self->model->logs; + + while (log != NULL) { + + MmsDomain_addJournal(domain, log->name); + + log = log->sibling; + } +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + int nodesCount = LogicalDevice_getLogicalNodeCount(logicalDevice); /* Logical nodes are first level named variables */ diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 22163fcd..8d16731a 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -770,7 +770,7 @@ createUnbufferedReportControlBlock(ReportControlBlock* reportControlBlock, rcb->typeSpec.structure.elementCount = structSize; - rcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(12, + rcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(structSize, sizeof(MmsVariableSpecification*)); MmsVariableSpecification* namedVariable = diff --git a/src/mms/inc/mms_device_model.h b/src/mms/inc/mms_device_model.h index 6bed93ee..b2290ab4 100644 --- a/src/mms/inc/mms_device_model.h +++ b/src/mms/inc/mms_device_model.h @@ -89,7 +89,7 @@ char* MmsDomain_getName(MmsDomain* self); void -MmsDomain_addJournal(MmsDomain* self, MmsJournal journal); +MmsDomain_addJournal(MmsDomain* self, const char* name); /** * Delete a MmsDomain instance diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index b2ceb194..6f406422 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -74,11 +74,13 @@ MmsDomain_getName(MmsDomain* self) } void -MmsDomain_addJournal(MmsDomain* self, MmsJournal journal) +MmsDomain_addJournal(MmsDomain* self, const char* name) { if (self->journals == NULL) self->journals = LinkedList_create(); + MmsJournal journal = MmsJournal_create(name); + LinkedList_add(self->journals, (void*) journal); } diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index a36dbedb..fe93737f 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -173,6 +173,35 @@ addSubNamedVaribleNamesToList(LinkedList nameList, char* prefix, MmsVariableSpec #endif /* (CONFIG_MMS_SUPPORT_FLATTED_NAME_SPACE == 1) */ + + + +static LinkedList +getJournalListDomainSpecific(MmsServerConnection connection, char* domainName) +{ + MmsDevice* device = MmsServer_getDevice(connection->server); + + LinkedList nameList = NULL; + + MmsDomain* domain = MmsDevice_getDomain(device, domainName); + + if (domain != NULL) { + nameList = LinkedList_create(); + + LinkedList journalList = domain->journals; + + while ((journalList = LinkedList_getNext(journalList)) != NULL) { + + MmsJournal journal = (MmsJournal) LinkedList_getData(journalList); + + LinkedList_add(nameList, (void*) journal->name); + } + + } + + return nameList; +} + static LinkedList getNameListDomainSpecific(MmsServerConnection connection, char* domainName) { @@ -503,6 +532,16 @@ mmsServer_handleGetNameListRequest( LinkedList_destroy(nameList); } } + else if (objectClass == OBJECT_CLASS_JOURNAL) { + LinkedList nameList = getJournalListDomainSpecific(connection, domainSpecificName); + + if (nameList == NULL) + mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + else { + createNameListResponse(connection, invokeId, nameList, response, continueAfterId); + LinkedList_destroy(nameList); + } + } #if (MMS_DATA_SET_SERVICE == 1) else if (objectClass == OBJECT_CLASS_NAMED_VARIABLE_LIST) { LinkedList nameList = getNamedVariableListsDomainSpecific(connection, domainSpecificName); diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index 784ac0acf1047d8784937654520b803e70718a94..3c50d337f1d0edd6a3cbe4d6e60b660d53df8005 100644 GIT binary patch literal 83789 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 AtN;K2 literal 79544 zcmaI61B@?0x9&Z*ZQC~XnE$bD+qSJewr$(CJ$uYOHotxDx%qN)bKcwOPP$Xi>a6On zU#jxdQjh@!Lj!_>f&!wCv62A#KMrUhFd%@KiV(e&oH*n6BoGiNkb(>()PE^J{q2Ho$(u&Q z%ZlmuFZw#+H#T}_Ak$j>YaSc9Zp9v@BF9Dz;~B*N%9^FFv)tuhPCoyx|H>K$2{cAP0w`Kg#3Z(z7u(33EJM({x5%sh) zw6!z_*qfNzNSNB0IvKjyJ24pB7&<$*XhM6atUUeXWMy=x?IREV3Lu&S5)u}Miq4ls zf|r6CKw=U?a>S)bW@5^SWJQJ3(n;)G(be7-V^*_O)uN<|YHb}TY1!0D!{};XZPn7Y z{w?;F_pm!{LLnRU>idJ}yr( zvt=pU7?%ac%<8#DY@G8vp3{`&u`sXt7-Pt%N9@VO3mBwTI%AF`IyYM$jces%g;p6j zi$3X$u%%UrCfQ~VSr5-SeX3rMoQ#XsMSgOHP_cc=E{mnyf@Oz~I@I3^o*J})#$s+j znd&{iZW*f!k8&q$3AapU6aG=b%Hji0`NYLrh;!4UR>%fU&L>Zet3ljtmdGU=4A;C| z%_3L{^!YNyo--aAhI@;$XiB_voo?onQG&DU#k^&(+-aolT-pgQjxE|GtV`#7;LMZL7>EKna*IkeeewYEik@5wC}M5C*^+E9=izyHXDGG@KDhG%~RF>P5v!q@;~vky&peKlrHfx%|@0qdp0+uyC{$RU9f5G5Fd-c!Td zm>qQR+D{qKlq;fy^0XZ+Dsb|%WkPuZ>+$KYkns5xEMIKWEjm=t|M_dYs}|87zWK4@ zUX(w!m&))7nGk)?i|hhIV=C7r22t9d z<^-~MCI#VMHAYAVN2~RFA{*!L(Uw9{8P;%Y!E^muTR-DCp=Jy5ndOr=ChP|eAtkUp z8(J<69$dGiJgx@;Ba*l|O2q=M#s##5k72>K={)OWwUA&@9;?|0xjY1NLAk(@l~ImP zsaI`lJ>vnaxbAm;RYT+K$}~xwv#R@Rk@9iGDcgIfP+zfKu-no@h+?;A59J?wC(mv@ zeX;{9*al?XnSy5n)V(_9V7+`yc&|u5(|p`6_;>c6>IHi^uTVX5xEtG&x4+-Nk{PEp zE$@;lFFHwr{bc$`%O_58A2XTWdnD&g=Pw^2%t3>9i`(GdLBA52&T+#y%||KgmXZ@%#071h`Y ze5VT=!bUv9chODvjInhfdgO1SRuO^)knBSDz>cOXte~CW6S((9K4fC>PWcNo;WMUFkoBkMgks9A~J=n;edtaJ}yLUVPS4Wea_9??4 zUpIy>RhHFpgy!?7kAktiOlLBiRSN>a&r86<*HKYn@0;&9ZP7O@YaT(?-PW;`2QXne z6I-nC>4)o*?7wY_>%e67Wtb;Y^!+h}6#)qe$d>!y2n>+>#dI zN}T17%WY)#5k$C`fkd#ah?F^RfoaqeN~Cqea75wg*mp)|*a%K42Ev>wi8FDm%6+(f z(s61lki%#cNJBpq!*E8}=!PvIhS(gfNm=noM>7}N8Zeq6dr;adf&*G}X$AiZB{fU?=Z-MNF91bjwiM-?^Gq3>ruTqfd|jaMEU5u z1Iw^hX1+1jwl^nfSXt+sYiN#n=8iL`Y^iTvW_W|k_-t!CFs@_(>y+vzVVgxdn;G6U z+#)mTnpYINB3PSeunzOZr%quW6`C>#5p~klcdnWHn%F4=9DP;9- zrYiTe3kTWul^P5{?*1wY?h$-L$#Z#ASQSAFrs4Mv>vx)on3lH;g822d#S9l#Y&ZoH zT&5I|?Prp#JU$hifpoMf3o}$n(v%@*=tG$0Y#&53+=i1bXbFwC7fyux+8^B`P~`Rz zUsRccp_rM`)gq2j2rktg`>IGy#KJiW2#jz(aB2vr5EdX&(`lp=L(>*}`a1QkN}lF3 zL&_u-h+*qv7`09=V-lwwd~Ka&(Jb$gvV_aqK8CKzNU5#Y@nE3aJL1QMHBmVOswR%e z(-e5I$?rLynp@t2;HnqxAAzFPv#qYATu;3vETRv{|Z#0IucT zR4rp>$ej?I=7v$U%GC2{s}E&8qolfd@|jq6WLPeKF{5l<@-ffl!WhiJZl)NCa177x zgv0n{h%6s`l!Ks%uvQD#`JwYNeNhdS&s2Mwo3_czf~M)V^`%6QE%8OW?M;K_HImnb z=}Q->rgXL$6h{~#xh741q!W{0vy~#g84HXktNiR1gE&SMexi4{6>D9E#rn?0Kcflz zN*A0MZ|~3{!k3#{8o4I#`iVkq_LxtWFVddxx^2WLPj^pgBPF0^J27>~T;f}sR@_58 zUcXG?HDXU7_PoNf1RMM3sI(Thrx&-PXvhs=M~wU(GW3)3V!txdA6zt?gCdXVU8!cFH@L|l^$EgJqCa1voc5y4{CL9GFKe~n@kOLp zpNjG78%UwYc>QvuvUxAy7t58h$kJykAPNfgn>|nV<@v z?i;_tSz>jONVDZG*F}kRF0!;l*Ui>iCpYySe7KO|Qzk|iO?SvSv9F}z6pxhl@_{Bx z+A_aH72j&2a14gEIR~HkYYWINuj3hiac`%t_0l|1 zszanOn#2x`J$*0QW&gJhaeBZgwV^+%2`ycK@#dD2N?j7(rw{~9+OsyJPIgIZ*z~#` z4HxmQ{|!T)RW~$;M6mr}l#mumtt>RBb1^ipGj$(2bk{t0_GgV@)DV*fi0XSScE{|- zv%7x}FM;pL+; zr^=^t`3_b)>ZWwrZfLgocn$h`7qbQi%oQ`&wiYEE_6SqJS~FM+@$&Qp*|HH9mdN!X z7THcW7DU(6Vva-Y)gaR8Jk~uu#8CSw-Yfu))QE~|KCfRgh_{UB6dF%#JqWdgGwPO3 zybHf%mYF#QP*3eJ{3q$_5anYGBMpPAlc{%^J21nQz-RJ;&kW~$ZJVPgK$|n`M1x~Z z2X25brXQNhBssMJrAaXqwIRS8_D!EY^^hz(bi_iKKS(1gdOm=*0S@sHjpNa&I|}`k zQtg1XdQdMr4wNexZFYFdXOejyk;ND=WY5-JxUUM|$FBW-;~qeI19Z>cU3%&l3XbDf zxBukpbC27-G#a<8almmdtkCR^yZlibFa#E9pY;4p>JP<&mmQpm4)=6B%D&?6v z2)1NN&s~l}H*CZ{-9^sQ|crWWnZ>VNR8PG)!4S`c0P16&wq|O4BB|FUR`}&`FyC!I^|k;|{vSVgY%=xn@4X0k+reac$mC{lldzzbX zN|I6aT2oCb4Pj%F_}tu#DV?P6*Pmj^JsB)bT{Yp#is8~I!@)PYJoB7GUy(S|B%=($ z}2Lo8MS`AXheLc33 zsF15`WshKcd0e=_ws=0L4)dYhXN6GtImj*y*P{^I@ej8mKHaL zhb)tWW;hX3mRz;@)a^-Tj(vFQOME&a?Cq%-`(9eXxKt5YXL#Gf_^$%GLs%PeTNaRZ zWqbPw?_lngc-ti~j%}Gi-ph2`Rg;c+mHiwoP%l*_XDYOJ0X(pXl@xr%T6f0i14UM# z1j?L-U0z^hGIiAN)oe0S7R}lI8HMbxfE#b=(y10oy4eU?N9+SuZ;E!q5EWt&7FGL zz+G!yCU-CO1+^BdIC$WP+I=C7xfA3}>{a(cm-j_pZ;=`L1?w|tbGmM!wZD}=(SP*Grgh-S!;)P-CKHY`Xt&Xp!PkeLbIfzh%IuUBt@*Myv(jsF76 z{@Gn-WS)dMM7(SBFun8YKG_&%T7Ud;@6b=3^%4zwGP`uT8Fc4G?AmgX3V${XU&Vhb(a~dmf%u!+%LJ7v2yG{ zhgLV~CG$L<6kzRFW!Bj_J$Nx{FP$5)Mx5YLnOj|Z7=9WmFTjiE*S)* zR}eal`blrT1)|Sux6YG)cBl$_aZ)(Fv|`z%DRPEIk6r&MlM0-v z{B<3(&!Cr|`L}&b={C0lQQDV#osI>V@`rxz7W40uJLu0>#E;&@?Mucd`QJWvglz8( zf=C6g?}U{Pt_=z`Lbeh=jIC`q=;e>Lh}oQz#g>U!#9_`|;*6hgYC{|@KB>RNW3W<2 zEo39ah@}Ci#8v*PK`F5D;WJj_x(A=g?jC=~dXi}S`fQ{gx3It5P`_>{Sil?b+Z>XP(NwE2mMkR znPWAtOcA*V4Pnu58f{02u=yuRw-ET$ve}E`#-cS1C9x*qYCk%ikT#AGAarcLUrmh9^$q@-lg)tMM)GUOn&#D!j#SL~V}cT<-Sb zemC~P%De1OFkI=-T|%zi3@hJP^)K0d$X#h^8Zc%k8PNV6WToX{-p%R3pQC91q4SIV z8GBon$UaL&n)!DWh>HIA@7vhgGyT^gY(mSKwIC`e%#YG}s( z{isVQC8CL5}rOtPB2HIj4iJ7i?$xwPh!M*EvJCoOZ`*JFb7RHx~U%<}NS6 z`*`rUL~EG5vi^EDZd{gTJ>qjP*F}PuUu-bjr?>2V!xtgO4=d})PZ}()U)IC6Go1a= z`48lO`NQZUzpMGtfq-V{|Jxt_pS@X@|LDysyBNCs-_|S{^M9MOB`LbrOA1(HKV{Zw zt!}kd4_~%c|9m#%u9jKMWxFXe-K>jEfSAw{Tf4<-44Y~<>LV!BJ1`iy{Yqp+1eDkD z?|i~v^N2)eEw6k#Ovcy3$gO)npK@}u+}w=5KM%*&K-AgMMwD)1^%y(IG*9)<((5yg z;R|EyQ|n_2SRdF@;!z88SO&$|*rULsM2AF&NQd>-1@6|S3tc|v+ zbW7u8kV_xOorY7aax2Zp)u$b5CHoo2xJ{+M{`kld`3%?BWFF(ATb!Tgpgy*nxc7>GdG!acAmC>)Sj)m zTmWjYTb0|a(qKzD|EiGVr6t4~LJcujkA-20=Z`W)F$cnbMj2hUOhuif;EX<&n{KME zxZ-n$p>ETSaZO;EA7O)sjZ(45G0U-1V3CVwFi)f#WgBLP(#%W9l+tOjj5yb&)-m?kM=Yk-qk|#E(zW zP)d`<|7>#FAYfIYj6I3oIGH|`$q4ko)ZK7(2^I6fCzN6!j)`3t?ZAejvH-UKp2b9} z(-%8M@|g6epF@VjASCA6T~=F6s6(-=sxwrYSc9@$U!$`0QM^RIiq0Hc$851Tj(_b< z;=l0b{eam!ykg$tUElJNudjRbMfdKy$+>deQ*UyA?G58!c%+mh3X;=WM54byxGKCV z)gNOsh0yH0CN6i8kx4Pt2@=vAp}R;Susgsww%s1ko%C*xhfG1d!*WiUyM?f)-rENi zQ1Tlz_bK>{vVWC(6ZzFlL>5HCpr+^+#n2FZ0lF@nwK(hKw!6T*O&8u;9o zf7lp13T43SD~J>#%P0M#m^q-;>hX&CUpTfCU4S!!0s(PA{J(Hy{f{{Qk6R`GCy%I_ z|9GSX@JPwpX0E!s*YNm0*vf7WSB~s7=d4<=EKt~@gXhRX@bctPCr2LS{5OHse`;z; z8tEGA^*qVXAd&uI>P0UgPa{{+qY!fRBG{MAdYaA6==t`RcM8P9g)2e$ZoD26i1iyn zGo0+oaN{43>vLTF3RsZXmf~T|(^!Tk*xaMUqg01Xhfs&*A{FZ=((g$& z?a__4EB}}*k!XTE4l|sJw%chw?!NI>t2xhFxs)mWb*3Xk#ACLeYWy|^wh0k_0yc=c ztd`$o!CktlQ=OrwTDPIHq2Q1FH3FHv$kgRB&ikVG3!!gP+lX<9|sWL`$6wf%<5u&o+Pm9%bB$K-L zaueQk1~X|11W6JNuQI*gAzZ?w_o+6~ zQ&)SKs8n^am@%}cUb|r&cTrqn#J!jK=0wezbXB6=m0{N)X?G$rK`z<=zeVj2Dh#vP z-?jGwcktGCHwN`Jlh;}X6+VTCB(SmQwkVm8V(Fc)s6;)FWyv3T(*Nx81x zd}9y~-kZx$;6eG(*`ItB+26I!3nJf8huEFf!+J~bZ1M+py$fV-JlE=tv=~VQk=`-{ z!P~8LY24Q~}ch4{IhxC{dcp)qTD!TZr^a{Dhh;t?Umem?D!DC$*9ZYdi8EKcvC4{JKonB+*9at{hh#0RkLx8&qPQ1yCCyk>XcJ5@%vnMj0Zc(?e8}LAdIb2R4A}#bNGDt~A_P$0}_T+ivuHo z&LC5olVb)AgL^+{cM|QZFFk0@VJdC%ueF(&9R5VZhqM7x-T)hC9{2k zxOQ+Tvpr*v^LEjGVYo*2+IG8ff!Gey-QqybZ?_~8@Y$`6`r>ffQ`tTTJlDjA%jVce z!SS$#A$||^A~-%Gf^f&QGOX4R>)D!P&Fhg7Z(&54k-ABQ&h(H%Wn?{3XS<^j z)L$CD%=8dJwYNH;2Xxm&y0hKYQtU1be`LP?LM601AO(Dec04ptjI-2Hu13p`nByeL z{!~WJvEPMKe4ilcNA+1B(E5D`N7u36$pd_bR_yT)c<*{B{N{(_Ghd}p1+5P-0iV%{ zU)rdB_Ya&52)x5LvJ1$5+>;~!2JXCQ5yeDnU0S)+en3?$yJUq_<6V_02xjEAPmDDU z=Dz`4EWcPVVWN7`3?X%*N3mtmNT0}{%)TL)Y;x8%>B6vS%_5SFuK}LysUyL_F+G1E;uebr?+o5Lfj>2q zhpGEeQYE|6dAthxr$T4=OZYj7Y%ds_e<+sbkE>=PFR|SgauDP3;o&D`N`uVk`RyL}D4HOch zmRg&Ay67+zclM>_it66l*+W>___K;Sz45b#1aXyj{szhN3T(1u5-&Qkith{42B>ms zh`=Rbg-)C8bqzSVK1w8_O^TJ3<+ag#tJ69gVMVC5;?njFLl7r#Qj z0i!Z?Wq1YN+m0*j$GMf=gV2Yb1n!y{V>fwPB9nphb>OeehQDyuN=pS4vTmBdub6kbs1-8h_=H&x$t57I@mi@Y|#p}A(-O7{s(sGbo6a9q9c&KVA zo>rO$)qh&Ly9eu`<*HwPXYi1+#%fmX=y{zdaMzu)yw53-7w}8{K1AxoO1J4@3puJ_ z^}JC(_6T|Yo%>4>Ku#^Q;xWf!pHVhH)@x>B28$9OM7S%M8!+Puh%h2BnLcMfQ$7Zp zG(qA?Mg9QIm11W%s*+C<lC=*I#BT2y{tVJh#c*GdU0SF}!W;Qx7 zcLbKJ=B!7lj;Jy<;23`Y*j4P4FGwE(g68R)pP7NJbTtSgOlVA_E3+jvamj5evqPAk z{QL6f`7mDaQr}colEE3H!uwiY<3KIeBbX7JF#DQ)`-d|-kFPuL?{XVy+;e!VWoL|O zpO)#1(x7+4SJMwqlfmqVh0;w|$u zlu`dYKZ|>xSErxtblbKl6(+8Zh}Wz9PX4PZOOu1dj{i5;23syZ4LA1Hc@mAkC6;@W z7`xmOQ+&TC!*ippD>j|yV-B!~riwRhM>mw&rB>O)}{kBi_Bl zR8@J$)i3u@OSAk?OGiTS-)QMpE$;8+Q_U@%BGIg#Lg5Z2`TCrAaOM7ub0@7+cBrbO z6T5cil$l*SrNk{VCUeK4JDbwv!mXWKJIURdJyrZ%f;Vd_IeT0M-A-e5;d{(bH>b)| zJFY6!eYKxiKZuK!Kjrz_9hY{9UdbntW-1wYTYAG5uj zWUxd+?d+9JpvV}M^_zp?`s6@tTk_)c7B|U>zUnTOckn!WfA2Hni@HR+WJeB+3sxl(f1D*NEHyt6fT3G|D? z^Er-TY5u-AsC7-EUZ*$~W5>x>IjigW?`Ud8PE5YNGtqgAR-+~wOBP**L={{8iwY9K zQxEv_{6*Hff24G(}xw@Ng0{NlivJ#q+}&%ep;Qr<-&o-nRhr7Nz zbtb}IfbCR{H#&95=N24k1@aFu7zj>`c+u~5I)u|srKH2(TUe0EuS`}?r)cnRMm{8` z!qlPvjZHj|xI)cG*@Vzv-tK-tC zr1%-UC0V=dKpeSX=$&0OU0zo1gr7U{-^f?{xMoY(b;pOF?Q+*|g(LPJXuk;DIx)|k z;Nz!kiMyQC*VH^GV>3vQUaQj1?~Mqu9?XNv85k4ODwBL(kE_Ekd>*jG($twMr&E)< z$NS}VGUg~zP5O|9$;`M(RU)zmVTz|(#`YDs8kQU}AzXbx_m=P2eKg0?+C5 z#q1P!Y3z&mBN#X*fvk;Ntw$fF9?E2MnEgM~v5imSv-nC)7Qvi^+MM0P*RL~_&xPVH z5U&5?E%5#;-zpr$b{;&uG3i^Sxax_r;%d7fuw#+0@~6Ty6gSC>y)vp0d98h4wdmMC{lRIy3&=5p~&+a6}ytC{jX>gnv3U*x#- z+qAsnt@tqO5KBWGhjjG;$s?2hWAG>GVbIo$nZqXUTd% zfP_;~G!_h3W?}U3*)%DDBuhlfio#Lk+2l%>K+;EY^n^JA19W;c6ztPbL zz#p5xI_t()+I~=5$KoLy!Wus@sSCW=1M+Nom61^y;ZhOFJ9YHCL`#ol5ZO!v(6B&S zx^oL=BK}JE;nc-CXcIdt(vXvgK?X^8@GCiOWS==2CnUO(p|bN5lzpY!#a(qpdUlU1 zO1V=_2!%PwQjMVFMo&*)QA2zfh4Z>Xv9r zF?}MV0Yic@zAk&2jxk!rvTQpvL6X?qCQ#10~ zw~GB1*_olfS~Z^5L9$I>q&Qw_TNzi2pO!$;q&c(!5hwl;+W`{hTm!2D?Y62PBB?yw z@`^XsfoCglsN&f@I0X|sN9x7BnD{3OCZqH01I(fK@8~tGibeB3f9}i4#+`2NUEjhO z_EPMTt73H1r{F}2qfXos(T?za(j?iu--@>uk7wh|4qF-aPAc~HUCWN!BV?n?14n-( zZyix!bbF_!{NvMpdOv@q5l+UdykYA4P1EN&+)-oZIo_!=2%H|kGJKDYq+K0L6w$M>jlbd^9;G#q- zppCpuNq1Lj$nE^hg#Mw#+;h02uY!ACJ$BCy?+w4Xy!Wgx`Lk?v%~~|8Ed^{8<*Ts^V(~bEBfNWOFH`6PCN}@N2EQsaHod7Ozr)p* zr_eSR<6T{lM6KEpJEMKl?&v6lvJ)~Qk(#kp*@SZ09spq#i1U$7-ZA+X5iLnVm$@W} zj=!nipP2mT`#sbTmMA#$KyO&Z3k0>tl`Z|Ho2OGtGV@w3K z15^7P(L*w?xWQBno>KYP`b3sIB=~Vo?<0M@s`8tD5y@9`B#jW6-dH^o4gMz?O^npm zFdDr@p=Gffi%pY<;amExO+*syp^y( zaj_}SesF#u;IW1;#u8;zhhG_3-j2*@rN$XZuKUea#%9GU>Xs!%RS?8C0T=E2) z)htHduw07Gp*FXf)$dmnW8@uuGz@zqx*j0N8FXNK4i*yV??)o1bqVslp|?8H&PnR5 zc?x^@@$_uiD(V#2lHuM9wL#w{V?1zHYYibpB*d;wIz3Rl(oR9q)SZLz8+;erk zGhkFvtEbwbJ2De(ZC#vu+Kk2EFsyULXAlf`_u!i@WJ*+rcW1^t2!U0#GW}Xp{PO-i z`m*Zvb-a*+0az&^@%wM_MeDUAtk8b4c5EMtpz5 zN9z7P7B1^B)e7&-p>~Iu=PGO?SHm!SlsFqt0`jC~G)K$MN>(%9C8=VY+@u@5B$>{L zDgkHM`c8{yv)0H{IK>pNmCkLO`xjoYVawNu23!D$ zOEMVxl_xluS42h??HXr$H93s`h#LFP21id0r5#kyKC4?ykCWux(U*dsMML1}8&lkr z!aLx5_2W&00#QQsQhU%0L=#RDH|+ViK4^fQj3i~E+W_`Ave&{X~4 zRsC$Ie?L@zuc-aJF@Irdd|{3@>xWp!ypMQ9-gTofvgZCmWV zi)Rsq)0TA(kwa=Ncgmp#|22E3DsO?;R>KXhky=x_$j`tE&uI^2j4&2JmkE`;Fu{;F09!d^^F(!P32|Tr5r@T~;aCxz> z%79u2#IGIVVqaVXB7Vrp14FGp+JU1R_O!pP9>*^T_K$iy5_$CihXb@(f5|#L!&q)T z#>W5;9)$Y7MmvQ0fGZcGT|c-3DMMhEl&Sqd(>@0uqTLwmzMRqspxDp-&`Zmafs6y? z-UQuV<&FZ~Ud=K1t}jdEM+Ds-c-*1+y!^0+O%yo9Q}o%kJ@GM_Eja>t8Dc(u{dc+> zi%cLbxLT+mS;xNk7L=87ZNWebLh&Nd3-TBa5&Si!oNZWdJ$TZsTlbKPcZD#^7woI^ zDzGVPvnh8x8QCNO)^Bo1%#s|X7;=sL;#daBgttgA7eqI;`xQ9$%YF*knMno6vRLTB zb@=8~I8{xi_7EnG%1n;96qYp=S>X=x5eSbtEaqLTMB6wj1(GmXB8M^*{u2V@>EUtB zDAF)llC$hHWY^J%3B@KdQ8?-&O_aj17?#m&s~F3&BT5!ZxuRK!GkoU82nc4PEgeU+ zAi>@}L~`fnA+&cuj`6@l=VEDwLzm&Z9n}szE6nlaA2{#V3|Us{Nljw3Eh)hBf%R)?fjuWy za#H6I^xDH6R9H7JsD*2SupG`rZA@-|8)^px<} zY{Td8_$W9NXZVf=HZtAN4VAw$YybbEL?A z?NUP{B!mCR5!^y~HWwiU7(=io3E_phpx?aVW6URb-w9ZM?TkA^c~K;s&~0Me!$n>x zFh%~twNXM9Ahj<*L98WPyKLceM#8`}P0Xa`9%8_1bz~N)2r{@Mt+!8-dU<%(CB{ zr25tMW5c0j4PPaUv>NsM9^fGDJl}nARXYiiMdEyiEQ#}rGu0)|Fvv_)jl_aqT}xyR zcF-#NdU$<FsYl_n&OEjq#PudHlE&7g~=O!{VK`eqx++?Wv_mF>wmD;6wWli!`7T`)M~z70CP>us zYJsFRu$b1RLbE-v*vG#o*{(%tGg-`6a&q@ykr~1Zh4iID-ZqGGS3r~olxLwi#E4zO zz`4J=a4PH*6|;hP$Bo(@Pt2d3!2L&%6uivH$~9? zP~gSQ{gL!RmLG6;fcuUy7K4bxWXPrxKOkc!{8t#w2hfQUGh$7R$l+4u3tbw<9cDm!x(RrcqUp8UZ5K z4mJ)pwpuHTY#g;X5NUOw%Y4@NfzYRU%(Nk}O*RZgGXd~9V8UFPf(t$mEcGEbK0>zx z@cOLAC*S5!ld!sw;i7z$C7c36!Hkhdv-)qp;Exv|HsR9W6JyU6uSBp@L78hH5JK2( zJCJDOp)Fay5q^k2gefzwW4~vfYJShaH#ACF$J#KEZ|I0Ob_AI?lTVz`B+amsWnD^g ztR>sn5p7gcZfr|7c0`&uvrd=|{qQz!;3UmVfvu%(3dSbR;Qo7wZ$qQzUX0Bw!oH4p~2dtiMYt;3WwNS27^WkowM?TmTAlf5?IS_^!$f$F zxW?zi@#o}m0@6f&ITG(6(PuQe0rNi?d{8g@&h^-N;gEOW>(a#WBPQOEa@+uxQcX8^o2r9T0L1uJBQ+dqhN)8kmR6 z(*Z+&?>OKwMlqh@Hx{HKTa33eLt66DJMxUiqBO5^b*>q!RP& zHNU_^L!fNKXT`kRwe!+$Rc{-sU$=N&Ym-{qH&9elHW3Ar-NT%{(zpz+8DiNsjm>jx z#oqKA`J~Cd@h`@|7$W-p+PaDVmVO zM<&W@&7`eUZO0aZg;|9A!r4^NeJOt))}y8tmpDT!5r(peATy}A$;|aa^cabsv(=4Y z7#MW~uxQCb6b`&=PWr2(-Gf5f(aas^Rk7VLg40_vvafx$j|jFeWZizEf+)O4K~p)( z;8m_14O!Nb{jyb$s7dRw9;2QpZLgS@c`VCTDHq$kS6C6d(r(IWqg!M!)KFvS+J~f5 zd@U%9`w89ohtUD$8+TNBnQClEA+~Rb-6KS2pV=ej{@UM|^pgRo3ntJvBq)OfxQY6M zfSyf<4zFE{?9ePR2r-)DwbLT{CPIaeZZQN2dyt$b>U)qHB68L!<&1_R%FYPqo62lS zE(kzM_*g__S(vA?4JciZ;DtDxA@f)1^V_@?mkb6QD%rJp*EMO&G|-$Qc>mk7Q+eNG z$?xR5UNhN3-1h#Wj0^)DI*yA`oa;;3M5=cYxhdVaN!J6LK z!{We6_v5u$_hax+McOXbeheX%V1t(r1L{a19sHrEX_{+ACMH^vmp7FB#5G=SA=wmbw zy#IvOvUB%!nn<+K+;SH*rle>c*uE<05p4CE%1FC3=ON1DUHeITS*l#>)AEzJ< znlhNaZ2Hs*)?p`2Vzo)~1DkWqsv|4MTpd!isVY%M&tMJX_Rfj~J2PO+^7eSX4d&M> zO>Q_!YYBr&H?-A?c0u+hsO;q3B!OWNF2+{@{mu16jYyVKlwk&CmSv0 zV4*ji3Gra~;8Ds_-keZ34X_stz&uNrAD05(L`W%o=BybNeCnuT6VQ-b(>4C_pABbE2{`hQh ziW@Z=qgBOUOGy#Ly$*k#g)l4iAR-T7EJ#;{!`e|gBdhY;+HstQTbM+3Acsca9kSZN z2~gVxQ4i!^Ww*igBf6M{wu3X<5kI0KrjZd18$7VeqHNn$Y{mjSz}BL3Jer^mjEy)2 zXrB+K4wz~EUGYNk(DQge*n6h^oh-QJm!2mSc=$lcEWlo~_QiUccQnrIPPYtR`?-9q z03nQVyD9=WyD9`f?_?ogUHQK1D5EppO3v{a{mkp#SqGUds4c!muY*DOjXgt3qznFp zFgAvdnsTHmyA8O078c!A1&*@QZpwVnQU}!I7<2t5cXTRIA`TTE1lO31{X%yrKGb@F zgdj@uFD zwo7(Pkj237556y=Nx}~_+eg);=4Z3CL0s69CJX|wOuoV^)1CZ`^9{BAT}R5rSaKx@ z_jA+s_s2D*6S4XG7F=V23As}l=Ttr&ksBpHC4!F--V4e|cx@m3htS3iJZspsVdWFS zLpb4>q8}^ga(r{&;gU5EI~+nTNnNQQLT)Pqs~@pioVOZ&Nh2xaHRcbGVXl=&hH@^h zJw&}I*!(8H;X}4hcKRhOetPceXEuoYR>Q;X&hO5g5QmS3hZayP?>cGJpii{0w~VJ$ zAMgyfw1*MDhz;4!s@u=W+k$-EKaF$QzqfX>fqbZlzM zvnyHi7MP1ONHm8lXOc_H@t}Y@FAc5m!O+}2Z2UI$!w)}6(|aDQ(Cq4CVk`oY0(@^U zBY{h%Ns@#&pCp5l{J6%Z7{pf&eKmuzO|}br!4$!~D@FN>m)uBK6%T72r8yTr7dY^z z#b`SIQZU(S&Y%Q!C-uhc{NdH&1Een1*pp8iBxblHG*C}T7xcvSo z)wd}isBr<}KG zo+%4|Hn#br)LvB$mV^r@kYi0=0%^Pqv8b@nEbnmT+Zp}$;=tWh1? z@GYj#3g0qyb&qOG489w}g%d=>?Nq3^RD|??ueN)Ck9qA)dn}!?IjcQovUQ=_@%U~r zj-|JQi29<&3$k7z^6Dky^Sb6!=Zbr1g7d25+HX?t(f+sP>$`{Bb}e|))QZ0;l&!saVYq?iooq^v`#-7npI-2*?^m3st&jhQT}1inVkOtYu{$K22Xg2 z<2uMVr;lz5-@eD~Jm~8@`1cT)nux0pje11GDVGDQa%7!GJsaxjLA*uA7$|Xs^CYVu z6@G_uLBTlad*@7-pt0-Hj!}7-?2g>5$57kPbM&`Op=K2EDB4|hhfennBc9p;-Q-AI ziTE0c-=kEMks}=Ph;LuaBiCJpJ*J|v6uY}vF!coRS$Cr>y?%c2_uB9<) zZ%|%2+WLZ)2x3%wQp;QUT#%U!4VF#SuM2Y>=d+U7Ks1Hs8Hzp(ay*b=hm z+ghA!qdIPcd}bvZvmh2D2vmLMg^OcMsOW;DoYXLi@{V3Qf>%!FFbem^Wr)94Uh<7~ zWpwDXy93?m_l80ok2TDE!y<^!#oC_$ZARwS;yA}Zod1Y2s(Z&CG@>fmHmdM|xl<3T zMhr66%{xKWaj0MOQ8%3zcErW9^-B-#1lvJ zSAjP69lLtI6g{{k!%d?D5Z~(&{aS>xkH~$+W^jo#-t;pLey0>)_gf+nesJ(cn~=Mn zHos2n+o2?;TT^;_k9o@id*!(MZXjIp7SWPtAd>$E!F$Jpa)?M)m%FR=%<@L^88UvS zaY2k|(!v`!SoGZgQz0(;My82K&3m&6-XEb?$S>m(-+MJF+nMC%bN<_yZ@e=T|EN+` zSSl9E$asI6%{hoys14ruFzVO)B71%nO-QVa34`+z#VdQrd-BoF_g~dybf|?pDjq5O z(Ecg%$UIlaC_1zKVSkuTOBlAe`0N~*L@5+rAp$U{!5V=OeMqHXeI86iqmz=2&7hFD z5HWBjmMCU?l|+c8sqCgW;TLgRJ){w}Kp>|*C|Lq1v%v^1A|pB4;l_*;dH7Bhv07(8zSf#hi`1l$+>9FHASaChwFBmh`Mm*W$4A^o>lKD zpN4>>YmQ*piBLY7c*re-9b;b*XVM~9{}|Ew61RQ_?E>Hs%X#Ys5qY`>>`k3=O`=hdKANZ*Axw0OXCzI#JnfL;T5|KK}%EK(`4Uz7t_`#$@eu& zyLAbj1#BkiKZPwoK2rPKViywNDv-_4D?g}L9zR6?z_fmVfi(*ru zBnb(tKt!^dg^ei!Bd#Wx;>)wBoRboVP0iZ!w&Tmx#lvlq=l6Zhs@C$OFcqj#haQ_u zq*Y%x;HK+ku5*U6vUZH2So*mqWE@K7mnesJ$Out72tnQ$aROYhRzT>^+s(kek zJT)y0q_oOGm8laiQT{;UPoNoZKt3p8*Bk6jRtEpoLiU?NemUs;$xPU~jsNbHiCvDq zL#<_yM3Wy0wjl{#zXo>zClEuJ?9atY zv|`RXnvgdZr#>Ry7uCAf?szf;EEg9&(mD1hkkBa(l;<;Z`Jz81fMM3k`SfuySR)Nck8k4QJfAaR?}6a#8=Vn#(MrKxJlZSs-}nO zS+C=dvi3*&(MEYkOP$~@9^A5xyaQf>WjSG29})|rjFacZB53|t?^cMRX2eJ%eCK4( zyq+^)PUv*DRzh8AJJ&||smX)^A%h@Qa$;#8Y~G)yYx!W3Muiai30G^A@}A0<8P#j9 zwaR8XWtSOakNdz{Pa-ucw&i`;4J6gUjj4ha$G=Gx4u)GnSx zvl{O2+})<7#mG*>5$=_oIsE+y{*^GS4_%2(woE<3sN5bDUDOTS%0oeI7|z1={+ z(l(`)M(4150@S(kE^e!km#$Z_W&An`|4zfK^i2<%Mry*|%)dc{OKrUrjW(8s|H%LA z!Qi{LyP~_oJ5^zeqqAV-iALyclWD)4^NA1B!6R^;jRE*?RYWh-b(jIJk+DFa7 zxM+Vsp0Q7z;X2X{2ym09N6efLAIB~5AvR>g3*)Tcwl( z7m23CjylzZ)j`Gp?U5~?(SIh(l3kJehWY`H+H0)}m4PLIec%8Xd6ibO4L48Vc+7E| z)ti}q{rGcW@$H8uB1y#UHwHstO=f|8p(YbvFmPxiIq9?nCVFqRCl(ZQ@+qk_%v)Fx z?R%5)SX{Wv78O>kf{cTdC5t~D+Ab5;8FgeLMb>Q;t#?2jHhiAe#^JszW*0kd*X4tj z)D!jT)3IL9c+dOGNgm*g+9G>58?Z%_SD~Eayxfa*k$s`rx!uUo~~t-0so!-dGDVi`<8N9rj3duW?9kjzAfCD}^LHs38c6L3-^vU+Kh39^Xu z`R*yc^@4meV1TTv4`XNTWl`(nV*Ap_!_J2%dne`yeHHYM9fZ^hex-T~wdW0)=MRm^ z;suKXY;Rg;tOhDqNK&{1ZfMhV@Wr`Q^HfZWr3(A|^y{Ase7`X6+ZD+r!EK{^Uz^GWs+B%rYDB0Q=9?F*5bRsQofm zto$+}{GYuQ|4$Bq|6CnWBNrnf7Z+zMw`$JgE;cfaGp!J$IQmkd_+vH0`aK^*|)OlrPR0aFMS71RPOpOHen3G2Z0V2WQ z;e()_m)$}}f?4F-PfEM|GezQ)!(O+m&+X5TNw-hgp4Z1ejNj>oBw#T1)DfrcO`Lcl zc{DBQ3g}!g-s#}5BhhVzWyOP{iUBy}aARS#WkcrRI&pFFN%6{Y;^HIf*jnIRX@ENv z2z1#|c8pE4i#Vs!;7+uu5qa!MvaM=E2Ah+80>-Oh2F9zwcRGUI(QN?1;Vm&@`X=d~ z5%|wF+C5|f!sMH@z~?o}Jtt@dC{C5Hp~jnLhZTrsNnx2`l5KPLoCtbMM=>u~Q^_;d z>i*)=Kh!FMP0&ep3*g7C=Vzv5$gv~v-A4TliSaUZX{e*m4)9-MNd&GZxCT z(wFzC6J=9uD70da^^p~Zr`13{486E#EGZP%2^6ripG}v>9xUg< zs*~I@GkS_eCN*c(TXK_bl7dUiK|rJ@UAM8qMIx}xOVxnC4XgEtO+nyR1M7#=Bh9N- zLqht)6WnvSWecWlMp8(4^RpL>>ty7__&Ecc?S?~kj3dq|nphde)$o+1tSH%;YucpU zxO11IS2#<3DRP;9J2cjZ+er3#&QwJ-uhkker<7S5)+Y@Ebc+j+C)KL@q|qzuw1<~9 zN%yePXZN`RXfLwPR#5e{*)2>ZBB_zE$lc+8Rb{x>P+-pa~7*tNhsKh6b;(fS+d7Zah1b+?^KUX=UgTYmct>h-TRnSkfSa2BXM5D9XXQ{c0q>Xl;PJyS`F~S~s~vhjtR9XJsV^;XV&~rtjmfB>%v~52Ji( zCqw(qYVGCPl)uOeX2+q-{KNmsd5bmSLU7ZaQ>gpROL%eLK^nYO!-VIjO-HXTpt$&l zkHloOji+`7ct;>6-tCj+<-sb$z~QI<-#=MO>G|bW?;{VUY}UeJcXaXo}M!EYfI8ODrVQIlP3>v}UAqy&#J;OaX zF8-iM3wsO{*z^!ev51xemW#?fDTz~+B%h#&#Ig(6R|<5+ziQh^ zUHKgcH}%5`dsxfz=Jd5s6|w!FBujC%8F2XGW7=7O2F_vad!0y?YB7Z|j%2>0;)67C zeQtFwoU9lKjWtXnMx5>Mk4O~RL-#%Yo&MOPOh44tI3n4U1#yIvi{m43c3hw<5B+%y z5<=Bx=A@>sGLPI|^bjPzvn55;IUoe_eV;|5Nc6Nf=u_z{9(BYnjmEh~q9h7P9jbTIUY^VLdJ{Vc90QFMZXb@dwFV|nEVB7r+ezX>{wm{}5|yxA+6 zeb0hIA+}ZuK%yXxvbsm$gVk85bmKcF7FK`_H;)YCj&#DGzkndwloMsm{+iLtC=NFc z60LiMs*gSWi>Gu>6n_pCUnxlG6JT>gfBA;6df?S`AmOr)>9WVsa1YsWOSy8ucn^bV zk8-p|<=v*Ww5u-k$uCNJg;bdz{__;GdyUL~(0{I8%d}o>D>~R6j{24ku>Y+k0_1KV z%~dftXrIy+Qvmf!<3gBLY&~>><*-gNG2Eurr{5^%$C4)Sx9 zD;X0n2E}p9bYb`e@60RRw(ki4NtrfXYl%x=^Qa{V|CTbz|Nm;o|9dt(TmAJ1sv6n{ zdoBE#vUGk35qdfLLJ|s6xgavVRh%VG9wLlbRpxXtw7siHL%=>Nn#a?(5Cc;@Yy(mj z?!tY^^s%Ue*%-fXevt>~S=&wUR@&H` z3*ZCf!1o35(XCI)WD(}V&Rso2kTLB_D3A0&T#N>MLxW>CBnQ4>nU70?A{Zy7ige)} zH`afP*#*~+nQ)(vbrR6ur%bLcb}y~hj@T~pK=yWwazh0G0QxC)WOmlYSxi;I<>e__ zadQbwu&D9lSp?J-iSZF_sjT$OR^-!7VHrR%OfoRk(d9&IQH|*Eu{gCc9Mn!3Ek1hN z%fyNuA@LX5WAdgAR|UIy5-S$W^pr$v)Jhn=n}fJwYqVwMQRnfA-^eVzPT=4M52hXKE!Zbro)@jL3oCwZlCCRZL!7<%ksRI%D}X6olYo z|7#y2spfb7$l&q>@`#qmVb?WK>m7u6Wg23n%q7vCi41Wip#1EBrIvr5PI+I9ew!L_mqg8=?o0)CI$wnREQL zd*e=$6BSJ<0pU6dG5QJ&g)EmgpB^qJqZAbqm+F^lnTu9|p>WD@{;eB(B@+clRtZn# z4o+#>Rk6#~bIZM>ctgQx;ix{M1G6*21%AET6>PQ)&N29Tr`gaeuUSo3b8-d3K+_iN zhTawW2WDp!5{BUbX7F9HU!sl1C|Vrh-j;^8DQ9}K#XbU!t>W0KhlgF}Te9&MlPGaT z<#0jXMYBe#s`#nLgjJCNo{CIIvj*Ej$L>UpI?it%Ruv7D4UBtgBfA`qQ0EuRm5Heb zr7cc#4Ce}y!cOG~twiN`MkPWO{3B1ElXH(;hA3!SlU0qZw5n2Z_&wDFxV`M3Fdx2{ z=*v6O6V^ECbn;L(7$DK1ae5E%tZ?E`w!PL$e6e&}8n4QuA?AN~PTXFcLmVQkWBC6< zbH!$OQd`J&I#dh5lhN&S(xN?E`4N>Lm;KeI+G?-oqJdQFCUcjEwi!tX{)@;;nifRP zc91wWo*HrJr)SBK*73CE!J^O(x6GH+IIPLwklmufFP>ES%nRRW38e79EX+X; z%`bmhe$4sAB|Y)J5d59I*7iMyq(QR8Nlm&v1e{_(iaV3Egj}P9Ol9mAUk`@CoQim3 zFykD-Jp>_kHVG7>}`F#5X`3Ln=Ak9h(!M}auf&Dj*0q1`} zeHRZ$vwyQ-|35hx{PTnLU*}Z6PK%m-*{*;2uYZvZKmLnth*q;xLDfL>wMWrxs4Dng zWT{qX{YP39yj-*jg32;%qe%PmJfX_PtTB_KGy4T{!*34zQ}De)9yfA^^L=af{d@KY zT&c>-7M&1^Q3m7VQI=QE3D?P0`|D^gqyBeJSiNDW1Q=x+5J{jD6e`Vtf8dI&I(60Y$o%Iwh zxlHX@&Dq+|3}?jq>-(mY40Udcm2Is6$9~<$pR1MFAS(D*vNeaQ3n}f^IK^k<>E$eE zp5xLoYn~m74H3FiGsuM;CL1L@lt@t{_OQVX^68eKT#M{?`ew^z=zrBBQ+&WR7^`bf zmydhOe<<}!fJEps$gl=1&NFb5hg);abZGo7WDxZ^O0lmkhP9t=S~WM(`vy&SiPAop z(0uRNEko4_TP9mjncyl>hJjbB4#1Gp+T1f9wNsF^hQHn~>zkYLYF>?Q=+c9~HdGx% zN%4TksoCSc78XIE#kp%EM%vUF3cOT<%ay#dZVT4|Iryy99vr2jqOyJhYem~yafHl; ziE_cXs<6;DE2MB_s@!Zjh}q`2yh{tlS#-pFWc8+S;$OW1?^(J*@4*PWp5D>IbsfMR z!L3A$N}#RleLuhq>td$s1n7hYLaf5M?X=Z;rO-waeL0bYkL=i2^Z(%at(e&xGQu*! z;mPOVea-H5Lf|!OZx7TyaicmsBYF+i!1vjtH1VRcYv01|`b*wYe6hLKHqx47-<(=_ zQ+I`f?#x9H0{1FelGrnjlo|lkkEgHIs07M2EY(!Ej`~a#w8=;MIAU6=7ty$-Y{y%` zW>o(g<;~m{4Qo+-w;$%rT|(w>4!ounaGD8ihuwt|)@>x_Bq%(R0JHiIxIIC-O@-R> ziP^1%dI#p{8iRI&(2m1Xfy6onNAd^!LY^7*6QXMV&gRb(%kk;EA<92p%r-=v9(90(kM2U^->;3hKECTLS&a2KKUCe*f7jetq#x-t&ebpz zv%L_CL}?#PtL^`Xx`Y+_sn-8xaFPM}Z^h)l!-JUpPrH9sGkigVx{>Wqv;PGTDmn_N zLTDer6#GuP5cQit-3hS`ds^5dV{~IYwFv!KY$m<0naO}c#C$}@Ozu~Hp#@t&#!P*BXOFI zdOExoZ8~k)nzUDdq1A4m0qnIVJ^kyJ%&lL{nFwM)*vy574cZszeTqOO-$Pu;@kq~3FRWNdUnT~qlc&JMF+H;+^VtnN_ z$4BaJNs%n)zp*dKIbUlQ5WFd z|26djD)oLmJ&=mEGw@`?6$uGDT*quoH@>Ppu(gd5GsoH;h?iVpgnj_$xumsUHVAq^ zVFm1`9lJdK-U=3CZ|y~YuA8lhs_aejyXsGYKI|rO^CCW%*KI8c&|375eI?;~+}o$! zHoSwRDX&%Me!(5C}#e5g>&@JXo_En}!~+6Vzg zepFa0H-i0PfuU%)-BeUUpIG@>m~7%X@z=`&`6mZv)Zt8;#5@7IqShCK(M#wJiNvGj zz5k@jNt;y{*Dp>{^-?<=z^jf8(G*4aLV;7OE>$OWO8zmp+!@^3a2Z&Dq*sX8hamu# zuZOY-%w+c&^18%ZrUgq=bkjee0AW%PQV)gEf=y94KL2c9v&Rwq@oIakXS0!XwiSA} z9aWr{H+fnSWw0acw+XV?&`w-iCR}vUL)sXrS52VNS?#^W74oAoO|~A5$V3wAYQl|t zQ&(7s!X~Yp*#l7td0S%|PAjUf(g1fN#@b8oiU=!FE_U9$M5U^d)HCZTx?%VoY|=E< z>eyqI2~(s`p!v^<@>$7TQEYUx>exBoynFPQNqH`}sJBN%n0i^)MaZa|w`k2Ty zL3FtW`ZXqM$lLl~NtWC++$rUC0L%hf=F~B1=1#D55`$-nC|11$BItAyT_Io<8|B5E zrXI<9(c)wqK%yeS;*dr8`E_xnisoTVU#=^I&+9M3x{dYHx!-@>q5fq0te|}b+CKpL zKa|2yE4|5s_nAhlLkIXT9L%IZE$APB4M&*~UjBGT z9bjMx(bd=!niX`U@egIZDakh4y_~4JwNKeM0Ynno4;es*lfgFuC}YJeU4(sDXj+sW z(>OY>mHi`t2vzrp-)_e6WnjurnG(G;W6RkeslTSHu{&gF^wg3gY_zpE=sJt$QrKYG z-Dt^!j5>_GxT|?^Gt7=S9Ls>f6mN#ul;IXo{9KBeh6Mai1Xx_qI{thyV$b6o&zBh6yw#@8K4u zMdY{uTs>3ZEE*VAS1~>28|n6FfsXO16C?ROc{(XKY{j7q5-#2EnG9N!YPvyKIh4TZ z8!?oE`b1{=Y@{AD)1xXC>a&q9>OgdP9&nYDl->3wHP#v^OYl@AHKwpgn4Q5_n40h- zrh>z;K!+Rp_KIB|?^=J-Q6#=Ut0~UGW5?vh=+w`)yPqhUIhr{8r|97WBmlTyo&8_BYi`L$&RCJ^Kx_pM}Ql=JNnm zrMn81YLCD5XEhb<=&{oA^>zJymd2!vk~}aGR8;O@f$*C#+BOu)gr=FqD^YbaPK-A2 z;f7e=jSTIY%h34Zj{R)XTR+EKM{n;8p)&q<=a?-2a0FHeYabm&)k|&kSVq-=^j^{r zK)&c-R6L_?s5UKAaMvjI(sbaMEzOSB;h2o5_23Iut37>nH2930e9hYXI!+5QAn`w_qfexA7UK z$S(FJU?>DZ+X@W|lNi;SGC$agG7!wiKli=^I=Um?9AvvFIrl^Rjmz81(oD0f3o?8U z`o#*x^aDNB9Zh zOMgdE^LtWHG0VMTQhv?vDIs*zhT#LCJ<`)kr?yA5=<;H}1*;Ki{z8U@TV+;noKv6` z|MYis=+uX-&T*_)S2}3QxH0GW_>U=UQP*qZ&#$yO^0g}Le?~Nhe?_!^nsN%p)@CLy z{|(U8Wi(VY&_3jaokiO2pdyruAsV|IhfmT*Edpq zdN1aLDDjud^(KX>ZJPD)Pqp#yIb3#(Bs!`dcsRg_gBdiXW=t%u-+e5ueIB~~ct4&o zzrEhbak&=sf-vqK!N3_RB8b2MyB+s}F)42&RKuvSZVPm!U5!+w?)Nz~%g66^{$V&ZFv?M7l ztU{26;-V@fEu`It9XSf-4SLcXsm)oQ9_~0afn{DGk_V{K(rz8LW3@Qb3i#cnJ_AVD z`%Y@3RLW~i_BBCi<`OU{=I4emq^z=6gv@ro7LfAL(!FO(hkd1Y(WF7hAv*|AHkU{h zojNYK``kPA;4??A|FO*QheGV*H%dn{*!5{Lv0a2}C+H;NRue@Bh#6lwr=A)-RtbMn zX2kDwWcrLEQ@x=DZ zMendF?hN^Lt{JMah6hbskR6j$6Hjd%mKJ;G-|^!HgqAeP9~t|*^CKFHdb*(7lfgE# zU0{)DU(DaGREU;Rpe{Wx2 zqO1HS#>~@O#Lq5#DD=Mm_WPF@Vsg34N6pZRXF{uB&8v%6c<9*e|iyhdQmJI9B8f~Dg_nPIxC0W|MG2;9tZnw|Z z!6N$RHFyo(f`+Z#_Qwvh1wL5zFJK=zD9w$rWat&@prZJ>=be-J80qIG8>B2ruEE_a* z;`gL^eLTKVq7N|hhhy||>U7>m9IfWHv82V#6tP?PE5XL^?Zq`Zv&>$JzLD%fj1K!7 zKA!067Jx-=?jS1;d0}9v-J`lqAJ}TA`E3?xse?&ZpIztlDDq5-)fXuDsLh?Johk!wZ_6qqT87+fZ+gD&wOX3Cv=NI?(N^2?C5UW|zF!+yig3 z%TcW!{+K^~`rmJ59^SD$pLFw_BJdZ>eVYHA`qB)EK7?CiSmkl38!jGIcXK&JM2Hx;=0I z!wN845+zrDk&R$*|CR>+y|DdntiXTE18Q-iG$#m6BTZx2wIy9ID-b~40()l#w)N12?W7NGQHA&-{$$dn5 z5qK>YB^Dj8e>1PL1%AD~!TNu>UOcBiR-dstSG=7ysKV5 z7g|-x3}P>qQcPhLm&y~i%`xqm#~Z5baZCI2o z)GR4ZOg1_`^(nIZx2_bXa-|aX1W$Q)+A;TiPRF{mjAVr2%Pym0d`Qg24!&|m8?i+U zsAF&pXt|H8gY<2l5?&Mjf%3$diehDz*Lx|b=TAgJ5IY_DNzD7-^!*S*97A^W?d*j| zcrd2k$GYn$Q$VhnW0ixR?e*Zz1+*karyF4b`}g_jvn+vZ2OdZ)HD2C}3tK z-~#nZ)n%6t-ACAES0;yhDy_Ms6wJZ+nilz`m_uJ&AAUt-+Y2k7Ohw~CBRxvJk%JaoS+0>HWJ zg$Zbrz0T$0#YH2aTWmumXBUfO!jw*)d#f_ ze%ed2fwx$YtW9{&pcjiGUg@g@iyXF)sHo05Gy9fJ(gPQa!VwPUC|Zk>DMri5JV~hZ zsf+aTo(k5pCKb}R^QEIRDQayqqq{h{u+VG;gPbit)I_?cxHc32;4a-eR_&d_-(Lu# z6qrvkpBLE%M>e1~p9ThIChdw03OnjPQ9E!lCQLfbqn*aP?!sIQbT{bR4Dy^yn>IMy0)0`8# zGf!##5?T;oiqwwz({v-52K^CpCQ_$2XrIfYDcl`=&5U&4E~99TbWYztIiuJ*e}l)5 z?1&jH)6K)F5%U_Y<5~YWniABoI9{1^hX9R;+7I<>j#<1{;R1C@_HG$p1P?JC-<`gO z!!p?kc7XswiMG&SG$t9Z1P`Sgim!-C zl^ZUm7v2_ue5OIq+N%v_PqAJT(~*o^YU}*O<%(U?FQ(Z1L z(@f$J7$)Ly$f4KO9gR5MR<8o8jikJO3oEJ6k()C>4}LMIccaVX(}1N zwd1q26%*2POCbUAp#dQQ$?-x0Tml*blq}RNZ0l2V151LvoLmyUMGZvFnuL}5;t)o` zUb_Ww5pc4CCqdsxkwD}i5^M!?P!zMEB^#}*94rjCpi(R#6LbxgsPnk3@}MB&ric2V z|3imtwX-rWe0>tQ@BjAE{wFQpf6*cTFGb&f&l|HEU%jReeP6jWI4IH@I30L$WFL)-{4nRC-SD9}Wy3|Z~%_S1&S4(Ia`dcIO}O6Qo%39-+J58S^^#gFj9 znP3=MSzUyW+mGAVzSlh*y`O*X(ETf!Ivo*A_`SoN2y2XlSy#!o;`T<0qR2{2yCSU! z%^g(+BmkyG-f6>GmJ0*klaZSPUfL8zvE*CvOpFJt%KF|UFW`)T0)wPI33XBJ1V@d5 zQaGPxPyT9c>xqU6&mQ|*91#a|m3MWyCHiei)BB8M)63izdiZ5J3g;$EjKp}< zewMc53GhJfeRcgj;dq$2A2~$wM}hR+6UbM3%DHWdIz8pQ5LmEWcc++lW0JJ z88ivDR=wDSlxE4IVVncsg1eX~ar6syh;FINhhvdSmul$kVZwO3b9cqUn4fnj?WzI8 zU1&^|8uAB-m*dpg>D4%F@|Cyi?j!m|wsy(wwVM?^_)L}6LV#$eYo>;*YXccXHdIu5 zd(0JKNX??5LvWZek0qiHK5w6AEDpC7*2&@S4qkTgF_#qiq#_D7*-Y+#MUn(%u<`R$ z>?Com=_d)}TUqeh6*&5uDBmGJT2f@5qp26&Ju2de& zl3_St;ciI{qD-X247=2yiOOXpww_7*!m3p0(aIub=UL#GIEVNOKX|9{p!%igmpQc< z62wZ=BVR=CE$S%n;37%oII(k#;vg7+zjkLv+;+n+1wA3$ykCcR2yHhZymB|bbszRI zf#+vR^iq+8kpdCND*B!=O`!)*e#LP#^5K!tC0eG2^5%$}z`GY-VAg4(>XE$I@P=Or z>)Z87sGmgTTMIk{cKrx1U(&!mGB5LFMK%t{U6ewJ5}?O;0v*H z{=8{ZynA9p-%3VE(U>ipF!+K@WcvZ4vYW|u?Wo{flogKccv)Q5kz>C65i zD)F-&j5c;mgy)ECF_8Tj0_H|Mq>#M92`~;+KZQKKDfbhgAFWmHvu`o1Tq)Kgg6^}F z84Cc1PUO)CpOOO&Xxn6l;qVk4TVWtwY1<@*zm~J@bF?X(b{O=7(pWnw_B*I?I?Vxp zmAfPLYTU#{aP!&@k{Z83g2z^*QIhde+8g zLn-Vz?Eh)P;x`#=g!!o1cj2Yu%xT>70RL2+Ct>Z%+s&m}3A>7f(N}gVIsK@FVm$c8 z-c_)xhHi692wn?4_KBXm3f{(n)>Uo{x}$v45)th#;k~1FlM^uwvh*qX+5;ad>ZREa z35L|PA}Jcn7W#JEMnI_<8mgR1?1f^+|m@MvPFCleG?}sK|PMu(!YWi z9n4Xzqb-BPm^J9I%%LQJ@)(%6=R360TW+ug|AWaQ#fc0J@g~lMgA8!KHAVKwV3D9J zTv}T$Z9NT$6QC%?veJ`h4rI7G!xEWBxpPleJdPgmeXl#-W8$ z$v%DvR8p`hVl-;PW!@&#XMe4H^nx>?c?we;N>GP1 zaUzaUJmAGZ-HhSNXw<871ZgSc$uF5Dj6BaA$d48(B#aT~%14AFFE}%;q|PqxoD-e>)t!nF#@k1CwzFkI z!l79E0P7z-t45+WWv=e!$6@mZ;ONe5ZDwTBPE;BB#lfqd@p6QoU_0bslTRzjPx|M( zaa5DKaK-LzAHWyX$H<-SPIr&jZxF~0fxP_A3p-7C5VXu@(ybDbTtyl|$6**I$LR@h zuwDbk39ConmHR8w%Uzs4;QEF#8+Pi7 zoxdx1Tn#-3ID&!RL;U2UC|X>1L`6HV@pWhz-FEIf?J=G*N@usw_P|fnnl5F1FI~6%f$4(RUYt<@&%I#(Gd<>Dx+V8cYE#em{ zNtN+c>;WF8EAS7V8bv0Nqc}G*yJP6HIN3nV^2G@x;wHcsWUM;vDSHRZ8H^hcQH_G?7ut zE?eqGfFwEDN=C}Z6;j8BzYNv?m8yYVlGL?4kHJs93}?u-zX%&?v1-!2S@NqYD4#E9 z)ha|Es8utey_pbYTB;s>s;SooN)k<<%$>4wXPX2RQc}?7iOUH~T4{%!4NU3EZ3eMW zta=PqEQBvvL-mSkecs=ig`sY;;%GcyL~dctlYAtMbGrIn#LBuo7XY^`#P>}bE+YN} z^z9+b)m23-Z5!gt;B@1fUa*;Dkhb zvNN0-71mS8hQjd{{|&TU4up#vb8;n(ez2bI2mpqt*9ewz*(j75C8<2 z$1|O{n}*pOGl$Ef77zMW{0MwCdy!_`o}0&wyh2)a!RABV-sv)#(U!_$%tuN&>G|c! z5<9EUw?wJ*B{K+*a{>x1;O}QcGbg@GL0Qz}Gxs_=%>3ALnTG)O#j}fT!FYKbkWddV zcMi@!S;Y1 zK`Z|%(by3|aPhQSE1}1$VK@A<891n-ZkOq1s4%nousf1iLGAxV+B-&9qPFXrsZ@-L ztx8g{ZQHhO+ZD~&wrxA9*tTukNoTF~owK_~@4b4AKJ$0-XO8(kyzlEFS`2;6>2m@X z|B*Ty=jvWHw@Bixsqe+huxsHPI#1e*kql5C>ru1KfrV8K>sJjzQH@eEJxuwb*@tbE zjexsH7EBgQLF-D5|2VAH|G8S@-=z=Ew_v!x(2Fh}^atEYV;z13qD^ibr!!(B!d~Yc zh&3)eg0^h9}gkH+fo(CpZNhuziFr2rDyo;&?lDCTx)*5uJ6kLAWn7 z2m=;J|rj&z(3G7%ct^8f;BnGlMb zLQDP+zeUO#L1vcNQ&O$?HZn&;%cFs`29J0@we(4lRK3IL18oi{l%knv;QP6xewmTr zAKIvko#)q-9iUKKxYN$8l8b)iwIFirc-KOFn*d1q+yG=x>j=L|4Tju)f{i>eT^#Il zf{mg&=yIdVcUupPo$pfsxy9jaolRvHW_nzG*r@7mTp==Cf1D_Zd7*5=YKmsfZnRp* zYb3UkQ+s1HVbg?G%!l4xCs+gAHnR`z_|`gu24_ZPv?oD9*u3_-OpZW*Rn~wws--2K zo4yUYp-e+VnVQ{90QjV>J{Lty!XJAO64EEcpmV+Exa0X^3%VhqhoPU~|cb;xgH7LsO1yrS*za=fGs0P`Vf)XGHRr^h;>e7&M zND(g~KhjEH)*r&%!!9-su8J3A0*6|Brw_U!?e&xsHpFYlU04SXA?UK zqvdlv_B9;mxeRQ?zYq0#q1QZOU~dtzT`@?uQ7C)LSYfMb^t2C8Zopcvh*kbHv7&Am zXLa~fTvJ!}dNe1P*$m>FwJ*y?{>j-YJ5$3r&@o`4iLw4@UH67a2wJ`|3oG4OOw4a% zvDZ~=&e$ct{gE>cT&0mZ>TWfY&aEfylF6v)4sJv*tcoTq2$Y>li5LQVhhK`p3uxu{ zgx-`c)fhMdF-f4X@pVDB5W1_hz#3T2aPn8ZTumdVk^hGBN1PcsE>xUceE;0VknWwj z(?pq++$4wK-P*YYY$}uT3~q~j8Ya6}g~nfbF2`_5o4}pknkCuj7#jhSI6+?}RuM~3 zM&8P@e%b2#QeQ(e|BFiqy7R(tXWC?xp5=ipXVY2xxAe)n!z8UgcyTXp-#q=`(ApxP zU41@>h%lHu=|VBQAT1R`#TYS^7(v5aSw>3`59LuBV{ul^*uSpR{B_7U(|A|7Gy&lJ z^Ao~2_4x+jfV6q$%;&e%Wv{&`YU!nfl!_gMuh)bONHEXpKjR?YVsinQuUR-h#y^t{ zC?KG(1u0dyxyV;P@xMR+lla!^OWW~pHsAkgo%+xJDcYNvm>Aj1{O$AokM!0{Vby+B z5m{?+q2Eq1mX%I-M)SBoCXU=pG_}u+!^%CyZx=+K-XAYH=1ZVTni#X62=4m*!`J5s zngJOd*Y)iCX8gAWwugMA-Gxjwk1VIe2Q?48<4d29H|Rf3YZLk~qlB*D6o{2#w!g~7 zRY!F>CFb2x@$?A&HNoOAB#ok3YRq;3zZb4L&7p!ZXKfTqnOe^Qzo#E|f9l7YgK3_F zRs3uril$M{Z9eAIc}y2}+)>N}=xDPaD0qy443P7URyoU@c)u-G1Q}9e6IDLmcdja1 zIGrViSoxm4+i-PdfXI~@=!^W+ig%*#PkkG9tUo4+QXj6ta`tL?59aop;M$KJO;z_U zO^j7(WZ*ljw$X%Uhcn~yKz1^i^nn*;iHq9JUcTG)-{@YCV$*xgap?TIXH1Tr_-eYB~z}+QX zgZHn$RSg-3KMX&A=oRS@5aCnt#bvLqNT3&_KZDc1j#kl{(|iIDM#NhvOHM-bm5~#u z_l3pIO+n95DBH5hlqvQphfhGYYFhR0#2(vd3w%%iEiCdh+@xVd7p1_Li7`2SrZ68F~D0=udco!{0 zZQo?Fk6{pM$IxN%89Fyf>kaRxrfx)LoI1j2+&V%(29ppW)5Y30rTvQMpp=v6#jH_~ z_FcOVzv`I5iA?RNsJairhw~n>s_Re>AQ!2g^g}zn!I<GeVK8{x|Kj%qt znNl`@e~S31jVc_8)o6~aQmguqx6CuLn8A`xOlEOon$tGTO{+wr7gPT^IJ76zS`Y-} z9n<$k3`7;&Wqll@I&kVG0-0lQ$R z$Uc7@A_a9B&BG*RuXK6P)U2O;F_qkXN5Mx>i5*~y2C-A6fZGks@j0!uWIL6{~e6{1X%apDqaYTQ>4 zE*5f-fp_!W1KSyWR-D8{^x0>gYHJ6Jqux_^&BSl9b;fzcC_P-59=yT=h&}sl+vFST zJ90JpejgRlw>Z)n?1o6h?Rq_oqHVyBWe|-Wlb7#3>Q1W1$`MP=+q=!r=q2@{(T5>Q zU1p&R@nDV0GK2c`GfV%GBfJ?S(D$dBZ}A%JJJIylS$Tiz#LFn2Te?g>#4G4P^oP@-F7;x%!)IxZH|b4)X)-C2I zAG}@#E9b9l`=3;6rLZdf#njL$$~dk!$}W&?SOWn$g9W_yBO(!{!ngDjgtSW)1<|u+ zm=!9(@8-S0efZN5Z|wL-aXv{2t{+bE`qdw=cql(^n4B{)bbY?Pf$E}C*!`kK6+jyR zm4s-f`AP8;O^7~{6Uq*m4ns%=xkD~LPt+`VMwiy{>Fv+qc&}F51jZ7#K&jaR)(@Jj z)`gtQmIo|tqhOur$?@N%^`#ieM!2+sX=~=s&bA})Y7JT(u0zoi)~?>;tSNQjr7A3q z?c_B359sksBj|`&jiXKK_fU^6Sr#Z>9z6$(~~k;&kJ1$8akM`HMT#boTc2|tQ-2kmnCIU#D_T_1z&Bn zH|%#zr)|x4Xa7S{9f4G^89tW`?_S0@$U|F6IzcOsp(9>ni)DnzAftQ9g6v`>|{ zORH@3ucG+>$>{N4X-(>{V)pNy=KnGfSHx@BU^nusiFs z6o8*Br7iRYF*hoR|E|`qO0{BfHmnM3767)35cB~;6kVG^#Sf4T1X0n%?}p>`!}|@g z7<}nO&Yb@f1#;MZ!)<_pqpoiA!o|na4UQKog4CE6s~h=%V8%U?y=vQS1d@P{5?+iz zh!RmkFm(u?B*bV3`5M?Rl~o{}Xaa!-@i3$ufC3BP7q99(c3p5DUYoCYh`eBj9c-i@ z-oNwPXwdX3TCdmQI(5S-n!Y_-(P(Sr*R9Kj=Qe1O9=)C*gBcsMCIxYzSJrDjsz0;0 zDbjN>H||Zy%=vOsu^0w+j>3`)mYr)4)~euq@FcQu)%683OE#HgHCt~m?z0Pz+OKLA zIaL^XtezJFlwi^Hn}hjbqPDNmwR_ZIgyNA4S&6n&D=b3n+g9otRd80VFf*jvkOWeC z$@_?Q=xd}ELonq?n1d<$M!$}`qUguDCb&kpW-JkG{FE#w8yj9GddSe?pP-)RY3tjx zk(MFn#aA|MIFp`WYSU^fgMWO~G1F|KlF>WRt17mA+YG{Wwe@FV6F^PRN?E7m-m9Oz zX%4sVFf2E*5Z2av;E|kL$tawqU-ym>od$v4U#aC6B}iEw?Vr=8UNrJ~|9Fk)pL-WJ zFX?3YRrgCpvqUjexRJ7AUY}?+t8;OuEAQSOUu76eV}8Xw$MehfBux=+iCh|(u%nZa zZeJZ0hj$d$k_Y}0TcQBx{GbB4PmRe&76`F8dHcuEAn9~jKgXGgn~LT+?`Ed#>6b+k zRlqe!W_&sr@TDFQW%vu4>|hS1gzpYK_O=;fY%Tk1OCLIR!Ux&EC1AtoKdTs@t< z+-t8qxamjOsOiTDyj&4a9?7T|Zb_a!k96EyuK@17SJLn#ObdiPj<+r)j8o^Qy|i(nxF5k?i6lt3JAoN* zw5L2K_g+52{pLt7GW{uKo0=_P%7tOl!;q6S?$v_O=0UX32@l=15Y>x~*hHayK^DyDx#V(Eu$xtlQqUF(R|f7tX+6xHOTvX3Es!g}xaKXC%~J zsQiY8Ky98@F_t$s!zI$T1^jD+)uhImnuRr+j2wnd~zyA#y`oGB%63tu{RWJrWgD4&7Tg0S^ zvDCzT`4JPN8}v-*Q~vPALZ*lv@vb`3w|wmh=^RMlALPX=H0B#u$jy~XbCt-ZWW%UT z?aGwOcH|o_7WT`yqwqX&@iraoX^6kQ#(T1My?R`#U2r|NU+&F+wm;GTHh!Z5PjMZI zjM_0^POsmt$KmtTWX_6q;U1>bADn%PkC$->7xxATByslEXxv~#(hX1Ahf8=z5>h_9 zMRLWOusw)-`w|j73q=<1)nU^O)7W{c-B4g3{>s{ArE&A!B^bQDu@Bvi-Gj!~N$`x> zt=(H>)sFQ{*$v%Oima0G_TNQ`w79uR!`_U2V;*$*)irueKKQt|Z-Kp;^!9BKj!5V5 zDP}Lq>L~_73ZqUBE8sAYJ4x^IK~7!twn>bA}hpJy(uvJsl!0WPw4?<@NCcK*_K0%BYcAw7fAAO|?6292CjNFlDXH*p!M!HSVZ) zdU4G@e{@X2wsCQRb>;-e`hGzT0InLne1Lm>`<~f3-h1QxaW=FDRH4DTckRaRv8F64 z&BH=$(n_{4u$7EtVXp1vP~9T+fPHq`+^$vU&*m*twR-6C@Gy&Ih3EL-`u1%FViC2; z0&6|ty>0JwdM9n&>*8CKMf2=#R;Dlh;{JKwDPNMi(+c<}udeEZ=u8*U*3AQZTPqm4 zE2}XKD4|(~ubXL3zraK$UsF_$-lCP~z!@Z)P^g}p*86$*sH0qRr=$#P;bTD_q5BLT zc8F|WA@LWOUR7tYj)WD#G91LfAc7kc7fZu~gM`vUixribnKpi1pCGEpJNRJ+4L1xl z!Z8+gqFfyzI*UdD~Xc*;;!O*ctV&z}vIq-UD$h+JU&RypJ z=oaLUntoQZ8zs2*VGhmo!?Lr8S-v!h%&5y}+MOySezAj@`NU{Q8!>VQ>XVCW9bP9M zl#EP+Nn10puNn>>E*?%EZXOO8_9BiWRv?CrGmAqaeoW)kwV&DJiwly(Wp?Y^lZzXa z#7*ksRKo<4!?%EOW46D7m-gM(s|^%uwwW2s7oz z7^mVl?u8y|4IN7ZAB#JTQ)km^CT8(>TrHgMv)_0`9Zf)$ib@Q@Le;EUHzf{ zq{YA4$nAV)#D3t#Uws0jIQFR#BT~Y>s^Z6%1CuF#_oos{rRoK-==Wam{XP~Vb}+!V z5F}s;6tql@t=G#sGGG@jWSd@a4V2ag=?n~4tH3&p+b?iNm#$xQP3w^=eq{3nM- zimb9}9hn0u#g3+0O)X*!RpDzQISmps-MUBTjeU2&<0sU90)faLX_dS-)mcwvyYMq` z426BdBKaj!>Qp?9;suHufs9+njdu*qO@fUI_fIH6-8&#O^#eVp-Q$>AwGnDe>DCQ@wVS{Um|{! za73u(rhnuT1tpjNnSK8;L|m?9t-@1PF@$_ydjB!ZT&`k`$Xq*D6byw;=Vx4L8cnp2 z(Ky;rq5A>!5dQ(RZK-bO?`&BzK4mIkPzne(#S#d`Hw%as#S93{c_Mz?1Wf)9H%k&^ zS$iZ96jXlv1Y|$|P{^EMa`2pFbFiF%QqY|E6NH>_b7VbkB}RU>d3t`(1lTY7IWRv; z0u(|PIZ*|A5AM9JuWaDUZrPPI+Qyj-CR**PP1+4z; znu1)Fa+9a3!k-LOgFH2Uv5rI(!h{;(O7Zbh<16%ax=Gl{+TgD$Q_oW1iyh)< zuV-fM_}75+KT<0tEd?wibS|vevsgeBU~)lzuMYnkzgdoA^i9PaFJHE}MOoT7A{q;p zWMbm<^#HWb?+w>OonnSY$MRjGi6?OEPYDsONkpiCdp|wa%eqUCjlW~~!RlDb1!VjPwV z{03R7#zH3WytA=YhhnV6#C3sfV|Dpq2QLo{@RyM`r(SNT$dbY!W#H(`pp&CGT&?iT zzfi9L-4?m64l2@Q0zIZMM>S|v@LSQ0b-FiqH!>K7){6bu{<`M?W#uVG8V8d_rL|{G{-x{W#uA*8XAl0tBaDN{3TcU} zp#Fs8EJ>ef_~Y|10>VlR{6+?ny5g-{C1ns=5(*@pb~b76n47iQ-px*C_{L#b1hP6= z7vVS+w)2SaB2=4DuV%GiAamE6Lu(nF_y8%QE4C2FM&y^vO(9z%TO(T}8*MQ+I4_E3 zcZz|WJ7S07sCFCM+Ax41ab6O5LszZ3NtZ0uE&ej6V&jmR@^}I|59iLvnJx|{z zJ@AV5SDqf>rr_b>=%IpPI~wZRof8%>ZdMeukHu{Y=vsu2sz72rFN)?@;P>FJ1oZL5 z`Q0La^Cs}h{`>`xDhtZ!dm>1W>J|dcFFlZ}t=L~Y5at(d-a*<|JQ3Cte$pW-H9$?T zdcyN3Q1DZtFPQL_C6=Vc4`-%mi}eXLkkB1}Q0`$w?YZ3%=; zc5Bj+?hytL4u9k#nfon^n7dwOXSVBg|Qcwz}&lWz-Tv@gjt?Hfn@V6 zFOV=Nw3AH3q$#FFu<=k(nlPud!-2d@sZ^G44Nr1a3D?jX+bWiRXO&$V`x5MW7+flO z?FHWa>Qc@|HG{i#Ls4&ek*AcYy(ND}zYV$(JG=EwnP~qTb%K}}?uc}XgFB0>C&L}M zx-|AcWjZVSpZVEGew6U56!-02f%`kB%S_88jA0WSN-lqQeK4h=n=nSRBWzgf>H5|B zz=)us+qrHe&k!5gz9`#kBJ|1~lx7}+};+5fLNbgA$)#qvg_Ube*!;8zc8WS)l-d$KEl=m(jC zs1TY(hDGt`i5g|L^jM>FK`Up^^9lO+?mdseK}MhFedF)*`!h%6%YySTn~VP_U7)ctB5 zh?)v$4^2=Hs82J^j@zV#RNX&7m_^#Cio^*ml+Gj+Tww$P+Xgyw7Cp_5L64sUjwyyq zt)7@vX!q0;(LM*&a#GlvBqgVX%p`nL@UzHSt(87K7v?DHyF{NfMTkGcxVDe6<|?6P z>DOqjX_CCN_|$T=V6Oe@g=!kxsMcT&R&!H)V{MWjtuZ~eS?5CXJ%aN7*v0X6IBz0* z;KTKnlwRFZ0ZSLoOcLnv%9+a9C6lf=YMQ5cGT+a|DmX~lX@Lcu(LlQBG|zs^0YH`>(inFuIA$KcN2S4p7~7qBt#v##q| zz>DWD$GDuCbxO!s?N_&3P;g-X z%G^q`7dg_-lZT92Z%o|i5qi1HEVy)mdq3h2L{<8UJ`wZ~c0`nR5_A^Ec#GyW$#%bK zc%qB(0K|pV+X0t@T;wgn=@2aHRvKx_I*vz>s3e^5{f^Evs9T_TKVf28D3%md`Jto^9GJp*os%kGxm)>j(2uZaPj zxvvQ8Al(N9A$Cbu2$7U%prGt=RyRn4FZ^MN$(#sjPbS`wt{&^OExp9~ufj-13&4Oz z&Zr2#%&aI}#ppi(G`Z)to{X=tm%_gaycz%X!T)!q$bXi!@$1T1O2~ts0^A3`FnV(8 z3p^9#vwv+9fd1pY>z8oQFSq}m103-#|ctcbpY^aR=DrRkV5vuJZXJ_%IOeg*3c z8tsrVmQ!!Z(R}kc^_kc>Okeg0>iT%UhXZ1`LJZE0?bGrsH&7ew_(e~v=InOp9cw7_ zbM!4h1s5Wmp$r&lOa_!V1rD?@ISXxOj`4w2GkcxJa z6JAx99XM=Eg#8CoeM%c<;i&UD>t)MprNJZ`YS`>E(&{MQ;ty<^uKBxjfW|FVmEx@o zrqk#>mPEYE;Wv^^qh&%es~F4pr48d{!xj;FJvEv*OMBsxT>M&PV!ZW|Guim;+Xu?3 zW7DZdrXxZRi~ZNMLO@S%SQq(6vJCq)ZQ5|BkbAvg8z%cm<>eV;)MLg=Wy9rkXyrr& z=S+1B8J=2HMZ3v!kXxOc%X@FZnFgoh^8NZiDB!|jVs&FeUJR+w<47aIIO8J9cuxLg z%FHq=ks@|jzB^ybr5sSFRE5(tQ<6X0h=wae7vJQ45GmE=*6ukCUNsdLXI!DyltGmQ zJ>#IgguJP*8#3s%{AbaYNmA>K_`3xwy=)H)*cTkxa&g%jeTcwBH{Hn3DAp*HXp(+h zUN=QA#VEyqtahx9vYL{h2>;!kjj_+N>R{GHYr=3czO>e7Z)vS!XPG#G^K{SfxSh=P zU2&w%UE86~qYU)r+32WC@X#F22bC8ajt#+{eot}8bLcoaFq#41L|`J25|9qp0&gD^ z!dyxH)1yUWZ&!4StDLv@b(-un!q4~! zT$2?1!IKlQDmns*6f6q~M98x7iG98HhvA4P%Q)XG$zVq@02xJBfy*__*=E=OYUCS1 z7Sdtey-Iu=`ED)BYQ-B<$*8hp)J=!{kg{ji%{x6l z(g+->j~20!{4?La%}~4~8BhM3l$of&v);alVOxCyp3+C6WD^+&F^6|ipy=;-wFod% zma-Zk9AhTj?Ah{@Z>?jc5Vrm0;_4^FQ|fTOsIL$qK;J=9WBB;V|G;-c&`O%Z(Ta;SP${ekZ+z<7&1H~|meA``wQvEH@` z-^ODSt?(t?jog{=`Kv5IvcX{O`-&2d{~9G2{xwP{=vmoX8X5k*sG8=CRRJv4gbQrgL6HO;OUka zgwCJ~oW`JOxnglDy?itxV?HzO81i_@e7rm3c)lIC@d0WMto4Jtq7BFQ?CCGk%r}Ig zc!G&)j!UE?Fo;jXz%W3pg;KP%zg8UpiQzN+4xD{3+^eV2q$LGD?z?*OSI=GvWY>6wS6G+4gm1c@o7M4w)+`e|>u5(e}` z6%9L}Wq&3+y2E)OfQMO1a|n5);Yc>%n7${Y{AVC9jxm>mG&IpgMvj&>v1#a|p2)Bg zCeymB=}jQGy?To|MnBHDIXYvaLYXSd6E(T9tSp`1Dfs|xB>}(;vNeoBaya*z~*DYK<2Hvk0%3EKrI4nol`SP3PreF1qO1lQ#k3XL9J_}k%G`&D@;Dz;2%KcuP#7muhQC$C-G_x1&@|4EX*?WwKolOH8n<^Ee^hJggyIq8VwU4O znaV6Ev=q3Y`nTOj!4KHqR`@_6xg^&H)vOjhXmE_%QLkwbeMm7|ko;1jhQmEKF({~s zvB8efF)3_9zdktT)>8}|JNeOMqy?!VC0z(+9Q;I`ACPgbmsO4mazloU+%=#CzZV^$ zgiQZYGu9!n599&M>u!qbt{eOi{8DU87r^&dT?h*dHZSyT(dgIx8a_NCaSMDn{@KVr z9f*4;Zwc|ePw?xW4gmD)+}5N>pJL{dhcHaX&y61tazu%kebY^S6E1z@kBax;BQFTI ztOHhS`6s&&Z$iV~kPe8}Vt0Ov=<&81L(hqQIQ(G2`)zlx(eCsUj826-PBYz4RngwW z{vkC21kIxM(?};4K^ufEdyq;iIc2|+%(la1KV__@U}GOS%bc53q+;4H)f%cg%mI5M znNuImM@X@FVIiwIzRsqZd*935c)?dq)`?oEr44s&{t7QvWE?ghU*U!0{}&(UzuTh! zC$gwm*(%OscuyF!xYV@}!*eUZQWEp3#!+T_qN7NO=5|+qiv@xVd@yzpqqe-za=t{& zQ;c6wuh2os7c5wL@h$j?*AGiXUQ9LnpptPMZ2&ZST?TY9@Bmys@B^|FzncYG7Mkj}wIIqJ>C|`GY6c)4 zca*!~=qNB%Nm_PiQ)DIxC!GhmntKUUItI@tg;X>-dN4;Dpr$3TC zDFYd~YWlq+-Ui^wj6DDLvjLZlyd-rc{~h>L2_i%Bqz#ndD(qME?z#|l`Ffp>Hz}d; z_;k$=M6*Lw@&NALP8oemb9UAz?x1TtqlJBMUppiW;x0_y3}pel|= zYPMW(KHoz(eg@m*d^wZ%zy&-je_%a}NsBeE_<0cGXyx2P>mhcyC^p57e(3$t0We## z0AW^a6s-8L9Xn#aI~p-6Q=x<<$*ZyLn$H{Tabi|$Q)r}4xkw?kComp1{&uNO4~^V} zUWhAp?{XUAT)`n6{@`;oB4p}7f?*vtKujk>-c>NZklVIsVJA|AG>Z>fMeaH-mc)%) z$D4f}Q;gdLJ8Ee9-qD&7vNAS8)Uw0jkBo!F&zb1TI7C$98fAPMTg@~bmjiqFu;wJ{ zKASCKpw90gnTrRdZ2~206=w8cY`CXHtwtg%YFKAxVmIzsG%ik*>mG;i&zZ+p*Kkjt z|3fN*QBH^}va}h_LUAn$Tyak9im<(VtJoDu3s{1l@lcbjjhW(ofe;LTlowc&Z>=E<*lH770BDjX9icw#-L0 zluPUGCv65&k-=B;Yp7TGnxVdg6`zkwn%q^VpDZ1<*+F8xP}E_sdqtO_Zu943UZh5B z3*Af&H>>WF7azs6L9+T-LK|;428xNDysq~GHA_WWxnP%Z6nhW0!nH#^wyZD8hCi)@{74!1ZHpg8PWrfPrDD|5h$zMxgY>W)QPA}18B9S)HK?HP?yY$oh z_UqK#LsBN!K1S3GRK$pt53lQ`4|E&1wC6@(;GG+^mH%|}LpRAZ!P1C~ zB}07RVE)D2`ogzNT)9eLMla$*oYqvUc5*-1ASiEgnf>!{Ox*=)>_B@oV4^2d-SQX9 zDF0-m0vUjdLMLp{sZJ!DJ*gG4SQ0BoFv~J`?{;-TifV(CdCFf*!A2wN!0(JVtTX){kGW3^5nD#?%~yODp@ zyawz^b}IHpdGRs7Ue!LGCB&c$)%NQukIYVshm3(mp) zYNjr@!FGRbQ_?1SNBx|269=0FHTUDqRty?)7w1i+{QG2R;=T!Tg;_p?tzNQ5j${tP1gV(Z|n03LF z_>4z3&)AjORDlPzrwdWSt^?LbB@uSoFoE5t)$yR@$r^uy;N3@Tojr7)?I-ZT`O+`6 zMMn<86Nf~{ycseKA+JPaNQ}Bp@eAkNZF8W-#WhDnu1bw9NMHsb=>_%xBfTUI8Gf<2 z0ay{E@ehNA{<>r>dXk8i!RlsaAM+anf}V`3`4@4o`O9os`%Uo+c^%9q*qnfY1gDuw zw@9u19QjSRF_+Fm$pKVKwdjF!Y8$sA7j%?ZTpy=Ix5Xr%Wd75hi5@*ldr=woQTa_; zRF2a;g=%GIR9J%$fmGYXSf;)5Hlq#Oi;=OkUmS2Am2trKa{B?5&W>8=0Mq=2VkpZE#!opywAJKb{@vctpau+sikG?*RiuCMXX%0PN8r1)0kIJ$2uv z>d>m>`6uj0d10J_^6o5Kp4)1^J$wSFYCdFxykvi8V$Go9Zqynm4I!$=5b$a(M!le5 zUZmA2D(4)Lvp{OFX_5znBv+`m{#)99it4`ACBliH_!Ia<)VjQyJ7Ld@u99y*Ui-4H zS>qhLo=7s0KYPAi+i((A?x@+pPP!qt|LJj^WRsv|>Ff`GY^HkNqHc)a`%A z7}>ITl&;b(0F{bTu5d+b8*zQL9xOivoTK}$G>3?Y)Z%wTarG9|z5Q@TBbWT#elLM+ zRMXGmNggV%*$z=LcVbk7YH#^xKoW^;mlApjjU9aQcv?H=@}~~rT0kRqzT2+Y=1)rz z5x^btYEYB|msm%V-6oE`w88RA>1c7h5sqDHtGf8e0uyk%`vZI8L|Rv zkz(jk)9&R2)p8tNM_Op6fFC1Pf>dI+GH$osNI{UKl^#Z9XluKjkKWU-7JqD_x-mRd z)vjm#4lZX%=w((lg)b3KFENZ##vtKQ>ZzUN=E*_bCr<(GTMt4HoSa{jD>B-0JxvWR zw-Kx-z{#_(VT-;hV3YOZpjU&rE}jP2(!tO(L-A*mZbAiE;q&L;%s&PYy>QpBdPeoC1@xBg`0FfueAhLK+cn1iJFKl$W2Z ziTnzuHd*M2U<0KQ^p#oPxu?kq1X3Lo*58>Rd$mJOKQWa~v_3zxgN|m@8BMj&$p<7x z$m82o3zzawAW)5XT^xa#SFBVv432Tir!`b{rrv^2X4jq1K^_iSK|Lvb;B}4 z3p1WgW`Y1QsIZ-MhspN~wnOj3wam@Tw=2Tm+Md{aSTjmXQQ>|P{4(Utly@ljl~8t4 z!HdZCBDOeHVg0ZeHl)oM-KeHeJ3R?X(Mar7*$R9~egS^g+fyiO^0q(yfC~d?k?(?e zXA#x_CD}6F;i?++XAlp$q(9aXl+^)*y$SHO^%z?GjBFL_^N5AUD2Qg0bmcwpk2kGp zaX$~4WF$^A1FBQ-8Z`Bgao!W@2DxT2nVA+*617C1YkKO1DW&`zrTF`%nHC!X=j}^$ zlvT7bhZDsC=7nU)jwbC#7>)f9HHH(JV#&e_Qx4!BU@vV*))SIrSNSPJ<=wT7R|viI z?RkhNtFes=qHCw4L^qt71fIoSvHdhfYpINsTudBs^BLiZOvlTy5A|#3bQMQcUbvhX zb@UgtD={tUo$^tdQyu$QRu&dCgHb@~H)Np@7J&ui)Un^>dNLuJ5wrK8v_A9$>H2Ns zzL})0tPH1`H=||kN(o}X78Cy7o1cjz+ph9g>ICwSspmS~yPE|Z&l;iutVSIy>m=3` z`++V>jszuv6&)dQ#EBKw$q|D~j`*rVx$%Ew>kOoavRo&II{R8ssR**0NSj8MaZI8o zMypD#7&c2W08V3>ELhz;#cqcjofl@)Qv2hX!*Vy25zxeXu*GXDas}dNsWdyV z;Lyg*>%w3Ea}`az|9kr+o$U3u2?l`yvT!sl6uccmNGmKOWn?$iem?BjshiCpWs3ya zI(CiSWizh9mJ+gzW2WML$UWrpR_T?-5{pD0y!4OteR>*}tyq)f^u&`~)eO~b!5-d} zpnC65ex&3pC)sANx1S7tIjF!Qg4+lmJb1Q9)CN!I*NX=*E+({1g{Gy zJHpWg@9npjR+=eKKlh*=wmNTjo-*|2XY8u)~O<-s1@lVtf;9$ zlmUo&b>yf~!l*TFxaMab#a-Di2#uN%B4udR_aqndPaX~Rxt&TP7HKw0Q>Bj;vYao7 z5jBxS184ALJJU(;8DImJuBdf#ec>Wa4ixh(0Vk@51kRj&(#i-?7wUZ!{y76vh|p#* zTz%Hc7*WiSHbw>omlby%;+(A}2;=_S16PQnZUG%CSiR7uDTAFf7z*WRQzP?D+ycNxek5Lh{)!XNom>(cpJXBzI_%8Be{O<( zouG&>8sWE)$XO&48R0(_&tV~xNeWbs=f90N8Q~Wwm?9ggGs@YPFBW4{M_rMHqLR-v zAXyODuad~dHL9glBnzC7i>#?S%~2|tdX3+;{B%F+oKeS<)xJ8s-}zfdfx2Imcl(#} z{>wS~KZnwP*Ps5SnfP0M{}(I#pKSYI95HWaFl}crZeg&8yE(+JzO^q%7y_)+{~IDE z491-ed>B5Kj$9-j%&q||X_U%6{M_=wmLir?GHeV$Ed+b%A7*=Lc_u4id!Y^?3hDgf zB}M~b4uLY#va*rn*8-U)mx4K05b-YSdll1X6H8|o{_e}2Yey6Yv%+?_RYgnJQIAhQ zNlf`(FAX8247Ud-4CX`tYhVb^>$=86M#0n)5E0`W;~Vk~L{$|XBN;>WH;gctG8i-M z_{5)Yg=`qjzsb=tntgvEeTVxSD^@oV(zE@QWB~sM+TH(9)&-56%?$qY-4!TlC}0aA zf24MdUC?gVevAJFKlkel77Ve)Fi95zgh-lsnut%oeQcs}GpJ0Fj_wP zhn|=a5DPy#TB%V&p9lULa&`i|HJPf?bP#AwOW{^yC|~*Gj+ZBV4*+N#Aq>t#Z>tg> z1N0GjP2~!~f0MceSDA(c-(AaU&{*GWd_q}lYuD02Jn0k4G zzE^yXR0ZmVWs#CgriduJ4CnG0*X3R+)?&?mEcAGJKQ=9rD}PD(7+Ii8FUhq~ee8Oq zFvUUCf%r6KA+GHpQ&WOGI64YrZgrBflZ1*E<{||psmb+-e29F>P!?)!HM$*E$Y4Dg z8tG4Q%p%Q!)tk^skUEBBmTcz_8}mlZ3RTZ_)NLX2HjB3st7|horCtu#?5!LCm1piY z@Bn(HBSTh0`YXYixIZND!kN@w0iR2*X&W_ct|PHzl;4CB7dO?#(gJNH9jut9{uM?F z25i(7S#OIok`pvYGBKjYE;ZM-8$t&lELpeNll({Dd=wc6CU@^4441HQFWQs5e+agb zq<>&LSY%bPE5~T4-c5oFHiKADT1#ir>OQIx@7tZw@YG_ap16`i&9+@n0!5#OqT*%0 zH6Y62_V-m!D&lToH%3cSeD`_Il0nCT}v;>gqBhbWzs}NU+|c zPvcA3>K?1tj7L_=Bm;~{#Drv_K~yoKhJgcC_juQjstPHbS}ZXfv(roWTFAV!WNQG= zt>MNnxQ`h1$|ofg-Zkp`&g87Zwm_7re(HTNAdc`J8Xdpy&B~nyovEKFRfl-o;prHL z7HbncjD&y-{Ho(O&j;aKSI#Ab@44WwX1IN{73kp(Th5rZpcj8em%u$%E<> zE10}&a6cCK2;!)}a-@m!LqIV|6K`hTW?XU{K59LlU4FjbG5l87l#>k+U?c6;rq&O8()WCg1{2(U%4HYmNa{h&Lc1#1!y<$nQ#>9q+7cEg{~{~TCLY$Mhx%FWeKXb#b%uw{%X6LC)qda&@pAUbRj9`(T zx|;M3G7@N=^Xg2vlQe5XwaH5qFOx2c!|L*&Or-{!)o#u=*R0|2(sXQ|j`J2ueFYDZ z4ovbQe#*3h&4TgqUJGz1UkW-{Nf)>6u2<=a-`2Kk$W(h#55E)!0v)D)zO_OROYbQ2 zE*`uScN608($+LQJXMq*Ckqwh-2bY~5;03T@N zn2mpZCa(~UiGq8jx<7!4f~7FZPv*tER{A?cPo*me7V_9>S{*ey!tFjMLabh3pu*X+ zDIFmCbf;`kUm7-z{a3hw^CMb3IL6I6bO3hV2q!;HVIa?mvSl9{*6?6JF9o8eqULgX>&(mw0Tom-p78)TzAz~*pc*c&D z8E$POr|zOYpbb>ZVZD zlEg>8h=d&?)ml`xK`f=n6CrF&ar5bC2(8`N`N&9

jh=}%8-W+4{8x9@~!B{eHm()KF4i#a-4h!^>?=73C{*8X5+K*>B+|6${WJp zRg&q|$5~#h==H&O<+c&h;dj!Rzrv(`{9_!j;}p>Yq~d@^N9gpzx=6DXa$~b+@`h}r zv2)B&!r&4B#th}e5p6IhpiAM1%TM^P-B>2SfZBYZ1M=U4D#R)WIMlr9 zB9}3;YJ#v|y3g5J?efxtDGOC7%mn~an<5M?3t=)b9nf2nC;Vo-!V^RB*0HIi`?e@i z3@u~k`0Xh_QW}~)25*R=YMi34hBxan%t1o8XBX-d8G=UQN6rY69(dKrc^^bueTnRB zeI`=*1r@{EBDZNyj2@6ziR4A&?}>31FGXQvFMeZk=yc(eVCD2ZNpj(G`xd`dV2Ll^ zs3Xrwj-i-uN{tn?D$L!um)-1Jn5rd3DauG5}-q1)&A;c z%~Z{Ar%Y8F8!Ig>f|pfOin?v>-{`Q+qw+ zhJF+Jjt=|L9e~{Ig#!P@--|_@xJStSN(|$JdQ%@T_W2AClLPw{8IbsRt0m+`x?_WE ze7fHi`4s9qMLan>^ow{)_4kuXtBvi2zsEz+lYFD=IiU5XIfx6h1I12}kBt?^MsQFj z?AMJKFplb&8o;U?vtCyZ)eSHSpQ2?9*fKF><(?v-ml*h!GL5An$TYd%tsMXpZ%;dk zv@Rdd@vhu6_6xfz4hCr*k`RlQ0S!x_vtRU=by8mhs4HO*JoGYnvUyVfxlX0EJY|q% zS#tvgDrB&x8Pi%&K1$}M3b+MygI!L+%v_)i zi$6b@_qtZfG5{Ill4xu+%uOy$=9>WKCNPXV77Ckdf#qIBNY^rox_Iwl{v^HFrvbus&in(nk60kSs)`C#%8e7 z^k~XsS2LsneK=i}5IgC9BZinH5ka@+^^#C^QaZX7KOu6%*BQQl!>N>tC6a zjTTqL07WJ!pDPtHV+poYS5zYiAvf>c(n)LZOo^&i2E>@!ID;}T7xsh(Ek@S-ptLo8 z_Vj5(G9}|;Jz{91n8}ze>*OdJY&x=#9rOt%0znKkd+74mQE+Yd+_b33Hp3oB2?k|j zVM|8ZdG9l-81ScjIq4A-?RiNiXb}+0v9_$plad9TWoTVHqG9pyDbvPu#>~N!1(-b> z=TIZ7r7*4*QOI@)|5AQ(Y$vLIeRrZ_aq6oVgh0XbI9PSbi4iWzOnYGxc$OpB(51fx zK_(`ehT99Cj;zAY)J9~k3{kf9h!JW}H+85{H_-uWm~3c{XiV~Zhp1bWhpbx@2&nJ_ z!2v{63Mcf)rF*Xqf)S8ZIE7Oc>)h$nr|(>S5r|h6*chCfsJ1Rn>~G@`KyrIU5c$Pz1Om_3AzQa&x-Pc>Sief07&Nvy94#`F>yo6|!5 zpGNR~n)FqJ@apauFmp`C+=C;H+wL`NIm$McauqM)Zb2)%;=M8whtp4+3Cw?C|ImtK z>WTM8CXAph-X;{?7e94UTYTz#=;Sc>1o|p(EAi!?v>bRb|K*K)M!y;Zsj5wK2}<_J zO5v%y0YrOhXwJ&=2wa9QIhpf>d#-EB(N?ZSXElr(aD{E*9n69NB3oOV zKJ1CyGmXc?kTc4$PMD+fOB^wKh=}Zhz+%K%m;s5 z9gs87u}-A_etCw7#BK;YD!|U=4o^t>D%Q`mZ;#Ew`S*qLYrdlbCmM+N`qy>7)_q#6+Slxj@)7s6e5<5nFxe#4nml*X z3*Z<-HF{p`T`@u9bnKdMfG1f2Xaismx!dk0BgKx3MhMZ;e@8S>5bAKyPPChhIC+@e z;&3%beF613DoFjIT%*ToPYc>li!@X&#D1g)d9-K&{9P^pf~p6Ow8u@+XSoP(?;2u{ z9>SL@AJdKrb-;Xy8}h9hf>IBOosSx1N9tzJ)Ub!eihW#1ZmLV%H>%t-BWP-v{#hqg zAF~~Te<_%DJ?i;FNO*} zq`7I?3HGY;vd?wF!>#M_m%W^HhgVy)y&4_U)vS~0g9Fnh+7kn=0RzBhJ0hb1*C5$< z`(yP8h7=$D6ul@Ey7SKbYam_dyKfM7@8A4@Ab(KhJ_#h_LmhQ`1 zaIej2`eD9Mo~0wFoFikW`@}9+SY}lI6Yv50}?>Y)`2vD@ogbd?qMmc1yu!5w2z4&$h44lS4uHSnl%^7`LCDJ3`{g zg99s8ftU{#uj5Q~N2tCtwJC?V+f4kCVk58g{zneU`KkEDk7(Efk1k<{&jwUDvwmMt zp#z8oWWG3??lfQuU;NGI*YH$N292d&e3xw zp7IkQIZ3{f1D(6kS#g=HdCNah9dnu|69?b2r$dP$d%Hk%dGPQJH47rW!f5Yo)(4oW z0WINg>lEuQ5Wbz_Y42FcjMruv${-et#IAdvwVA?|l&*)QGvl`GZy!@D+5$44XTr$G zTi6TwX@pMJcc5msjEIdz5x#;KS|tSrohKyATUM0h1rff&7hEL;4xJ|_%UhW++hUmJ zyD?`*L8y3gt&4&OlI&S|zQ6fTV@}I6$h2vq z(`q9K*|p|P&WFL99R8O8ypNCkKJVi7*p8P@oY|WvuNQ&d!k3EBuFtS|{_^wC3;iW0 zm8S&<5Y>{+jr*OZEP({F>q=QjZ-{f(dh&jjW_Lka>$MkY>9@^;_z-~U-u(aKi;iiqb~ zgxI7FSpklKvlbxtnWKe^9L7upe^RO@uEylGZ8g%^x?%0qTIxNhofB3h4EJTeXTx}& zSy2WiVa@Dja(dnJn$2cv*b*70tstFS+40Ei0-_*;d`LwUCS)umq)wmEP;QbriFR+dj<5->9h6 zDp8v0BG&9g9)_>JAgz7ol(A)7DiO#j@2Ir2%PH5fyLPFkFo+cJeULrWp#uq8I#&48nYpa%wbHU-Fb-?{5w(Fun*+$&z5`qK=c}h zB2klStC1ed#TnRlM&cP0sS4SJrUbzh};DIoQ z=m}p|(nUFu_%RrOfBT&qYm_dPuVj1+cN5QAm2{Q66lXK;fb2tr= zxQE66CWQF(G%~Vm2VOv!v9&!$ts(Qo2tk<5KzX!z#~A48^E?3{tT?Y zB=WbEoSw8Bx+fawfr+?MviMCu7JNlMKs<_>zy#ARes95@IqiPHrAwdlHh_$T zVYtt6nAuhPXnngf*M9l_#_7Yk5*2V3vIUr>F?Uv+c{L9RJ0{wDE(w;< z>R1x;KS>vU+65$6V{(;tNd{@ofTwRz6f>i8=f->aoNlL}XE6OzEj9z}K%qN6;*&TH z2bRoYbaCvgT}kuFiHX7~BT!&qE--e$>zkL+C*>POdy*0bxd3ci%_V3$i%_Y%?H3dQ zSj)|_DACrMbo9;{?ffGC{F-`&^AZryO#BjH#^`Ht_2B|Jnki>hSHbP&r5=37-u{`4 zE&2=e<6{JLhmd2PwVrtxwmK06xHRO0z}dF>S|g+A$O=jKNme-qnfZr?Vo4msywx5I z&G}YKd2`37srmO;Q}gyYl!h&`ZP+SjkPlX?$0uz|A)&#iI}h3s8$-So=16dZwyoPH z#5y-Hc<1LXmiTN9s?c6O8q@U9I>ScCU`W)5?Keq?g|qP)vzMkB1P7i%wCKbS2hC0- zC_=Nxj06W=Ban$UW;O3xfOj`zoo2+3mTTj;G`}~T{VTEJB||~4C1#RcOzy9k&5w%4 zyufENk>u7fp(e-4v`9V7JJ^p?Al#)pD~VD}-Y5OHISxNzI2g+8OL4J5DMvbquYwfj zKDm65I{Dn*6BKSQ_>a%#TKUv4Y(e8U47S)koog<^KNmp6euKP6bG7j1o1@Jj%zqI$ z1(CWRuJhBSKcNc+JMF_Rq3PzPAJ-x|D*FQ%C(m2fWO` zh9#JVt@tp_uLJlkSQAQ63@B*>A<5H5f2^G0ePO%ZgMi-?0p`TcWZh#R@xy*bhJ~G> zK}NCNMYjFPxPtgeB!8~!c@szDG;y1kJwJnFwOb70gjnX3MAWpk=L_+>DX z#e?6!VEyShibUP-qssUnEf1*w5!U~Yjl8IZiM8>64Q^eOWbD49^>}9Wm|U@c(o($R zgGovt$wdV4i`&0VprL0KMH7@M+pyruRR!FyJx2vpK*i}V-*l7>m2|3B41q&xDy!(s z(|%-Dw4CzD{lwJP;(VRTe%$#^yJGYE^LTmtLpCpKM2nroKy<(YvJvrwI4fR0i9WYq zH*1+8t)C-nb`O~LVy)Cje6%hG0jH32gqhl09a;SS1d#CH)tp*uPy(8DhHdEO z0a7AKfUsNQw#jItMzdvWZMsToy02=pZEZ&N9S6&;yR5yezJ0-#^(vHGX|-VWI(e0Y zkAtI9yWT>bwAbtekD%S_RC* z@%2kv|AB0fcVpeQVadW(6OGq!e9CJT6&h8{^~z^q>lBwMYLGiuqz@v11X=<;o}Sb| zdB7Jk)InnKm|C)JproPgmyrIJ;b4uDi?!9s+f}&{*sdcKjHU}zZ7L#q-0-iO^JS>Z zWv!Os74QO~!d{$Z!N&8Al2<+w%9P*v>o3ik+?8qbg^fAE> z%u&Aw*-H$+4LJ9H+~eo^8b>kggw3Aj3UG^F#KRji3>z9d3?mvt4m&p#8G;y748tZ^ zMCjlz;t~CMKw$Hw{}4vu6`pFOw@;8`8arUFo?7U`el$y?dC1HjHOU%w$r|lcei8b@ z(uh~>rDo%bdTmR~83q|)C-FP-6X3-2gC!s!B_JT6C7}5Bz&}Nrjc0UBM?2HX{y_41 zFVBrXrX*U$lNgTLsu8C&IC2$JbRc4bmmrDW$`O}ja%>lu^mD3RT2w>&(0uOp2aUmx zcDNe&&HV(4drKTkJKGA*r1VK=gh`dPPcl;>>>Bb_xzM7Y=_C$4XpQFe_*)7LCuaprXiHBIaJk0)|68uOGY^nIW{Qmimqs{-^)`=;J z{6FN_>x!GE$bXHizzTmL%ngMwlbR$0g%ssmlbFy!rV)c{nlCD+n5YS-U~7;zrFNe~ zIhQb{^9s^#r2nu;x1i%36xl8GiE!rVwGK6Oo}XDWTP$t#{)&Iv)Z_aDy$Akcbk^@k zv@-%BT}AH@8r~^gC3zNY07G$7C3Ti+ADjM40v+KFFT}JUZ7nX|@KzE`0N9P#7xE@H z5Dp|Y&>E11#ydnpfD`e?6hccO;WqqWg5+FGN9QyM8;hXl5XR}|m(BP#3 z1sZz67%{|@YLG>vxOnmY>_PT8rgU**Z_lQY9TLTE%_e3B}(@R!9bX^nas%F15IvJ!@--$&XZb001&jvm_S_^ zN$5a$2ze4F^A$~846z|c_U7s!6sKU934Z;8mB(#<~%lZQWO!`{CPljFy1a2_ej zJxLJ=a{>xf)lUtX01~ofDB~)aY<`8<`7R#k;1r>z24;z4Lw|(?$xW%IQh?lC5iDN8 zcWgv5+t~*3xs>$d{Tbv&An3h04G7+;9>lw*yf|g*I*y<=djKk0(i_2!H(h$%7<(H^1yGamSZ(sj-e|63=%fa*K`Sds352`hB{=qHs5qi4mLG}?O^}Yh>Cw4{1 zBZasUbTLs836XSyjU$~*0xhnV9Zi6h2hAXIz+p2h#({3F&)deFyMs!n92Ux+2Ty;j?aMe^9FGAc^r7a>}2RzKdWF z^8A7UeetWFRu3`uo>i6lp}dy1HOLT|i+6*CkuZ5awbC<>1vE12N;(FEkM6#cS)6s3 zfg`~=qs<;N;N9vmN6ZHGl{i67A#e*6DooXT@j>Sde;Pu7B z$40_@vreH5Qpd>g0Q5OjacH!WEmR;ln{)VbZm5BQI^3eGp1MFP#OWj?SuvO#T;Xeb zZSdiOXaY{U4P?dc8Kh+g?~#s<&efcb8Eu`9+#M?18F- zVOnv}(wQ-e@KFy%#26dGhxA!tUg8kwabl7WzG>2@?LL+|foEVCvt)i+bdKQ>XuY9o z@m}5&(zxI`@_j8ji6TQLev@vB`8!{QH|*xon5sB9y;!jsy<|bc_e7}={o=Q>bMZ^0 zDnAM)!c~6{yuK9vU8=m`pd^9l#HOJcu%t1}35M3H)cs^>zC(i2op`GCnB|%HMzh);|IQjQ?0+E1KBbIXeG$ zxe8j_8Cm@oA5gt^Lte)C+BR-v;*ti21>I7eM1)V=jw8Vb7O;k-v$0R)*X{d>Le`l$ z-DgZXBa2@!tg6}6R9tOiGyE#&Jztn3^iwS6W8C{r?ygYp6N!5?XnD806VkYm45F9& zwcX8kcCx$o)Wfy+^NTDOXbtcRZ7o6&K#ff?QbaDvKpR3eY(X(2zJGqkOb?`I3yiaQ z%vpY@(GK90#3TekEpfg|5;JqjVxo^ebxw~xCcfvw=`2o#0@oe7b@>#?=qfra z;QlNG@L{ZlNhu;bcUA49fWRFl#Q1Rjt=ex0i2C+z)^q+;8Qf*oMhJU<2`17%mfbYF zt}^>Qe5H@v;p+g`i;|_N%TKiy&vPaWOr^u;7?F>~<2D4jorhm{LEEXFSA{t+UUvFp z`XPXibuS9?tWe3wNcIe#E(ei*Vv|4(95oay1HDe&dEoE(7|dq^KW8c&X+sXz1?RgM zm)rGP2F-Ezm9V^h*VP;$^!6kOq4A&!EK1z0(970n`b2X|6qCJ1TvA9hRd1PjeWlfa` zEv5CjFl-WJBJRtqlWAM=4cN+s*5;TNQLo#s2#%b@jt@6Z#X)U$yf1JN)p>y*Lio7} zj`Dn(y%f)*FzLjqf_4k-r7=S0h(0~aNw!c8vH7+?6Grd@MFIU?M>oD-iBL~wIEef5 z5G=Q)VJdRxyon(kE>k^PY~~w@uZdwU_vMH$%opRm-RGzvkS}=Zof}7kk?d45)2?OaRR)ZRl%JLzsiQk}1vt5HL=S~r?|0~WK$UUXx?h}Q^&+hK zZj+WCU-d!!+*%(>w}rE>G%Q^jY?eR7O-ybpUCc~+D;rLU<*7mgG&$gwCgSr}e3Eam zp|!CHjIHeb!a`3G27mEK6`K((7=pVfE|p%I!Ap^u&f&K@P&7GPgLf<;o=2H%4Mkw2 zEnz0F2-_4dsG3y$E_-U3H8A-=t%`rqs7mo=TEPS}m(>${#9kR2*7O3o)TQgRnNfd5 znrk(IO<~+BfWEBFzei0VTA4Myf1G7n0b#q>SEI(rGgL_D4WP{oPGZq(D!m`o)xR^;b zGpdx#1w~Hn{wXRsWlorpAv8v7EQ%PZ(fpRto#YSY3TZUf8lqQef?MbmS2pvTJsuUV zugp6nC;8_42aUKsQpO#;su9!8X>JL`{KO)wr3E49m}82x?XZFBh=S@cJ=MU;Ifg2~ z_zPX!Zlv)aMvPv;BQ4_(Opd>C(0XP~a`QoScc4AOuzLA$dWS!I@=NU)$S>%rp|Q~} z=?Pa3gV%x+4WV*Sx$dIQ0%>3g=^V+!4A>8lM?wb6dJRst)$c(P%U0(K_v=J&*X*$* zD8o0+SK>OOA=(rk>h5_KekK3L&6Yg>0}C?++SR+~Cl%m(LToJZ@balQ(i{+L;VUds z1`$~)l>1;VW3I%)T!dW)siOkJBgGMxk2%+2dFC;(pP`#snU6*uyZof72jdg|z@AeD z6y2={)-B8tSc;nG0yPJKtH?{GklQncslC4dt&#tWD!1xyxjd&vA-6oGQakg^GGV4p zB9$L*S*u1Nrs8(w2l_8S2re#^N$y+cdHfws_&<{}|KC$(|0~(xKQ~pg+HWspGZbI4 z^=WHXAw*VWOmbFvr39G)N2`2^yo5{<33G{`ForG-ToT}{o#!*uV3mp@#m#IAT3}xD zs^Kdwg5x5XHvLfM-+2aK0e%p%a`!xqHPgpf@o3@#1!mKtjf$wGL*A=pKE=*A~xr`lQ^;33+24e?TKI}P^IZ9@z0Dm#D> zs5uq(u0iG-iaoj?9q1~{di+0fNP8Gv9vNfcu@IYt%(2cN%cxxS>_K*ifO<9`! zj^s%}NWlDy;ZHDb_+|LMcx^T#(jd*R2tdKFmaAA&F(L=2Ip%^_yKR@9hM6p{0euA;36mV$voB5lbt>3huRBr}Lg7mtVQGJqu#xKc{rz8ScsNocs1tCE^ zs_Lh_v4;A21rt&|i_Ivpm&}x`j;2*?*)vzpVRS9+S}i`4&Z^54o~NP*liY9Wp8J}( zTeXO-Q237{CLA1AIf#)Wbfta7hoiFvP85ca=|GYgm`qLz2Z5{cvW4~ngLQre`O3$w{t%Y3?cEN0Y37>)U1;aBgF_@@<2^5)^Wnt@W^;k5>(}$?)2fmF7 zWonPm16EaKlIT|Gfhl>aEMCNMrf$$q;^uA=)IwE!Ks!f){Z6>1A%KPh6-SmZ%FXwZ zv@kf>=&sv#kBU;xmM$w2O{)aXlhin9%25jdX{*Rb>16SS^uIKI*c5-cx@M+`2t+%ZOn;il zGbKqcqicv)hBFLd`p}M?V8X20>kQ@+gZKdw8HVzCKUIQuc@4c&3l|_JBxyGJ&dO9$`8T=!tJ1ds2sM!AgvJ+SUyqM!X`0o2bS*G zK6z%#AD()OJ`7;+_Ki@!=I-RYr{EIaKXB2xO@*)JY+A4Q@4O7oQ)2?BCp+=N>f76< z!!yyY8jB~!lY&M;QzU1cWx=3u2a7dUeDg|MZ};+FdS+nT^SxkQ+;F!tJ;KNmM;DDS zy0fK5+}Y$)&y0*j2~Yg1LnFDFSR?O((ziie86Q>qg>ZFI#WxN+OlTMy{L`n8F0v{e zX$KwUV$W1#9Trk*ea*)IppQL8ZzX$DpA7t6cXJF??GQ56j2d0^+!_G?+qDJ+_9`G0 z-$&@Di~>o{NkuTP0a-aCsqd^xSk>4veSt)ko$M~9m+STx`X+bg;$BeOi0I!AC>wOx zdnEFAL?6CW;zY;+Ail^HkXK0;sE5gg>4}HKtBB)o(T6NZfSS-`29sV!NF+^aGK5dq zv-ng&vxQxNHf3sOl&v@$rIf#H_ib42x&+S<2_IYil*y0S8C4+VRUH>aBKL!H(=N(2 zge=Q<4^tF#J~cU2hLAw|%!fFc4V57m;hA{vtobF=mdFRTYDC3q$hCWQ zv`D)Ekv3|2QXYa~QTzuNTK)mOk#j5ymMN3f$pe5n%NR<>5Q&yH$d*3rX6_KOetB$r zsoWkt-30r=q=Ab@Y$giK>Q4&boZ;uSFO1vshy8Xs!GOXSCE!D7oMqZJ0CHpq9 z$1<(6)dzQjkv8%(!78m4>JNSGV5jr=Tc#mwMnxK|PqX;U@;VFT-Geb(jwo7@_U@lA zy{EAq(V?J7di{3c%X1uj3aa!YF?fb*v3GZ)Ag6As5i0;g%g+FA|Ho zK+L2XwZ(sRN88l@;r^6K9kKfoYfq|Pv4qgcIn0?Ygr&2m)jQJ8aiT29f9w6(fatbc z&@!M&!b3>yko#q_z`%WzuX&}~&7a{&Nm_x`E*0N}wG6|#u!??unY5N;v**<5 z8^j!qq+8=-yNY)1nT6^T*HmYok{6&Wsg^$2d!`}_7pSuSmFJa<4g?y z^8$o2521wnVv^mw`mxfwvXRNR`}Kk4A8X(_7a~9hpuZaaDSAv#+R6ZDd#KconL3C= zcQaAz#!#X^08-1%98Lj8d&pGoIUD`kmRtT#zP|~=jNxn~DvQsJi1suX;#!>2{it7r zTlG%3UnyW@Q*@_p1PnW1ugl-5B5oN>^H5Y*E@gGm(?L}#GpWhid^myEKtqH`r-d9H za_HNb|7lYz-Mpm^SLQ*Rx~HOhsh}ifRzyj8>eR6RkTBpRsn}!%aRXqZB_P>f7040L z$g!QF$uL#b^fFbaYTCSY&V!?**cUdlyg`kFj`IQkW->`%=ul`pf5a3}k`zB_rP?lz zY5F%cdvg>nIuSir=D|2$|3b3|a5)cm(qS3I`pvUVo?)dZjHR|opiB%$`R>M1*mzNQ zqn!S%aMr<9aR5_GheesK?+Y$dA3~$-8B(Lv)eqPB9?mOE8A*tMZa@jN5}K?%*TBH{ zP|TOw#`P{T#E`10UN3ce1=W5fJD`~Pg8?wav=Eww-p0o0LZC63GN&|BmB-0LZIj14 zH%>oaiUjsqEGa)_Ui?yIn(Xjvmwzgy3i#dZ^YY0<>8OqXuZ@oQhEnw_i(@4DvyRmM znOL1gI=*$VFm|ERbEKe*mq)CLvJ8wf#@ynvLnrYHBthMdKVhtfOOw?aNA*#_0DkhA zUuSz&-Q*#&40rXB!)n;}&VsbR?#V?}Q~x6vfN=NVuPw6pN+i5m^klv|YbqS-{A) zvQF+#^P)>!7Z6BfzlZaq=5^p_JHD}&{gcz`u!gvWIo1ITT3`i#A%O$c#deU?zCKh;KYz6wBpQzeK6b`lu zXAlThk-sM8GvWE`BEGyjoE>mUeA(`ie3lrFo;bpLBnoa}fB#%u^fxX7@1$yYpO9Gk z3KLr5v3J5Iyg=a9W^jJ!zUXH!XcJ0efHs;C-GUkQY9b#Zdnw!7fWW8_VfUw>mJH@n z0hqb&>#PAX%J5}GcMOM4I21)NBgAufM%`5h9XGf9%~|`V{sJ&{u856gb=a50mYE3%17JJf|nt!+0=hSE0_4XM+;cT9L`kKX58d41hqD~-oUJ&gel(i1nUlphUNZ( z{Hs^021!7V`iAwU|9?Tz|9k9P*u>Pr*5ZGs=0z)M{Y%64x1@NgfwuLypiCSnz%^2b zR#BlZSYAfFEu9cDcGJWfwRN*2+w)z5l-Hclka(U=`cp=fr%@%m4vzP#-IwW<>vYQZ z_x(AOKS$NsS-&wv2Sahbni#mou{!2Q(u{+&Ra>!;kggD4X!T%U6k;Q`6ZRvtO@I;# z^vOv6<5s;7S=ky1hNpl@xZ{ILwj;&2GafOBmYKQU3$Wad8Ty#@(m=!?!<}Y0OBJt5 zSNxO)FK5$eYQPN)9lX}GrABb4?0TuVnFQy}qD(37yTbb>HWc(nP-;z?R4pnqy1U3& zb(n%5Db}b2kSmcUriK-b>9}-C+j+4jlk>2UTrw6V=fXNI-Y+%OVI+}^Uq{O!;NS}M zbFn}%T;QkqpLOs<2^*9n)m>;^)?e&vkNXswGXwe#@N z&g3~P-SXH)I1IDvA8Z$1jLw zOm$|=jR|q>VpXZw;?u>w&Wj1HKZ`#G4W|O>Xo+3Ll)l`Y={!a19j2jf1!*W3ngkbY^qChYkMsy`S>7cUVyleksXT&%lbI6n=3Rr4HLl}_t6%p@ z=Ol`I3NAr6d}prM0e?x%bP0z}o+zX*K}Y^RF>vzbMb>zrC1Vp>2#|3|hKSAuFcdJw z7)axXN+6%%X$zoM25;n#sR+Unz9&wBj~kB<$^JUwTeUpW@Ja(jAxAojkwTI_yt_-slVQ7wD)(?E90 zQwFd@fh@IreZhV(6iU&eFGKi5#H?TYh$Vt;QB;m;s`e@Znj%~^s@9u~-?xIUJ;p{$zDmE8C!jOXpN2!X2}Z%? zCr`H-S26>YN%zp>7UQTG=?K7OmHPaMO3lU=Gf>T%M@NdDL*za~Y3sx=VqczG$syw7 zP(g`KKFPXRUuNW1$;6@oU6X06rD54|#}PH!JDeNBae#jHB+A9^I-Yok4P}b4Y&AD+ zr9)brI;nu90rxpZy$ituQvKoxJ39(8W1un1JhrLNy!gN$$vWIh4vhli&HMq-F zj&Bxbq231??8}u~c~iLAXr4J$qreq%e}$P>S;1}`aD-NHK>uV|H{OnJu%Ysdy<39V zcWKYqL5MXd5a_+Js{#C3`EnC0uYp~UrRq|SLw5_0Ocp5|qyJ0Seu>34=7+@BRT=6A z8t*q4v2R*UHn4b-@6FmZeB)=PWi0qRTCo4O$LsYlUO}jCInh(c`+g#hbc!5nJ)4qM zz<#h}ffvY^73i4dL)FO;6(}!Z7nlhL2BdA?v5z6t z#RIrtZ9WMR#11|;TV1#+;A)R_13hOS8Uq4@7vGVAf3%sViOvm-b*?^Kh?kn@>;AY{ zZIS{r6uzK7w<3%nihVh!=mI=0FPj0HNTl`#QM5k7i9GR?IX|}lE9eI=m>KdD@b28V zYn;HB+Anw_|Dv)vG&1Bz^PIf&`7Pq}F8m;m^-P{q(CBf%+)4Kwax@6~Cy6eP@+MUG zgVa5N=6MdwC(^%mdiKm~n&h|cBk&vP`=58}|72j~iU$*L| z5|#@37tYW1I%^6*q`Zw(vJ~wcjYwRkc@%Oz!7xCooW`ZG6A)sYcw{7BumPB?D$(o&n%9><(iBiP>tKMH` zCg$Jm@jTPR_jAwp+;h&{bI-ZwL=hyaNKMnfyEON-(tlH_k%elhil={D^qmEj8-*oG zYm&45q`php^N{B--Rrz(9VkKOQ9|JU?Z;5Ka@;^<%59n$c=AKHqb0Y67JYS}x=2dV zMX$xgCEA8A#j5*1KbI02U)knVZsW(9YcGjI_tp~c69F1b*rlA?F%UKu!Sw!YK4oi6s^;NWu!!vZA!yl1CxF1=YE1mS^UVjjl znP;*VoSLC0Y+8A|pp{aXY!vbOF1YzANkCrCyPgw?#h!x4@A9R1d1MG_g@}CUHyeQh zr3mqEfGSktgF%`#Y}93KeLQhj*b)g(9G@(y^8rop2@3RxoNu#LR=SrKAtM(jIjYY1 zGB}u+k*-u=l7nIms>*ZirYn!8&5g|6-6ILBD#WtFUvJGYiAYMZ*4uhKq+qaTp3qHy z1gknyHzI0y-iW`OU{r7Pg!?sm0k#6<_9wqOrV!^9s?>x+0Tn0|r)x>A5s0G=T6&px(`!~ldxmoVi zw3LJ62j1zE#J)XSGeGK)GzN&2_7<>(;ML+R(^z@bn9|P))%mR_E2@|$#pLIXG+;kTPK*l zvI~d(XkZ!KWqxzBs_tf}L+tY%$(}x$vC60?pw%67DRFs41_4*dHoxw~L z>&RSHsq;`{|{c%C=Ta5vJ zb>GvZJaRUI2C3Q%pT6UF^A^^W8`PMI)m%Bt#-U5N9&Whc7#;l7ZP}4yR+l5q$NK52 zq4x>bGm1hZJzuMR6-LC6z+2sPX1Oz+?)PNwO3)5V$d&kW7bu?tg$HK(MKZs*hPTW& zo?o-ttP)I)+QrYU$IE>J>dKLTtmqTDhc%A#K!gbASc7zqMVruR4sLAESngTHd5w4H zD}cR^46yMz+$tV+AqCSa0$FkY#pv}eU7*pS-ElFMYnGwqj}q$VJkMaopJosnOv%kl zq#nt{nkPAea+7STH zjit-~vmlXrN^xX)h6>2u+MzY9he;6Mn&BT}QYM(zS5TISB3R?MiTvWkbRt{g{x6t~)y z67up|x*AQCRQAa)413Z}e1p`QI`ORLtiYg#Z|`ew4z8qUkIX1qHmsztTuXZACF}4tDeuf_ywVf*svoli zX&{x^_|#-%JKCd2El^zHwg%qdOu^v39(5H}{U)~niThT)w>Z-CmFR3-q$auf@?^k? zZYE9PC&;~FO@$J#u6(>i^ntl?Je@$RDgTB{<%PE1iGMloTSb57=9@Nq7wcxxN_2EY zMWd6D22`l@p@O7atu|XM`SC`!%Z5b$wu}=v7yeogq%D#*+p%0u|CNtAF{4vlKbo+; z2cOIKbr>U)sZwsjy7?QLuRLAlCGRxn>x;yX3Y--ReG?nkxApm>&ID7xsvh@4*7AzY zkkUjtzaSw&E*>rsF0STmM>XDQ-(l%4O>Nc0Oo@kMk2x1x6Nk$hFGtBmy{5apN%n6c z0&HEWZ|DQMj*_LoXKJ26&lIgh5u>mE*t)D=y!nCYyF z78iI^tVklG;@vtJq3hlw#moMu{Hwh?PBLDAL51>6$qa0aO+7x`?bY>}xIE`vB0E=+ zuOfG0iYei&qYCkjxS;pF5M_ULs_H4?K~$g=)1s{-bzQV_XhG?j-p3a;hcl;?ueFrg z_s!mS2B*wS*2QVe(+^1(<+N%F74g-XG<6kD*b3RNX)P`7oOzwd(3NA^by?oo zmrg^;jUTEX!=Xe2LCj0=+^1(2AkdG!{K!SX;Mmjtjf59S)z&JF((S_gjNntP6)FO4 z_FI9SB2PJQ@}L|<(5v|s)H z`dt3|66zws&1Pj_FvIZIkOFB0otnR>4>)iS#5)~$4W zrHVKt^Vu}3AlRjp$)pO*`k+9~DN%kwxWF&HTl+${l#}cICqO z@4Q)5P;$36^)se`h7~0=4FuRcFc`Zk8~*RDH(f%#SFCFUKkf8f=f6lC{gt!OAFQtA22K z^!Ux0hS5%H7U&Uq4NpHdt|CQ^awKymm&@N+ub-b6eeh2Ao??AcDM`ZXXeMNnATmeH zPe}UWieLT;{(^V1Z$ON2|GKh7M19o_|4o9eRcMV{QaS4bhG$dk0nHrSF%19mZ9ihd z9}eg0ipoMVtp1Q`^`*242nPSiE#*)dl?9b@A5Dp1OlwsC!mIuGGNb*7TdIEy)fb*u zrD-yq4O#QGBnY-aStdjER#vbdd@;Tcs4+ZkQyKUpgYVzY`2eNQ(69}mjUCmHmjj=g z+-KThKv(a3Y^rS(lsP`Fa#PV4WLG*^OdvYkNt8hUDB;2de1S1hk7-7{>zxFmwUs`_9jUqgx*JrgXwIv8gm5Y_t=?UwnjXPGb+b#$`gbirD~0} zWj=0AXml5TrT z&6G`)?y9R&;EW$ZG1JRNj#@oUrqmmuFR?QB#9taeEPK~#cG59F=o{-@R>p+a@ zH_78CiMrm?!$qy=1w!9C(dbenar=^v%Z$~#| z3LfXdr;;K|ZkA6_Z)6##E4}VFI6qL~aBhdpkGR~R(LWp9awgFGfkg1~oJs6@-6nfw zafdW?$xbXk^<^ZHDU@dn07^lmW;au~>VC(>0>k9U`cWEnk1e zfBRyF^%|%|r;=hYiED}z!D6ed+ou;~xyvW5zpw}M!|9cYsn@v?kgZk1Bi^J`4DE&FZ;wd$ z%9guVwe(S)>21sWd}_kWeC9nDXMnHRbO%Su)!U3q0et^diuw^(v>F^|GrXUc&H8n4 zi)>GS9Bbr}6TlWp6XzD`?;HX>EyMpg1pl4xi%h`HN$*4%Wy3^sM1*q)40?kpp?e+y zBWq=?EqL6;&FjiuzQ@#K_cZe^um6Ah_IqYE!+NdkRjO>+9yg?Eh4g*w6g z{R{z@fbKl0hTQUGLk#y(50K4u>oUEN$XR4+Mo+z@00^OxKpjvZdhK3#p8QUYfV@jy z*Y!L4Y4}Od@az3k4{V7j7+c>gSNJBBeLMec`Oc|sD>L0ZVNb1>l%ZhWoxmi1$odbz zEr%fm7ag_lZ??KAyW0}~x!U2g#@%yd@}fKS7OKLx;BMxiwePFS+G#?dF{t1wQ|yy6 zA!bt>i*PHi&PV2T>#~&n>y;Y=6enORuLxmOvh?i(jMT;E9 z(n-)*D3HhNnHdO@yjh4Z#ON` z2Zl80WVdOMbIpLiiMDTW5eW-3QJjpj8(_&?y*f`lDsJP$`ey97IqxafjAUw zkU9CcPk>KAi${xh?AWp2F#zCKKL-gO;3i`XEttNdh`P3_*iOfuJAwJAL`hA)2k__D zfFD|n#}t77{eDzSQCnRVh;xf-sqP_=;vF1-z?}LsfB?AOK)~VfCx9+AXoC)@IkEQy z5@CaY+e(YeKp{Avrbl}g4^R1kr#)O2RjobX724hRnd*^=TI7+!~BsQ9>8 zJ0om=*5p@%yTDP_9Zb#Z>kO4a1)=_=`k64{Hx>bZ+rq zb_pBbBVk7b3gF8E_fLvEIBVt9BEH{fR<9rz@}de{U9y}&;C zhe-^k%vosviSBNT{A2yB?fmIW0EsUI1a9vJ^!|uF1VPUh`73D!=4}hVvLDdJX6@y= z*YSe@TXTN|=kM^PZ{!$Ui&u?n@N9K7UU0AEpJ+_vq(C;{YDs5*!yg zz}!9Z4+hPy+j`;&klPAGS^-@^8-|X!K>zDLvDdfbaBy))_Jl$}5D0-}?!6mv`-9&Z z7x;c^!r|D(E*ovx0O8y~gP8SaB?3Jgs={0+|LJ1K(zwkv`0@VNHL2R=>t9}NFn z2loZ#?=W zBLQqo4H1gLMdKb%fjgP|FOIHuUq8@Qw*YDf2Nnwx^kW<@P(A0X0I`Qqn&eeHj|3=v z22hG=DF_m9f$mZI^UL|Ch@o3cbdrBeFCbsEQ@>0luMsnxcc* zv0@(AJOLPsN0dNn28LkarQ*c)@IWDKJUwujixWgFWl{i_WdN0y!GM;@!Ue7B>0-NQ zJFSsGEZE%xWers0+GEtgFQD_S_W50a^?HEf@nX!Lmx|U&2VVrcWk{xZ<&ZGGv~^2O@Jwp2NRFpb&m^!JdJ8Vx-%Qx=V}(sJdZ!jl=(-oUpbu#C zgbj^e8y}M!L>;)G_ozm1{DO^(UUYHqkYRr;_u|4u+Z!CrX7tK?m;^7q#RZIA!4aEa z^x|ij1S?MB!u_L)A2u+0nIcR9rk}wD{JWAGHXwT86U@E+i+Nmtf7Uv}Mn5$8zx0RcO7gAa!anR?IyOG~JpfGI8~%<9e@{Vv zdp!q>GI|a!rmw?w1pf~=-+j-`!@gF4O)YvFBqpZ`jvWlSugC|vW{2(KIQR7#_|#8& zFg9>fv48IVhmVS$$c4Fb)MS5yhn;4L%?tFD2+Xg&y5oO?|3`8d_5|orXiSsQPxChu z{LZt$21bv4V7d^E^nU|<@z0Pp_7v#H!`n3h4>N6e!~g&Q diff --git a/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java b/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java index b781aca2..82cd3576 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java @@ -6,7 +6,7 @@ import com.libiec61850.scl.ParserUtils; import com.libiec61850.scl.SclParserException; /* - * Copyright 2014 Michael Zillgith + * Copyright 2014-2016 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java b/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java index 021dfc80..c43c5860 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/LogicalNode.java @@ -280,6 +280,14 @@ public class LogicalNode implements DataModelNode { return settingGroupControlBlocks; } + public List getLogControlBlocks() { + return logControlBlocks; + } + + public List getLogs() { + return logs; + } + @Override public DataModelNode getChildByName(String childName) { for (DataObject dataObject : dataObjects) { diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index ea88eeee..fca09171 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -48,6 +48,8 @@ import com.libiec61850.scl.model.FunctionalConstraint; import com.libiec61850.scl.model.FunctionalConstraintData; import com.libiec61850.scl.model.GSEControl; import com.libiec61850.scl.model.IED; +import com.libiec61850.scl.model.Log; +import com.libiec61850.scl.model.LogControl; import com.libiec61850.scl.model.LogicalDevice; import com.libiec61850.scl.model.LogicalNode; import com.libiec61850.scl.model.ReportControlBlock; @@ -70,7 +72,11 @@ public class StaticModelGenerator { private StringBuffer logControlBlocks; private List lcbVariableNames; - private int currentLcbVariableNumber; + private int currentLcbVariableNumber = 0; + + private StringBuffer logs; + private List logVariableNames; + private int currentLogVariableNumber = 0; private StringBuffer gseControlBlocks; private List gseVariableNames; @@ -104,9 +110,12 @@ public class StaticModelGenerator { { this.cOut = cOut; this.hOut = hOut; + this.initializerBuffer = new StringBuffer(); + this.reportControlBlocks = new StringBuffer(); this.rcbVariableNames = new LinkedList(); + this.gseControlBlocks = new StringBuffer(); this.gseVariableNames = new LinkedList(); @@ -116,6 +125,12 @@ public class StaticModelGenerator { this.settingGroupControlBlocks = new StringBuffer(); this.sgcbVariableNames = new LinkedList(); + this.logControlBlocks = new StringBuffer(); + this.lcbVariableNames = new LinkedList(); + + this.logs = new StringBuffer(); + this.logVariableNames = new LinkedList(); + SclParser sclParser = new SclParser(stream); this.outputFileName = outputFileName; @@ -256,15 +271,6 @@ public class StaticModelGenerator { } } -// private String getLogicalDeviceName(LogicalDevice logicalDevice) { -// String logicalDeviceName = logicalDevice.getLdName(); -// -// if (logicalDeviceName == null) -// logicalDeviceName = ied.getName() + logicalDevice.getInst(); -// -// return logicalDeviceName; -// } - private String getLogicalDeviceInst(LogicalDevice logicalDevice) { return logicalDevice.getInst(); } @@ -277,6 +283,10 @@ public class StaticModelGenerator { createReportVariableList(logicalDevices); + createLogControlVariableList(logicalDevices); + + createLogVariableList(logicalDevices); + createGooseVariableList(logicalDevices); createSmvVariableList(logicalDevices); @@ -322,6 +332,7 @@ public class StaticModelGenerator { cOut.println(reportControlBlocks); + for (String smv : smvVariableNames) cOut.println("extern SVControlBlock " + smv + ";"); @@ -336,7 +347,17 @@ public class StaticModelGenerator { cOut.println("extern SettingGroupControlBlock " + sgcb + ";"); cOut.println(settingGroupControlBlocks); - + + for (String lcb : lcbVariableNames) + cOut.println("extern LogControlBlock " + lcb + ";"); + + cOut.println(logControlBlocks); + + for (String log : logVariableNames) + cOut.println("extern Log " + log + ";"); + + cOut.println(logs); + String firstLogicalDeviceName = logicalDevices.get(0).getInst(); cOut.println("\nIedModel " + modelPrefix + " = {"); cOut.println(" \"" + ied.getName() + "\","); @@ -367,6 +388,16 @@ public class StaticModelGenerator { else cOut.println(" NULL,"); + if (lcbVariableNames.size() > 0) + cOut.println(" &" + lcbVariableNames.get(0) + ","); + else + cOut.println(" NULL,"); + + if (logVariableNames.size() > 0) + cOut.println(" &" + logVariableNames.get(0) + ","); + else + cOut.println(" NULL,"); + cOut.println(" initializeValues\n};"); } @@ -442,6 +473,54 @@ public class StaticModelGenerator { } } + private void createLogControlVariableList(List logicalDevices) + { + for (LogicalDevice ld : logicalDevices) { + List lnodes = ld.getLogicalNodes(); + + String ldName = ld.getInst(); + + for (LogicalNode ln : lnodes) { + List lcbs = ln.getLogControlBlocks(); + + int lcbCount = 0; + + for (LogControl logControl : lcbs) { + + String lcbVariableName = modelPrefix + "_" + ldName + "_" + ln.getName() + "_lcb" + lcbCount; + + lcbVariableNames.add(lcbVariableName); + + lcbCount++; + } + } + } + } + + private void createLogVariableList(List logicalDevices) + { + for (LogicalDevice ld : logicalDevices) { + List lnodes = ld.getLogicalNodes(); + + String ldName = ld.getInst(); + + for (LogicalNode ln : lnodes) { + List logs = ln.getLogs(); + + int logCount = 0; + + for (Log log : logs) { + + String logVariableName = modelPrefix + "_" + ldName + "_" + ln.getName() + "_log" + logCount; + + logVariableNames.add(logVariableName); + + logCount++; + } + } + } + } + private void createSettingControlsVariableList(List logicalDevices) { for (LogicalDevice ld : logicalDevices) { List lnodes = ld.getLogicalNodes(); @@ -488,6 +567,10 @@ public class StaticModelGenerator { printDataObjectDefinitions(lnName, logicalNode.getDataObjects(), null); printReportControlBlocks(lnName, logicalNode); + + printLogControlBlocks(lnName, logicalNode); + + printLogs(lnName, logicalNode); printGSEControlBlocks(ldName, lnName, logicalNode); @@ -994,6 +1077,34 @@ public class StaticModelGenerator { } } + private void printLogControlBlocks(String lnPrefix, LogicalNode logicalNode) + { + List logControlBlocks = logicalNode.getLogControlBlocks(); + + int lcbCount = logControlBlocks.size(); + + int lcbNumber = 0; + + for (LogControl lcb : logControlBlocks) { + printLogControlBlock(lnPrefix, lcb, lcbNumber, lcbCount); + lcbNumber++; + } + } + + private void printLogs(String lnPrefix, LogicalNode logicalNode) + { + List logs = logicalNode.getLogs(); + + int logCount = logs.size(); + + int logNumber = 0; + + for (Log log : logs) { + printLog(lnPrefix, log, logNumber, logCount); + logNumber++; + } + } + private void printSettingControlBlock(String lnPrefix, LogicalNode logicalNode) { List settingControls = logicalNode.getSettingGroupControlBlocks(); @@ -1023,8 +1134,85 @@ public class StaticModelGenerator { currentSGCBVariableNumber++; } } + + private void printLog(String lnPrefix, Log log, int logNumber, int logCount) + { + String logVariableName = lnPrefix + "_log" + logNumber; + + String logString = "Log " + logVariableName + " = {"; + + logString += "&" + lnPrefix + ", "; + + logString += "\"" + log.getName() + "\", "; + + currentLogVariableNumber++; + + if (currentLogVariableNumber < logVariableNames.size()) + logString += "&" + logVariableNames.get(currentLogVariableNumber); + else + logString += "NULL"; + + logString += "};\n"; - private void printReportControlBlockInstance(String lnPrefix, ReportControlBlock rcb, String index, int reportNumber, int reportsCount) { + this.logs.append(logString); + } + + private void printLogControlBlock(String lnPrefix, LogControl lcb, int lcbNumber, int lcbCount) + { + String lcbVariableName = lnPrefix + "_lcb" + lcbNumber; + + String lcbString = "LogControlBlock " + lcbVariableName + " = {"; + + lcbString += "&" + lnPrefix + ", "; + + lcbString += "\"" + lcb.getName() + "\", "; + + if (lcb.getDataSet() != null) + lcbString += "\"" + lcb.getDataSet() + "\", "; + else + lcbString += "NULL, "; + + if (lcb.getLogName() != null) + lcbString += "\"" + lcb.getLogName() + "\", "; + else + lcbString += "NULL, "; + + int triggerOps = 16; + + if (lcb.getTriggerOptions() != null) + triggerOps = lcb.getTriggerOptions().getIntValue(); + + lcbString += triggerOps + ", "; + + if (lcb.getIntgPd() != 0) + lcbString += lcb.getIntgPd() + ", "; + else + lcbString += "0, "; + + if (lcb.isLogEna()) + lcbString += "true, "; + else + lcbString += "false, "; + + if (lcb.isReasonCode()) + lcbString += "true, "; + else + lcbString += "false, "; + + currentLcbVariableNumber++; + + if (currentLcbVariableNumber < lcbVariableNames.size()) + lcbString += "&" + lcbVariableNames.get(currentLcbVariableNumber); + else + lcbString += "NULL"; + + lcbString += "};\n"; + + this.logControlBlocks.append(lcbString); + } + + private void printReportControlBlockInstance(String lnPrefix, ReportControlBlock rcb, String index, int reportNumber, int reportsCount) + { String rcbVariableName = lnPrefix + "_report" + reportNumber; String rcbString = "ReportControlBlock " + rcbVariableName + " = {"; From 9a9e62e29c5bd03a0d70a358215add0ffc1be8cc Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 5 May 2016 14:41:16 +0200 Subject: [PATCH 06/36] - added read access to LCBs --- src/iec61850/inc_private/logging.h | 8 ++ src/iec61850/server/impl/ied_server.c | 4 + src/iec61850/server/mms_mapping/logging.c | 102 +++++++++++++++--- src/iec61850/server/mms_mapping/mms_mapping.c | 21 ++++ src/iec61850/server/mms_mapping/mms_sv.c | 1 - 5 files changed, 119 insertions(+), 17 deletions(-) diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 9aedaadb..ffd47928 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -27,11 +27,16 @@ typedef struct { char* name; + LogControlBlock* logControlBlock; DataSet* dataSet; + LogicalNode* logicalNode; MmsDomain* domain; + MmsValue* mmsValue; + MmsVariableSpecification* mmsType; + bool enabled; int triggerOps; @@ -48,4 +53,7 @@ MmsVariableSpecification* Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, int lcbCount); +MmsValue* +LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig); + #endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ */ diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index e212db2d..50fba9b2 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -198,6 +198,10 @@ createMmsServerCache(IedServer self) && (strcmp(fcName, "MS") != 0) && (strcmp(fcName, "US") != 0) #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + && (strcmp(fcName, "LG") != 0) +#endif + ) { char* variableName = createString(3, lnName, "$", fcName); diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 33ba25a9..f9d9931d 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -44,6 +44,7 @@ LogControl_create(LogicalNode* parentLN) self->enabled = false; self->dataSet = NULL; self->triggerOps = 0; + self->logicalNode = parentLN; return self; } @@ -79,6 +80,73 @@ getLCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int in return NULL ; } +static LogControl* +lookupLogControl(MmsMapping* self, MmsDomain* domain, char* lnName, char* objectName) +{ + LinkedList element = LinkedList_getNext(self->logControls); + + while (element != NULL) { + LogControl* logControl = (LogControl*) element->data; + + if (logControl->domain == domain) { + if (strcmp(logControl->logicalNode->name, lnName) == 0) { + if (strcmp(logControl->logControlBlock->name, objectName) == 0) { + return logControl; + } + } + } + + element = LinkedList_getNext(element); + } + + return NULL; +} + + + +MmsValue* +LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig) +{ + MmsValue* value = NULL; + + char variableId[130]; + + strncpy(variableId, variableIdOrig, 129); + + char* separator = strchr(variableId, '$'); + + *separator = 0; + + char* lnName = variableId; + + if (lnName == NULL) + return NULL; + + char* objectName = MmsMapping_getNextNameElement(separator + 1); + + if (objectName == NULL) + return NULL; + + char* varName = MmsMapping_getNextNameElement(objectName); + + if (varName != NULL) + *(varName - 1) = 0; + + LogControl* logControl = lookupLogControl(self, domain, lnName, objectName); + + if (logControl != NULL) { + if (varName != NULL) { + value = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName); + } + else { + value = logControl->mmsValue; + } + } + + return value; +} + + static char* createDataSetReferenceForDefaultDataSet(LogControlBlock* lcb, LogControl* logControl) { @@ -118,9 +186,9 @@ static MmsVariableSpecification* createLogControlBlock(LogControlBlock* logControlBlock, LogControl* logControl) { - MmsVariableSpecification* rcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); - rcb->name = copyString(logControlBlock->name); - rcb->type = MMS_STRUCTURE; + MmsVariableSpecification* lcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); + lcb->name = copyString(logControlBlock->name); + lcb->type = MMS_STRUCTURE; MmsValue* mmsValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); mmsValue->deleteValue = false; @@ -131,9 +199,9 @@ createLogControlBlock(LogControlBlock* logControlBlock, mmsValue->value.structure.size = structSize; mmsValue->value.structure.components = (MmsValue**) GLOBAL_CALLOC(structSize, sizeof(MmsValue*)); - rcb->typeSpec.structure.elementCount = structSize; + lcb->typeSpec.structure.elementCount = structSize; - rcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(structSize, + lcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(structSize, sizeof(MmsVariableSpecification*)); /* LogEna */ @@ -143,7 +211,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->name = copyString("LogEna"); namedVariable->type = MMS_BOOLEAN; - rcb->typeSpec.structure.elements[0] = namedVariable; + lcb->typeSpec.structure.elements[0] = namedVariable; mmsValue->value.structure.components[0] = MmsValue_newBoolean(logControlBlock->logEna); /* LogRef */ @@ -151,7 +219,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->name = copyString("LogRef"); namedVariable->typeSpec.visibleString = -129; namedVariable->type = MMS_VISIBLE_STRING; - rcb->typeSpec.structure.elements[1] = namedVariable; + lcb->typeSpec.structure.elements[1] = namedVariable; if (logControlBlock->logRef != NULL) { mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logControlBlock->logRef); @@ -170,7 +238,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->name = copyString("DatSet"); namedVariable->typeSpec.visibleString = -129; namedVariable->type = MMS_VISIBLE_STRING; - rcb->typeSpec.structure.elements[2] = namedVariable; + lcb->typeSpec.structure.elements[2] = namedVariable; if (logControlBlock->dataSetName != NULL) { char* dataSetReference = createDataSetReferenceForDefaultDataSet(logControlBlock, logControl); @@ -186,7 +254,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->name = copyString("OldEntrTm"); namedVariable->type = MMS_BINARY_TIME; namedVariable->typeSpec.binaryTime = 6; - rcb->typeSpec.structure.elements[3] = namedVariable; + lcb->typeSpec.structure.elements[3] = namedVariable; mmsValue->value.structure.components[3] = MmsValue_newBinaryTime(false); @@ -195,7 +263,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->name = copyString("NewEntrTm"); namedVariable->type = MMS_BINARY_TIME; namedVariable->typeSpec.binaryTime = 6; - rcb->typeSpec.structure.elements[4] = namedVariable; + lcb->typeSpec.structure.elements[4] = namedVariable; mmsValue->value.structure.components[4] = MmsValue_newBinaryTime(false); @@ -205,7 +273,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->type = MMS_OCTET_STRING; namedVariable->typeSpec.octetString = 8; - rcb->typeSpec.structure.elements[5] = namedVariable; + lcb->typeSpec.structure.elements[5] = namedVariable; mmsValue->value.structure.components[5] = MmsValue_newOctetString(8, 8); @@ -215,7 +283,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->type = MMS_OCTET_STRING; namedVariable->typeSpec.octetString = 8; - rcb->typeSpec.structure.elements[6] = namedVariable; + lcb->typeSpec.structure.elements[6] = namedVariable; mmsValue->value.structure.components[6] = MmsValue_newOctetString(8, 8); @@ -224,7 +292,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->name = copyString("TrgOps"); namedVariable->type = MMS_BIT_STRING; namedVariable->typeSpec.bitString = -6; - rcb->typeSpec.structure.elements[7] = namedVariable; + lcb->typeSpec.structure.elements[7] = namedVariable; mmsValue->value.structure.components[7] = createTrgOps(logControlBlock); /* IntgPd */ @@ -232,14 +300,16 @@ createLogControlBlock(LogControlBlock* logControlBlock, namedVariable->name = copyString("IntgPd"); namedVariable->type = MMS_UNSIGNED; namedVariable->typeSpec.unsignedInteger = 32; - rcb->typeSpec.structure.elements[8] = namedVariable; + lcb->typeSpec.structure.elements[8] = namedVariable; mmsValue->value.structure.components[8] = MmsValue_newUnsignedFromUint32(logControlBlock->intPeriod); - //TODO logControl->rcbValues = mmsValue; + logControl->mmsType = lcb; + logControl->mmsValue = mmsValue; + logControl->logControlBlock = logControlBlock; - return rcb; + return lcb; } diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 60d0767f..ed36e2da 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1427,6 +1427,19 @@ isSampledValueControlBlock(char* separator) #endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + +static bool +isLogControlBlock(char* separator) +{ + if (strncmp(separator + 1, "LG", 2) == 0) + return true; + + return false; +} + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + char* MmsMapping_getNextNameElement(char* name) { @@ -2202,6 +2215,14 @@ mmsReadHandler(void* parameter, MmsDomain* domain, char* variableId, MmsServerCo } #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + /* LOG control block - LG */ + if (isLogControlBlock(separator)) { + retValue = LIBIEC61850_LOG_SVC_readAccessControlBlock(self, domain, variableId); + goto exit_function; + } +#endif + #if (CONFIG_IEC61850_REPORT_SERVICE == 1) /* Report control blocks - BR, RP */ if (isReportControlBlock(separator)) { diff --git a/src/iec61850/server/mms_mapping/mms_sv.c b/src/iec61850/server/mms_mapping/mms_sv.c index b367994f..83e8b74c 100644 --- a/src/iec61850/server/mms_mapping/mms_sv.c +++ b/src/iec61850/server/mms_mapping/mms_sv.c @@ -249,7 +249,6 @@ LIBIEC61850_SV_readAccessSampledValueControlBlock(MmsMapping* self, MmsDomain* d return value; } - static SVControlBlock* getSVCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index, bool isUnicast) { From 2a96d5b40cb92cc1fff3ddff0887ddca95bbe909 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 6 May 2016 16:02:54 +0200 Subject: [PATCH 07/36] - changed version to 0.9.2 --- CMakeLists.txt | 2 +- config/stack_config.h | 2 +- src/doxygen.config | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e374c898..68600b34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ ENABLE_TESTING() set(LIB_VERSION_MAJOR "0") set(LIB_VERSION_MINOR "9") -set(LIB_VERSION_PATCH "1") +set(LIB_VERSION_PATCH "2") # feature checks include(CheckLibraryExists) diff --git a/config/stack_config.h b/config/stack_config.h index a1a9170f..e84ac65a 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -159,7 +159,7 @@ /* default results for MMS identify service */ #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" #define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" -#define CONFIG_DEFAULT_MMS_REVISION "0.9.1" +#define CONFIG_DEFAULT_MMS_REVISION "0.9.2" /* MMS virtual file store base path - where file services are looking for files */ #define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/" diff --git a/src/doxygen.config b/src/doxygen.config index 31522e54..cae69572 100644 --- a/src/doxygen.config +++ b/src/doxygen.config @@ -18,7 +18,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "libIEC61850" -PROJECT_NUMBER = 0.9.1 +PROJECT_NUMBER = 0.9.2 PROJECT_BRIEF = "Open-source IEC 61850 MMS/GOOSE/SV server and client library" From 4b3a9dc8508bd9908bff606e665cc36f67aff413 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 6 May 2016 17:44:16 +0200 Subject: [PATCH 08/36] - started programming logging trigger logic --- src/iec61850/inc_private/logging.h | 5 ++- src/iec61850/inc_private/mms_mapping.h | 13 ++++++- src/iec61850/server/impl/ied_server.c | 22 ++++++++++- src/iec61850/server/mms_mapping/logging.c | 38 +++++++++++++++---- src/iec61850/server/mms_mapping/mms_mapping.c | 29 ++++++++++++++ 5 files changed, 95 insertions(+), 12 deletions(-) diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index ffd47928..2846323e 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -28,8 +28,10 @@ typedef struct { char* name; LogControlBlock* logControlBlock; + MmsMapping* mmsMapping; DataSet* dataSet; + char* dataSetRef; LogicalNode* logicalNode; MmsDomain* domain; @@ -37,6 +39,7 @@ typedef struct { MmsValue* mmsValue; MmsVariableSpecification* mmsType; + bool enabled; int triggerOps; @@ -44,7 +47,7 @@ typedef struct { } LogControl; LogControl* -LogControl_create(LogicalNode* parentLN); +LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping); void LogControl_destroy(LogControl* self); diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index b969b48c..24a74484 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -1,7 +1,7 @@ /* * mms_mapping.h * - * Copyright 2013, 2014 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -25,7 +25,6 @@ #define MMS_MAPPING_H_ #include "iec61850_model.h" -//#include "mms_server_connection.h" #include "mms_device_model.h" #include "control.h" @@ -36,6 +35,13 @@ typedef enum { REPORT_CONTROL_QUALITY_CHANGED } ReportInclusionFlag; +typedef enum { + LOG_CONTROL_NONE, + LOG_CONTROL_VALUE_UPDATE, + LOG_CONTROL_VALUE_CHANGED, + LOG_CONTROL_QUALITY_CHANGED +} LogInclusionFlag; + typedef struct sMmsMapping MmsMapping; MmsMapping* @@ -86,6 +92,9 @@ MmsMapping_createDataSetByNamedVariableList(MmsMapping* self, MmsNamedVariableLi void MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag); +void +MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag); + void MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value); diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 50fba9b2..d7e40275 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -786,8 +786,8 @@ checkForUpdateTrigger(IedServer self, DataAttribute* dataAttribute) #endif #if (CONFIG_IEC61850_LOG_SERVICE == 1) - //MmsMapping_triggerLogObserver(self->mmsMapping, dataAttribute->mmsValue,...) - + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_VALUE_UPDATE); #endif @@ -809,6 +809,11 @@ checkForChangedTriggers(IedServer self, DataAttribute* dataAttribute) MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_VALUE_CHANGED); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_VALUE_CHANGED); +#endif } else if (dataAttribute->triggerOptions & TRG_OPT_QUALITY_CHANGED) { @@ -821,6 +826,12 @@ checkForChangedTriggers(IedServer self, DataAttribute* dataAttribute) MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_QUALITY_CHANGED); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_QUALITY_CHANGED); +#endif + } #endif /* (CONFIG_IEC61850_REPORT_SERVICE== 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */ @@ -1034,6 +1045,13 @@ IedServer_updateQuality(IedServer self, DataAttribute* dataAttribute, Quality qu MmsMapping_triggerReportObservers(self->mmsMapping, dataAttribute->mmsValue, REPORT_CONTROL_QUALITY_CHANGED); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + if (dataAttribute->triggerOptions & TRG_OPT_QUALITY_CHANGED) + MmsMapping_triggerLogging(self->mmsMapping, dataAttribute->mmsValue, + LOG_CONTROL_QUALITY_CHANGED); +#endif + } diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index f9d9931d..160b3bc5 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -37,7 +37,7 @@ LogControl* -LogControl_create(LogicalNode* parentLN) +LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) { LogControl* self = (LogControl*) GLOBAL_MALLOC(sizeof(LogControl)); @@ -45,6 +45,8 @@ LogControl_create(LogicalNode* parentLN) self->dataSet = NULL; self->triggerOps = 0; self->logicalNode = parentLN; + self->mmsMapping = mmsMapping; + self->dataSetRef = NULL; return self; } @@ -175,13 +177,25 @@ createTrgOps(LogControlBlock* reportControlBlock) { if (triggerOps & TRG_OPT_INTEGRITY) MmsValue_setBitStringBit(trgOps, 4, true); - //TODO remove - GI doesn't exist here! - if (triggerOps & TRG_OPT_GI) - MmsValue_setBitStringBit(trgOps, 5, true); - return trgOps; } +static bool +enableLogging(LogControl* logControl) +{ + printf("enableLogging\n"); + DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, logControl->dataSetRef); + + if (dataSet == NULL) { + printf(" data set (%s) not found!\n", logControl->dataSetRef); + return false; + } + + + + return true; +} + static MmsVariableSpecification* createLogControlBlock(LogControlBlock* logControlBlock, LogControl* logControl) @@ -243,8 +257,12 @@ createLogControlBlock(LogControlBlock* logControlBlock, if (logControlBlock->dataSetName != NULL) { char* dataSetReference = createDataSetReferenceForDefaultDataSet(logControlBlock, logControl); + printf("createLogControlBlock dataSetRef: %s\n", dataSetReference); + + logControl->dataSetRef = dataSetReference; + mmsValue->value.structure.components[2] = MmsValue_newVisibleString(dataSetReference); - GLOBAL_FREEMEM(dataSetReference); + //GLOBAL_FREEMEM(dataSetReference); } else mmsValue->value.structure.components[2] = MmsValue_newVisibleString(""); @@ -309,6 +327,12 @@ createLogControlBlock(LogControlBlock* logControlBlock, logControl->mmsValue = mmsValue; logControl->logControlBlock = logControlBlock; + logControl->enabled = logControlBlock->logEna; + + if (logControl->enabled) { + enableLogging(logControl); + } + return lcb; } @@ -331,7 +355,7 @@ Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode while (currentLcb < lcbCount) { - LogControl* logControl = LogControl_create(logicalNode); + LogControl* logControl = LogControl_create(logicalNode, self); LogControlBlock* logControlBlock = getLCBForLogicalNodeWithIndex(self, logicalNode, currentLcb); diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index ed36e2da..2560c886 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2482,6 +2482,8 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li } } } + + //TODO check if data set is referenced in a log control block } return allow; @@ -2608,6 +2610,33 @@ MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclu #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + +void +MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag) +{ + LinkedList element = self->logControls; + + while ((element = LinkedList_getNext(element)) != NULL) { + LogControl* lc = (LogControl*) element->data; + + if ((lc->enabled) && (lc->dataSet != NULL)) { + // switch (flag) { + + int index; + + if (DataSet_isMemberValue(lc->dataSet, value, &index)) { + printf("Log value\n"); + } + + + } + } +} + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + + void MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value) { From 7de010e1f55baf74e515c5bb9c3ae2af90178759 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 10 May 2016 17:39:51 +0200 Subject: [PATCH 09/36] - started to implemente server side read journal handling --- Makefile | 3 + config/stack_config.h | 1 + config/stack_config.h.cmake | 1 + src/iec61850/server/mms_mapping/logging.c | 5 +- src/iec61850/server/mms_mapping/mms_mapping.c | 29 +++- src/iec61850/server/mms_mapping/reporting.c | 2 +- src/logging/log_storage_sqlite.c | 67 +++++++++ src/logging/logging_api.h | 31 +++++ src/mms/inc_private/mms_server_internal.h | 8 ++ src/mms/iso_mms/client/mms_client_files.c | 4 +- src/mms/iso_mms/server/mms_journal.c | 3 + src/mms/iso_mms/server/mms_journal_service.c | 131 ++++++++++++++++++ .../iso_mms/server/mms_server_connection.c | 6 + src/mms/iso_mms/server/mms_status_service.c | 2 +- 14 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 src/logging/log_storage_sqlite.c create mode 100644 src/logging/logging_api.h create mode 100644 src/mms/iso_mms/server/mms_journal_service.c diff --git a/Makefile b/Makefile index 6bb5ca4a..b5619d49 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ LIB_SOURCE_DIRS += src/mms/iso_common LIB_SOURCE_DIRS += src/mms/iso_mms/common LIB_SOURCE_DIRS += src/mms/iso_mms/asn1c LIB_SOURCE_DIRS += src/mms/iso_server + +LIB_SOURCE_DIRS += src/logging + ifndef EXCLUDE_ETHERNET_WINDOWS LIB_SOURCE_DIRS += src/goose LIB_SOURCE_DIRS += src/sampled_values diff --git a/config/stack_config.h b/config/stack_config.h index e84ac65a..51ae3cfb 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -186,6 +186,7 @@ #define MMS_READ_SERVICE 1 #define MMS_WRITE_SERVICE 1 #define MMS_GET_NAME_LIST 1 +#define MMS_JOURNAL_SERVICE 1 #define MMS_GET_VARIABLE_ACCESS_ATTRIBUTES 1 #define MMS_DATA_SET_SERVICE 1 #define MMS_DYNAMIC_DATA_SETS 1 diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index c7ad4c4c..d1698165 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -175,6 +175,7 @@ #define MMS_READ_SERVICE 1 #define MMS_WRITE_SERVICE 1 #define MMS_GET_NAME_LIST 1 +#define MMS_JOURNAL_SERVICE 1 #define MMS_GET_VARIABLE_ACCESS_ATTRIBUTES 1 #define MMS_DATA_SET_SERVICE 1 #define MMS_DYNAMIC_DATA_SETS 1 diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 160b3bc5..8a07a524 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -190,8 +190,8 @@ enableLogging(LogControl* logControl) printf(" data set (%s) not found!\n", logControl->dataSetRef); return false; } - - + else + logControl->dataSet = dataSet; return true; } @@ -326,6 +326,7 @@ createLogControlBlock(LogControlBlock* logControlBlock, logControl->mmsType = lcb; logControl->mmsValue = mmsValue; logControl->logControlBlock = logControlBlock; + logControl->triggerOps = logControlBlock->trgOps; logControl->enabled = logControlBlock->logEna; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 2560c886..30a1fb59 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2621,12 +2621,37 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl LogControl* lc = (LogControl*) element->data; if ((lc->enabled) && (lc->dataSet != NULL)) { - // switch (flag) { + int index; + switch (flag) { + + case LOG_CONTROL_VALUE_UPDATE: + if ((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0) + continue; + + break; + + case LOG_CONTROL_VALUE_CHANGED: + if (((lc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) && + ((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)) + continue; + + break; + + case LOG_CONTROL_QUALITY_CHANGED: + if ((lc->triggerOps & TRG_OPT_QUALITY_CHANGED) == 0) + continue; + + break; + + default: + continue; + } + if (DataSet_isMemberValue(lc->dataSet, value, &index)) { - printf("Log value\n"); + printf("Log value - flag:%i\n", flag); } diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 8d16731a..b232d0d3 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1679,7 +1679,7 @@ removeAllGIReportsFromReportBuffer(ReportBuffer* reportBuffer) static void enqueueReport(ReportControl* reportControl, bool isIntegrity, bool isGI, uint64_t timeOfEntry) { - // if (DEBUG_IED_SERVER) + if (DEBUG_IED_SERVER) printf("IED_SERVER: enqueueReport: RCB name: %s (SQN:%u) enabled:%i buffered:%i buffering:%i intg:%i GI:%i\n", reportControl->name, (unsigned) reportControl->sqNum, reportControl->enabled, reportControl->isBuffering, reportControl->buffered, isIntegrity, isGI); diff --git a/src/logging/log_storage_sqlite.c b/src/logging/log_storage_sqlite.c new file mode 100644 index 00000000..df766325 --- /dev/null +++ b/src/logging/log_storage_sqlite.c @@ -0,0 +1,67 @@ +/* + * log_storage_sqlite.c + * + */ + + +#include "logging_api.h" + + +static bool +SqLiteLogStorage_initializeLog(const char* logName, int logSize); + +static bool +SqLiteLogStorage_storeEntry(const char* logName, uint64_t entryID, MmsValue* timestamp, + int entrySize, uint8_t* entryData); + +static bool +SqLiteLogStorage_getEntries(const char* logName, MmsValue* timestamp, + LogEntryCallback callback, void* parameter); + +static bool +SqLiteLogStorage_getEntriesAfter(const char* logName, MmsValue* timestamp, uint64_t entryID, + LogEntryCallback callback, void* parameter); + +static struct sLogStorage logStorageInstance = { + SqLiteLogStorage_initializeLog, + SqLiteLogStorage_storeEntry, + SqLiteLogStorage_getEntries, + SqLiteLogStorage_getEntriesAfter +}; + +LogStorage +SqLiteStorage_createInstance() +{ + return &logStorageInstance; +} + +static bool +SqLiteLogStorage_initializeLog(const char* logName, int logSize) +{ + return true; +} + +static bool +SqLiteLogStorage_storeEntry(const char* logName, uint64_t entryID, MmsValue* timestamp, + int entrySize, uint8_t* entryData) +{ + return true; +} + +static bool +SqLiteLogStorage_getEntries(const char* logName, MmsValue* timestamp, + LogEntryCallback callback, void* parameter) +{ + return true; +} + +static bool +SqLiteLogStorage_getEntriesAfter(const char* logName, MmsValue* timestamp, uint64_t entryID, + LogEntryCallback callback, void* parameter) +{ + return true; +} + + + + diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h new file mode 100644 index 00000000..deb0a9c8 --- /dev/null +++ b/src/logging/logging_api.h @@ -0,0 +1,31 @@ +/* + * logging_api.h + * + */ + +#ifndef LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ +#define LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ + +#include +#include +#include "mms_value.h" + +typedef struct sLogStorage* LogStorage; + +typedef void (*LogEntryCallback) (void* parameter, MmsValue* timestamp, uint64_t entryID, int entrySize, + uint8_t* entryData); + +struct sLogStorage { + bool (*initializeLog) (const char* logName, int logSize); + + bool (*storeEntry) (const char* logName, uint64_t entryID, MmsValue* timestamp, int entrySize, + uint8_t* entryData); + + bool (*getEntries) (const char* logName, MmsValue* timestamp, + LogEntryCallback callback, void* parameter); + + bool (*getEntriesAfter) (const char* logName, MmsValue* timestamp, uint64_t entryID, + LogEntryCallback callback, void* parameter); +}; + +#endif /* LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ */ diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 69913375..2ec666de 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -221,6 +221,14 @@ mmsServer_handleStatusRequest( int invokeId, ByteBuffer* response); +void +mmsServer_handleReadJournalRequest( + MmsServerConnection connection, + uint8_t* requestBuffer, + int bufPos, int maxBufPos, + uint32_t invokeId, + ByteBuffer* response); + void mmsServer_handleFileDirectoryRequest( MmsServerConnection connection, diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index 2ecf29a1..fcc7de18 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -60,7 +60,7 @@ mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); - /* Encode FileOpen tag (context | structured ) [65 = 41h] */ + /* Encode read journal tag (context | structured ) [65 = 41h] */ buffer[bufPos++] = 0xbf; buffer[bufPos++] = 0x41; @@ -69,8 +69,6 @@ mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); -// bufPos = BerEncoder_encodeTL(0xa1, domainIdStringSize, buffer, bufPos); - bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); diff --git a/src/mms/iso_mms/server/mms_journal.c b/src/mms/iso_mms/server/mms_journal.c index 2f2f91df..ecb26d19 100644 --- a/src/mms/iso_mms/server/mms_journal.c +++ b/src/mms/iso_mms/server/mms_journal.c @@ -28,6 +28,9 @@ MmsJournal MmsJournal_create(const char* name) { + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: create new journal %s\n", name); + MmsJournal self = (MmsJournal) GLOBAL_MALLOC(sizeof(struct sMmsJournal)); self->name = copyString(name); diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c new file mode 100644 index 00000000..e10e7e6d --- /dev/null +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -0,0 +1,131 @@ +/* + * mms_journal_service.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "libiec61850_platform_includes.h" +#include "mms_server_internal.h" + +#if (MMS_JOURNAL_SERVICE == 1) + +static bool +parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bufPos, int maxBufPos , uint32_t invokeId, ByteBuffer* response) +{ + uint8_t tag = buffer[(*bufPos)++]; + int length; + + if (tag != 0x19) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return false; + } + + *bufPos = BerDecoder_decodeLength(buffer, &length, *bufPos, maxBufPos); + + if (*bufPos < 0) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return false; + } + + if (length > maxLength) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return false; + } + + memcpy(filename, buffer + *bufPos, length); + filename[length] = 0; + *bufPos += length; + + return true; +} + + +void +mmsServer_handleReadJournalRequest( + MmsServerConnection connection, + uint8_t* requestBuffer, + int bufPos, int maxBufPos, + uint32_t invokeId, + ByteBuffer* response) +{ + printf("READ-JOURNAL\n"); + + char domainId[65]; + char logName[65]; + + bool hasNames = false; + + while (bufPos < maxBufPos) { + uint8_t tag = requestBuffer[bufPos++]; + int length; + + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (bufPos < 0) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + + switch (tag) { + + case 0xa0: + { + + uint8_t objectIdTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + switch (objectIdTag) { + case 0xa1: /* domain-specific */ + printf(" domain-specific-log\n"); + + if (!parseStringWithMaxLength(domainId, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + if (!parseStringWithMaxLength(logName, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + printf(" domain: %s log: %s\n", domainId, logName); + + break; + + + default: + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + + } + } + + break; + + default: + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); + return; + } + + bufPos += length; + } +} + +#endif /* (MMS_JOURNAL_SERVICE == 1) */ diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index c0cf1c20..377d3e71 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -113,6 +113,12 @@ handleConfirmedRequestPdu( if (extendedTag) { switch(tag) { +#if (MMS_JOURNAL_SERVICE == 1) + case 0x41: /* read-journal */ + mmsServer_handleReadJournalRequest(self, buffer, bufPos, bufPos + length, invokeId, response); + break; +#endif /* (MMS_JOURNAL_SERVICE == 1) */ + #if (MMS_FILE_SERVICE == 1) case 0x48: /* file-open-request */ mmsServer_handleFileOpenRequest(self, buffer, bufPos, bufPos + length, invokeId, response); diff --git a/src/mms/iso_mms/server/mms_status_service.c b/src/mms/iso_mms/server/mms_status_service.c index d0d8f14b..a547b9f0 100644 --- a/src/mms/iso_mms/server/mms_status_service.c +++ b/src/mms/iso_mms/server/mms_status_service.c @@ -24,7 +24,7 @@ #include "libiec61850_platform_includes.h" #include "mms_server_internal.h" -#if MMS_STATUS_SERVICE == 1 +#if (MMS_STATUS_SERVICE == 1) void mmsServer_handleStatusRequest( From 923b683e218c87d90737ea103f2e81633ef6c79a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 12 May 2016 17:50:40 +0200 Subject: [PATCH 10/36] - fixed bug in ethernet_win32.c --- src/CMakeLists.txt | 2 ++ src/hal/ethernet/win32/ethernet_win32.c | 2 +- src/mms/iso_mms/server/mms_journal_service.c | 2 +- src/mms/iso_mms/server/mms_server_connection.c | 8 ++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 266c2799..e8f5319a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,8 @@ set (lib_common_SRCS ./mms/iso_mms/server/mms_domain.c ./mms/iso_mms/server/mms_device.c ./mms/iso_mms/server/mms_information_report.c +./mms/iso_mms/server/mms_journal.c +./mms/iso_mms/server/mms_journal_service.c ./mms/iso_mms/server/mms_server_connection.c ./mms/iso_mms/server/mms_write_service.c ./mms/iso_mms/server/mms_get_var_access_service.c diff --git a/src/hal/ethernet/win32/ethernet_win32.c b/src/hal/ethernet/win32/ethernet_win32.c index 2770df81..68911eeb 100644 --- a/src/hal/ethernet/win32/ethernet_win32.c +++ b/src/hal/ethernet/win32/ethernet_win32.c @@ -233,7 +233,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr) long interfaceIndex = strtol(interfaceId, &endPtr, 10); - if (endPtr != NULL) { + if ((*interfaceId != '\0') && (*endPtr != '\0')) { printf("Ethernet_getInterfaceMACAddress: invalid interface number %s\n", interfaceId); return; } diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index e10e7e6d..6e8135df 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -32,7 +32,7 @@ parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bu uint8_t tag = buffer[(*bufPos)++]; int length; - if (tag != 0x19) { + if (tag != 0x19) { /* TODO 0x1a */ mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); return false; } diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index 377d3e71..10b5790c 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -47,6 +47,8 @@ mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response asn_long2INTEGER(mmsPdu->choice.rejectPDU.originalInvokeID, *invokeId); } + printf("invokeId %p originalInvokeID: %p\n", invokeId, mmsPdu->choice.rejectPDU.originalInvokeID); + if (reason == MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE) { mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU; mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU = @@ -75,6 +77,12 @@ mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response); + if (mmsPdu->choice.rejectPDU.originalInvokeID != NULL) { + GLOBAL_FREEMEM(mmsPdu->choice.rejectPDU.originalInvokeID); + mmsPdu->choice.rejectPDU.originalInvokeID = NULL; + } + + asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); } From 2d45c2d6792d51a87d495fef2b66238d3d0389f3 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 13 May 2016 17:41:04 +0200 Subject: [PATCH 11/36] - add client side code to create read journals requests required for IEC 61850 logging services --- examples/mms_utility/mms_utility.c | 25 ++- src/CMakeLists.txt | 1 + src/mms/inc/mms_client_connection.h | 7 + src/mms/inc/mms_common.h | 2 + src/mms/inc_private/mms_client_internal.h | 8 + .../iso_mms/client/mms_client_connection.c | 64 +++++- src/mms/iso_mms/client/mms_client_files.c | 43 ---- src/mms/iso_mms/client/mms_client_journals.c | 212 ++++++++++++++++++ .../iso_mms/server/mms_get_namelist_service.c | 2 +- src/mms/iso_mms/server/mms_journal_service.c | 4 +- .../iso_mms/server/mms_server_connection.c | 12 +- 11 files changed, 313 insertions(+), 67 deletions(-) create mode 100644 src/mms/iso_mms/client/mms_client_journals.c diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index b56ea13b..d34172d2 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -170,7 +170,30 @@ int main(int argc, char** argv) { printf(" %s\n", name); printf(" read journal...\n"); - MmsConnection_readJournal(con, &error, domainName, name); + // MmsConnection_readJournal(con, &error, domainName, name); + +#if 1 + uint64_t timestamp = Hal_getTimeInMs(); + + MmsValue* startTime = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(startTime, timestamp - 60000); + + MmsValue* endTime = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(endTime, timestamp); + + MmsConnection_readJournalTimeRange(con, &error, domainName, name, startTime, endTime); +#endif + +#if 0 + uint64_t timestamp = Hal_getTimeInMs(); + + MmsValue* startTime = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(startTime, timestamp - 60000); + + MmsValue* entrySpec = MmsValue_newOctetString(8, 8); + + MmsConnection_readJournalStartAfter(con, &error, domainName, name, startTime, entrySpec); +#endif } } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e8f5319a..d68865a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ set (lib_common_SRCS ./mms/iso_mms/client/mms_client_get_var_access.c ./mms/iso_mms/client/mms_client_common.c ./mms/iso_mms/client/mms_client_read.c +./mms/iso_mms/client/mms_client_journals.c ./mms/iso_mms/server/mms_read_service.c ./mms/iso_mms/server/mms_file_service.c ./mms/iso_mms/server/mms_association_service.c diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 5dfa60d8..4275c234 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -724,6 +724,13 @@ MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const cha LinkedList MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId); +LinkedList +MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime); + +LinkedList +MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification); /** * \brief Destroy (free) an MmsServerIdentity object diff --git a/src/mms/inc/mms_common.h b/src/mms/inc/mms_common.h index 50d6b121..b8bf9444 100644 --- a/src/mms/inc/mms_common.h +++ b/src/mms/inc/mms_common.h @@ -45,6 +45,8 @@ typedef enum MMS_ERROR_PARSING_RESPONSE = 4, MMS_ERROR_HARDWARE_FAULT = 5, MMS_ERROR_CONCLUDE_REJECTED = 6, + MMS_ERROR_INVALID_ARGUMENTS = 7, + MMS_ERROR_OTHER = 9, /* confirmed error PDU codes */ diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index 95f4a8b8..c96cfc3b 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -263,4 +263,12 @@ mmsClient_createMmsGetNameListRequestAssociationSpecific(long invokeId, ByteBuff void mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId); +void +mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime); + +void +mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification); + #endif /* MMS_MSG_INTERNAL_H_ */ diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 9a50fe17..930ed6fc 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1642,18 +1642,11 @@ MmsConnection_getServerStatus(MmsConnection self, MmsError* mmsError, int* vmdLo } - -LinkedList -MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId) +static LinkedList +readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuffer* payload) { - ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); - *mmsError = MMS_ERROR_NONE; - uint32_t invokeId = getNextInvokeId(self); - - mmsClient_createReadJournalRequest(invokeId, payload, domainId, itemId); - ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload); if (self->lastResponseError != MMS_ERROR_NONE) @@ -1671,6 +1664,59 @@ MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* do return NULL; } +LinkedList +MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId) +{ + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + mmsClient_createReadJournalRequest(invokeId, payload, domainId, itemId); + + return readJournal(self, mmsError, invokeId, payload); +} + +LinkedList +MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime) +{ + if ((MmsValue_getType(startingTime) != MMS_BINARY_TIME) || + (MmsValue_getType(endingTime) != MMS_BINARY_TIME)) { + + *mmsError = MMS_ERROR_INVALID_ARGUMENTS; + return NULL; + } + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + mmsClient_createReadJournalRequestWithTimeRange(invokeId, payload, domainId, itemId, startingTime, endingTime); + + return readJournal(self, mmsError, invokeId, payload); +} + +LinkedList +MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification) +{ + + if ((MmsValue_getType(timeSpecification) != MMS_BINARY_TIME) || + (MmsValue_getType(entrySpecification) != MMS_OCTET_STRING)) { + + *mmsError = MMS_ERROR_INVALID_ARGUMENTS; + return NULL; + } + + ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + + uint32_t invokeId = getNextInvokeId(self); + + mmsClient_createReadJournalRequestStartAfter(invokeId, payload, domainId, itemId, timeSpecification, entrySpecification); + + return readJournal(self, mmsError, invokeId, payload); +} + int32_t MmsConnection_fileOpen(MmsConnection self, MmsError* mmsError, const char* filename, uint32_t initialPosition, uint32_t* fileSize, uint64_t* lastModified) diff --git a/src/mms/iso_mms/client/mms_client_files.c b/src/mms/iso_mms/client/mms_client_files.c index fcc7de18..81b18bb2 100644 --- a/src/mms/iso_mms/client/mms_client_files.c +++ b/src/mms/iso_mms/client/mms_client_files.c @@ -32,49 +32,6 @@ #include "ber_decode.h" #include "conversions.h" -void -mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId) -{ - /* calculate sizes */ - uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); - - uint32_t domainIdStringSize = strlen(domainId); - uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; - - uint32_t itemIdStringSize = strlen(itemId); - uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; - - uint32_t objectIdSize = domainIdSize + itemIdSize; - - uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); - - uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalNameSize) + (journalNameSize); - - uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; - - /* encode to buffer */ - int bufPos = 0; - uint8_t* buffer = request->buffer; - - bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); - - /* Encode read journal tag (context | structured ) [65 = 41h] */ - buffer[bufPos++] = 0xbf; - buffer[bufPos++] = 0x41; - - bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); - - bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); - - bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); - bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); - - request->size = bufPos; -} - void mmsClient_createFileOpenRequest(uint32_t invokeId, ByteBuffer* request, const char* fileName, uint32_t initialPosition) { diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c new file mode 100644 index 00000000..0099f416 --- /dev/null +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -0,0 +1,212 @@ +/* + * mms_client_journals.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "libiec61850_platform_includes.h" +#include "stack_config.h" +#include "mms_common.h" +#include "mms_client_connection.h" +#include "byte_buffer.h" + +#include "mms_client_internal.h" +#include "ber_encoder.h" +#include "ber_decode.h" +#include "conversions.h" +#include "mms_value_internal.h" + +void +mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId) +{ + /* calculate sizes */ + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); + + uint32_t domainIdStringSize = strlen(domainId); + uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; + + uint32_t itemIdStringSize = strlen(itemId); + uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; + + uint32_t objectIdSize = domainIdSize + itemIdSize; + + uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); + + uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalNameSize) + (journalNameSize); + + uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; + + /* encode to buffer */ + int bufPos = 0; + uint8_t* buffer = request->buffer; + + bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + + /* Encode read journal tag (context | structured ) [65 = 41h] */ + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); + + request->size = bufPos; +} + +void +mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* startingTime, MmsValue* endingTime) +{ + /* calculate sizes */ + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); + + uint32_t domainIdStringSize = strlen(domainId); + uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; + + uint32_t itemIdStringSize = strlen(itemId); + uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; + + uint32_t objectIdSize = domainIdSize + itemIdSize; + + uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); + + uint32_t startingTimeSize = 2 + startingTime->value.binaryTime.size; + + uint32_t rangeStartSpecSize = 2 + startingTimeSize; + + uint32_t endingTimeSize = 2 + endingTime->value.binaryTime.size; + + uint32_t rangeEndSpecSize = 2 + endingTimeSize; + + uint32_t journalReadContentSize = journalNameSize + rangeStartSpecSize + rangeEndSpecSize; + + uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalReadContentSize) + (journalReadContentSize); + + uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; + + /* encode to buffer */ + int bufPos = 0; + uint8_t* buffer = request->buffer; + + bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + + /* encode read journal tag (context | structured ) [65 = 41h] */ + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); + + /* encode journalName */ + bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); + + /* encode rangeStartSpecification|startingTime */ + bufPos = BerEncoder_encodeTL(0xa1, startingTimeSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x80, startingTime->value.binaryTime.buf, + startingTime->value.binaryTime.size, buffer, bufPos); + + /* encode rangeStopSpecification|endingTime */ + bufPos = BerEncoder_encodeTL(0xa2, endingTimeSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x80, endingTime->value.binaryTime.buf, + endingTime->value.binaryTime.size, buffer, bufPos); + + request->size = bufPos; +} + +void +mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, + MmsValue* timeSpecification, MmsValue* entrySpecification) +{ + /* calculate sizes */ + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); + + uint32_t domainIdStringSize = strlen(domainId); + uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; + + uint32_t itemIdStringSize = strlen(itemId); + uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; + + uint32_t objectIdSize = domainIdSize + itemIdSize; + + uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); + + uint32_t timeSpecificationSize = 2 + timeSpecification->value.binaryTime.size; + + uint32_t entrySpecificationSize = 2 + entrySpecification->value.octetString.size; + + uint32_t entryToStartAfterContentSize = timeSpecificationSize + entrySpecificationSize; + + uint32_t entryToStartAfterSize = 1 + BerEncoder_determineLengthSize(entryToStartAfterContentSize) + + entryToStartAfterContentSize; + + uint32_t journalReadContentSize = journalNameSize + entryToStartAfterSize; + + uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalReadContentSize) + (journalReadContentSize); + + uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; + + /* encode to buffer */ + int bufPos = 0; + uint8_t* buffer = request->buffer; + + bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); + + /* encode read journal tag (context | structured ) [65 = 41h] */ + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); + + /* encode journalName */ + bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); + bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); + + /* encode entryToStartAfter */ + bufPos = BerEncoder_encodeTL(0xa5, entryToStartAfterContentSize, buffer, bufPos); + + /* encode entryToStartAfter|timeSpecification */ + bufPos = BerEncoder_encodeOctetString(0x80, timeSpecification->value.binaryTime.buf, + timeSpecification->value.binaryTime.size, buffer, bufPos); + + /* encode entryToStartAfter|entrySpecification */ + bufPos = BerEncoder_encodeOctetString(0x81, entrySpecification->value.octetString.buf, + entrySpecification->value.octetString.size, buffer, bufPos); + + request->size = bufPos; +} + diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index fe93737f..7ce55afe 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -539,7 +539,7 @@ mmsServer_handleGetNameListRequest( mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { createNameListResponse(connection, invokeId, nameList, response, continueAfterId); - LinkedList_destroy(nameList); + LinkedList_destroyStatic(nameList); } } #if (MMS_DATA_SET_SERVICE == 1) diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index 6e8135df..a9ef4ad0 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -32,7 +32,7 @@ parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bu uint8_t tag = buffer[(*bufPos)++]; int length; - if (tag != 0x19) { /* TODO 0x1a */ + if (tag != 0x1a) { mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); return false; } @@ -96,12 +96,10 @@ mmsServer_handleReadJournalRequest( printf(" domain-specific-log\n"); if (!parseStringWithMaxLength(domainId, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { - mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } if (!parseStringWithMaxLength(logName, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { - mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index 10b5790c..d4057621 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -47,8 +47,6 @@ mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response asn_long2INTEGER(mmsPdu->choice.rejectPDU.originalInvokeID, *invokeId); } - printf("invokeId %p originalInvokeID: %p\n", invokeId, mmsPdu->choice.rejectPDU.originalInvokeID); - if (reason == MMS_ERROR_REJECT_UNRECOGNIZED_SERVICE) { mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU; mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU = @@ -66,8 +64,8 @@ mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response } else if (reason == MMS_ERROR_REJECT_INVALID_PDU) { mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_pduError; - mmsPdu->choice.rejectPDU.rejectReason.choice.confirmedResponsePDU = - RejectPDU__rejectReason__pduError_invalidPdu; + asn_long2INTEGER(&mmsPdu->choice.rejectPDU.rejectReason.choice.pduError, + RejectPDU__rejectReason__pduError_invalidPdu); } else { mmsPdu->choice.rejectPDU.rejectReason.present = RejectPDU__rejectReason_PR_confirmedRequestPDU; @@ -77,12 +75,6 @@ mmsServer_writeMmsRejectPdu(uint32_t* invokeId, int reason, ByteBuffer* response der_encode(&asn_DEF_MmsPdu, mmsPdu, mmsServer_write_out, (void*) response); - if (mmsPdu->choice.rejectPDU.originalInvokeID != NULL) { - GLOBAL_FREEMEM(mmsPdu->choice.rejectPDU.originalInvokeID); - mmsPdu->choice.rejectPDU.originalInvokeID = NULL; - } - - asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); } From a23b584d13a7c933b5ef428ef54c673c60636d87 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 16 May 2016 23:35:33 +0200 Subject: [PATCH 12/36] - extended logging implementation --- Makefile | 2 + examples/mms_utility/mms_utility.c | 4 +- make/stack_includes.mk | 1 + src/iec61850/inc/iec61850_common.h | 1 + src/iec61850/inc/iec61850_model.h | 3 + src/iec61850/inc/iec61850_server.h | 3 + src/iec61850/inc_private/logging.h | 18 ++ .../inc_private/mms_mapping_internal.h | 1 + src/iec61850/server/impl/ied_server.c | 8 + src/iec61850/server/mms_mapping/logging.c | 120 ++++++++++++++ src/iec61850/server/mms_mapping/mms_mapping.c | 79 ++++++++- src/iec61850/server/model/model.c | 16 +- src/logging/log_storage_sqlite.c | 67 -------- src/logging/logging_api.h | 75 +++++++-- src/mms/iso_mms/server/mms_journal_service.c | 154 +++++++++++++++++- 15 files changed, 460 insertions(+), 92 deletions(-) delete mode 100644 src/logging/log_storage_sqlite.c diff --git a/Makefile b/Makefile index b5619d49..eeaa5cd0 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,7 @@ LIB_INCLUDE_DIRS += src/goose LIB_INCLUDE_DIRS += src/sampled_values LIB_INCLUDE_DIRS += src/iec61850/inc LIB_INCLUDE_DIRS += src/iec61850/inc_private +LIB_INCLUDE_DIRS += src/logging ifeq ($(HAL_IMPL), WIN32) LIB_INCLUDE_DIRS += third_party/winpcap/Include endif @@ -100,6 +101,7 @@ LIB_API_HEADER_FILES += src/goose/goose_receiver.h LIB_API_HEADER_FILES += src/goose/goose_publisher.h LIB_API_HEADER_FILES += src/sampled_values/sv_subscriber.h LIB_API_HEADER_FILES += src/sampled_values/sv_publisher.h +LIB_API_HEADER_FILES += src/logging/logging_api.h get_sources_from_directory = $(wildcard $1/*.c) get_sources = $(foreach dir, $1, $(call get_sources_from_directory,$(dir))) diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index d34172d2..3e22b242 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -172,7 +172,7 @@ int main(int argc, char** argv) { printf(" read journal...\n"); // MmsConnection_readJournal(con, &error, domainName, name); -#if 1 +#if 0 uint64_t timestamp = Hal_getTimeInMs(); MmsValue* startTime = MmsValue_newBinaryTime(false); @@ -184,7 +184,7 @@ int main(int argc, char** argv) { MmsConnection_readJournalTimeRange(con, &error, domainName, name, startTime, endTime); #endif -#if 0 +#if 1 uint64_t timestamp = Hal_getTimeInMs(); MmsValue* startTime = MmsValue_newBinaryTime(false); diff --git a/make/stack_includes.mk b/make/stack_includes.mk index 3ddcc443..f1afbeb3 100644 --- a/make/stack_includes.mk +++ b/make/stack_includes.mk @@ -8,3 +8,4 @@ INCLUDES += -I$(LIBIEC_HOME)/src/iec61850/inc_private INCLUDES += -I$(LIBIEC_HOME)/src/hal/inc INCLUDES += -I$(LIBIEC_HOME)/src/goose INCLUDES += -I$(LIBIEC_HOME)/src/sampled_values +INCLUDES += -I$(LIBIEC_HOME)/src/logging diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index 938c9d17..929074a0 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -30,6 +30,7 @@ extern "C" { #include "libiec61850_common_api.h" +#include "logging_api.h" /** * @defgroup iec61850_common_api_group IEC 61850 API common parts diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 961e640d..969b35d9 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -517,6 +517,9 @@ LogicalNode_hasUnbufferedReports(LogicalNode* node); DataSet* LogicalNode_getDataSet(LogicalNode* self, const char* dataSetName); +void +LogicalNode_setLogStorage(LogicalNode* self, const char* logName, LogStorage logStorage); + bool DataObject_hasFCData(DataObject* dataObject, FunctionalConstraint fc); diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index 0c9f7ca1..338f76bf 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -650,6 +650,9 @@ IedServer_updateQuality(IedServer self, DataAttribute* dataAttribute, Quality qu /**@}*/ +void +IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorage); + /** * @defgroup IEC61850_SERVER_SETTING_GROUPS Server side setting group handling * diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 2846323e..70b36f8b 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -25,6 +25,13 @@ #ifndef LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ #define LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ +typedef struct { + char* name; + LogicalNode* parentLN; + + LogStorage logStorage; +} LogInstance; + typedef struct { char* name; LogControlBlock* logControlBlock; @@ -39,6 +46,7 @@ typedef struct { MmsValue* mmsValue; MmsVariableSpecification* mmsType; + LogInstance* logInstance; bool enabled; @@ -46,12 +54,22 @@ typedef struct { } LogControl; + +LogInstance* +LogInstance_create(LogicalNode* parentLN, const char* name); + +void +LogInstance_destroy(LogInstance* self); + LogControl* LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping); void LogControl_destroy(LogControl* self); +void +LogControl_setLogStorage(LogControl* self, LogStorage logStorage); + MmsVariableSpecification* Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, int lcbCount); diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index 82660792..b5c193e6 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -37,6 +37,7 @@ struct sMmsMapping { #if (CONFIG_IEC61850_LOG_SERVICE == 1) LinkedList logControls; + LinkedList logInstances; #endif #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index d7e40275..5a2a467b 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -1244,6 +1244,14 @@ IedServer_setEditSettingGroupConfirmationHandler(IedServer self, SettingGroupCon #endif } +void +IedServer_setLogStorage(IedServer self, const char* logRef, LogStorage logStorage) +{ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + MmsMapping_setLogStorage(self->mmsMapping, logRef, logStorage); +#endif +} + ClientConnection private_IedServer_getClientConnectionByHandle(IedServer self, void* serverConnectionHandle) { diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 8a07a524..cdef07fe 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -36,6 +36,26 @@ #include "mms_value_internal.h" +LogInstance* +LogInstance_create(LogicalNode* parentLN, const char* name) +{ + LogInstance* self = (LogInstance*) GLOBAL_MALLOC(sizeof(LogInstance)); + + + self->name = copyString(name); + self->parentLN = parentLN; + self->logStorage = NULL; + + return self; +} + +void +LogInstance_destroy(LogInstance* self) +{ + GLOBAL_FREEMEM(self->name); + GLOBAL_FREEMEM(self); +} + LogControl* LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) { @@ -47,6 +67,7 @@ LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) self->logicalNode = parentLN; self->mmsMapping = mmsMapping; self->dataSetRef = NULL; + self->logInstance = NULL; return self; } @@ -59,6 +80,12 @@ LogControl_destroy(LogControl* self) } } +void +LogControl_setLog(LogControl* self, LogInstance* logInstance) +{ + self->logInstance = logInstance; +} + static LogControlBlock* getLCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index) { @@ -337,7 +364,86 @@ createLogControlBlock(LogControlBlock* logControlBlock, return lcb; } +static LogInstance* +getLogInstanceByLogRef(MmsMapping* self, const char* logRef) +{ + char refStr[130]; + char* domainName; + char* lnName; + char* logName; + + strncpy(refStr, logRef, 129); + + printf("getLogInst... %s\n", refStr); + + domainName = refStr; + + lnName = strchr(refStr, '/'); + + if (lnName == NULL) + return NULL; + + if ((lnName - domainName) > 64) + return NULL; + + lnName[0] = 0; + lnName++; + + logName = strchr(lnName, '$'); + + if (logName == NULL) + return NULL; + + logName[0] = 0; + logName++; + + printf("LOG: dn: %s ln: %s name: %s\n", domainName, lnName, logName); + + LinkedList instance = LinkedList_getNext(self->logInstances); + + while (instance != NULL) { + + LogInstance* logInstance = LinkedList_getData(instance); + + printf("logInstance: %s\n", logInstance->name); + + if (strcmp(logInstance->name, logName) == 0) { + printf (" lnName: %s\n", logInstance->parentLN->name); + + if (strcmp(lnName, logInstance->parentLN->name) == 0) { + LogicalDevice* ld = (LogicalDevice*) logInstance->parentLN->parent; + + printf(" ldName: %s\n", ld->name); + + if (strcmp(ld->name, domainName) == 0) + return logInstance; + } + } + + instance = LinkedList_getNext(instance); + } + + return NULL; +} + +static LogInstance* +getLogInstance(MmsMapping* self, LogicalNode* logicalNode, const char* logName) +{ + LinkedList logInstance = LinkedList_getNext(self->logInstances); + + while (logInstance != NULL) { + LogInstance* instance = (LogInstance*) LinkedList_getData(logInstance); + printf("LOG: %s (%s)\n", instance->name, logName); + + if ((strcmp(instance->name, logName) == 0) && (logicalNode == instance->parentLN)) + return instance; + + logInstance = LinkedList_getNext(logInstance); + } + + return NULL; +} MmsVariableSpecification* Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, @@ -366,6 +472,9 @@ Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode namedVariable->typeSpec.structure.elements[currentLcb] = createLogControlBlock(logControlBlock, logControl); + //getLogInstanceByLogRef(self, logControlBlock->logRef); + logControl->logInstance = getLogInstance(self, logicalNode, logControlBlock->logRef); + LinkedList_add(self->logControls, logControl); currentLcb++; @@ -374,4 +483,15 @@ Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode return namedVariable; } +void +MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logStorage) +{ + LogInstance* logInstance = getLogInstanceByLogRef(self, logRef); + + if (logInstance != NULL) + logInstance->logStorage = logStorage; + //if (DEBUG_IED_SERVER) + if (logInstance == NULL) + printf("IED_SERVER: MmsMapping_setLogStorage no matching log for %s found!\n", logRef); +} diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 30a1fb59..ae7d351e 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1118,6 +1118,12 @@ createMmsDomainFromIedDevice(MmsMapping* self, LogicalDevice* logicalDevice) MmsDomain_addJournal(domain, log->name); + printf("log->name: %s\n", log->name); + + LogInstance* logInstance = LogInstance_create(log->parent, log->name); + + LinkedList_add(self->logInstances, (void*) logInstance); + log = log->sibling; } #endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ @@ -1248,6 +1254,7 @@ MmsMapping_create(IedModel* model) #if (CONFIG_IEC61850_LOG_SERVICE == 1) self->logControls = LinkedList_create(); + self->logInstances = LinkedList_create(); #endif #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) @@ -1311,6 +1318,11 @@ MmsMapping_destroy(MmsMapping* self) LinkedList_destroy(self->settingGroups); #endif +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + LinkedList_destroyDeep(self->logControls, (LinkedListValueDeleteFunction) LogControl_destroy); + LinkedList_destroyDeep(self->logInstances, (LinkedListValueDeleteFunction) LogInstance_destroy); +#endif + LinkedList_destroy(self->observedObjects); LinkedList_destroy(self->attributeAccessHandlers); @@ -2512,7 +2524,7 @@ MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicat self->connectionIndicationHandlerParameter = parameter; } -#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)) + static bool isMemberValueRecursive(MmsValue* container, MmsValue* value) @@ -2540,6 +2552,8 @@ isMemberValueRecursive(MmsValue* container, MmsValue* value) return isMemberValue; } +#if ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)) + static bool DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index) { @@ -2569,6 +2583,38 @@ DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index) } #endif /* ((CONFIG_IEC61850_REPORT_SERVICE == 1) || (CONFIG_INCLUDE_GOOSE_SUPPORT)) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + +static bool +DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef) +{ + int i = 0; + + DataSetEntry* dataSetEntry = dataSet->fcdas; + + while (dataSetEntry != NULL) { + + MmsValue* dataSetValue = dataSetEntry->value; + + if (dataSetValue != NULL) { /* prevent invalid data set members */ + if (isMemberValueRecursive(dataSetValue, value)) { + if (dataRef != NULL) + sprintf(dataRef, "%s/%s", dataSetEntry->logicalDeviceName, dataSetEntry->variableName); + + return true; + } + } + + i++; + + dataSetEntry = dataSetEntry->sibling; + } + + return false; +} + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + #if (CONFIG_IEC61850_REPORT_SERVICE == 1) void MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag) @@ -2622,9 +2668,6 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl if ((lc->enabled) && (lc->dataSet != NULL)) { - - int index; - switch (flag) { case LOG_CONTROL_VALUE_UPDATE: @@ -2650,8 +2693,32 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl continue; } - if (DataSet_isMemberValue(lc->dataSet, value, &index)) { - printf("Log value - flag:%i\n", flag); + char dataRef[130]; + + if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef)) { + printf("Log value - dataRef: %s flag: %i\n", dataRef, flag); + + //TODO log to logInstance + + if (lc->logInstance != NULL) { + + LogStorage logStorage = lc->logInstance->logStorage; + + if (logStorage != NULL) { + + uint64_t entryID = LogStorage_addEntry(logStorage, Hal_getTimeInMs()); + + int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); + + uint8_t* data = GLOBAL_MALLOC(dataSize); + + MmsValue_encodeMmsData(value, data, 0, true); + + LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); + + + } + } } diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 9b96b704..b6ced897 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -372,7 +372,7 @@ LogicalNode_hasFCData(LogicalNode* node, FunctionalConstraint fc) DataSet* LogicalNode_getDataSet(LogicalNode* self, const char* dataSetName) { - assert(self->modelType == LogicalNodeModelType); + assert(self->modelType == LogicalNodeModelType); assert(dataSetName != NULL); char dsName[66]; @@ -409,6 +409,20 @@ exit_error: return NULL; } + +void +LogicalNode_setLogStorage(LogicalNode* self, const char* logName, LogStorage logStorage) +{ + assert(self->modelType == LogicalNodeModelType); + assert(logName != NULL); + + LogicalDevice* ld = (LogicalDevice*) self->parent; + + IedModel* iedModel = (IedModel*) ld->parent; + + +} + int LogicalDevice_getLogicalNodeCount(LogicalDevice* logicalDevice) { diff --git a/src/logging/log_storage_sqlite.c b/src/logging/log_storage_sqlite.c deleted file mode 100644 index df766325..00000000 --- a/src/logging/log_storage_sqlite.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * log_storage_sqlite.c - * - */ - - -#include "logging_api.h" - - -static bool -SqLiteLogStorage_initializeLog(const char* logName, int logSize); - -static bool -SqLiteLogStorage_storeEntry(const char* logName, uint64_t entryID, MmsValue* timestamp, - int entrySize, uint8_t* entryData); - -static bool -SqLiteLogStorage_getEntries(const char* logName, MmsValue* timestamp, - LogEntryCallback callback, void* parameter); - -static bool -SqLiteLogStorage_getEntriesAfter(const char* logName, MmsValue* timestamp, uint64_t entryID, - LogEntryCallback callback, void* parameter); - -static struct sLogStorage logStorageInstance = { - SqLiteLogStorage_initializeLog, - SqLiteLogStorage_storeEntry, - SqLiteLogStorage_getEntries, - SqLiteLogStorage_getEntriesAfter -}; - -LogStorage -SqLiteStorage_createInstance() -{ - return &logStorageInstance; -} - -static bool -SqLiteLogStorage_initializeLog(const char* logName, int logSize) -{ - return true; -} - -static bool -SqLiteLogStorage_storeEntry(const char* logName, uint64_t entryID, MmsValue* timestamp, - int entrySize, uint8_t* entryData) -{ - return true; -} - -static bool -SqLiteLogStorage_getEntries(const char* logName, MmsValue* timestamp, - LogEntryCallback callback, void* parameter) -{ - return true; -} - -static bool -SqLiteLogStorage_getEntriesAfter(const char* logName, MmsValue* timestamp, uint64_t entryID, - LogEntryCallback callback, void* parameter) -{ - return true; -} - - - - diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h index deb0a9c8..575e5900 100644 --- a/src/logging/logging_api.h +++ b/src/logging/logging_api.h @@ -1,6 +1,24 @@ /* - * logging_api.h + * logging_api.h * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. */ #ifndef LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ @@ -8,24 +26,59 @@ #include #include -#include "mms_value.h" typedef struct sLogStorage* LogStorage; -typedef void (*LogEntryCallback) (void* parameter, MmsValue* timestamp, uint64_t entryID, int entrySize, - uint8_t* entryData); +/** + * + * \param moreFollow - more data will follow - if false, the data is not valid + * + * \return true ready to receive new data, false abort operation + */ +typedef bool (*LogEntryCallback) (void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow); + +typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow); struct sLogStorage { - bool (*initializeLog) (const char* logName, int logSize); - bool (*storeEntry) (const char* logName, uint64_t entryID, MmsValue* timestamp, int entrySize, - uint8_t* entryData); + void* instanceData; + + LogEntryCallback entryCallback; + LogEntryDataCallback entryDataCallback; + + void* callbackParameter; + + // bool (*initializeLog) (const char* logName, int logSize); - bool (*getEntries) (const char* logName, MmsValue* timestamp, - LogEntryCallback callback, void* parameter); + uint64_t (*addEntry) (LogStorage self, uint64_t timestamp); - bool (*getEntriesAfter) (const char* logName, MmsValue* timestamp, uint64_t entryID, - LogEntryCallback callback, void* parameter); + bool (*addEntryData) (LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); + + bool (*getEntries) (LogStorage self, uint64_t startingTime, uint64_t endingTime); + + bool (*getEntriesAfter) (LogStorage self, uint64_t startingTime, uint64_t entryID); + + void (*destroy) (LogStorage self); }; + + +uint64_t +LogStorage_addEntry(LogStorage self, uint64_t timestamp); + +bool +LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); + +bool +LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime); + +bool +LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID); + +void +LogStorage_setCallbacks(LogStorage self, LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* callbackParameter); + +void +LogStorage_destroy(LogStorage self); + #endif /* LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ */ diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index a9ef4ad0..8b8843c2 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -23,6 +23,7 @@ #include "libiec61850_platform_includes.h" #include "mms_server_internal.h" +#include "mms_value_internal.h" #if (MMS_JOURNAL_SERVICE == 1) @@ -69,8 +70,21 @@ mmsServer_handleReadJournalRequest( char domainId[65]; char logName[65]; + char entryIdBuf[64]; /* maximum size of entry id is 64 bytes! */ + + MmsValue entrySpec; + entrySpec.type = MMS_OCTET_STRING; + entrySpec.value.octetString.buf = entryIdBuf; + entrySpec.value.octetString.maxSize = 64; + + MmsValue rangeStart; + MmsValue rangeStop; bool hasNames = false; + bool hasRangeStartSpec = false; + bool hasRangeStopSpec = false; + bool hasTimeSpec = false; + bool hasEntrySpec = false; while (bufPos < maxBufPos) { uint8_t tag = requestBuffer[bufPos++]; @@ -85,7 +99,7 @@ mmsServer_handleReadJournalRequest( switch (tag) { - case 0xa0: + case 0xa0: /* journalName */ { uint8_t objectIdTag = requestBuffer[bufPos++]; @@ -93,7 +107,7 @@ mmsServer_handleReadJournalRequest( switch (objectIdTag) { case 0xa1: /* domain-specific */ - printf(" domain-specific-log\n"); + printf("domain-specific-log \n"); if (!parseStringWithMaxLength(domainId, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { return; @@ -105,8 +119,9 @@ mmsServer_handleReadJournalRequest( printf(" domain: %s log: %s\n", domainId, logName); - break; + hasNames = true; + break; default: mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); @@ -117,13 +132,142 @@ mmsServer_handleReadJournalRequest( break; + case 0xa1: /* rangeStartSpecification */ + { + uint8_t subTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (subTag != 0x80) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + } + + + if ((length == 4) || (length == 6)) { + + rangeStart.type = MMS_BINARY_TIME; + rangeStart.value.binaryTime.size = length; + + memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); + + char stringBuf[100]; + MmsValue_printToBuffer(&rangeStart, stringBuf, 100); + + printf("rangeStartSpec: %s\n", stringBuf); + + hasRangeStartSpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + bufPos += length; + } + + break; + + case 0xa2: /* rangeStopSpecification */ + { + uint8_t subTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + if (subTag != 0x80) { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + } + + + if ((length == 4) || (length == 6)) { + rangeStop.type = MMS_BINARY_TIME; + rangeStop.value.binaryTime.size = length; + + memcpy(rangeStop.value.binaryTime.buf, requestBuffer + bufPos, length); + + char stringBuf[100]; + MmsValue_printToBuffer(&rangeStop, stringBuf, 100); + + printf("rangeStopSpec: %s\n", stringBuf); + + hasRangeStopSpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + bufPos += length; + } + + break; + + case 0xa5: /* entryToStartAfter */ + { + printf("entryToStartAfter\n"); + int maxSubBufPos = bufPos + length; + + while (bufPos < maxSubBufPos) { + uint8_t subTag = requestBuffer[bufPos++]; + bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); + + switch (subTag) { + case 0x80: /* timeSpecification */ + + if ((length == 4) || (length == 6)) { + rangeStart.type = MMS_BINARY_TIME; + rangeStart.value.binaryTime.size = length; + + memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); + + char stringBuf[100]; + MmsValue_printToBuffer(&rangeStart, stringBuf, 100); + + printf("timeSpecification: %s\n", stringBuf); + + hasRangeStopSpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + break; + + case 0x81: /* entrySpecification */ + + if (length <= entrySpec.value.octetString.maxSize) { + memcpy(entrySpec.value.octetString.buf, requestBuffer + bufPos, length); + entrySpec.value.octetString.size = length; + + printf("EntrySpecification with size %i\n", length); + + hasEntrySpec = true; + } + else { + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + break; + + default: + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_UNRECOGNIZED_MODIFIER, response); + return; + } + + bufPos += length; + } + + } + break; + default: mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_INVALID_PDU, response); return; } - - bufPos += length; } + + //TODO check if required fields are present } #endif /* (MMS_JOURNAL_SERVICE == 1) */ From 7acd515a9608dd46c5be62633966ca4ecb5150e2 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Wed, 18 May 2016 17:44:11 +0200 Subject: [PATCH 13/36] - WIP: server side logging --- src/iec61850/inc_private/logging.h | 18 +++ src/iec61850/inc_private/mms_mapping.h | 3 + src/iec61850/server/mms_mapping/logging.c | 111 ++++++++++++++++-- src/iec61850/server/mms_mapping/mms_mapping.c | 49 ++++---- src/logging/logging_api.h | 7 ++ src/mms/inc/mms_device_model.h | 3 + src/mms/iso_mms/server/mms_domain.c | 18 +++ src/mms/iso_mms/server/mms_journal_service.c | 108 +++++++++++++++++ tools/model_generator/genmodel.jar | Bin 83789 -> 84111 bytes .../com/libiec61850/scl/model/LogControl.java | 13 ++ .../tools/StaticModelGenerator.java | 27 +++-- 11 files changed, 316 insertions(+), 41 deletions(-) diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 70b36f8b..afa3084b 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -30,6 +30,13 @@ typedef struct { LogicalNode* parentLN; LogStorage logStorage; + + uint64_t newEntryId; + uint64_t newEntryTime; + + uint64_t oldEntryId; + uint64_t oldEntryTime; + } LogInstance; typedef struct { @@ -46,6 +53,11 @@ typedef struct { MmsValue* mmsValue; MmsVariableSpecification* mmsType; + MmsValue* oldEntr; + MmsValue* oldEntrTm; + MmsValue* newEntr; + MmsValue* newEntrTm; + LogInstance* logInstance; bool enabled; @@ -58,6 +70,12 @@ typedef struct { LogInstance* LogInstance_create(LogicalNode* parentLN, const char* name); +void +LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage); + +void +LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag); + void LogInstance_destroy(LogInstance* self); diff --git a/src/iec61850/inc_private/mms_mapping.h b/src/iec61850/inc_private/mms_mapping.h index 24a74484..0a1bb5a2 100644 --- a/src/iec61850/inc_private/mms_mapping.h +++ b/src/iec61850/inc_private/mms_mapping.h @@ -147,6 +147,9 @@ MmsMapping_setIedServer(MmsMapping* self, IedServer iedServer); void MmsMapping_setConnectionIndicationHandler(MmsMapping* self, IedConnectionIndicationHandler handler, void* parameter); +void +MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logStorage); + void MmsMapping_installWriteAccessHandler(MmsMapping* self, DataAttribute* dataAttribute, WriteAccessHandler handler, void* parameter); diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index cdef07fe..905e0978 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -41,11 +41,15 @@ LogInstance_create(LogicalNode* parentLN, const char* name) { LogInstance* self = (LogInstance*) GLOBAL_MALLOC(sizeof(LogInstance)); - self->name = copyString(name); self->parentLN = parentLN; self->logStorage = NULL; + self->oldEntryId = 0; + self->oldEntryTime = 0; + self->newEntryId = 0; + self->newEntryTime = 0; + return self; } @@ -56,6 +60,49 @@ LogInstance_destroy(LogInstance* self) GLOBAL_FREEMEM(self); } +void +LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag) +{ + LogStorage logStorage = self->logStorage; + + if (logStorage != NULL) { + + printf("Log value - dataRef: %s flag: %i\n", dataRef, flag); + + uint64_t timestamp = Hal_getTimeInMs(); + + uint64_t entryID = LogStorage_addEntry(logStorage, timestamp); + + int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); + + uint8_t* data = GLOBAL_MALLOC(dataSize); + + MmsValue_encodeMmsData(value, data, 0, true); + + LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); + + self->newEntryId = entryID; + self->newEntryTime = timestamp; + + } + else + printf("no log storage available!\n"); +} + +void +LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage) +{ + self->logStorage = logStorage; + + LogStorage_getOldestAndNewestEntries(logStorage, &(self->newEntryId), &(self->newEntryTime), + &(self->oldEntryId), &(self->oldEntryTime)); + + printf("Attached storage to log: %s\n", self->name); + printf(" oldEntryID: %llu oldEntryTm: %llu\n", self->oldEntryId, self->oldEntryTime); + printf(" newEntryID: %llu newEntryTm: %llu\n", self->newEntryId, self->newEntryTime); + +} + LogControl* LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) { @@ -131,6 +178,20 @@ lookupLogControl(MmsMapping* self, MmsDomain* domain, char* lnName, char* object return NULL; } +static void +updateLogStatusInLCB(LogControl* self) +{ + LogInstance* logInstance = self->logInstance; + + if (logInstance != NULL) { + + MmsValue_setBinaryTime(self->oldEntrTm, logInstance->oldEntryTime); + MmsValue_setBinaryTime(self->newEntrTm, logInstance->newEntryTime); + + MmsValue_setOctetString(self->oldEntr, &(logInstance->oldEntryId), 8); + MmsValue_setOctetString(self->newEntr, &(logInstance->newEntryId), 8); + } +} MmsValue* @@ -138,6 +199,8 @@ LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, { MmsValue* value = NULL; + printf("READ ACCESS LOG CB\n"); + char variableId[130]; strncpy(variableId, variableIdOrig, 129); @@ -164,6 +227,9 @@ LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, LogControl* logControl = lookupLogControl(self, domain, lnName, objectName); if (logControl != NULL) { + + updateLogStatusInLCB(logControl); + if (varName != NULL) { value = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName); } @@ -210,7 +276,13 @@ createTrgOps(LogControlBlock* reportControlBlock) { static bool enableLogging(LogControl* logControl) { - printf("enableLogging\n"); + printf("Enable LCB %s...\n", logControl->name); + + if (logControl->dataSetRef == NULL) { + printf(" no data set specified!\n"); + return false; + } + DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, logControl->dataSetRef); if (dataSet == NULL) { @@ -220,11 +292,13 @@ enableLogging(LogControl* logControl) else logControl->dataSet = dataSet; + printf(" enabled\n"); + return true; } static MmsVariableSpecification* -createLogControlBlock(LogControlBlock* logControlBlock, +createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, LogControl* logControl) { MmsVariableSpecification* lcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); @@ -263,7 +337,14 @@ createLogControlBlock(LogControlBlock* logControlBlock, lcb->typeSpec.structure.elements[1] = namedVariable; if (logControlBlock->logRef != NULL) { - mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logControlBlock->logRef); + char logRef[130]; + + int maxLogRefLength = 129 - strlen(self->model->name); + + strcpy(logRef, self->model->name); + strncat(logRef, logControlBlock->logRef, maxLogRefLength); + + mmsValue->value.structure.components[1] = MmsValue_newVisibleString(logRef); } else { char* logRef = createString(4, logControl->domain->domainName, "/", logControlBlock->parent->name, @@ -303,6 +384,8 @@ createLogControlBlock(LogControlBlock* logControlBlock, mmsValue->value.structure.components[3] = MmsValue_newBinaryTime(false); + logControl->oldEntrTm = mmsValue->value.structure.components[3]; + /* NewEntrTm */ namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("NewEntrTm"); @@ -312,6 +395,8 @@ createLogControlBlock(LogControlBlock* logControlBlock, mmsValue->value.structure.components[4] = MmsValue_newBinaryTime(false); + logControl->newEntrTm = mmsValue->value.structure.components[4]; + /* OldEntr */ namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("OldEntr"); @@ -322,6 +407,8 @@ createLogControlBlock(LogControlBlock* logControlBlock, mmsValue->value.structure.components[5] = MmsValue_newOctetString(8, 8); + logControl->oldEntr = mmsValue->value.structure.components[5]; + /* NewEntr */ namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("NewEntr"); @@ -332,6 +419,8 @@ createLogControlBlock(LogControlBlock* logControlBlock, mmsValue->value.structure.components[6] = MmsValue_newOctetString(8, 8); + logControl->newEntr = mmsValue->value.structure.components[6]; + /* TrgOps */ namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("TrgOps"); @@ -426,9 +515,13 @@ getLogInstanceByLogRef(MmsMapping* self, const char* logRef) return NULL; } +#if 0 static LogInstance* getLogInstance(MmsMapping* self, LogicalNode* logicalNode, const char* logName) { + if (logName == NULL) + return NULL; + LinkedList logInstance = LinkedList_getNext(self->logInstances); while (logInstance != NULL) { @@ -444,6 +537,7 @@ getLogInstance(MmsMapping* self, LogicalNode* logicalNode, const char* logName) return NULL; } +#endif MmsVariableSpecification* Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, @@ -470,10 +564,13 @@ Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode logControl->domain = domain; namedVariable->typeSpec.structure.elements[currentLcb] = - createLogControlBlock(logControlBlock, logControl); + createLogControlBlock(self, logControlBlock, logControl); //getLogInstanceByLogRef(self, logControlBlock->logRef); - logControl->logInstance = getLogInstance(self, logicalNode, logControlBlock->logRef); + //logControl->logInstance = getLogInstance(self, logicalNode, logControlBlock->logRef); + + if (logControlBlock->logRef != NULL) + logControl->logInstance = getLogInstanceByLogRef(self, logControlBlock->logRef); LinkedList_add(self->logControls, logControl); @@ -489,7 +586,7 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto LogInstance* logInstance = getLogInstanceByLogRef(self, logRef); if (logInstance != NULL) - logInstance->logStorage = logStorage; + LogInstance_setLogStorage(logInstance, logStorage); //if (DEBUG_IED_SERVER) if (logInstance == NULL) diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index ae7d351e..32c159c3 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1116,13 +1116,25 @@ createMmsDomainFromIedDevice(MmsMapping* self, LogicalDevice* logicalDevice) while (log != NULL) { - MmsDomain_addJournal(domain, log->name); + char journalName[65]; - printf("log->name: %s\n", log->name); + int nameLength = strlen(log->parent->name) + strlen(log->name); - LogInstance* logInstance = LogInstance_create(log->parent, log->name); + if (nameLength > 63) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Log name %s invalid! Resulting journal name too long! Skip log\n", log->name); + } + else { + strcpy(journalName, log->parent->name); + strcat(journalName, "$"); + strcat(journalName, log->name); - LinkedList_add(self->logInstances, (void*) logInstance); + MmsDomain_addJournal(domain, journalName); + + LogInstance* logInstance = LogInstance_create(log->parent, log->name); + + LinkedList_add(self->logInstances, (void*) logInstance); + } log = log->sibling; } @@ -2586,7 +2598,7 @@ DataSet_isMemberValue(DataSet* dataSet, MmsValue* value, int* index) #if (CONFIG_IEC61850_LOG_SERVICE == 1) static bool -DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef) +DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef, const char* iedName) { int i = 0; @@ -2599,7 +2611,7 @@ DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef) if (dataSetValue != NULL) { /* prevent invalid data set members */ if (isMemberValueRecursive(dataSetValue, value)) { if (dataRef != NULL) - sprintf(dataRef, "%s/%s", dataSetEntry->logicalDeviceName, dataSetEntry->variableName); + sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName); return true; } @@ -2695,30 +2707,13 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl char dataRef[130]; - if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef)) { - printf("Log value - dataRef: %s flag: %i\n", dataRef, flag); - - //TODO log to logInstance + if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef, self->model->name)) { if (lc->logInstance != NULL) { - - LogStorage logStorage = lc->logInstance->logStorage; - - if (logStorage != NULL) { - - uint64_t entryID = LogStorage_addEntry(logStorage, Hal_getTimeInMs()); - - int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); - - uint8_t* data = GLOBAL_MALLOC(dataSize); - - MmsValue_encodeMmsData(value, data, 0, true); - - LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); - - - } + LogInstance_logSingleData(lc->logInstance, dataRef, value, flag); } + else + printf("No log instance available!\n"); } diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h index 575e5900..0f3b36fd 100644 --- a/src/logging/logging_api.h +++ b/src/logging/logging_api.h @@ -58,6 +58,9 @@ struct sLogStorage { bool (*getEntriesAfter) (LogStorage self, uint64_t startingTime, uint64_t entryID); + bool (*getOldestAndNewestEntries) (LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime); + void (*destroy) (LogStorage self); }; @@ -78,6 +81,10 @@ LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entr void LogStorage_setCallbacks(LogStorage self, LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* callbackParameter); +bool +LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime); + void LogStorage_destroy(LogStorage self); diff --git a/src/mms/inc/mms_device_model.h b/src/mms/inc/mms_device_model.h index b2290ab4..404b8c8a 100644 --- a/src/mms/inc/mms_device_model.h +++ b/src/mms/inc/mms_device_model.h @@ -91,6 +91,9 @@ MmsDomain_getName(MmsDomain* self); void MmsDomain_addJournal(MmsDomain* self, const char* name); +MmsJournal +MmsDomain_getJournal(MmsDomain* self, const char* name); + /** * Delete a MmsDomain instance * diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index 6f406422..f8db8aed 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -84,6 +84,24 @@ MmsDomain_addJournal(MmsDomain* self, const char* name) LinkedList_add(self->journals, (void*) journal); } +MmsJournal +MmsDomain_getJournal(MmsDomain* self, const char* name) +{ + LinkedList journal = LinkedList_getNext(self->journals); + + while (journal != NULL) { + + MmsJournal mmsJournal = (MmsJournal) LinkedList_getData(journal); + + if (strcmp(mmsJournal->name, name) == 0) + return mmsJournal; + + journal = LinkedList_getNext(journal); + } + + return NULL; +} + bool MmsDomain_addNamedVariableList(MmsDomain* self, MmsNamedVariableList variableList) { diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index 8b8843c2..e0a2a4e5 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -27,6 +27,90 @@ #if (MMS_JOURNAL_SERVICE == 1) + +typedef struct sJournalVariable* JournalVariable; + +struct sJournalVariable { + char* tag; /* UTF8(1..255) */ + int tagSize; + + uint8_t* data; + int dataSize; + + JournalVariable next; +}; + +typedef struct { + uint8_t* entryID; + int entryIDSize; + + uint64_t timestamp; + + JournalVariable listOfVariables; +} JournalEntry; + + +struct sJournalEncoder { + uint8_t* buffer; + int maxSize; + int bufPos; + int currentEntryBufPos; /* store start buffer position of current entry in case the whole JournalEntry will become too long */ + +}; + +static bool +entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow) +{ + if (moreFollow) + printf("Found entry ID:%llu timestamp:%llu\n", entryID, timestamp); + + return true; +} + +static bool +entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow) +{ + if (moreFollow) { + printf(" EntryData: ref: %s\n", dataRef); + + MmsValue* value = MmsValue_decodeMmsData(data, 0, dataSize); + + char buffer[256]; + + MmsValue_printToBuffer(value, buffer, 256); + + printf(" value: %s\n", buffer); + } + + return true; +} + + +bool +MmsJournal_queryJournalByRange(MmsJournal self, uint64_t startTime, uint64_t endTime, ByteBuffer* response) +{ + // forward request to implementation class + + //TODO get handle of LogStorage + + struct sJournalEncoder encoderData; + + LogStorage logStorage; + + LogStorage_setCallbacks(logStorage, entryCallback, entryDataCallback, &encoderData); + + LogStorage_getEntries(logStorage, startTime, endTime); + + /* actual encoding will happen in callback handler. When getEntries returns the data is + * already encoded in the buffer. + */ + + // encoder header in response buffer + + // move encoded JournalEntry data to continue the buffer header +} + + static bool parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bufPos, int maxBufPos , uint32_t invokeId, ByteBuffer* response) { @@ -268,6 +352,30 @@ mmsServer_handleReadJournalRequest( } //TODO check if required fields are present + //TODO check valid field combinations + + /* lookup journal */ + MmsServer server = connection->server; + + MmsDevice* mmsDevice = MmsServer_getDevice(connection->server); + + MmsDomain* mmsDomain = MmsDevice_getDomain(mmsDevice, domainId); + + if (mmsDomain == NULL) { + printf("Domain %s not found\n", domainId); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + MmsJournal mmsJournal = MmsDomain_getJournal(mmsDomain, logName); + + if (mmsJournal == NULL) { + printf("Journal %s not found\n", logName); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + printf("Read journal %s ...\n", mmsJournal->name); } #endif /* (MMS_JOURNAL_SERVICE == 1) */ diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index 3c50d337f1d0edd6a3cbe4d6e60b660d53df8005..89a9b09a43118f6b7b68e33ad46650d67c4d7b44 100644 GIT binary patch delta 17624 zcmZX6Wk6iZvMv+c-6bKoyIXK~cMtAPfWh5uaCZ&v!QI_mgG10j$RlK*d(Xaa{;XbA zUrASYSM^#mJ>$`kF>#QH3ew;Z7+^3kFkq~DHF1dC;D0VswQrZ-@Bds9hygTjS9t)_ z+x2%Cae_SnDj@?8v#t|Bh53hAcY+lH{>Gi^dgIn*;92}7zz~T7F+~?SeI~}NU7hOu zVq-s1ItofjKw`}KQf8V9!HbIY$LjRV#|Ez9hx7Aep?twkBo7rj_3we~*X!E&GWfEz znlCYWpRwM-#zFk^=L`SEdx5R2Ovi(CKHnDG2en^@dZ)f^Y$@2uipGOy-jxG6jR_xtnw9 zj)qcYbe$c$4VRAIRUeJzz?+Jy3lj`X+D7hvp2`$N7URqil%KdWV(!4bk;KrFcg_qK&d@N#`PC#HQpf2v~bBM^2+bO?sW3S((;NRl4IoX;pTR2VfN3gCv~hw>p;n>6@F zkA-7IOo8n%t{Kd^it?Mek1P10iFkso&m)*~69t>`vANH-^D0OdX01e&xjCvqaxxqxH%}H>DVZMnBB2f5W5L?$5nXEFqEfdi(Yr_%wLxF zd+H7m)IIZ3p8-q26VBSCK?HFl-p8(`RUGTZesFsC6!D)Q4&4}Mgmtd%^V~&vEkg%W zmSb4ldw?kNcso?WyyLe#xTlPL^w`@jEPX5)z~th)4B{-aqV_H_hSp2)t9W9)gF~8N z_oVD<+#Q!HFJWxE)-CVk7wus8Rg??+b6xUlqQ%b1EpX67tkOg7L#W43regkW!*lk& zVw0>L7GW>jPzHo6(~o-e-yFlL`+*2oh`Ziap}-4-D?SP!V_!`0!!U{f{tmYAvt{Te z!j-=8vu&sVLd_rw5YMQZ_>O7+S1@PSWI(VvD=7{dA{7D^Vktr?E^~KmD>0%lIo^z^ zgd?n;1s?Msc#et0;%!^(Gk9DBkoX!`?Dp9Bdq53gtg9ao!dd1+M#9PVwge=aX_s_ngDfds z26@C3H4FELxrxjvW3R@PHLzP9<20kK_Fq^c%pSag!-Xs#~POzI45+CBsSPil0G=It(&I#{^ML0l1L3RpX53AT#0ag+p z;?_>Y@(ce&)U+FV4AwCi3To8}G{90-|8gv*lJw|B^j_%txt7~*xq>>Tdg43GK7nFTl~Vb~BIT;Kj+Tvr^%srNA6r;mVm~-Uc?87gVDjV-IiL%;FURs=Okfb6WBk(Yd^`BIaZX)jE;z zbTmbgF0)ySDn*f~S<1qh!?3fY@HApZ@;q{hE)F*zS^#x8aqM>#%1i+xcOY$$O!nXf zMRas&d1-YN@%)7Pav&tJmT}q)GtJ1eX(3T(K$?yoi<_-?LsR?MMP;ejk-iKhr0RyI zp8R9Zi#YPB`W8(!b*K)W`~4*|u{YRgl@qwLVNH9kZZleZfv?UFzBFJ{Qax4WG3~0ih}h-;a*{ ztUOCsi^0zgA9g68yS;ZM=gt=gD7VGbvh}PSTc0_ELL7}6H(82`$gLY1Ib=t}U5a_v zb88BPwfMSnvHYCddVw=vZ&%k{>PIOweVALzB6L*=9KEOKzvmtwU7yRzH}2Y}{mgT) ze&O6D5D$pnLhfm<;DlRP`983_;SY>8puw3$cU;;SR)cW#+BM4*^r5iK67R=X8@t@K z;OH3>ZnCz>*uI)#+`XS-W4$!=q0Usjh)GtgR*TF$yVTLm3IQ^11Nk?m7zrj?31+Lx zDBPG2t6@3QHi%K^o*I7E{A}QxJxXhZG<@pW*A+`&j^^M@Oc<*zak5-Ege zO4bYWZ0jhLZkRk`grB3dI65kQx(zWL9)Zi1`eMH=2j#@&ey^vY(o`Rqsx7geRm)aV zm2#5QKgnG$CXU@Mi^6p5E=#qe5ObNtkYdKime&`3t6Bs0-U%7928sLFwd{KixSRh_ zJ*X}nHKQiy_l-$!ey5(+9Nf(f)x@|owB-u5xZ^^u7qh77g#olO*5Fv0$= zQZ{*`^3{szO00dPFCEv4uh@2f4oZ&A{+L~^a%av`ZoRLx!5y;_|H@rUOOGwLVq)+Y z{|5%fE1xx>5V68+u1EQhaf)M&j?oDGzLK&YDMOL3-N907QF|bq1C1>cQ&Aw_;yOv)5sFkb-p?oJt(1opZ-uW?J=NDI5ZmY0dZoY*$=Zqs9oX%n!)?a+cq(H65KNtFP3Wd*a#~(jIDmDzO3^sTPORsRX-^ zmX*Csld^=|3X4~We#GXXl2ke%xwM(>P3!fa5eYF?r?JBc%NOR=YTcuJv5Hq8?Y3zS zW+yra65pMZ=$I>3Hn?J)6RVL=P9XBlC!Xxn)r;3b^Uju$Zhq=WT9TB^OubfX@nbET zQx=7fSUq31xjik4<(*uSCF5!s^Q5oX_WqKl%(;OVm)?*;JwAjQ^j(Xu^^6e3uwfkD zscL}vAkqLk*RiRC%aWt;=h$%?SC!LnEd9TUH zs>bTAVgh+ow#pIv{+S~z;?zF#KH;^T@&?%j`##^b9-dvrbgE;*nv6exd(R5y^g|&J zu$;r8vYP6`IAJv==H%K(*)~pF)(cXf^-2}0n#tX-dlW&B;sdy29aYotS-r4r;cBtZ zQ2qN@d>JpOY%;~RyfRJT+Fj#ti? zj=NVdD%wp$Fe5zWDA01Pn|!?8In11(Zb3;_sX5q@UOmpZ)iy++L$*DD&l!56@8`!w zF4jB3vBa!#7BXIg_|Y+`>=42m;Uce;)%^yPpULBumDeg7e4;3q|YaKz+?n z*~bzu8BpH=g@)5@>KzZiGQUUYc8KOchkDW^^S<&L>3KY+rP206Y$oq`vnCznLtHl| zlQ((idlGfDTxrsXF&No{yst#*j)%mbQIL*hz3?}guArZ%`?4?1(1F`?_Yaj}n~V>b zK+-6fUFAW-mR);Elg_rG#(kDGpa*;~euIlaZ-GFOSF$zHX79c1V}_R=h#}=#^bzex zg)@fc-RL!@UXDH)ejcw-I^{>(!K!>XlEGU%WU3&=*K+oCfW_$s=3@eZs>#UWK7K{G z9e3JB%!11G{59QUBh_Y{TiwKAGle`Ji_>%9FpD@+IYXtJe>b0(@?*<$(<+e>aCgU@cE zGu_M-WV=Z)qvxh6DoV&uwAa7Jx2BhkSv^JpUzt-N7oA3{I@+$OW$CO`$=fHt9j4Wp-2T zigI^}ay!yF^0lzm7Rhw*?9L%o`NYTXB3L0t-)W74PP^xL#24gmfj`^p%6JVk=7v_L zRoP5^KBi)6a@H1QX(?C~8@PXp(AD%#>NiN^WJw_iTb0*RA3Bf6hRsqHxmoBl{(Lp0 zM39ZYZ8h6#%V|j*LDkd_z(w&1z6hZqL0Bkz{@BPo<7)Ckp!Wl=)?<94=-qZvL3+ID z?Mqqxv90iEGu|AppvVttKZPq&6SNQBz8t< zUfAmeZl?2@OIQ+PTTBq!a4>v9Lnq`*8I8m^T&-l1Poe-Z>C`bt`&8va+wM2BXLW7wq+`vwFJY0erT`UZJKOn3T&sQ|T- z#D**^R6L&_e_O@Kq2UhfORFccF=Zp5pH!ys3PRZkg{7q6-BvdFOEfU$0Uu3$WyRg==Z?7x zg?e3gV5yQj%9%Si`8I)qicE-k4Cpvhynb8Xg*7pwHV&(4oEp#DM<(g3 zbIhL1)AG%uBSPJVnLLzMiHb%JxDFYIOk)Qf?)28o%Dg@oZ@WSm(wTYua$4qC_A3|l zo#W`9KnL2|BN4z{N&MWeMMQ{3S7z&6K3Wzq3c+YFmW*&}6*>{xLo(Be0|7VMk3#5Zvt~+6(>bgxX!z`P$yx$Acf?T-0~x~qF7leK9&J!S*h%k9rnK^Q zE{gI8>i0WM2mQSxlO$Esg+^AZzR+e$SW}V0v`8RtwW?i61~&2B)a4?CJ}sGQ(&Mw} zUIun_wk$bFb*?G2k@ee8O@3(N%? zZc`y2(OPD8_I`YkNH4gh{?z29qUsXi$1(lk3_jXRWAD4wCYBkB_shuO7gVxtT*Q|M zmT+?5N>3w`WsYZ|_kwJzd?{)i#eAKXC)3EY)!Bm<_nKUR-kCD=ic0+u>!G#(a}-&6 z3dhR?lfTRHvF6e3>WX*^#ALgtqsn+rcH|Vjwbm&$En4b!?T|XXTIS8%w1zJ0Ihv8N zW?{(YryyPlv8k6lUU==T4CYPF>&}H`MwfD6S*1*U2X}9tqPj;J%`Yd~y^q>bce&M1 z3J7_y{Y2Qad&>F2`#Qdg{BRhh5mGUoVWz=iAE}V?<|$L z%-FV0BQ;Ls95)XVRgfiETKMIO(w2+T)znbNSK+Mf(6dFF$yXIv8(#9Yv$PZyleX#t zkDU&tp9|613+)q6>qM2b1j;2fRqAC-gG~c&E7tLzk{q=zwKHtn*|N#AelKcGS?X}Z z=p zzN;yBw~gQvGp@_@^d=cSR9Ol8xdKN6WSXWntfbzzaWY4hugLRmLVp*lo85KbC=@|M z6K8*^O{6}C${6Hap_7|$AI_O(u`#yHr8x8mf7ICh6(cXk;z#=Tr2DVm{58CeS(7v* z(z_JrZ=D7gRUz^VA5IilM3zJk8=3tb=(iA$G+Vn)7_ya>)JBQ&%|zt+N{h3wfH7ah zEbukQBJ;2|Mf!72*jzC6YKeH}sNi(r80oUnl;FqJwfiH~v)x%j@ec>Q3ztvBt=7-W zx$3?()|*&LFzgcXyi_*uQCkvPuXd??WX_$?RzAFCFnW(l1#{>fg$~X5Suv>cuGi9v zE7iK?;Cf0yRZaikOB*?91*dz38PJds%@nWD=@2`rUNT`v>kAD?R|d*jH^`i6)sx|!9=fxY9O49FW+DuuhQLOOvBr%@ zVght==+E^7ncTLP8ZDj9k(Fz^L+)mvXouvp9XKBQZAC0~?R%$88QDTYck&e|MJ_+l zi(AE(hKY2&O;Y*P>Y|lzS!PSbzWq4Wla0DrCMk<`Bt02ZR&wI7HZgoFC#7XM=XKf` zUZm$+pQD@7jI@jyZpZSMwShB64paQDwv}WFu>_g*Y%3yGM7xKRRkbqLaOoG@#-8ipgzkgc1o6J!y}llV?F|<5s^W`L6|kcw zCb-49JY#)^U{i>&%9~qrn^?-gHf5l=%wwiIDCryiMNQWI z4*5w7?nPbJ{Vvu?%e)6v#v%Kf+~m2)3BW7%ny2IvbE*rXYp|{f@OiZIf8V@jWj0|( zp2{>}qSXQ2e64=6ORT_s=1i3tW>2(k!&K@iJnJbe-JYqpw|KI*@N8x!(ODtZSz*#i z!=*o~2j`ZbT!zwx(_KzoQ9F`S4N+8WlA!}TPkc^H1sr9?oAJGyBDuAH2RLfM^fWcY z)6R8s_1sZAO6Y*}EX08LV1+q-AXD-wlgRg5WRy0Ln1{s>yMBDa8S+DjBjQx>Oe!p3 zsF0vxXaiF6UQo}{2Mi9UQ@`k2gnJho;7I-v$_VL@`4$WzhnMY;_Sa9ytxzz67EGwm zhqeKN2+?rz*XH=UDLn zLKRK{QHuoCQviH9P(MG>?FkYRAo}0{ZW)oVx|}s&FzvDCvt%y`no(X5)q6d@!cBxk zeZ^t#MRSKq?vZQ8(estw>a?di4H!k)hLZ^{zqD`0s@w7$3ovy@So0+@gv^4Wslia& zm9$6Cg0s4kX~xCwp>yZ5{-FA~zmw1|nCA)!*qomDprJ7PV0B+ z1MA)^p5)Sv0wGWpR}D=X2^2E8%OC~~bEOD%ONIqu1D$DHl>%;@`JUo#Q{pqLkR=Gw zeQRiP7z*dVgAbrWiU%o2NNE1B4kC+}cDD9)tH7erpmRLM3Zl*%4hzgG&0AfwY@muh zEhNp8gf%=AYbvSJii|iGjWB1<`ZArj3PfmFk@>n(uN;7uf{tEV3-A=H&k%{iiL*7&too&kG;mYH zkX=aMKSuB!uUyeR1l9O#fk@j2^ei$mZzQCE!(j-0U%gATy{=J46g@w~OX^6-qAPNh zp4F`Yw;`C#0MkoA>gM1SSauiRC6(eMfnH$smhJ^ZtwJZQ<%C{}9W!Qzi+VY1;B`$r@{ z-^YF5fY&17dZ$i6Fo<@Anq#tTD7e~N9EQy!-01dRf*P`f;)j&X?Gm}$4fv;R5r+xz zPghrPDT1&o^#V{EXd%miv*~LbG)0-b0qtMM?;T_)DHB)-!6hplojF}>CAfizQ764V zHRxdwQ=-XMLUu-)sEvLeOIXh-Moc8IeLfPi4vTu6nQ6QMlia%(Qdz=N$|U2Z8yMFJ zu%`-)Q3~1ca;n)f8$uLgPeWvjoJ2fe4gJdnpMT)YFJzO76?K9c@?~Yr z?gKO^Ls26JM49r$k2UW^%FlqynAD$*iafz4uo&rh?C_t<6^wsl?u~K@GQ|G8KoOw* z?OK%p^DzsXb#LxRK1#qX_SY6}V-12Hd$9`YgC zIRjA<1@0CKne-7;c_17TFc6Qls3TZaj`))rYJ07w|BZAVRbgZJj4GX?rJ3@GIc8zG zc3z^b^6?V2)MC9zX@G30ayg~$Dq5jTULpy}O$ll3cNbJ{?8?(PZreeFMULvDQ6m)& z4lT`6%a+0)9D=_&$CIIfA%`}4gEn;5$s$rG5(SN)J~p_Jt~7nh^FEOc0>6R z_c&w0aVhZoDadR~$OpS%8qwlPi99ew{rQF*SUvZgI8Qq@oM17%KwM6Q!(Dq`Z1J7K zlpyt8jg=nE?NRrS--5`R@hP?hGbqwh`v3FCi*(>6!jT@eEtW=mMlyQ%g`KGGLx4hU0v8j>i zl@_;`E!nd2ZF_;`z@|&yTPD4nZ~gsR%oiW z=@tp&R;HK)u3&J^UPQ<}+1#|XyMTODIY`6)?~~xCAtF{xe2xOnNaQO`AG;NU9c23S z<455i@(8VVm9)YWEFgFs^EyV>1*>b#33y(F`lU^C2jEF_ftchbhtEJ~2r4 z1rG!V57gn~Q3MLd3x%TqK!OzEb8GG-L1Z+B7*+m8I^eqwbSk^t;jp&BU+np8B5nQX z+-aAQwNrPMR)Dnm$gN{v^q+ zEy)guA7_srca0whCryWzWap7!NB*@LH%>#mJcYiZ1bn1ms`aRBs9naPNpBb*I;e=X zUce+T75w~J3Cwl_c6cGdoqZ@VAbvJscKlXhjeB|>jjENsUEy;hY?!Siv*g7~@Blm> zUNJlCK_Db2(v0~=OdQyI3IM<4JFhf|<^+fb1lUV1v@Ljy-<=Q zE!bCx?!cgHyY1CW`fuRxx7se5zL~+^ew27F4)g@8bU)}W9=NC+12h(N z!U{AwO$|4!RxOH@whTTyw86Pl$wnDGybBw1os}t0653b*uTLC8rU<@$1jZ4al3}7m zfKeO}$t)b{tCC5N*fg#!w(b`FH0C*Gh^9ZzI-VdwpdR}mRTH$7_Q5g!Ly=SM6gVqm zXdBJmhoq|OjC*|by~6UXtZxcV&9Ugi{zKj`(_&#uZ zMP(eV3))vaf8JecZeAD-bH?=c&z+X$VhU@xl*5jFgjqjO&XnuJn z<}VtU7xPqc!`>{w6N28dY7n7ZJ5Rq1w5Qh_*(5L90#tCH>(XBxf-5<0A zoIGU;IwdljVb5vCB2aJa@In zS&tCm@bZ+o+i9TC$@kOHA3M|=(eB}cKulz=@%NTBkHXyt$k1D;#Vv8A7l9igN*hp| z%(I~(X7%k{$UFSUky}h(Qo~uH&F-k8HBX0Fhv+l78XVx>uRz7rt-NpMsJO%SO^-L0 zJd2PxlP7IpAZ~asZz1npd|sQ4M7Q}ie2%FE zEAusk9-6j`IdgET=(CE3l9m}BJ3xMSN0P|lIiuu~V*ihxo@6(OgkMK6!tq@lf6+^S z$~eO(HPg&nm_y5kt#oBdez4}1c7|H=ls?5{?_+Dx6+XDLHxxqp?kVIR?rBw2GAN@M zlVQZdzgl|}nw5#$x9FI)<^@5;G~p? z*y=B$kw-ZpgFe{@Ye#G2?7;I~Y(95Eihg%F0B6Q%=g5WGj!EmjO65+WaY>J<5!NrL z742s1cySi!%Cq(ByP7AymqH20r|c4^j+hL=j=|j*PFBi08NPITKam?rFjsBLYRJc1 zOM5Wv5K*CtUgogFA&<)+%1FQyS3x^DAeQeWR~tI_SNJER*M||-Q}r=@s4bc*iN>@# zj-Q@keZjweu!D!2^@$sCcpWw8*ij}!IbfyiSRYYN`1$SVaO!0s7l*+cglS089~mu? z()&U86<+!^Bix0JDq^b}v^K->gj-$OJJ2DaFB%qVM014UUw*E6cYy+=xl*iwyANX% z3Tw!V(V&p(8{hM_#f<0^bI59z)uFY;UJT{whjRdFFr1^hg?{P(WmM-Lx;1M=6Xn?E zF3S7K*jwTh3psqU52-Xvyg-_l+Np2%TH>mJSB;lWJj`ql;wmG9TraUW6tri370#>N zPVK$J=27LYOpj_eOw$k4y!O2Mwtr0i3D3?Qx$BXeS0?Ke%*&GECD1t1o3VQd8Fca> zC^h1}g587{3sD?Qw(w0l$>KbFfWZRHSUm z-hA=*(P#ga8{zYqDyW|EGohpJ_gM0yw1Qh_>VmHr&Y?Q~A;-)rCBXx4K!d?ilKod!uzT#MV{*bC2_#>)sfjU=g1jH}k0sT#1wVn}>BlNdkzsoqq z`m*IzgUwchjOgIIvlx&+CTP?IZG$r5%5WJfJGt~B&-$XTy6RVK zd4SdXIRe|?!-d|qWmtGm4FYv5#)OK9fw||?T5TK zS9YsLZEyRs;S{oSWaE-Y^cgtv4J(1? zirnyn4GnTn=>)L!-36D~G*JR25G&GgOLNEN9pB_c134%CWa`EmubFH54m5XnsCq0xGNpG;_OP#~_5EHekT?ubAcO9C#|1MH zFRqzjg*>xi{oWUjXKEFEIQsX`3?F@n;z2X{g&U%%)Qtn+*3C<2%x=%xOxm{EoaZnt zHBucV{?PugkkPb((R2-?5Ro`7HxYe2$rd{o9Q*KeJqs>)%uo$hIrmqhr3!A2W_|)x zpXsiR7wPAbmaLkXM8TOK=a(XFGea{|E!Yb2Cpst6!{dmXhjmD?vkEcMwlh;-5tFDO z7znv2EmClR5vOn>dK9&B@kViDpZ3#Wmlp?{f};(8I(N41#GP zV=@p9hpFsKGvM&|SevD@Os)pO_Q+7%MRre6NbXSZAF@VVlhw^F!yJm)NgO|g<(87M z3$O`EMT=S*E1zNpVS6wQmYP5liI&<>ChZguWEnsJ8JelqkYD1g>S(7Je8GElXX;~G>6+ZJU& zoLve9qJb~*$1{Lilv^eldgjs~vcM(aq@-45;^-h$(7-N8VoAEG;uQM|uizE*Y6%@<(Kbf=D4{I`P~df+DSXe$%IGBg z0x>h%7b4FTnY!QM8p(*}joYZ>h^IqGex~x9?_iIe zf#0^j_5WPHH(C(xEXv>48VGMzM(h0H=a96$qmZrrAu8fq>Hvq{eFvrYt;0DK_fJmpQ){4)ptp*xq)8d^t}T6j?k_@+D_>8BJIvEcie*CrlK859>u5qsnwne8!BO+#j;pT5yIjZW@? zQ;VJ}c<=QdYdn_Vk-S%@7@|-K^P1sewONF*m2hsfeV*i(b029Hwj`W4P&c2o?f@cL zIX_RdIE}!+gyGiTjxHf{D0go^;y!JaMV>?O;YLltM?N^yvIE~c9{eCMv)#Kwj8)@w zdXyYWt)`SgXgd)E@Ro_XbzJ{;Tl}5ahE&5G-Nyej+i@`48z6>a0;jk7X`XmuLwQJfoO%gKHitc-i|-oE#& z$JQMPO1UuGlXL8yjK}whUb}?f3)cV0@eCK4T-_aZjhOOb)1T_vrUZ1+gLuv7F7XYy zZ67-2gFs)^wecy5K(|o~KE<$njg(JxMCmEoBW6{?=2pNpkC(nr%+r2M4;>JQrFo6b zr1L`ld2~~>;aQkA0T|x4$A8t%`#}!`-G2{Ppd>_2P4y#*T%1Z*jZ&Cijou@Oc^@x{ zHe16d7@M~iTkSa|s!DkPOP?~(ikD8kg(PMG~z0y<(vNrA9sfHebsG~ZoG zL_94Oa5o7=Tbemd>PCqHjgOBXTuY24Nhx9_!X-)T3twNc5}+G|rojxBN-ik0T`TO? zc`)3N8+8Ud-x4W2D?%gX32tm7T$7JQs}$Gx4Xr!sUAiAk9Nn`5yH5zTN(h1Lm@a5o zJ~SW{M-MJVJx(}cWys6GSeE^@FtsYqP#Ie}WZi`W^csNdcPGBxpowySIX2>+QgRWl z`4Yjv4C?#j3DHd%V^CF{^kQ!w?g%!@yb9X5ToFSkObjyI zmO*T~Akypw%F+f}po++?z&^^7qlyTWHn2!a;wDNw%)0px$AD$=QZk{;xvsYwQN;tnJNy|oKvf-niv0kzLB+{n*TaKskp1DmM=iDLIQI}|(nls%EDNQQ|cj>4_ZYx?5u(W)e)`c#kx+(yw0f9)7W*AuM zzix?ms%oXEeliqNIk-Dl$*E=2$9GJ1Rlh>Dp*|VPFjtpcc<1K|ozq=2 zNA#2nB;otwb|x>Vg?Ao+ifj9`4Oy%X^&)qxo7u=E(Wb9fzQC!S>>FzK0v<{9U9@ae zO8f4ZuXi?;P-*GSjcyHh9AUE7g}bwyolySi#SvyhI_6}Uy@!=-jQyxvaFbu_dONQ# z=JcugM%s`&qU8?p?mjerHL7X5SiP=NfwcSU_dh+nJ`i6WAxRa6EX@uya1G7c)x?%l zSt=q8%_i!Zsa}`yNb}j44vkZLW~m;4N}kS7gtHoOgW)zRM-~jp23R6glqPGCeN=hE ze(2Nt^c5S$B>h?1Vj8}HaY0}UGTykOOWGbHbuwZPQjeVBlG?*>aeMPvwppP=l<5}_ z@G{U7IZO4T*X&sM`rUhyGWoADKQTheCLI!gzUw%q;g$ad4Jxs_V!kERtM=)VJ*H^a z|K*Qdrhi3si+`^4yI+0&;vx7axPa0Z`=+1hovL51pc|0yOMW%W6QZPXQzFb$DzTw_ z#|yVjL(xn_Ytiz@DaSQxJV0}5Aa#gJrUly0u7p3;yY9f!sXmu9fMB(PPxD+7I~rt0 z15AVm02)?bVu{Wp*CMS)Jx)Ua=2}3deg2ZKnV}KCFH|W4FU~WXNIw_v6~`j94SEq* zx)2wgA$t?Af9iIoPNE-;e&FW5>C~Z(Y4P`#zQMHqqMY-vp5a8L7ZhD*QHS2;aR-d{ zt1+)WGRW+s8t7^5$G-Y3X0!X-d@Baf(e{yZJU)+CuxJ48omjbD!k>YoTY z#f;CGCn2*uh)Bt$npW127inwa@vlQhBq`&WKCyFgQ!8m}&W0%7UXGk%_x%kgqD#NG z+_Zb`1p(o9QvUmLBl*f(E;B{xn5DGjsxsp4= zeDCA{QRGeDL3+NnDk{Gw*1?#lg_k5HuT=blLVMbINhwdH+w5fG*BX>WW4bY*t z8mWalF80NjV#Cc2#HXGnG<%f9*o1Q=NiF%IDNkVmI(Tx+W&8MKc=&p+rHb2tkcOAb z6n3zzxoF?a6~)UuS5~il!T|b8l!S)vB|ZFb+?A^(ef;Rc6$xHV{3u{VeT{pn>pQ(t z%v~uPf~tA)Eis#q{Q0sylMTZ{vqf;i>E!YC3FfLiscH?%eTplkc`E5t&nw?|#B7o^ z@>N3-)5Ll6RKuItsvk6Xgioi-OeiX~kfts7C2X=XDj!WN)flR$4@Om2-n9s7Q(nwZ z#IH8g?H{giT|=MgJca;Wzpa(isfDeeB~{a@QkY|xqMf0sR_Ye1S*VwYH_*&auBd8J zoLSn0x>l+c=oV)d=vF^An6A7#gF5rF335$slxkDbuGG!1%uQ=pkot+?%nzn}c45rf z&Cum43KVr($^LM6{hK4+$orP}60BYnzD-;CR162&<_y`E@xkHqM3q8iw9CTp1+-@^ zGlq=%_cL^8$H?2N6&J z{s9nj1Mm}2!cO0;{tk2cD<6JCmT;~ITbCc5{N{bU9a+LwDb&Bui6zkY)6`|B2mS5N zX5Q@V?E7_`OZh!Gw@ zQ-@a$3;X5@d41ZOD|Nli2Y=O7v=jfOOm%7hZP54kz`R8x{D!8EXkrlXc2mAW1L_a} zpa7FprJXQTF<#CYQ_opt(b7b4!I=Za$@yRtpoxhEA>K)tq(Cr3yz{>qV-*&bmp?kc zR6tP_oHp7XpvI)sy^S;`v;r%J?h{$fMiTQ~@(7y&zJ!Rn5|QXb!oxqJ*+ z<#~D$ToS7l7}w(@H>tTWq~!Od`U`AK=~$Zb%R8G`zVDx!#L2A;k11K{i(&bf*yWliiz|Tgn$f9Q)SqQJj7@B`G}#XDTcpJ!CAX!U$+uer zIZEch7ab40n3TG2Mi(t2n^cX%!0+JmFl@)2RHfypb0Svr0mcN0X%w=YKb(h?L2xkI zsCFbHDx*A&p{zuvoLf;~eQTCl!z!_ha;p5`-Xj_oBjc&iblUbR)Cfrgn{>eC)dyMC z)1Gu{?&)qYOkJH2j2-*BzA!B=}|+6s;E<#kJ-F<4 z8^h1MI=(zI1;p9JT}FqyU1b5oc{s~<=r`1_05gdsyI5l6e3mpm6*Vc zJbwV+Yf&cV8XESA)nbnmM9B;9>j(r zyB@KhpDDUF^+N@yYlXbdgXExES*CRT&XJtubDE&jHp-^F{fe+@SP@Hxx*po&JJYe; zNDy!hzTDz6X3CNjSkV*#U!O12EJOn7zHeI$>C7GPQF{t$bHTgdmEYLa98=8k39X9= zm1hg~BG)%*om!YwqNkEagaynEDO&DOS!M{* z(J1ZeSS}-4F6mm%1HCQh1~DGdPy5YoK)<=}cHy~i!9o}V44*@LA0Vlo!&!auB|3L0 zU%GH!M4*7%obcNa%J%W|#{QN47FfNwjgV%V3==hcLq^D|t076h6^3FVbn{pxcn8Q~ zpNR6aOq_a+D=_sE0&_iDqJ80#-nCMCYahA!eq`iV)sRnn0m>-!QtSb6Wf;*>Bglu< z%Y+)FN<^;}RLzaa>LnbTsaaKEJ7lis%cktfoWu9F%cdmAZn%e4RLz~pZpep8<|+#n z9?Rj5Y~o2&a~qPTcu2)d@1=94$Zm*+jaffR=U$CyT_lW!F7v-I{3(dhjX8COkYHc{ z_`d}a_J0l(|4Ho(b$at~ZwdM>(Sz(i0nqD87k>ZE?d258)7?Poe;#rVf07?qAzeU1XpfQNa5q!}oyUzgh&gUkj}W0IA7<6wvbKLDOsT zU3#m>f66cmXjBY<-o!@-xc*N!$v_{(0oYBpjDXrV5{QHa!1>24NRI^|@Ycv-0le)d zKr<`=o;L~#D?sqAp~4E#d27_MzPj~C3Y`t0hxFQz_}?uH2*?Id`0$4&cngq{KmY>+ z;evrtfc^KULkZ--3c&fZjUjx4{x=ijzi+^_^b-VsVS%*R0dKnnkOeyc|6Sx8@pT)4 z1LEWcAiSw~e)=)F2=!_W2@H(sUn)|K{-{WJG9>(8^u|VWNABy4VtbuYWdA}>TD+lO zwe;gorgT`zkJ-faRxcF;~NdBdeGv;NL<0o&Jg@05S6X(_I0c zKhewhLrlm{#{ePxCqn6e7{}Rf=sz1o&=L>e?cV}Gmwz~U`EMd{`G~X%(tvjuVE>ag zB&hs{qs+j;z*=5s4C8-$0@hOWA9Y9x3`H>ipX9IIW9y|q60cFC|Cgjs*_-6+&hx|D z$o}Si^`W->%?G@4^#2_`ExuRA_j(W$KY$Tb%m=`FgLnO*2DiMy&zlkcKf^pZE2llL zE<3#r#OuSse|_va{-_dqi|Ty#uG6nq6dMc}7|VZQUI(!I&An=V0NEQd#Pvsn#Vbk( z<^Oi%s{al78qp7LNX%O!(}LITCcOK<VLFDfF?fyaNa_E{OQ%M^UhyIHrq)5tH{?ecj4)^i;}Ol|E(zf@=eie z!610kkuLbEhYJn> zb-&KIKSk;9uyN3R8RmHn&fM#gWd4`A5lE1|FyPahQ@M%h&~UHO27k@F|BkpR0%%J3 zpBjK90>B3!!2p1^*#VfK*Gh(p{C}1d$nXy&7xxW<3rZ3BC-rswA=?rBBl%jd@ZQ3R zDEdmS{tx-J*ipZcZT^szKKz4zEqHJL3<8??LpCD&UnNMO*P;gt!V&u?E^t{ei|()a z90~6K<}yXf|CK@pDNq2=Kwn;^-mHa-y{e_9|6gk;pm8w(;T!Ml56_J0AKq)(q<~Um JeVrj-{}0ouo^A0g+S`=0yF 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 T|58!@6gPYrEcTb}0P+6;1QK<{ diff --git a/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java b/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java index 82cd3576..9960ee53 100644 --- a/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java +++ b/tools/model_generator/src/com/libiec61850/scl/model/LogControl.java @@ -34,6 +34,7 @@ public class LogControl { private String ldInst = null; private String prefix = ""; private String lnClass = "LLN0"; + private String lnInst = ""; private String logName; private boolean logEna = true; private boolean reasonCode = true; @@ -49,6 +50,10 @@ public class LogControl { desc = ParserUtils.parseAttribute(logControlNode, "desc"); dataSet = ParserUtils.parseAttribute(logControlNode, "datSet"); + if (dataSet != null) + if (dataSet.equals("")) + dataSet = null; + ldInst = ParserUtils.parseAttribute(logControlNode, "ldInst"); prefix = ParserUtils.parseAttribute(logControlNode, "prefix"); @@ -57,11 +62,19 @@ public class LogControl { if (lnClassString != null) lnClass = lnClassString; + String lnInstString = ParserUtils.parseAttribute(logControlNode, "lnInst"); + + if (lnInstString != null) + lnInst = lnInstString; + logName = ParserUtils.parseAttribute(logControlNode, "logName"); if (logName == null) throw new SclParserException(logControlNode, "LogControl is missing required attribute \"logName\""); + if (logName.equals("")) + logName = null; + String intgPdString = ParserUtils.parseAttribute(logControlNode, "intgPd"); if (intgPdString != null) diff --git a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java index fca09171..bd40376a 100644 --- a/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java +++ b/tools/model_generator/src/com/libiec61850/tools/StaticModelGenerator.java @@ -321,7 +321,7 @@ public class StaticModelGenerator { cOut.println(" (ModelNode*) &" + firstChildName); cOut.println("};\n"); - printLogicalNodeDefinitions(ldName, logicalDevice.getLogicalNodes()); + printLogicalNodeDefinitions(ldName, logicalDevice, logicalDevice.getLogicalNodes()); } @@ -541,7 +541,7 @@ public class StaticModelGenerator { } } - private void printLogicalNodeDefinitions(String ldName, List logicalNodes) { + private void printLogicalNodeDefinitions(String ldName, LogicalDevice logicalDevice, List logicalNodes) { for (int i = 0; i < logicalNodes.size(); i++) { LogicalNode logicalNode = logicalNodes.get(i); @@ -568,7 +568,7 @@ public class StaticModelGenerator { printReportControlBlocks(lnName, logicalNode); - printLogControlBlocks(lnName, logicalNode); + printLogControlBlocks(ldName, lnName, logicalNode, logicalDevice); printLogs(lnName, logicalNode); @@ -1077,7 +1077,7 @@ public class StaticModelGenerator { } } - private void printLogControlBlocks(String lnPrefix, LogicalNode logicalNode) + private void printLogControlBlocks(String ldName, String lnPrefix, LogicalNode logicalNode, LogicalDevice logicalDevice) { List logControlBlocks = logicalNode.getLogControlBlocks(); @@ -1086,7 +1086,7 @@ public class StaticModelGenerator { int lcbNumber = 0; for (LogControl lcb : logControlBlocks) { - printLogControlBlock(lnPrefix, lcb, lcbNumber, lcbCount); + printLogControlBlock(logicalDevice, lnPrefix, lcb, lcbNumber, lcbCount); lcbNumber++; } } @@ -1157,7 +1157,7 @@ public class StaticModelGenerator { this.logs.append(logString); } - private void printLogControlBlock(String lnPrefix, LogControl lcb, int lcbNumber, int lcbCount) + private void printLogControlBlock(LogicalDevice logicalDevice, String lnPrefix, LogControl lcb, int lcbNumber, int lcbCount) { String lcbVariableName = lnPrefix + "_lcb" + lcbNumber; @@ -1172,8 +1172,21 @@ public class StaticModelGenerator { else lcbString += "NULL, "; + + String logRef; + + if (lcb.getLdInst() == null) + logRef = logicalDevice.getInst() + "/"; + else + logRef = lcb.getLdInst() + "/"; + + if (lcb.getLnClass().equals("LLN0")) + logRef += "LLN0$"; + else + logRef += lcb.getLnClass() + "$"; + if (lcb.getLogName() != null) - lcbString += "\"" + lcb.getLogName() + "\", "; + lcbString += "\"" + logRef + lcb.getLogName() + "\", "; else lcbString += "NULL, "; From ba08a2ad590c538c09587975ab14a91580e5211a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 23 May 2016 01:14:00 +0200 Subject: [PATCH 14/36] - implemented client and server side readJournal message parsing and generation --- demos/beaglebone/beagle_demo.icd | 120 +++--- demos/beaglebone/static_model.c | 10 +- examples/mms_utility/mms_utility.c | 6 +- src/iec61850/server/mms_mapping/logging.c | 46 ++- src/logging/logging_api.h | 22 +- src/mms/inc/mms_device_model.h | 2 + .../iso_mms/client/mms_client_connection.c | 6 +- src/mms/iso_mms/client/mms_client_journals.c | 358 ++++++++++++++++++ src/mms/iso_mms/server/mms_domain.c | 5 + src/mms/iso_mms/server/mms_journal_service.c | 208 ++++++++-- tools/model_generator/modelviewer.jar | Bin 83139 -> 84105 bytes 11 files changed, 652 insertions(+), 131 deletions(-) diff --git a/demos/beaglebone/beagle_demo.icd b/demos/beaglebone/beagle_demo.icd index 259c21f5..df06bd57 100644 --- a/demos/beaglebone/beagle_demo.icd +++ b/demos/beaglebone/beagle_demo.icd @@ -1,24 +1,9 @@ - -

+ +
- - - Station bus - 10 - -
-

10.0.0.2

-

255.255.255.0

-

10.0.0.1

-

0001

-

00000001

-

0001

-
-
-
-
- + + @@ -30,17 +15,13 @@ - - + + - - - - @@ -63,22 +44,12 @@ - - - status-only - - - - - - status-only - - + direct-with-normal-security @@ -86,7 +57,7 @@ - sbo-with-normal-security + direct-with-normal-security @@ -96,7 +67,7 @@ - direct-with-enhanced-security + direct-with-normal-security @@ -112,20 +83,20 @@ - + - + - + @@ -167,16 +138,22 @@ + + + + + + - - + + direct-with-normal-security - + @@ -194,7 +171,7 @@ - + status-only @@ -233,31 +210,25 @@ - - - - - - + - - + - + - + @@ -265,15 +236,7 @@ - - - - - - - - - + @@ -286,15 +249,6 @@ - - - - - - - - - @@ -305,12 +259,21 @@ - + + + + + + + + + + on @@ -320,6 +283,12 @@ off + + Ok + Warning + Alarm + + unknown forward @@ -330,9 +299,6 @@ status-only direct-with-normal-security - sbo-with-normal-security - direct-with-enhanced-security - sbo-with-enhanced-security diff --git a/demos/beaglebone/static_model.c b/demos/beaglebone/static_model.c index 83951899..1010b5c6 100644 --- a/demos/beaglebone/static_model.c +++ b/demos/beaglebone/static_model.c @@ -1114,7 +1114,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_SBO = { NULL, 0, IEC61850_FC_CO, - IEC61850_VISIBLE_STRING_64, + IEC61850_VISIBLE_STRING_129, 0, NULL, 0}; @@ -2404,6 +2404,8 @@ ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, + + IedModel iedModel = { "beagle", &iedModel_GenericIO, @@ -2412,6 +2414,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; @@ -2425,11 +2429,11 @@ iedModel_GenericIO_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0) iedModel_GenericIO_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); -iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(2); +iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); iedModel_GenericIO_GGIO1_SPCSO3_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); -iedModel_GenericIO_GGIO1_DPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(3); +iedModel_GenericIO_GGIO1_DPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); iedModel_GenericIO_TIM_GAPC1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index 3e22b242..3a5c8b67 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -172,11 +172,11 @@ int main(int argc, char** argv) { printf(" read journal...\n"); // MmsConnection_readJournal(con, &error, domainName, name); -#if 0 +#if 1 uint64_t timestamp = Hal_getTimeInMs(); MmsValue* startTime = MmsValue_newBinaryTime(false); - MmsValue_setBinaryTime(startTime, timestamp - 60000); + MmsValue_setBinaryTime(startTime, timestamp - 6000000000); MmsValue* endTime = MmsValue_newBinaryTime(false); MmsValue_setBinaryTime(endTime, timestamp); @@ -184,7 +184,7 @@ int main(int argc, char** argv) { MmsConnection_readJournalTimeRange(con, &error, domainName, name, startTime, endTime); #endif -#if 1 +#if 0 uint64_t timestamp = Hal_getTimeInMs(); MmsValue* startTime = MmsValue_newBinaryTime(false); diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 905e0978..e6516bbc 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -89,6 +89,7 @@ LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* valu printf("no log storage available!\n"); } + void LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage) { @@ -100,7 +101,6 @@ LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage) printf("Attached storage to log: %s\n", self->name); printf(" oldEntryID: %llu oldEntryTm: %llu\n", self->oldEntryId, self->oldEntryTime); printf(" newEntryID: %llu newEntryTm: %llu\n", self->newEntryId, self->newEntryTime); - } LogControl* @@ -585,9 +585,51 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto { LogInstance* logInstance = getLogInstanceByLogRef(self, logRef); - if (logInstance != NULL) + if (logInstance != NULL) { LogInstance_setLogStorage(logInstance, logStorage); +#if 1 + char domainName[65]; + + MmsMapping_getMmsDomainFromObjectReference(logRef, domainName); + + char domainName2[65]; + + strcpy(domainName2, self->model->name); + strcat(domainName2, domainName); + + MmsDomain* mmsDomain = MmsDevice_getDomain(self->mmsDevice, domainName2); + + if (mmsDomain == NULL) { + printf("IED_SERVER: MmsMapping_setLogStorage: domain %s not found!\n", domainName2); + } +#if 0 + char journalName[65]; + + strcpy(journalName, self->parentLN->name); + strcat(journalName, "$"); + strcat(journalName, self->name); +#endif + + printf("Connect LogStorage to MMS journal %s\n", logRef); + + MmsJournal mmsJournal = NULL; + + char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + logName += 1; + mmsJournal = MmsDomain_getJournal(mmsDomain, logName); + } + + if (mmsJournal != NULL) + mmsJournal->logStorage = logStorage; + else + printf("Failed to retrieve MMS journal for log!\n"); +#endif + + } + //if (DEBUG_IED_SERVER) if (logInstance == NULL) printf("IED_SERVER: MmsMapping_setLogStorage no matching log for %s found!\n", logRef); diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h index 0f3b36fd..078b9bb7 100644 --- a/src/logging/logging_api.h +++ b/src/logging/logging_api.h @@ -43,20 +43,15 @@ struct sLogStorage { void* instanceData; - LogEntryCallback entryCallback; - LogEntryDataCallback entryDataCallback; - - void* callbackParameter; - - // bool (*initializeLog) (const char* logName, int logSize); - uint64_t (*addEntry) (LogStorage self, uint64_t timestamp); bool (*addEntryData) (LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); - bool (*getEntries) (LogStorage self, uint64_t startingTime, uint64_t endingTime); + bool (*getEntries) (LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); - bool (*getEntriesAfter) (LogStorage self, uint64_t startingTime, uint64_t entryID); + bool (*getEntriesAfter) (LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); bool (*getOldestAndNewestEntries) (LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, uint64_t* oldEntry, uint64_t* oldEntryTime); @@ -73,13 +68,12 @@ bool LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); bool -LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime); +LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); bool -LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID); - -void -LogStorage_setCallbacks(LogStorage self, LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* callbackParameter); +LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); bool LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, diff --git a/src/mms/inc/mms_device_model.h b/src/mms/inc/mms_device_model.h index 404b8c8a..f4c9be1d 100644 --- a/src/mms/inc/mms_device_model.h +++ b/src/mms/inc/mms_device_model.h @@ -29,6 +29,7 @@ #include "mms_type_spec.h" #include "mms_common.h" #include "mms_named_variable_list.h" +#include "logging_api.h" #ifdef __cplusplus extern "C" { @@ -52,6 +53,7 @@ typedef struct { struct sMmsJournal { char* name; + LogStorage logStorage; }; typedef struct sMmsJournal* MmsJournal; diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 930ed6fc..997415dc 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1652,8 +1652,10 @@ readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuff if (self->lastResponseError != MMS_ERROR_NONE) *mmsError = self->lastResponseError; else if (responseMessage != NULL) { - // if (mmsClient_parseFileOpenResponse(self, &frsmId, fileSize, lastModified) == false) - // *mmsError = MMS_ERROR_PARSING_RESPONSE; + bool moreFollows; + + if (mmsClient_parseReadJournalResponse(self, &moreFollows) == false) + *mmsError = MMS_ERROR_PARSING_RESPONSE; } releaseResponse(self); diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c index 0099f416..ba431140 100644 --- a/src/mms/iso_mms/client/mms_client_journals.c +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -33,6 +33,364 @@ #include "conversions.h" #include "mms_value_internal.h" +typedef struct sMmsJournalEntry* MmsJournalEntry; + +typedef struct sMmsJournalVariable* MmsJournalVariable; + +struct sMmsJournalEntry { + MmsValue* entryID; /* type MMS_OCTET_STRING */ + MmsValue* occurenceTime; /* type MMS_BINARY_TIME */ + LinkedList journalVariables; +}; + +struct sMmsJournalVariable { + char* tag; + MmsValue* value; +}; + +//TODO add event-based API to parse journal entries + +static bool +parseJournalVariable(uint8_t* buffer, int bufPos, int maxLength, MmsJournalVariable journalVariable) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0x80: /* variableTag */ + + if (journalVariable->tag == NULL) { + journalVariable->tag = (char*) GLOBAL_MALLOC(length + 1); + memcpy(journalVariable->tag, buffer + bufPos, length); + journalVariable->tag[length] = 0; + + printf(" tag: %s\n", journalVariable->tag); + } + + break; + + case 0xa1: /* valueSpec */ + + if (journalVariable->value == NULL) { + journalVariable->value = MmsValue_decodeMmsData(buffer, bufPos, length); + } + + break; + + default: + break; + + } + + bufPos += length; + } + + return true; +} + +static bool +parseJournalVariables(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + MmsJournalVariable journalVariable; + + switch (tag) { + case 0x30: /* journalVariable */ + + journalVariable = (MmsJournalVariable) + GLOBAL_CALLOC(1, sizeof(struct sMmsJournalVariable)); + + parseJournalVariable(buffer, bufPos, length, journalVariable); + + LinkedList_add(journalEntry->journalVariables, (void*) journalVariable); + + break; + + default: + break; + + } + + bufPos += length; + } + + return true; +} + +static bool +parseData(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0xa1: /* journalVariables */ + + journalEntry->journalVariables = LinkedList_create(); + + parseJournalVariables(buffer, bufPos, length, journalEntry); + + break; + + default: + break; + + } + + bufPos += length; + } + + return true; +} + +static bool +parseEntryContent(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry journalEntry) +{ + int maxBufPos = bufPos + maxLength; + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0x80: /* occurenceTime */ + printf(" parse occurenceTime\n"); + + if (length == 6) + journalEntry->occurenceTime = MmsValue_newBinaryTime(false); + else if (length == 4) + journalEntry->occurenceTime = MmsValue_newBinaryTime(true); + else + break; + + memcpy(journalEntry->occurenceTime->value.binaryTime.buf, buffer + bufPos, length); + + break; + + case 0xa2: /* data */ + + parseData(buffer, bufPos, length, journalEntry); + + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: ignore unknown tag %02x\n", tag); + break; + } + + bufPos += length; + } + + return true; +} + +static bool +parseJournalEntry(uint8_t* buffer, int bufPos, int maxLength, LinkedList journalEntries) +{ + int maxBufPos = bufPos + maxLength; + + MmsJournalEntry journalEntry = (MmsJournalEntry) GLOBAL_CALLOC(1, sizeof(struct sMmsJournalEntry)); + LinkedList_add(journalEntries, (void*) journalEntry); + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + + case 0x80: /* entryID */ + journalEntry->entryID = MmsValue_newOctetString(length, length); + MmsValue_setOctetString(journalEntry->entryID, buffer + bufPos, length); + break; + + case 0xa1: /* originatingApplication */ + /* ignore */ + break; + + case 0xa2: /* entryContent */ + if (parseEntryContent(buffer, bufPos, length, journalEntry) == false) + return false; + + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: unknown tag %02x\n", tag); + + return false; + } + + bufPos += length; + } + + return true; +} + +static bool +parseListOfJournalEntries(uint8_t* buffer, int bufPos, int maxLength, LinkedList journalEntries) +{ + int maxBufPos = bufPos + maxLength; + + + while (bufPos < maxBufPos) { + + int length; + uint8_t tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + if ((bufPos + length) > maxBufPos) { /* check length field for validity */ + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: invalid length field\n"); + + return false; + } + + switch (tag) { + case 0x30: + printf("Parse journalEntry\n"); + if (parseJournalEntry(buffer, bufPos, length, journalEntries) == false) + return false; + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: parseReadJournalResponse: unknown tag %02x\n", tag); + + return false; + } + + bufPos += length; + } + + return true; +} + +bool +mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows) +{ + uint8_t* buffer = self->lastResponse->buffer; + int maxBufPos = self->lastResponse->size; + int bufPos = self->lastResponseBufPos; + int length; + + uint8_t tag = buffer[bufPos++]; + + if (tag != 0xbf) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: unknown tag %02x\n", tag); + return false; + } + + tag = buffer[bufPos++]; + + *moreFollows = false; + + if (tag != 0x41) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: unknown tag %02x\n", tag); + return false; + } + + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + if (bufPos < 0) + return false; + + int endPos = bufPos + length; + + if (endPos > maxBufPos) { + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: message to short (length:%i maxBufPos:%i)!\n", length, maxBufPos); + return false; + } + + LinkedList journalEntries = NULL; + + while (bufPos < endPos) { + tag = buffer[bufPos++]; + bufPos = BerDecoder_decodeLength(buffer, &length, bufPos, maxBufPos); + + switch (tag) { + case 0xa0: /* listOfJournalEntry */ + journalEntries = LinkedList_create(); + + if (!parseListOfJournalEntries(buffer, bufPos, length, journalEntries)) + return false; + break; + + case 0x81: /* moreFollows */ + *moreFollows = BerDecoder_decodeBoolean(buffer, bufPos); + break; + + default: + if (DEBUG_MMS_CLIENT) + printf("MMS_CLIENT: mmsClient_parseReadJournalResponse: message contains unknown tag %02x!\n", tag); + + return false; + } + + bufPos += length; + } + + return true; +} + void mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId) { diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index f8db8aed..ca54284b 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -76,6 +76,8 @@ MmsDomain_getName(MmsDomain* self) void MmsDomain_addJournal(MmsDomain* self, const char* name) { + printf("CREATE JOURNAL\n"); + if (self->journals == NULL) self->journals = LinkedList_create(); @@ -84,6 +86,7 @@ MmsDomain_addJournal(MmsDomain* self, const char* name) LinkedList_add(self->journals, (void*) journal); } + MmsJournal MmsDomain_getJournal(MmsDomain* self, const char* name) { @@ -93,6 +96,8 @@ MmsDomain_getJournal(MmsDomain* self, const char* name) MmsJournal mmsJournal = (MmsJournal) LinkedList_getData(journal); + printf(" MMS journal: %s (%s)\n", mmsJournal->name, name); + if (strcmp(mmsJournal->name, name) == 0) return mmsJournal; diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index e0a2a4e5..8e1626d8 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -50,19 +50,41 @@ typedef struct { } JournalEntry; +typedef struct sJournalEncoder* JournalEncoder; + struct sJournalEncoder { uint8_t* buffer; int maxSize; int bufPos; int currentEntryBufPos; /* store start buffer position of current entry in case the whole JournalEntry will become too long */ - + uint64_t currentEntryId; //TODO use a byte array for the generic MMS case! + uint64_t currentTimestamp; + bool moreFollows; }; static bool entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow) { - if (moreFollow) - printf("Found entry ID:%llu timestamp:%llu\n", entryID, timestamp); + JournalEncoder encoder = (JournalEncoder) parameter; + + if (moreFollow) { + //printf("Encode entry ID:%" PRIu64 " timestamp:%" PRIu64 "\n", entryID, timestamp); + if (encoder->moreFollows) { + printf("entryCallback return false\n"); + return false; + } + + encoder->currentEntryBufPos = encoder->bufPos; + + encoder->bufPos += 48; /* reserve space for common entry parts */ + + encoder->currentEntryId = entryID; + encoder->currentTimestamp = timestamp; + + } + else { + printf("Encoded last entry: FINISHED\n"); + } return true; } @@ -70,46 +92,87 @@ entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFo static bool entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow) { + JournalEncoder encoder = (JournalEncoder) parameter; + + uint8_t* buffer = encoder->buffer; + + //TODO check if entry is too long for buffer! + if (moreFollow) { - printf(" EntryData: ref: %s\n", dataRef); + int bufPos = encoder->bufPos; + + uint32_t dataRefStrLen = strlen(dataRef); + uint32_t dataRefLen = 1 + BerEncoder_determineLengthSize(dataRefStrLen) + dataRefStrLen; + + uint32_t valueSpecLen = 1 + BerEncoder_determineLengthSize(dataSize) + dataSize; + + if (bufPos > encoder->maxSize) { + encoder->moreFollows = true; + encoder->bufPos = encoder->currentEntryBufPos; /* remove last entry */ + return false; + } + + //TODO check if entry is too long for buffer! - MmsValue* value = MmsValue_decodeMmsData(data, 0, dataSize); + bufPos = BerEncoder_encodeTL(0x30, valueSpecLen + dataRefLen, buffer, bufPos); - char buffer[256]; + /* encode dataRef */ + bufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) dataRef, dataRefStrLen, buffer, bufPos); - MmsValue_printToBuffer(value, buffer, 256); + /* encode valueSpec */ + bufPos = BerEncoder_encodeOctetString(0xa1, data, dataSize, buffer, bufPos); - printf(" value: %s\n", buffer); + //TODO optionally encode reasonCode + + encoder->bufPos = bufPos; } + else { + int dataContentLen = encoder->bufPos - (encoder->currentEntryBufPos + 48); - return true; -} + int journalVariablesLen = 1 + BerEncoder_determineLengthSize(dataContentLen) + dataContentLen; + int dataLen = 1 + BerEncoder_determineLengthSize(journalVariablesLen) + journalVariablesLen; -bool -MmsJournal_queryJournalByRange(MmsJournal self, uint64_t startTime, uint64_t endTime, ByteBuffer* response) -{ - // forward request to implementation class + int dataAndTimeLen = dataLen + 8; - //TODO get handle of LogStorage + int entryContentLen = 1 + BerEncoder_determineLengthSize(dataAndTimeLen) + dataAndTimeLen; - struct sJournalEncoder encoderData; + int journalEntryContentLen = 10 /* entryIdentifier */ + + 4 /* originatingApplication */ + + entryContentLen; - LogStorage logStorage; + int headerBufPos = encoder->currentEntryBufPos; - LogStorage_setCallbacks(logStorage, entryCallback, entryDataCallback, &encoderData); + headerBufPos = BerEncoder_encodeTL(0x30, journalEntryContentLen, buffer, headerBufPos); - LogStorage_getEntries(logStorage, startTime, endTime); + headerBufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) &(encoder->currentEntryId), 8, buffer, headerBufPos); - /* actual encoding will happen in callback handler. When getEntries returns the data is - * already encoded in the buffer. - */ + headerBufPos = BerEncoder_encodeTL(0xa1, 2, buffer, headerBufPos); + headerBufPos = BerEncoder_encodeTL(0x30, 0, buffer, headerBufPos); - // encoder header in response buffer + headerBufPos = BerEncoder_encodeTL(0xa2, dataAndTimeLen, buffer, headerBufPos); - // move encoded JournalEntry data to continue the buffer header -} + MmsValue occurenceTime; + occurenceTime.type = MMS_BINARY_TIME; + occurenceTime.value.binaryTime.size = 6; + MmsValue_setBinaryTime(&occurenceTime, encoder->currentTimestamp); + headerBufPos = BerEncoder_encodeOctetString(0x80, occurenceTime.value.binaryTime.buf, 6, buffer, headerBufPos); + + headerBufPos = BerEncoder_encodeTL(0xa2, journalVariablesLen, buffer, headerBufPos); + headerBufPos = BerEncoder_encodeTL(0xa1, dataContentLen, buffer, headerBufPos); + int entryHeaderLength = headerBufPos - encoder->currentEntryBufPos; + + /* move data to entry header */ + memmove(buffer + (encoder->currentEntryBufPos + entryHeaderLength), buffer + (encoder->currentEntryBufPos + 48), + dataContentLen); + + /* prepare buffer for next entry. */ + encoder->bufPos = encoder->currentEntryBufPos + entryHeaderLength + dataContentLen; + } + + return true; +} static bool parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bufPos, int maxBufPos , uint32_t invokeId, ByteBuffer* response) @@ -141,6 +204,7 @@ parseStringWithMaxLength(char* filename, int maxLength, uint8_t* buffer, int* bu return true; } +#define RESERVED_SPACE_FOR_HEADER 22 void mmsServer_handleReadJournalRequest( @@ -154,7 +218,7 @@ mmsServer_handleReadJournalRequest( char domainId[65]; char logName[65]; - char entryIdBuf[64]; /* maximum size of entry id is 64 bytes! */ + uint8_t entryIdBuf[64]; /* maximum size of entry id is 64 bytes! */ MmsValue entrySpec; entrySpec.type = MMS_OCTET_STRING; @@ -243,7 +307,7 @@ mmsServer_handleReadJournalRequest( } else { mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); - return; + return; // forward request to implementation class } bufPos += length; @@ -277,7 +341,7 @@ mmsServer_handleReadJournalRequest( } else { mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); - return; + return; // forward request to implementation class } bufPos += length; @@ -352,11 +416,15 @@ mmsServer_handleReadJournalRequest( } //TODO check if required fields are present + if (hasNames == false) { + printf("MMS_SERVER: readJournal missing journal name\n"); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + //TODO check valid field combinations /* lookup journal */ - MmsServer server = connection->server; - MmsDevice* mmsDevice = MmsServer_getDevice(connection->server); MmsDomain* mmsDomain = MmsDevice_getDomain(mmsDevice, domainId); @@ -376,6 +444,86 @@ mmsServer_handleReadJournalRequest( } printf("Read journal %s ...\n", mmsJournal->name); + + struct sJournalEncoder encoder; + + encoder.buffer = response->buffer; + encoder.moreFollows = false; + encoder.maxSize = connection->maxPduSize - 3; /* reserve three bytes for moreFollows */ + encoder.bufPos = RESERVED_SPACE_FOR_HEADER; /* reserve space for header */ + + LogStorage logStorage = mmsJournal->logStorage; + + if (logStorage != NULL) { + + if (hasRangeStartSpec && hasRangeStopSpec) { + LogStorage_getEntries(logStorage, MmsValue_getBinaryTimeAsUtcMs(&rangeStart), MmsValue_getBinaryTimeAsUtcMs(&rangeStop), + entryCallback, entryDataCallback, &encoder); + } + else if (hasEntrySpec && hasTimeSpec) { + LogStorage_getEntriesAfter(logStorage, MmsValue_getBinaryTimeAsUtcMs(&rangeStart), *((uint64_t*) entryIdBuf), + entryCallback, entryDataCallback, &encoder); + } + else { + printf("MMS_SERVER: readJournal missing valid argument combination\n"); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); + return; + } + + } + /* actual encoding will happen in callback handler. When getEntries returns the data is + * already encoded in the buffer. + */ + + /* encode message header in response buffer */ + + uint8_t* buffer = encoder.buffer; + bufPos = 0; + + int dataSize = encoder.bufPos - RESERVED_SPACE_FOR_HEADER; + + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize((uint32_t) invokeId) + 2; + uint32_t listOfEntriesLen = 1 + BerEncoder_determineLengthSize(dataSize) + dataSize; + + uint32_t moreFollowsLen; + + if (encoder.moreFollows) + moreFollowsLen = 3; + else + moreFollowsLen = 0; + +// uint32_t readJournalLen = 2 + BerEncoder_determineLengthSize(listOfEntriesLen + moreFollowsLen) + +// (listOfEntriesLen + moreFollowsLen); + + uint32_t readJournalLen = 2 + BerEncoder_determineLengthSize(listOfEntriesLen + moreFollowsLen) + + (listOfEntriesLen + moreFollowsLen); + + uint32_t confirmedResponsePDUSize = readJournalLen + invokeIdSize; + + bufPos = BerEncoder_encodeTL(0xa1, confirmedResponsePDUSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0x02, invokeIdSize - 2, buffer, bufPos); + bufPos = BerEncoder_encodeUInt32((uint32_t) invokeId, buffer, bufPos); + + buffer[bufPos++] = 0xbf; + buffer[bufPos++] = 0x41; + + bufPos = BerEncoder_encodeLength(listOfEntriesLen + moreFollowsLen, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa0, dataSize, buffer, bufPos); + + /* move encoded JournalEntry data to continue the buffer header */ + + printf("Encoded message header with %i bytes\n", bufPos); + memmove(buffer + bufPos, buffer + RESERVED_SPACE_FOR_HEADER, dataSize); + + bufPos = bufPos + dataSize; + + /* encode morefollows */ + if (encoder.moreFollows) + bufPos = BerEncoder_encodeBoolean(0x81, true, buffer, bufPos); + + response->size = bufPos; } #endif /* (MMS_JOURNAL_SERVICE == 1) */ diff --git a/tools/model_generator/modelviewer.jar b/tools/model_generator/modelviewer.jar index 27a0c016a24e1fc82d38cbb461db4fd3d33f33d4..ec5e4ba8c80505fdef589c4fa73185e3dd0e6692 100644 GIT binary patch literal 84105 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 z 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 literal 83139 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 z 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≧̸(VR4o7A)kuR^(p*iX+ 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 Lgt7o@Q<(n;=|y2- From 9a55687379980304c6bd9a9e9b351c2d27057fa8 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 23 May 2016 09:41:34 +0200 Subject: [PATCH 15/36] - added missing resources - fixed bug (seg fault) in unbuffered reporting --- demos/beaglebone/beagle_demo.iid | 331 ++++++++++++++++++++ src/iec61850/server/mms_mapping/reporting.c | 3 +- src/logging/log_storage.c | 65 ++++ 3 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 demos/beaglebone/beagle_demo.iid create mode 100644 src/logging/log_storage.c diff --git a/demos/beaglebone/beagle_demo.iid b/demos/beaglebone/beagle_demo.iid new file mode 100644 index 00000000..32b1946c --- /dev/null +++ b/demos/beaglebone/beagle_demo.iid @@ -0,0 +1,331 @@ + + +

+
+ + + Station bus + 10 + +
+

10.0.0.2

+

255.255.255.0

+

10.0.0.1

+

0001

+

00000001

+

0001

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EXT:2015 + + + + + + + + + + + + + + + + + + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + blocked + test + test/blocked + off + + + + Ok + Warning + Alarm + + + + unknown + forward + backward + both + + + + status-only + direct-with-normal-security + + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index b232d0d3..a94d59da 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -602,7 +602,8 @@ updateReportDataset(MmsMapping* mapping, ReportControl* rc, MmsValue* newDatSet, success = true; - rc->isBuffering = true; + if (rc->buffered) + rc->isBuffering = true; goto exit_function; } diff --git a/src/logging/log_storage.c b/src/logging/log_storage.c new file mode 100644 index 00000000..e7847e4a --- /dev/null +++ b/src/logging/log_storage.c @@ -0,0 +1,65 @@ +/* + * log_storage.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include "logging_api.h" + +uint64_t +LogStorage_addEntry(LogStorage self, uint64_t timestamp) +{ + return self->addEntry(self, timestamp); +} + +bool +LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode) +{ + return self->addEntryData(self, entryID, dataRef, data, dataSize, reasonCode); +} + +bool +LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + return self->getEntries(self, startingTime, endingTime, entryCallback, entryDataCallback, parameter); +} + +bool +LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + return self->getEntriesAfter(self, startingTime, entryID, entryCallback, entryDataCallback, parameter); +} + +void +LogStorage_destroy(LogStorage self) +{ + self->destroy(self); +} + + +bool +LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime) +{ + return self->getOldestAndNewestEntries(self, newEntry, newEntryTime, oldEntry, oldEntryTime); +} + From fd4261cd2d00df0d729af48ce27a5a8e8f59d37b Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Tue, 24 May 2016 00:14:23 +0200 Subject: [PATCH 16/36] - implemented client side readJournal service - extended service side readJournal with ReasonCode - extende mms_utility example with read journal command --- examples/mms_utility/mms_utility.c | 151 ++++++++++++++++-- src/iec61850/server/mms_mapping/logging.c | 23 +-- src/mms/inc/mms_client_connection.h | 20 ++- .../iso_mms/client/mms_client_connection.c | 19 ++- src/mms/iso_mms/client/mms_client_journals.c | 28 +--- src/mms/iso_mms/server/mms_journal_service.c | 57 ++++--- 6 files changed, 216 insertions(+), 82 deletions(-) diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index 3a5c8b67..eac52aa3 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -20,6 +20,7 @@ print_help() printf("-a specify domain for read or write command\n"); printf("-f show file list\n"); printf("-g get file attributes\n"); + printf("-j read journal\n"); } static void @@ -43,6 +44,59 @@ mmsGetFileAttributeHandler (void* parameter, char* filename, uint32_t size, uint printf("DATE: %s\n", gtString); } +static void +printJournalEntries(LinkedList journalEntries) +{ + char buf[1024]; + + LinkedList journalEntriesElem = LinkedList_getNext(journalEntries); + + while (journalEntriesElem != NULL) { + + MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem); + + MmsValue_printToBuffer(journalEntry->entryID, buf, 1024); + printf("EntryID: %s\n", buf); + MmsValue_printToBuffer(journalEntry->occurenceTime, buf, 1024); + printf(" occurence time: %s\n", buf); + + LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables); + + while (journalVariableElem != NULL) { + + MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem); + + printf(" variable-tag: %s\n", journalVariable->tag); + MmsValue_printToBuffer(journalVariable->value, buf, 1024); + printf(" variable-value: %s\n", buf); + + journalVariableElem = LinkedList_getNext(journalVariableElem); + } + + journalEntriesElem = LinkedList_getNext(journalEntriesElem); + } +} + +static void +MmsJournalVariable_destroy(MmsJournalVariable self) +{ + if (self != NULL) { + GLOBAL_FREEMEM(self->tag); + MmsValue_delete(self->value); + GLOBAL_FREEMEM(self); + } +} + +void +MmsJournalEntry_destroy(MmsJournalEntry self) +{ + if (self != NULL) { + MmsValue_delete(self->entryID); + MmsValue_delete(self->occurenceTime); + LinkedList_destroyDeep(self->journalVariables, MmsJournalVariable_destroy); + GLOBAL_FREEMEM(self); + } +} int main(int argc, char** argv) { @@ -53,6 +107,7 @@ int main(int argc, char** argv) { char* domainName = NULL; char* variableName = NULL; char* filename = NULL; + char* journalName = NULL; int readDeviceList = 0; int getDeviceDirectory = 0; @@ -61,11 +116,11 @@ int main(int argc, char** argv) { int readVariable = 0; int showFileList = 0; int getFileAttributes = 0; - + int readJournal = 0; int c; - while ((c = getopt(argc, argv, "ifdh:p:l:t:a:r:g:")) != -1) + while ((c = getopt(argc, argv, "ifdh:p:l:t:a:r:g:j:")) != -1) switch (c) { case 'h': hostname = copyString(optarg); @@ -102,6 +157,11 @@ int main(int argc, char** argv) { filename = copyString(optarg); break; + case 'j': + readJournal = 1; + journalName = copyString(optarg); + break; + default: print_help(); return 0; @@ -146,16 +206,20 @@ int main(int argc, char** argv) { LinkedList variableList = MmsConnection_getDomainVariableNames(con, &error, domainName); - LinkedList element = variableList; + LinkedList element = LinkedList_getNext(variableList); printf("\nMMS domain variables for domain %s\n", domainName); - while ((element = LinkedList_getNext(element)) != NULL) { + while (element != NULL) { char* name = (char*) element->data; printf(" %s\n", name); + + element = LinkedList_getNext(element); } + LinkedList_destroy(variableList); + variableList = MmsConnection_getDomainJournals(con, &error, domainName); if (variableList != NULL) { @@ -169,20 +233,9 @@ int main(int argc, char** argv) { printf(" %s\n", name); - printf(" read journal...\n"); - // MmsConnection_readJournal(con, &error, domainName, name); -#if 1 - uint64_t timestamp = Hal_getTimeInMs(); - MmsValue* startTime = MmsValue_newBinaryTime(false); - MmsValue_setBinaryTime(startTime, timestamp - 6000000000); - - MmsValue* endTime = MmsValue_newBinaryTime(false); - MmsValue_setBinaryTime(endTime, timestamp); - MmsConnection_readJournalTimeRange(con, &error, domainName, name, startTime, endTime); -#endif #if 0 uint64_t timestamp = Hal_getTimeInMs(); @@ -195,9 +248,77 @@ int main(int argc, char** argv) { MmsConnection_readJournalStartAfter(con, &error, domainName, name, startTime, entrySpec); #endif } + + LinkedList_destroy(variableList); } } + if (readJournal) { + + printf(" read journal %s...\n", journalName); + + char* logDomain = journalName; + char* logName = strchr(journalName, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + + uint64_t timestamp = Hal_getTimeInMs(); + + MmsValue* startTime = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(startTime, timestamp - 6000000000); + + MmsValue* endTime = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(endTime, timestamp); + + bool moreFollows; + + LinkedList journalEntries = MmsConnection_readJournalTimeRange(con, &error, logDomain, logName, startTime, endTime, + &moreFollows); + + MmsValue_delete(startTime); + MmsValue_delete(endTime); + + if (journalEntries != NULL) { + + bool readNext; + + do { + readNext = false; + + LinkedList lastEntry = LinkedList_getLastElement(journalEntries); + MmsJournalEntry lastJournalEntry = (MmsJournalEntry) LinkedList_getData(lastEntry); + + MmsValue* nextEntryId = MmsValue_clone(lastJournalEntry->entryID); + MmsValue* nextTimestamp = MmsValue_clone(lastJournalEntry->occurenceTime); + + printJournalEntries(journalEntries); + + LinkedList_destroyDeep(journalEntries, MmsJournalEntry_destroy); + + if (moreFollows) { + char buf[100]; + MmsValue_printToBuffer(nextEntryId, buf, 100); + + printf("READ NEXT AFTER entryID: %s ...\n", buf); + + journalEntries = MmsConnection_readJournalStartAfter(con, &error, logDomain, logName, nextTimestamp, nextEntryId, &moreFollows); + + MmsValue_delete(nextEntryId); + MmsValue_delete(nextTimestamp); + + readNext = true; + } + } while ((moreFollows == true) || (readNext == true)); + } + } + else + printf(" Invalid log name!\n"); + } + if (readVariable) { if (readWriteHasDomain) { MmsValue* result = MmsConnection_readVariable(con, &error, domainName, variableName); diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index e6516bbc..7222373a 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -81,6 +81,8 @@ LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* valu LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); + GLOBAL_FREEMEM(data); + self->newEntryId = entryID; self->newEntryTime = timestamp; @@ -97,10 +99,6 @@ LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage) LogStorage_getOldestAndNewestEntries(logStorage, &(self->newEntryId), &(self->newEntryTime), &(self->oldEntryId), &(self->oldEntryTime)); - - printf("Attached storage to log: %s\n", self->name); - printf(" oldEntryID: %llu oldEntryTm: %llu\n", self->oldEntryId, self->oldEntryTime); - printf(" newEntryID: %llu newEntryTm: %llu\n", self->newEntryId, self->newEntryTime); } LogControl* @@ -123,6 +121,13 @@ void LogControl_destroy(LogControl* self) { if (self != NULL) { + + MmsValue_delete(self->mmsValue); + GLOBAL_FREEMEM(self->name); + + if (self->dataSetRef != NULL) + GLOBAL_FREEMEM(self->dataSetRef); + GLOBAL_FREEMEM(self); } } @@ -188,8 +193,8 @@ updateLogStatusInLCB(LogControl* self) MmsValue_setBinaryTime(self->oldEntrTm, logInstance->oldEntryTime); MmsValue_setBinaryTime(self->newEntrTm, logInstance->newEntryTime); - MmsValue_setOctetString(self->oldEntr, &(logInstance->oldEntryId), 8); - MmsValue_setOctetString(self->newEntr, &(logInstance->newEntryId), 8); + MmsValue_setOctetString(self->oldEntr, (uint8_t*) &(logInstance->oldEntryId), 8); + MmsValue_setOctetString(self->newEntr, (uint8_t*) &(logInstance->newEntryId), 8); } } @@ -365,12 +370,9 @@ createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, if (logControlBlock->dataSetName != NULL) { char* dataSetReference = createDataSetReferenceForDefaultDataSet(logControlBlock, logControl); - printf("createLogControlBlock dataSetRef: %s\n", dataSetReference); - logControl->dataSetRef = dataSetReference; mmsValue->value.structure.components[2] = MmsValue_newVisibleString(dataSetReference); - //GLOBAL_FREEMEM(dataSetReference); } else mmsValue->value.structure.components[2] = MmsValue_newVisibleString(""); @@ -611,7 +613,6 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto strcat(journalName, self->name); #endif - printf("Connect LogStorage to MMS journal %s\n", logRef); MmsJournal mmsJournal = NULL; @@ -625,7 +626,7 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto if (mmsJournal != NULL) mmsJournal->logStorage = logStorage; else - printf("Failed to retrieve MMS journal for log!\n"); + printf("IED_SERVER: Failed to retrieve MMS journal for log!\n"); #endif } diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index 4275c234..d57bbf68 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -720,17 +720,29 @@ bool MmsConnection_getFileDirectory(MmsConnection self, MmsError* mmsError, const char* fileSpecification, const char* continueAfter, MmsFileDirectoryHandler handler, void* handlerParameter); +typedef struct sMmsJournalEntry* MmsJournalEntry; + +typedef struct sMmsJournalVariable* MmsJournalVariable; + +struct sMmsJournalEntry { + MmsValue* entryID; /* type MMS_OCTET_STRING */ + MmsValue* occurenceTime; /* type MMS_BINARY_TIME */ + LinkedList journalVariables; +}; + +struct sMmsJournalVariable { + char* tag; + MmsValue* value; +}; -LinkedList -MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId); LinkedList MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, - MmsValue* startingTime, MmsValue* endingTime); + MmsValue* startingTime, MmsValue* endingTime, bool* moreFollows); LinkedList MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, - MmsValue* timeSpecification, MmsValue* entrySpecification); + MmsValue* timeSpecification, MmsValue* entrySpecification, bool* moreFollows); /** * \brief Destroy (free) an MmsServerIdentity object diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 997415dc..6006c803 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1643,18 +1643,19 @@ MmsConnection_getServerStatus(MmsConnection self, MmsError* mmsError, int* vmdLo } static LinkedList -readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuffer* payload) +readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuffer* payload, bool* moreFollows) { *mmsError = MMS_ERROR_NONE; ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload); + LinkedList response = NULL; + if (self->lastResponseError != MMS_ERROR_NONE) *mmsError = self->lastResponseError; else if (responseMessage != NULL) { - bool moreFollows; - if (mmsClient_parseReadJournalResponse(self, &moreFollows) == false) + if (mmsClient_parseReadJournalResponse(self, moreFollows, &response) == false) *mmsError = MMS_ERROR_PARSING_RESPONSE; } @@ -1663,9 +1664,10 @@ readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuff if (self->associationState == MMS_STATE_CLOSED) *mmsError = MMS_ERROR_CONNECTION_LOST; - return NULL; + return response; } +#if 0 LinkedList MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId) { @@ -1677,10 +1679,11 @@ MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* do return readJournal(self, mmsError, invokeId, payload); } +#endif LinkedList MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, - MmsValue* startingTime, MmsValue* endingTime) + MmsValue* startingTime, MmsValue* endingTime, bool* moreFollows) { if ((MmsValue_getType(startingTime) != MMS_BINARY_TIME) || (MmsValue_getType(endingTime) != MMS_BINARY_TIME)) { @@ -1695,12 +1698,12 @@ MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const mmsClient_createReadJournalRequestWithTimeRange(invokeId, payload, domainId, itemId, startingTime, endingTime); - return readJournal(self, mmsError, invokeId, payload); + return readJournal(self, mmsError, invokeId, payload, moreFollows); } LinkedList MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, - MmsValue* timeSpecification, MmsValue* entrySpecification) + MmsValue* timeSpecification, MmsValue* entrySpecification, bool* moreFollows) { if ((MmsValue_getType(timeSpecification) != MMS_BINARY_TIME) || @@ -1716,7 +1719,7 @@ MmsConnection_readJournalStartAfter(MmsConnection self, MmsError* mmsError, cons mmsClient_createReadJournalRequestStartAfter(invokeId, payload, domainId, itemId, timeSpecification, entrySpecification); - return readJournal(self, mmsError, invokeId, payload); + return readJournal(self, mmsError, invokeId, payload, moreFollows); } int32_t diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c index ba431140..f09535a3 100644 --- a/src/mms/iso_mms/client/mms_client_journals.c +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -33,21 +33,6 @@ #include "conversions.h" #include "mms_value_internal.h" -typedef struct sMmsJournalEntry* MmsJournalEntry; - -typedef struct sMmsJournalVariable* MmsJournalVariable; - -struct sMmsJournalEntry { - MmsValue* entryID; /* type MMS_OCTET_STRING */ - MmsValue* occurenceTime; /* type MMS_BINARY_TIME */ - LinkedList journalVariables; -}; - -struct sMmsJournalVariable { - char* tag; - MmsValue* value; -}; - //TODO add event-based API to parse journal entries static bool @@ -75,8 +60,6 @@ parseJournalVariable(uint8_t* buffer, int bufPos, int maxLength, MmsJournalVaria journalVariable->tag = (char*) GLOBAL_MALLOC(length + 1); memcpy(journalVariable->tag, buffer + bufPos, length); journalVariable->tag[length] = 0; - - printf(" tag: %s\n", journalVariable->tag); } break; @@ -201,8 +184,6 @@ parseEntryContent(uint8_t* buffer, int bufPos, int maxLength, MmsJournalEntry jo switch (tag) { case 0x80: /* occurenceTime */ - printf(" parse occurenceTime\n"); - if (length == 6) journalEntry->occurenceTime = MmsValue_newBinaryTime(false); else if (length == 4) @@ -304,7 +285,6 @@ parseListOfJournalEntries(uint8_t* buffer, int bufPos, int maxLength, LinkedList switch (tag) { case 0x30: - printf("Parse journalEntry\n"); if (parseJournalEntry(buffer, bufPos, length, journalEntries) == false) return false; break; @@ -323,7 +303,7 @@ parseListOfJournalEntries(uint8_t* buffer, int bufPos, int maxLength, LinkedList } bool -mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows) +mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, LinkedList* result) { uint8_t* buffer = self->lastResponse->buffer; int maxBufPos = self->lastResponse->size; @@ -388,9 +368,12 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows) bufPos += length; } + *result = journalEntries; + return true; } +#if 0 void mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId) { @@ -433,6 +416,7 @@ mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const request->size = bufPos; } +#endif void mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, @@ -515,7 +499,7 @@ mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* requ uint32_t objectIdSize = domainIdSize + itemIdSize; - uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); + uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); uint32_t timeSpecificationSize = 2 + timeSpecification->value.binaryTime.size; diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index 8e1626d8..e1b3d294 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -89,6 +89,8 @@ entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFo return true; } +static const char* REASON_CODE_STR = "ReasonCode"; + static bool entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow) { @@ -106,14 +108,32 @@ entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int data uint32_t valueSpecLen = 1 + BerEncoder_determineLengthSize(dataSize) + dataSize; - if (bufPos > encoder->maxSize) { + uint32_t firstVariableLen = 1 + BerEncoder_determineLengthSize(valueSpecLen + dataRefLen) + + valueSpecLen + dataRefLen; + + uint8_t reasonCodeNBuf[2]; + MmsValue reasonCodeValue; + reasonCodeValue.type = MMS_BIT_STRING; + reasonCodeValue.value.bitString.size = 7; + reasonCodeValue.value.bitString.buf = reasonCodeNBuf; + + MmsValue_setBitStringFromInteger(&reasonCodeValue, reasonCode); + + uint32_t reasonCodeValueLen = MmsValue_encodeMmsData(&reasonCodeValue, NULL, 0, false); + + uint32_t reasonCodeContentLen = reasonCodeValueLen + 2 + 12; + + uint32_t secondVariableLen = 1 + BerEncoder_determineLengthSize(reasonCodeContentLen) + + reasonCodeContentLen; + + uint32_t totalLen = firstVariableLen + secondVariableLen; + + if ((bufPos + totalLen) > encoder->maxSize) { encoder->moreFollows = true; encoder->bufPos = encoder->currentEntryBufPos; /* remove last entry */ return false; } - //TODO check if entry is too long for buffer! - bufPos = BerEncoder_encodeTL(0x30, valueSpecLen + dataRefLen, buffer, bufPos); /* encode dataRef */ @@ -122,7 +142,14 @@ entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int data /* encode valueSpec */ bufPos = BerEncoder_encodeOctetString(0xa1, data, dataSize, buffer, bufPos); - //TODO optionally encode reasonCode + /* encode reasonCode */ + + bufPos = BerEncoder_encodeTL(0x30, reasonCodeContentLen , buffer, bufPos); + + bufPos = BerEncoder_encodeOctetString(0x80, (uint8_t*) REASON_CODE_STR, 10, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0xa1, reasonCodeValueLen, buffer, bufPos); + bufPos = MmsValue_encodeMmsData(&reasonCodeValue, buffer, bufPos, true); encoder->bufPos = bufPos; } @@ -255,7 +282,6 @@ mmsServer_handleReadJournalRequest( switch (objectIdTag) { case 0xa1: /* domain-specific */ - printf("domain-specific-log \n"); if (!parseStringWithMaxLength(domainId, 64, requestBuffer, &bufPos, bufPos + length, invokeId, response)) { return; @@ -265,8 +291,6 @@ mmsServer_handleReadJournalRequest( return; } - printf(" domain: %s log: %s\n", domainId, logName); - hasNames = true; break; @@ -301,8 +325,6 @@ mmsServer_handleReadJournalRequest( char stringBuf[100]; MmsValue_printToBuffer(&rangeStart, stringBuf, 100); - printf("rangeStartSpec: %s\n", stringBuf); - hasRangeStartSpec = true; } else { @@ -325,7 +347,6 @@ mmsServer_handleReadJournalRequest( return; } - if ((length == 4) || (length == 6)) { rangeStop.type = MMS_BINARY_TIME; rangeStop.value.binaryTime.size = length; @@ -335,8 +356,6 @@ mmsServer_handleReadJournalRequest( char stringBuf[100]; MmsValue_printToBuffer(&rangeStop, stringBuf, 100); - printf("rangeStopSpec: %s\n", stringBuf); - hasRangeStopSpec = true; } else { @@ -351,7 +370,6 @@ mmsServer_handleReadJournalRequest( case 0xa5: /* entryToStartAfter */ { - printf("entryToStartAfter\n"); int maxSubBufPos = bufPos + length; while (bufPos < maxSubBufPos) { @@ -370,9 +388,7 @@ mmsServer_handleReadJournalRequest( char stringBuf[100]; MmsValue_printToBuffer(&rangeStart, stringBuf, 100); - printf("timeSpecification: %s\n", stringBuf); - - hasRangeStopSpec = true; + hasTimeSpec = true; } else { mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); @@ -387,8 +403,6 @@ mmsServer_handleReadJournalRequest( memcpy(entrySpec.value.octetString.buf, requestBuffer + bufPos, length); entrySpec.value.octetString.size = length; - printf("EntrySpecification with size %i\n", length); - hasEntrySpec = true; } else { @@ -492,9 +506,6 @@ mmsServer_handleReadJournalRequest( else moreFollowsLen = 0; -// uint32_t readJournalLen = 2 + BerEncoder_determineLengthSize(listOfEntriesLen + moreFollowsLen) + -// (listOfEntriesLen + moreFollowsLen); - uint32_t readJournalLen = 2 + BerEncoder_determineLengthSize(listOfEntriesLen + moreFollowsLen) + (listOfEntriesLen + moreFollowsLen); @@ -514,7 +525,9 @@ mmsServer_handleReadJournalRequest( /* move encoded JournalEntry data to continue the buffer header */ - printf("Encoded message header with %i bytes\n", bufPos); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal: Encoded message header with %i bytes\n", bufPos); + memmove(buffer + bufPos, buffer + RESERVED_SPACE_FOR_HEADER, dataSize); bufPos = bufPos + dataSize; From 0c042f2ba480583c9d4059808508d4e72a4c157d Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Mon, 30 May 2016 00:08:59 +0200 Subject: [PATCH 17/36] - added server examples logging - added logging API - added sqlite3 driver for logging --- examples/server_example_logging/Makefile | 30 + .../server_example_logging.c | 232 ++ .../simpleIO_direct_control.icd | 281 +++ .../server_example_logging/static_model.c | 2003 +++++++++++++++++ .../server_example_logging/static_model.h | 301 +++ src/iec61850/client/ied_connection.c | 4 + src/iec61850/common/iec61850_common.c | 8 + src/iec61850/inc/iec61850_common.h | 2 + src/iec61850/inc_private/logging.h | 24 + src/iec61850/server/impl/ied_server.c | 13 + src/iec61850/server/mms_mapping/logging.c | 527 +++-- src/iec61850/server/mms_mapping/mms_mapping.c | 106 +- src/logging/drivers/README | 3 + .../drivers/sqlite/log_storage_sqlite.c | 455 ++++ src/logging/logging_api.h | 2 +- tools/model_generator/genmodel.jar | Bin 84111 -> 84127 bytes .../tools/StaticModelGenerator.java | 8 +- 17 files changed, 3814 insertions(+), 185 deletions(-) create mode 100644 examples/server_example_logging/Makefile create mode 100644 examples/server_example_logging/server_example_logging.c create mode 100644 examples/server_example_logging/simpleIO_direct_control.icd create mode 100644 examples/server_example_logging/static_model.c create mode 100644 examples/server_example_logging/static_model.h create mode 100644 src/logging/drivers/README create mode 100644 src/logging/drivers/sqlite/log_storage_sqlite.c diff --git a/examples/server_example_logging/Makefile b/examples/server_example_logging/Makefile new file mode 100644 index 00000000..7c1e389f --- /dev/null +++ b/examples/server_example_logging/Makefile @@ -0,0 +1,30 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_logging +PROJECT_SOURCES = server_example_logging.c +PROJECT_SOURCES += static_model.c +PROJECT_SOURCES += $(LIBIEC_HOME)/src/logging/drivers/sqlite/log_storage_sqlite.c + +PROJECT_ICD_FILE = simpleIO_direct_control.icd + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +LDLIBS += -lm -lsqlite3 + +CP = cp + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) + + diff --git a/examples/server_example_logging/server_example_logging.c b/examples/server_example_logging/server_example_logging.c new file mode 100644 index 00000000..76c71d82 --- /dev/null +++ b/examples/server_example_logging/server_example_logging.c @@ -0,0 +1,232 @@ +/* + * server_example_logging.c + * + * - How to use a server with logging service + * - How to store arbitrary data in a log + * + */ + +#include "iec61850_server.h" +#include "hal_thread.h" +#include +#include +#include +#include + +#include "static_model.h" + +#include "logging_api.h" + +/* import IEC 61850 device model created from SCL-File */ +extern IedModel iedModel; + +static int running = 0; +static IedServer iedServer = NULL; + +void +sigint_handler(int signalId) +{ + running = 0; +} + +static ControlHandlerResult +controlHandlerForBinaryOutput(void* parameter, MmsValue* value, bool test) +{ + if (test) + return CONTROL_RESULT_FAILED; + + if (MmsValue_getType(value) == MMS_BOOLEAN) { + printf("received binary control command: "); + + if (MmsValue_getBoolean(value)) + printf("on\n"); + else + printf("off\n"); + } + else + return CONTROL_RESULT_FAILED; + + uint64_t timeStamp = Hal_getTimeInMs(); + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO1) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO2) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO3) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal, value); + } + + if (parameter == IEDMODEL_GenericIO_GGIO1_SPCSO4) { + IedServer_updateUTCTimeAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_t, timeStamp); + IedServer_updateAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal, value); + } + + return CONTROL_RESULT_OK; +} + + +static void +connectionHandler (IedServer self, ClientConnection connection, bool connected, void* parameter) +{ + if (connected) + printf("Connection opened\n"); + else + printf("Connection closed\n"); +} + +static bool +entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow) +{ +#if 0 + if (moreFollow) + printf("Found entry ID:%llu timestamp:%llu\n", entryID, timestamp); +#endif + return true; +} + +static bool +entryDataCallback (void* parameter, const char* dataRef, const uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow) +{ +#if 0 + if (moreFollow) { + printf(" EntryData: ref: %s\n", dataRef); + + MmsValue* value = MmsValue_decodeMmsData(data, 0, dataSize); + + char buffer[256]; + + MmsValue_printToBuffer(value, buffer, 256); + + printf(" value: %s\n", buffer); + + MmsValue_delete(value); + } +#endif + + return true; +} + +int +main(int argc, char** argv) +{ + printf("Using libIEC61850 version %s\n", LibIEC61850_getVersionString()); + + iedServer = IedServer_create(&iedModel); + + /* Install handler for operate command */ + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO1, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO1); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO2, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO2); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO3, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO3); + + IedServer_setControlHandler(iedServer, IEDMODEL_GenericIO_GGIO1_SPCSO4, + (ControlHandler) controlHandlerForBinaryOutput, + IEDMODEL_GenericIO_GGIO1_SPCSO4); + + IedServer_setConnectionIndicationHandler(iedServer, (IedConnectionIndicationHandler) connectionHandler, NULL); + + LogStorage statusLog = SqliteLogStorage_createInstance("log_status.db"); + + IedServer_setLogStorage(iedServer, "GenericIO/LLN0$EventLog", statusLog); + + uint64_t entryID = LogStorage_addEntry(statusLog, Hal_getTimeInMs()); + + MmsValue* value = MmsValue_newIntegerFromInt32(123); + + uint8_t blob[256]; + + int blobSize = MmsValue_encodeMmsData(value, blob, 0, true); + + + LogStorage_addEntryData(statusLog, entryID, "simpleIOGenerioIO/GPIO1$ST$SPCSO1$stVal", blob, blobSize, 0); + + MmsValue_delete(value); + + value = MmsValue_newUtcTimeByMsTime(Hal_getTimeInMs()); + + blobSize = MmsValue_encodeMmsData(value, blob, 0, true); + + MmsValue_delete(value); + + LogStorage_addEntryData(statusLog, entryID, "simpleIOGenerioIO/GPIO1$ST$SPCSO1$t", blob, blobSize, 0); + + LogStorage_getEntries(statusLog, 0, Hal_getTimeInMs(), entryCallback, entryDataCallback, NULL); + + /* MMS server will be instructed to start listening to client connections. */ + IedServer_start(iedServer, 102); + + if (!IedServer_isRunning(iedServer)) { + printf("Starting server failed! Exit.\n"); + IedServer_destroy(iedServer); + exit(-1); + } + + running = 1; + + signal(SIGINT, sigint_handler); + + float t = 0.f; + + while (running) { + uint64_t timestamp = Hal_getTimeInMs(); + + t += 0.1f; + + float an1 = sinf(t); + float an2 = sinf(t + 1.f); + float an3 = sinf(t + 2.f); + float an4 = sinf(t + 3.f); + + IedServer_lockDataModel(iedServer); + + Timestamp iecTimestamp; + + Timestamp_clearFlags(&iecTimestamp); + Timestamp_setTimeInMilliseconds(&iecTimestamp, timestamp); + Timestamp_setLeapSecondKnown(&iecTimestamp, true); + + /* toggle clock-not-synchronized flag in timestamp */ + if (((int) t % 2) == 0) + Timestamp_setClockNotSynchronized(&iecTimestamp, true); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f, an1); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f, an2); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f, an3); + + IedServer_updateTimestampAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_t, &iecTimestamp); + IedServer_updateFloatAttributeValue(iedServer, IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f, an4); + + IedServer_unlockDataModel(iedServer); + + Thread_sleep(100); + } + + /* stop MMS server - close TCP server socket and all client sockets */ + IedServer_stop(iedServer); + + /* Cleanup - free all resources */ + IedServer_destroy(iedServer); + + /* Release connection to database and free resources */ + LogStorage_destroy(statusLog); + +} /* main() */ diff --git a/examples/server_example_logging/simpleIO_direct_control.icd b/examples/server_example_logging/simpleIO_direct_control.icd new file mode 100644 index 00000000..b48a383e --- /dev/null +++ b/examples/server_example_logging/simpleIO_direct_control.icd @@ -0,0 +1,281 @@ + + +
+
+ + + Station bus + 10 + +
+

10.0.0.2

+

255.255.255.0

+

10.0.0.1

+

0001

+

00000001

+

0001

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + MZ Automation + + + 0.7.3 + + + libiec61850 server example + + + + + + + + status-only + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
diff --git a/examples/server_example_logging/static_model.c b/examples/server_example_logging/static_model.c new file mode 100644 index 00000000..07b5b4a4 --- /dev/null +++ b/examples/server_example_logging/static_model.c @@ -0,0 +1,2003 @@ +/* + * static_model.c + * + * automatically generated from simpleIO_direct_control.icd + */ +#include "static_model.h" + +static void initializeValues(); + +extern DataSet iedModelds_GenericIO_LLN0_Events; +extern DataSet iedModelds_GenericIO_LLN0_Events2; +extern DataSet iedModelds_GenericIO_LLN0_Measurements; + + +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda3; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda0 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO1$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda1 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO2$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda2 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO3$stVal", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events_fcda3 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO4$stVal", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Events = { + "GenericIO", + "LLN0$Events", + 4, + &iedModelds_GenericIO_LLN0_Events_fcda0, + &iedModelds_GenericIO_LLN0_Events2 +}; + +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda3; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda0 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO1", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda1 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO2", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda2 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO3", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Events2_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Events2_fcda3 = { + "GenericIO", + false, + "GGIO1$ST$SPCSO4", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Events2 = { + "GenericIO", + "LLN0$Events2", + 4, + &iedModelds_GenericIO_LLN0_Events2_fcda0, + &iedModelds_GenericIO_LLN0_Measurements +}; + +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda0; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda1; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda2; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda3; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda4; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda5; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda6; +extern DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda7; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda0 = { + "GenericIO", + false, + "GGIO1$MX$AnIn1$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda1 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda1 = { + "GenericIO", + false, + "GGIO1$MX$AnIn1$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda2 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda2 = { + "GenericIO", + false, + "GGIO1$MX$AnIn2$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda3 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda3 = { + "GenericIO", + false, + "GGIO1$MX$AnIn2$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda4 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda4 = { + "GenericIO", + false, + "GGIO1$MX$AnIn3$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda5 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda5 = { + "GenericIO", + false, + "GGIO1$MX$AnIn3$q", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda6 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda6 = { + "GenericIO", + false, + "GGIO1$MX$AnIn4$mag$f", + -1, + NULL, + NULL, + &iedModelds_GenericIO_LLN0_Measurements_fcda7 +}; + +DataSetEntry iedModelds_GenericIO_LLN0_Measurements_fcda7 = { + "GenericIO", + false, + "GGIO1$MX$AnIn4$q", + -1, + NULL, + NULL, + NULL +}; + +DataSet iedModelds_GenericIO_LLN0_Measurements = { + "GenericIO", + "LLN0$Measurements", + 8, + &iedModelds_GenericIO_LLN0_Measurements_fcda0, + NULL +}; + +LogicalDevice iedModel_GenericIO = { + LogicalDeviceModelType, + "GenericIO", + (ModelNode*) &iedModel, + NULL, + (ModelNode*) &iedModel_GenericIO_LLN0 +}; + +LogicalNode iedModel_GenericIO_LLN0 = { + LogicalNodeModelType, + "LLN0", + (ModelNode*) &iedModel_GenericIO, + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, +}; + +DataObject iedModel_GenericIO_LLN0_Mod = { + DataObjectModelType, + "Mod", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + (ModelNode*) &iedModel_GenericIO_LLN0_Mod_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Mod_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_LLN0_Mod, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + (ModelNode*) &iedModel_GenericIO_LLN0_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_GenericIO_LLN0, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_Health_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Health_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + (ModelNode*) &iedModel_GenericIO_LLN0_Health_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_Health_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LLN0_Health, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LLN0_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_GenericIO_LLN0, + NULL, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_vendor, + 0 +}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_swRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_d, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_configRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_configRev = { + DataAttributeModelType, + "configRev", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt_ldNs, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs = { + DataAttributeModelType, + "ldNs", + (ModelNode*) &iedModel_GenericIO_LLN0_NamPlt, + NULL, + NULL, + 0, + IEC61850_FC_EX, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +LogicalNode iedModel_GenericIO_LPHD1 = { + LogicalNodeModelType, + "LPHD1", + (ModelNode*) &iedModel_GenericIO, + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam, +}; + +DataObject iedModel_GenericIO_LPHD1_PhyNam = { + DataObjectModelType, + "PhyNam", + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam_vendor, + 0 +}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyNam, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LPHD1_PhyHealth = { + DataObjectModelType, + "PhyHealth", + (ModelNode*) &iedModel_GenericIO_LPHD1, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LPHD1_PhyHealth, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_LPHD1_Proxy = { + DataObjectModelType, + "Proxy", + (ModelNode*) &iedModel_GenericIO_LPHD1, + NULL, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_LPHD1_Proxy_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_LPHD1_Proxy, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +LogicalNode iedModel_GenericIO_GGIO1 = { + LogicalNodeModelType, + "GGIO1", + (ModelNode*) &iedModel_GenericIO, + NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, +}; + +DataObject iedModel_GenericIO_GGIO1_Mod = { + DataObjectModelType, + "Mod", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_q, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod_ctlModel, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_Mod, + NULL, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Beh = { + DataObjectModelType, + "Beh", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Beh_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Beh, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Health = { + DataObjectModelType, + "Health", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_INT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + (ModelNode*) &iedModel_GenericIO_GGIO1_Health_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Health_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Health, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_NamPlt = { + DataObjectModelType, + "NamPlt", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_vendor, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_vendor = { + DataAttributeModelType, + "vendor", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_swRev, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_swRev = { + DataAttributeModelType, + "swRev", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt_d, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_NamPlt_d = { + DataAttributeModelType, + "d", + (ModelNode*) &iedModel_GenericIO_GGIO1_NamPlt, + NULL, + NULL, + 0, + IEC61850_FC_DC, + IEC61850_VISIBLE_STRING_255, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn1 = { + DataObjectModelType, + "AnIn1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn1, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn2 = { + DataObjectModelType, + "AnIn2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn2, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn3 = { + DataObjectModelType, + "AnIn3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn3, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_AnIn4 = { + DataObjectModelType, + "AnIn4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag = { + DataAttributeModelType, + "mag", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_q, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag_f, + 0, + IEC61850_FC_MX, + IEC61850_CONSTRUCTED, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag_f = { + DataAttributeModelType, + "f", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_mag, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_FLOAT32, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4_t, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_AnIn4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_AnIn4, + NULL, + NULL, + 0, + IEC61850_FC_MX, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO1 = { + DataObjectModelType, + "SPCSO1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO1, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO2 = { + DataObjectModelType, + "SPCSO2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO2, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO3 = { + DataObjectModelType, + "SPCSO3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO3, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_SPCSO4 = { + DataObjectModelType, + "SPCSO4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper = { + DataAttributeModelType, + "Oper", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_ctlModel, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal = { + DataAttributeModelType, + "ctlVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin = { + DataAttributeModelType, + "origin", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat, + 0, + IEC61850_FC_CO, + IEC61850_CONSTRUCTED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat = { + DataAttributeModelType, + "orCat", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent = { + DataAttributeModelType, + "orIdent", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_OCTET_STRING_64, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum = { + DataAttributeModelType, + "ctlNum", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_T, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_INT8U, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T = { + DataAttributeModelType, + "T", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test = { + DataAttributeModelType, + "Test", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_BOOLEAN, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check = { + DataAttributeModelType, + "Check", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_Oper, + NULL, + NULL, + 0, + IEC61850_FC_CO, + IEC61850_CHECK, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel = { + DataAttributeModelType, + "ctlModel", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4_t, + NULL, + 0, + IEC61850_FC_CF, + IEC61850_ENUMERATED, + 0, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_SPCSO4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind1 = { + DataObjectModelType, + "Ind1", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind1_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind1, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind2 = { + DataObjectModelType, + "Ind2", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind2_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind2, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind3 = { + DataObjectModelType, + "Ind3", + (ModelNode*) &iedModel_GenericIO_GGIO1, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind3_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind3, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +DataObject iedModel_GenericIO_GGIO1_Ind4 = { + DataObjectModelType, + "Ind4", + (ModelNode*) &iedModel_GenericIO_GGIO1, + NULL, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_stVal, + 0 +}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal = { + DataAttributeModelType, + "stVal", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_q, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_BOOLEAN, + 0 + TRG_OPT_DATA_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_q = { + DataAttributeModelType, + "q", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4_t, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_QUALITY, + 0 + TRG_OPT_QUALITY_CHANGED, + NULL, + 0}; + +DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { + DataAttributeModelType, + "t", + (ModelNode*) &iedModel_GenericIO_GGIO1_Ind4, + NULL, + NULL, + 0, + IEC61850_FC_ST, + IEC61850_TIMESTAMP, + 0, + NULL, + 0}; + +extern ReportControlBlock iedModel_GenericIO_LLN0_report0; +extern ReportControlBlock iedModel_GenericIO_LLN0_report1; +extern ReportControlBlock iedModel_GenericIO_LLN0_report2; +extern ReportControlBlock iedModel_GenericIO_LLN0_report3; +extern ReportControlBlock iedModel_GenericIO_LLN0_report4; +extern ReportControlBlock iedModel_GenericIO_LLN0_report5; +extern ReportControlBlock iedModel_GenericIO_LLN0_report6; + +ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, "EventsRCB01", "Events1", false, "Events", 4294967295, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report1}; +ReportControlBlock iedModel_GenericIO_LLN0_report1 = {&iedModel_GenericIO_LLN0, "EventsIndexed01", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report2}; +ReportControlBlock iedModel_GenericIO_LLN0_report2 = {&iedModel_GenericIO_LLN0, "EventsIndexed02", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report3}; +ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, "EventsIndexed03", "Events2", false, "Events", 1, 24, 111, 50, 1000, &iedModel_GenericIO_LLN0_report4}; +ReportControlBlock iedModel_GenericIO_LLN0_report4 = {&iedModel_GenericIO_LLN0, "Measurements01", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, &iedModel_GenericIO_LLN0_report5}; +ReportControlBlock iedModel_GenericIO_LLN0_report5 = {&iedModel_GenericIO_LLN0, "Measurements02", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, &iedModel_GenericIO_LLN0_report6}; +ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, "Measurements03", "Measurements", true, "Measurements", 1, 16, 111, 50, 1000, NULL}; + + + + +extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; +extern LogControlBlock iedModel_GenericIO_LLN0_lcb1; +LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, &iedModel_GenericIO_LLN0_lcb1}; +LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", NULL, NULL, 3, 0, true, true, NULL}; + +extern Log iedModel_GenericIO_LLN0_log0; +extern Log iedModel_GenericIO_LLN0_log1; +Log iedModel_GenericIO_LLN0_log0 = {&iedModel_GenericIO_LLN0, "GeneralLog", &iedModel_GenericIO_LLN0_log1}; +Log iedModel_GenericIO_LLN0_log1 = {&iedModel_GenericIO_LLN0, "EventLog", NULL}; + + +IedModel iedModel = { + "simpleIO", + &iedModel_GenericIO, + &iedModelds_GenericIO_LLN0_Events, + &iedModel_GenericIO_LLN0_report0, + NULL, + NULL, + NULL, + &iedModel_GenericIO_LLN0_lcb0, + &iedModel_GenericIO_LLN0_log0, + initializeValues +}; + +static void +initializeValues() +{ + +iedModel_GenericIO_LLN0_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); + +iedModel_GenericIO_LLN0_NamPlt_vendor.mmsValue = MmsValue_newVisibleString("MZ Automation"); + +iedModel_GenericIO_LLN0_NamPlt_swRev.mmsValue = MmsValue_newVisibleString("0.7.3"); + +iedModel_GenericIO_LLN0_NamPlt_d.mmsValue = MmsValue_newVisibleString("libiec61850 server example"); + +iedModel_GenericIO_GGIO1_Mod_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(0); + +iedModel_GenericIO_GGIO1_SPCSO1_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_SPCSO2_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_SPCSO3_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); + +iedModel_GenericIO_GGIO1_SPCSO4_ctlModel.mmsValue = MmsValue_newIntegerFromInt32(1); +} diff --git a/examples/server_example_logging/static_model.h b/examples/server_example_logging/static_model.h new file mode 100644 index 00000000..b5670e9f --- /dev/null +++ b/examples/server_example_logging/static_model.h @@ -0,0 +1,301 @@ +/* + * static_model.h + * + * automatically generated from simpleIO_direct_control.icd + */ + +#ifndef STATIC_MODEL_H_ +#define STATIC_MODEL_H_ + +#include +#include "iec61850_model.h" + +extern IedModel iedModel; +extern LogicalDevice iedModel_GenericIO; +extern LogicalNode iedModel_GenericIO_LLN0; +extern DataObject iedModel_GenericIO_LLN0_Mod; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_q; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_t; +extern DataAttribute iedModel_GenericIO_LLN0_Mod_ctlModel; +extern DataObject iedModel_GenericIO_LLN0_Beh; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_q; +extern DataAttribute iedModel_GenericIO_LLN0_Beh_t; +extern DataObject iedModel_GenericIO_LLN0_Health; +extern DataAttribute iedModel_GenericIO_LLN0_Health_stVal; +extern DataAttribute iedModel_GenericIO_LLN0_Health_q; +extern DataAttribute iedModel_GenericIO_LLN0_Health_t; +extern DataObject iedModel_GenericIO_LLN0_NamPlt; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_vendor; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_swRev; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_d; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_configRev; +extern DataAttribute iedModel_GenericIO_LLN0_NamPlt_ldNs; +extern LogicalNode iedModel_GenericIO_LPHD1; +extern DataObject iedModel_GenericIO_LPHD1_PhyNam; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyNam_vendor; +extern DataObject iedModel_GenericIO_LPHD1_PhyHealth; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_stVal; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_q; +extern DataAttribute iedModel_GenericIO_LPHD1_PhyHealth_t; +extern DataObject iedModel_GenericIO_LPHD1_Proxy; +extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_stVal; +extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_q; +extern DataAttribute iedModel_GenericIO_LPHD1_Proxy_t; +extern LogicalNode iedModel_GenericIO_GGIO1; +extern DataObject iedModel_GenericIO_GGIO1_Mod; +extern DataAttribute iedModel_GenericIO_GGIO1_Mod_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Mod_t; +extern DataAttribute iedModel_GenericIO_GGIO1_Mod_ctlModel; +extern DataObject iedModel_GenericIO_GGIO1_Beh; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Beh_t; +extern DataObject iedModel_GenericIO_GGIO1_Health; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Health_t; +extern DataObject iedModel_GenericIO_GGIO1_NamPlt; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_vendor; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_swRev; +extern DataAttribute iedModel_GenericIO_GGIO1_NamPlt_d; +extern DataObject iedModel_GenericIO_GGIO1_AnIn1; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn1_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn2; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn2_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn3; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn3_t; +extern DataObject iedModel_GenericIO_GGIO1_AnIn4; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_mag_f; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_AnIn4_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO1; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO1_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO2; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO3; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO3_t; +extern DataObject iedModel_GenericIO_GGIO1_SPCSO4; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_T; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_ctlModel; +extern DataAttribute iedModel_GenericIO_GGIO1_SPCSO4_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind1; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind1_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind2; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind2_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind3; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind3_t; +extern DataObject iedModel_GenericIO_GGIO1_Ind4; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_stVal; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_q; +extern DataAttribute iedModel_GenericIO_GGIO1_Ind4_t; + + + +#define IEDMODEL_GenericIO (&iedModel_GenericIO) +#define IEDMODEL_GenericIO_LLN0 (&iedModel_GenericIO_LLN0) +#define IEDMODEL_GenericIO_LLN0_Mod (&iedModel_GenericIO_LLN0_Mod) +#define IEDMODEL_GenericIO_LLN0_Mod_stVal (&iedModel_GenericIO_LLN0_Mod_stVal) +#define IEDMODEL_GenericIO_LLN0_Mod_q (&iedModel_GenericIO_LLN0_Mod_q) +#define IEDMODEL_GenericIO_LLN0_Mod_t (&iedModel_GenericIO_LLN0_Mod_t) +#define IEDMODEL_GenericIO_LLN0_Mod_ctlModel (&iedModel_GenericIO_LLN0_Mod_ctlModel) +#define IEDMODEL_GenericIO_LLN0_Beh (&iedModel_GenericIO_LLN0_Beh) +#define IEDMODEL_GenericIO_LLN0_Beh_stVal (&iedModel_GenericIO_LLN0_Beh_stVal) +#define IEDMODEL_GenericIO_LLN0_Beh_q (&iedModel_GenericIO_LLN0_Beh_q) +#define IEDMODEL_GenericIO_LLN0_Beh_t (&iedModel_GenericIO_LLN0_Beh_t) +#define IEDMODEL_GenericIO_LLN0_Health (&iedModel_GenericIO_LLN0_Health) +#define IEDMODEL_GenericIO_LLN0_Health_stVal (&iedModel_GenericIO_LLN0_Health_stVal) +#define IEDMODEL_GenericIO_LLN0_Health_q (&iedModel_GenericIO_LLN0_Health_q) +#define IEDMODEL_GenericIO_LLN0_Health_t (&iedModel_GenericIO_LLN0_Health_t) +#define IEDMODEL_GenericIO_LLN0_NamPlt (&iedModel_GenericIO_LLN0_NamPlt) +#define IEDMODEL_GenericIO_LLN0_NamPlt_vendor (&iedModel_GenericIO_LLN0_NamPlt_vendor) +#define IEDMODEL_GenericIO_LLN0_NamPlt_swRev (&iedModel_GenericIO_LLN0_NamPlt_swRev) +#define IEDMODEL_GenericIO_LLN0_NamPlt_d (&iedModel_GenericIO_LLN0_NamPlt_d) +#define IEDMODEL_GenericIO_LLN0_NamPlt_configRev (&iedModel_GenericIO_LLN0_NamPlt_configRev) +#define IEDMODEL_GenericIO_LLN0_NamPlt_ldNs (&iedModel_GenericIO_LLN0_NamPlt_ldNs) +#define IEDMODEL_GenericIO_LPHD1 (&iedModel_GenericIO_LPHD1) +#define IEDMODEL_GenericIO_LPHD1_PhyNam (&iedModel_GenericIO_LPHD1_PhyNam) +#define IEDMODEL_GenericIO_LPHD1_PhyNam_vendor (&iedModel_GenericIO_LPHD1_PhyNam_vendor) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth (&iedModel_GenericIO_LPHD1_PhyHealth) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_stVal (&iedModel_GenericIO_LPHD1_PhyHealth_stVal) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_q (&iedModel_GenericIO_LPHD1_PhyHealth_q) +#define IEDMODEL_GenericIO_LPHD1_PhyHealth_t (&iedModel_GenericIO_LPHD1_PhyHealth_t) +#define IEDMODEL_GenericIO_LPHD1_Proxy (&iedModel_GenericIO_LPHD1_Proxy) +#define IEDMODEL_GenericIO_LPHD1_Proxy_stVal (&iedModel_GenericIO_LPHD1_Proxy_stVal) +#define IEDMODEL_GenericIO_LPHD1_Proxy_q (&iedModel_GenericIO_LPHD1_Proxy_q) +#define IEDMODEL_GenericIO_LPHD1_Proxy_t (&iedModel_GenericIO_LPHD1_Proxy_t) +#define IEDMODEL_GenericIO_GGIO1 (&iedModel_GenericIO_GGIO1) +#define IEDMODEL_GenericIO_GGIO1_Mod (&iedModel_GenericIO_GGIO1_Mod) +#define IEDMODEL_GenericIO_GGIO1_Mod_q (&iedModel_GenericIO_GGIO1_Mod_q) +#define IEDMODEL_GenericIO_GGIO1_Mod_t (&iedModel_GenericIO_GGIO1_Mod_t) +#define IEDMODEL_GenericIO_GGIO1_Mod_ctlModel (&iedModel_GenericIO_GGIO1_Mod_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_Beh (&iedModel_GenericIO_GGIO1_Beh) +#define IEDMODEL_GenericIO_GGIO1_Beh_stVal (&iedModel_GenericIO_GGIO1_Beh_stVal) +#define IEDMODEL_GenericIO_GGIO1_Beh_q (&iedModel_GenericIO_GGIO1_Beh_q) +#define IEDMODEL_GenericIO_GGIO1_Beh_t (&iedModel_GenericIO_GGIO1_Beh_t) +#define IEDMODEL_GenericIO_GGIO1_Health (&iedModel_GenericIO_GGIO1_Health) +#define IEDMODEL_GenericIO_GGIO1_Health_stVal (&iedModel_GenericIO_GGIO1_Health_stVal) +#define IEDMODEL_GenericIO_GGIO1_Health_q (&iedModel_GenericIO_GGIO1_Health_q) +#define IEDMODEL_GenericIO_GGIO1_Health_t (&iedModel_GenericIO_GGIO1_Health_t) +#define IEDMODEL_GenericIO_GGIO1_NamPlt (&iedModel_GenericIO_GGIO1_NamPlt) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_vendor (&iedModel_GenericIO_GGIO1_NamPlt_vendor) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_swRev (&iedModel_GenericIO_GGIO1_NamPlt_swRev) +#define IEDMODEL_GenericIO_GGIO1_NamPlt_d (&iedModel_GenericIO_GGIO1_NamPlt_d) +#define IEDMODEL_GenericIO_GGIO1_AnIn1 (&iedModel_GenericIO_GGIO1_AnIn1) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag (&iedModel_GenericIO_GGIO1_AnIn1_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_mag_f (&iedModel_GenericIO_GGIO1_AnIn1_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_q (&iedModel_GenericIO_GGIO1_AnIn1_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn1_t (&iedModel_GenericIO_GGIO1_AnIn1_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn2 (&iedModel_GenericIO_GGIO1_AnIn2) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag (&iedModel_GenericIO_GGIO1_AnIn2_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_mag_f (&iedModel_GenericIO_GGIO1_AnIn2_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_q (&iedModel_GenericIO_GGIO1_AnIn2_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn2_t (&iedModel_GenericIO_GGIO1_AnIn2_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn3 (&iedModel_GenericIO_GGIO1_AnIn3) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag (&iedModel_GenericIO_GGIO1_AnIn3_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_mag_f (&iedModel_GenericIO_GGIO1_AnIn3_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_q (&iedModel_GenericIO_GGIO1_AnIn3_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn3_t (&iedModel_GenericIO_GGIO1_AnIn3_t) +#define IEDMODEL_GenericIO_GGIO1_AnIn4 (&iedModel_GenericIO_GGIO1_AnIn4) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag (&iedModel_GenericIO_GGIO1_AnIn4_mag) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_mag_f (&iedModel_GenericIO_GGIO1_AnIn4_mag_f) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_q (&iedModel_GenericIO_GGIO1_AnIn4_q) +#define IEDMODEL_GenericIO_GGIO1_AnIn4_t (&iedModel_GenericIO_GGIO1_AnIn4_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1 (&iedModel_GenericIO_GGIO1_SPCSO1) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_stVal (&iedModel_GenericIO_GGIO1_SPCSO1_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_q (&iedModel_GenericIO_GGIO1_SPCSO1_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper (&iedModel_GenericIO_GGIO1_SPCSO1_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO1_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO1_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO1_t (&iedModel_GenericIO_GGIO1_SPCSO1_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2 (&iedModel_GenericIO_GGIO1_SPCSO2) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_stVal (&iedModel_GenericIO_GGIO1_SPCSO2_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_q (&iedModel_GenericIO_GGIO1_SPCSO2_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper (&iedModel_GenericIO_GGIO1_SPCSO2_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO2_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO2_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO2_t (&iedModel_GenericIO_GGIO1_SPCSO2_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3 (&iedModel_GenericIO_GGIO1_SPCSO3) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_stVal (&iedModel_GenericIO_GGIO1_SPCSO3_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_q (&iedModel_GenericIO_GGIO1_SPCSO3_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper (&iedModel_GenericIO_GGIO1_SPCSO3_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO3_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO3_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO3_t (&iedModel_GenericIO_GGIO1_SPCSO3_t) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4 (&iedModel_GenericIO_GGIO1_SPCSO4) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_stVal (&iedModel_GenericIO_GGIO1_SPCSO4_stVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_q (&iedModel_GenericIO_GGIO1_SPCSO4_q) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper (&iedModel_GenericIO_GGIO1_SPCSO4_Oper) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlVal (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlVal) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orCat) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_origin_orIdent) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_ctlNum (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_ctlNum) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_T (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_T) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Test (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Test) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_Oper_Check (&iedModel_GenericIO_GGIO1_SPCSO4_Oper_Check) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_ctlModel (&iedModel_GenericIO_GGIO1_SPCSO4_ctlModel) +#define IEDMODEL_GenericIO_GGIO1_SPCSO4_t (&iedModel_GenericIO_GGIO1_SPCSO4_t) +#define IEDMODEL_GenericIO_GGIO1_Ind1 (&iedModel_GenericIO_GGIO1_Ind1) +#define IEDMODEL_GenericIO_GGIO1_Ind1_stVal (&iedModel_GenericIO_GGIO1_Ind1_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind1_q (&iedModel_GenericIO_GGIO1_Ind1_q) +#define IEDMODEL_GenericIO_GGIO1_Ind1_t (&iedModel_GenericIO_GGIO1_Ind1_t) +#define IEDMODEL_GenericIO_GGIO1_Ind2 (&iedModel_GenericIO_GGIO1_Ind2) +#define IEDMODEL_GenericIO_GGIO1_Ind2_stVal (&iedModel_GenericIO_GGIO1_Ind2_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind2_q (&iedModel_GenericIO_GGIO1_Ind2_q) +#define IEDMODEL_GenericIO_GGIO1_Ind2_t (&iedModel_GenericIO_GGIO1_Ind2_t) +#define IEDMODEL_GenericIO_GGIO1_Ind3 (&iedModel_GenericIO_GGIO1_Ind3) +#define IEDMODEL_GenericIO_GGIO1_Ind3_stVal (&iedModel_GenericIO_GGIO1_Ind3_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind3_q (&iedModel_GenericIO_GGIO1_Ind3_q) +#define IEDMODEL_GenericIO_GGIO1_Ind3_t (&iedModel_GenericIO_GGIO1_Ind3_t) +#define IEDMODEL_GenericIO_GGIO1_Ind4 (&iedModel_GenericIO_GGIO1_Ind4) +#define IEDMODEL_GenericIO_GGIO1_Ind4_stVal (&iedModel_GenericIO_GGIO1_Ind4_stVal) +#define IEDMODEL_GenericIO_GGIO1_Ind4_q (&iedModel_GenericIO_GGIO1_Ind4_q) +#define IEDMODEL_GenericIO_GGIO1_Ind4_t (&iedModel_GenericIO_GGIO1_Ind4_t) + +#endif /* STATIC_MODEL_H_ */ + diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 7c9eaa04..e6c46d1c 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -1440,6 +1440,10 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, addVariablesWithFc("GO", logicalNodeName, ld->variables, lnDirectory); break; + case ACSI_CLASS_LCB: + addVariablesWithFc("LG", logicalNodeName, ld->variables, lnDirectory); + break; + case ACSI_CLASS_DATA_SET: { LinkedList dataSet = LinkedList_getNext(ld->dataSets); diff --git a/src/iec61850/common/iec61850_common.c b/src/iec61850/common/iec61850_common.c index f8e0f016..e16eb9a1 100644 --- a/src/iec61850/common/iec61850_common.c +++ b/src/iec61850/common/iec61850_common.c @@ -133,6 +133,8 @@ FunctionalConstraint_toString(FunctionalConstraint fc) { return "RP"; case IEC61850_FC_BR: return "BR"; + case IEC61850_FC_LG: + return "LG"; default: return NULL; } @@ -212,6 +214,12 @@ FunctionalConstraint_fromString(const char* fcString) return IEC61850_FC_NONE; } + if (fcString[0] == 'L') { + if (fcString[1] == 'G') + return IEC61850_FC_LG; + return IEC61850_FC_NONE; + } + return IEC61850_FC_NONE; } diff --git a/src/iec61850/inc/iec61850_common.h b/src/iec61850/inc/iec61850_common.h index 929074a0..5b73f337 100644 --- a/src/iec61850/inc/iec61850_common.h +++ b/src/iec61850/inc/iec61850_common.h @@ -234,6 +234,8 @@ typedef enum eFunctionalConstraint { IEC61850_FC_RP = 15, /** Buffered report */ IEC61850_FC_BR = 16, + /** Log control blocks */ + IEC61850_FC_LG = 17, /** All FCs - wildcard value */ IEC61850_FC_ALL = 99, diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index afa3084b..365f7a07 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -29,6 +29,8 @@ typedef struct { char* name; LogicalNode* parentLN; + bool locked; + LogStorage logStorage; uint64_t newEntryId; @@ -62,7 +64,10 @@ typedef struct { bool enabled; + uint64_t nextIntegrityScan; + int triggerOps; + uint32_t intgPd; } LogControl; @@ -76,12 +81,24 @@ LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage); void LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* value, uint8_t flag); +uint64_t +LogInstance_logEntryStart(LogInstance* self); + +void +LogInstance_logEntryData(LogInstance* self, uint64_t entryID, const char* dataRef, MmsValue* value, uint8_t flag); + +void +LogInstance_logEntryFinished(LogInstance* self, uint64_t entryID); + void LogInstance_destroy(LogInstance* self); LogControl* LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping); +void +LogControl_setLog(LogControl* self, LogInstance* logInstance); + void LogControl_destroy(LogControl* self); @@ -92,7 +109,14 @@ MmsVariableSpecification* Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, int lcbCount); +void +Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs); + MmsValue* LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig); +MmsDataAccessError +LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, + MmsValue* value, MmsServerConnection connection); + #endif /* LIBIEC61850_SRC_IEC61850_INC_PRIVATE_LOGGING_H_ */ diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 5a2a467b..7dd9eec1 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -447,6 +447,19 @@ IedServer_destroy(IedServer self) { MmsServer_destroy(self->mmsServer); IsoServer_destroy(self->isoServer); + +#if (CONFIG_MMS_SINGLE_THREADED == 1) + + /* trigger stopping background task thread */ + self->mmsMapping->reportThreadRunning = false; + + /* waiting for thread to finish */ + while (self->mmsMapping->reportThreadFinished == false) { + Thread_sleep(10); + } + +#endif + MmsMapping_destroy(self->mmsMapping); LinkedList_destroyDeep(self->clientConnections, (LinkedListValueDeleteFunction) private_ClientConnection_destroy); diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 7222373a..6df0be28 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -35,6 +35,7 @@ #include "mms_mapping_internal.h" #include "mms_value_internal.h" +#if (CONFIG_IEC61850_LOG_SERVICE == 1) LogInstance* LogInstance_create(LogicalNode* parentLN, const char* name) @@ -44,6 +45,7 @@ LogInstance_create(LogicalNode* parentLN, const char* name) self->name = copyString(name); self->parentLN = parentLN; self->logStorage = NULL; + self->locked = false; self->oldEntryId = 0; self->oldEntryTime = 0; @@ -67,7 +69,13 @@ LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* valu if (logStorage != NULL) { - printf("Log value - dataRef: %s flag: %i\n", dataRef, flag); + while (self->locked) + Thread_sleep(1); + + self->locked = true; + + //if (DEBUG_IED_SERVER) + printf("IED_SERVER: Log value - dataRef: %s flag: %i\n", dataRef, flag); uint64_t timestamp = Hal_getTimeInMs(); @@ -81,6 +89,8 @@ LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* valu LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); + self->locked = false; + GLOBAL_FREEMEM(data); self->newEntryId = entryID; @@ -88,9 +98,62 @@ LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* valu } else - printf("no log storage available!\n"); + if (DEBUG_IED_SERVER) + printf("IED_SERVER: no log storage available for logging!\n"); } +uint64_t +LogInstance_logEntryStart(LogInstance* self) +{ + LogStorage logStorage = self->logStorage; + + if (logStorage != NULL) { + + while (self->locked) + Thread_sleep(1); + + self->locked = true; + + uint64_t timestamp = Hal_getTimeInMs(); + + uint64_t entryID = LogStorage_addEntry(logStorage, timestamp); + + return entryID; + } + else { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: no log storage available for logging!\n"); + + return 0; + } +} + +void +LogInstance_logEntryData(LogInstance* self, uint64_t entryID, const char* dataRef, MmsValue* value, uint8_t flag) +{ + LogStorage logStorage = self->logStorage; + + if (logStorage != NULL) { + + int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); + + uint8_t* data = GLOBAL_MALLOC(dataSize); + + MmsValue_encodeMmsData(value, data, 0, true); + + LogStorage_addEntryData(logStorage, entryID, dataRef, data, dataSize, flag); + + self->locked = false; + + GLOBAL_FREEMEM(data); + } +} + +void +LogInstance_logEntryFinished(LogInstance* self, uint64_t entryID) +{ + self->locked = false; +} void LogInstance_setLogStorage(LogInstance* self, LogStorage logStorage) @@ -113,6 +176,7 @@ LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) self->mmsMapping = mmsMapping; self->dataSetRef = NULL; self->logInstance = NULL; + self->intgPd = 0; return self; } @@ -138,6 +202,45 @@ LogControl_setLog(LogControl* self, LogInstance* logInstance) self->logInstance = logInstance; } +static void +prepareLogControl(LogControl* logControl) +{ + if (logControl->dataSetRef == NULL) { + printf(" no data set specified!\n"); + return; + } + + DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, logControl->dataSetRef); + + if (dataSet == NULL) { + printf(" data set (%s) not found!\n", logControl->dataSetRef); + return; + } + else + logControl->dataSet = dataSet; +} + +static bool +enableLogging(LogControl* self) +{ + if ((self->dataSet != NULL) && (self->logInstance != NULL)) { + self->enabled = true; + + if ((self->triggerOps & TRG_OPT_INTEGRITY) && (self->intgPd != 0)) + self->nextIntegrityScan = Hal_getTimeInMs(); + else + self->nextIntegrityScan = 0; + + MmsValue* enabled = MmsValue_getSubElement(self->mmsValue, self->mmsType, "LogEna"); + + MmsValue_setBoolean(enabled, true); + + return true; + } + else + return false; +} + static LogControlBlock* getLCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index) { @@ -183,6 +286,59 @@ lookupLogControl(MmsMapping* self, MmsDomain* domain, char* lnName, char* object return NULL; } +static LogInstance* +getLogInstanceByLogRef(MmsMapping* self, const char* logRef) +{ + char refStr[130]; + char* domainName; + char* lnName; + char* logName; + + strncpy(refStr, logRef, 129); + + domainName = refStr; + + lnName = strchr(refStr, '/'); + + if (lnName == NULL) + return NULL; + + if ((lnName - domainName) > 64) + return NULL; + + lnName[0] = 0; + lnName++; + + logName = strchr(lnName, '$'); + + if (logName == NULL) + return NULL; + + logName[0] = 0; + logName++; + + LinkedList instance = LinkedList_getNext(self->logInstances); + + while (instance != NULL) { + + LogInstance* logInstance = LinkedList_getData(instance); + + if (strcmp(logInstance->name, logName) == 0) { + + if (strcmp(lnName, logInstance->parentLN->name) == 0) { + LogicalDevice* ld = (LogicalDevice*) logInstance->parentLN->parent; + + if (strcmp(ld->name, domainName) == 0) + return logInstance; + } + } + + instance = LinkedList_getNext(instance); + } + + return NULL; +} + static void updateLogStatusInLCB(LogControl* self) { @@ -199,13 +355,168 @@ updateLogStatusInLCB(LogControl* self) } +MmsDataAccessError +LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, + MmsValue* value, MmsServerConnection connection) +{ + bool updateValue = false; + + char variableId[130]; + + strncpy(variableId, variableIdOrig, 129); + + char* separator = strchr(variableId, '$'); + + *separator = 0; + + char* lnName = variableId; + + if (lnName == NULL) + return DATA_ACCESS_ERROR_INVALID_ADDRESS; + + char* objectName = MmsMapping_getNextNameElement(separator + 1); + + if (objectName == NULL) + return DATA_ACCESS_ERROR_INVALID_ADDRESS; + + char* varName = MmsMapping_getNextNameElement(objectName); + + if (varName != NULL) + *(varName - 1) = 0; + + LogControl* logControl = lookupLogControl(self, domain, lnName, objectName); + + if (logControl == NULL) { + return DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT; + } + + if (strcmp(varName, "LogEna") == 0) { + bool logEna = MmsValue_getBoolean(value); + + if (logEna == false) { + logControl->enabled = false; + } + else { + + if (enableLogging(logControl)) { + logControl->enabled = true; + + if (DEBUG_IED_SERVER) + printf("IED_SERVER: enabled log control %s\n", logControl->name); + } + else + return DATA_ACCESS_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT; + } + + updateValue = true; + } + else if (strcmp(varName, "LogRef") == 0) { + + if (logControl->enabled == false) { + /* check if logRef is valid or NULL */ + const char* logRef = MmsValue_toString(value); + + if (logRef == NULL) { + + logControl->logInstance = NULL; + + updateValue = true; + } + else { + if (strcmp(logRef, "") == 0) { + logControl->logInstance = NULL; + updateValue = true; + } + else { + + /* remove IED name from logRef */ + char* iedName = self->mmsDevice->deviceName; + + uint32_t iedNameLen = strlen(iedName); + + if (iedNameLen < strlen(logRef)) { + if (memcmp(iedName, logRef, iedNameLen) == 0) { + logRef = logRef + iedNameLen; + } + } + + LogInstance* logInstance = getLogInstanceByLogRef(self, logRef); + + if (logInstance != NULL) { + + logControl->logInstance = logInstance; + updateValue = true; + } + else + return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + + } + } + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + else if (strcmp(varName, "DatSet") == 0) { + + if (logControl->enabled == false) { + /* check if datSet is valid or NULL/empty */ + + const char* dataSetRef = MmsValue_toString(value); + + if (strlen(dataSetRef) == 0) { + logControl->dataSet = NULL; + updateValue = true; + } + else { + DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, dataSetRef); + + if (dataSet == NULL) { + if (DEBUG_IED_SERVER) + printf("IED_SERVER: data set (%s) not found!\n", logControl->dataSetRef); + return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + } + else { + logControl->dataSet = dataSet; + updateValue = true; + } + } + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + else if (strcmp(varName, "IntgPd") == 0) { + if (logControl->enabled == false) { + logControl->intgPd = MmsValue_toUint32(value); + updateValue = true; + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + else if (strcmp(varName, "TrgOps") == 0) { + if (logControl->enabled == false) { + logControl->triggerOps = (MmsValue_getBitStringAsInteger(value) / 2); + updateValue = true; + } + else + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + } + + if (updateValue) { + MmsValue* element = MmsValue_getSubElement(logControl->mmsValue, logControl->mmsType, varName); + + MmsValue_update(element, value); + + return DATA_ACCESS_ERROR_SUCCESS; + } + + return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; +} + MmsValue* LIBIEC61850_LOG_SVC_readAccessControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig) { MmsValue* value = NULL; - printf("READ ACCESS LOG CB\n"); - char variableId[130]; strncpy(variableId, variableIdOrig, 129); @@ -261,10 +572,10 @@ createDataSetReferenceForDefaultDataSet(LogControlBlock* lcb, LogControl* logCon } static MmsValue* -createTrgOps(LogControlBlock* reportControlBlock) { +createTrgOps(LogControlBlock* logControlBlock) { MmsValue* trgOps = MmsValue_newBitString(-6); - uint8_t triggerOps = reportControlBlock->trgOps; + uint8_t triggerOps = logControlBlock->trgOps; if (triggerOps & TRG_OPT_DATA_CHANGED) MmsValue_setBitStringBit(trgOps, 1, true); @@ -278,29 +589,7 @@ createTrgOps(LogControlBlock* reportControlBlock) { return trgOps; } -static bool -enableLogging(LogControl* logControl) -{ - printf("Enable LCB %s...\n", logControl->name); - - if (logControl->dataSetRef == NULL) { - printf(" no data set specified!\n"); - return false; - } - - DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, logControl->dataSetRef); - if (dataSet == NULL) { - printf(" data set (%s) not found!\n", logControl->dataSetRef); - return false; - } - else - logControl->dataSet = dataSet; - - printf(" enabled\n"); - - return true; -} static MmsVariableSpecification* createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, @@ -440,6 +729,7 @@ createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, mmsValue->value.structure.components[8] = MmsValue_newUnsignedFromUint32(logControlBlock->intPeriod); + logControl->intgPd = logControlBlock->intPeriod; logControl->mmsType = lcb; logControl->mmsValue = mmsValue; @@ -448,138 +738,109 @@ createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, logControl->enabled = logControlBlock->logEna; - if (logControl->enabled) { + prepareLogControl(logControl); + + if (logControl->enabled) enableLogging(logControl); - } return lcb; } -static LogInstance* -getLogInstanceByLogRef(MmsMapping* self, const char* logRef) +MmsVariableSpecification* +Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, + int lcbCount) { - char refStr[130]; - char* domainName; - char* lnName; - char* logName; - - strncpy(refStr, logRef, 129); - - printf("getLogInst... %s\n", refStr); - - domainName = refStr; - - lnName = strchr(refStr, '/'); + MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, + sizeof(MmsVariableSpecification)); + namedVariable->name = copyString("LG"); + namedVariable->type = MMS_STRUCTURE; - if (lnName == NULL) - return NULL; + namedVariable->typeSpec.structure.elementCount = lcbCount; + namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(lcbCount, + sizeof(MmsVariableSpecification*)); - if ((lnName - domainName) > 64) - return NULL; + int currentLcb = 0; - lnName[0] = 0; - lnName++; + while (currentLcb < lcbCount) { - logName = strchr(lnName, '$'); + LogControl* logControl = LogControl_create(logicalNode, self); - if (logName == NULL) - return NULL; + LogControlBlock* logControlBlock = getLCBForLogicalNodeWithIndex(self, logicalNode, currentLcb); - logName[0] = 0; - logName++; + logControl->name = createString(3, logicalNode->name, "$LG$", logControlBlock->name); + logControl->domain = domain; - printf("LOG: dn: %s ln: %s name: %s\n", domainName, lnName, logName); + namedVariable->typeSpec.structure.elements[currentLcb] = + createLogControlBlock(self, logControlBlock, logControl); - LinkedList instance = LinkedList_getNext(self->logInstances); + if (logControlBlock->logRef != NULL) + logControl->logInstance = getLogInstanceByLogRef(self, logControlBlock->logRef); - while (instance != NULL) { + LinkedList_add(self->logControls, logControl); - LogInstance* logInstance = LinkedList_getData(instance); + currentLcb++; + } - printf("logInstance: %s\n", logInstance->name); + return namedVariable; +} - if (strcmp(logInstance->name, logName) == 0) { - printf (" lnName: %s\n", logInstance->parentLN->name); +static void +LogControl_logAllDatasetEntries(LogControl* self, const char* iedName) +{ + if (self->dataSet == NULL) + return; - if (strcmp(lnName, logInstance->parentLN->name) == 0) { - LogicalDevice* ld = (LogicalDevice*) logInstance->parentLN->parent; + if (self->logInstance != NULL) { - printf(" ldName: %s\n", ld->name); + char dataRef[130]; - if (strcmp(ld->name, domainName) == 0) - return logInstance; - } - } + LogInstance* log = self->logInstance; - instance = LinkedList_getNext(instance); - } + uint64_t entryID = LogInstance_logEntryStart(log); - return NULL; -} + DataSetEntry* dataSetEntry = self->dataSet->fcdas; -#if 0 -static LogInstance* -getLogInstance(MmsMapping* self, LogicalNode* logicalNode, const char* logName) -{ - if (logName == NULL) - return NULL; + while (dataSetEntry != NULL) { - LinkedList logInstance = LinkedList_getNext(self->logInstances); + sprintf(dataRef, "%s%s/%s", iedName, dataSetEntry->logicalDeviceName, dataSetEntry->variableName); - while (logInstance != NULL) { - LogInstance* instance = (LogInstance*) LinkedList_getData(logInstance); + LogInstance_logEntryData(log, entryID, dataRef, dataSetEntry->value, TRG_OPT_INTEGRITY * 2); - printf("LOG: %s (%s)\n", instance->name, logName); + dataSetEntry = dataSetEntry->sibling; + } - if ((strcmp(instance->name, logName) == 0) && (logicalNode == instance->parentLN)) - return instance; + LogInstance_logEntryFinished(log, entryID); - logInstance = LinkedList_getNext(logInstance); } - - return NULL; } -#endif -MmsVariableSpecification* -Logging_createLCBs(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, - int lcbCount) +void +Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs) { - MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, - sizeof(MmsVariableSpecification)); - namedVariable->name = copyString("LG"); - namedVariable->type = MMS_STRUCTURE; - - namedVariable->typeSpec.structure.elementCount = lcbCount; - namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(lcbCount, - sizeof(MmsVariableSpecification*)); + LinkedList logControlElem = LinkedList_getNext(self->logControls); - int currentLcb = 0; + while (logControlElem != NULL) { - while (currentLcb < lcbCount) { - - LogControl* logControl = LogControl_create(logicalNode, self); + LogControl* logControl = (LogControl*) LinkedList_getData(logControlElem); - LogControlBlock* logControlBlock = getLCBForLogicalNodeWithIndex(self, logicalNode, currentLcb); + if (logControl->enabled) { - logControl->name = createString(3, logicalNode->name, "$LG$", logControlBlock->name); - logControl->domain = domain; + if (logControl->nextIntegrityScan != 0) { - namedVariable->typeSpec.structure.elements[currentLcb] = - createLogControlBlock(self, logControlBlock, logControl); + if (currentTimeInMs >= logControl->nextIntegrityScan) { - //getLogInstanceByLogRef(self, logControlBlock->logRef); - //logControl->logInstance = getLogInstance(self, logicalNode, logControlBlock->logRef); + if (DEBUG_IED_SERVER) + printf("IED_SERVER: INTEGRITY SCAN for log %s\n", logControl->name); - if (logControlBlock->logRef != NULL) - logControl->logInstance = getLogInstanceByLogRef(self, logControlBlock->logRef); + LogControl_logAllDatasetEntries(logControl, self->mmsDevice->deviceName); - LinkedList_add(self->logControls, logControl); + logControl->nextIntegrityScan += logControl->intgPd; + } + } + } - currentLcb++; + logControlElem = LinkedList_getNext(logControlElem); } - - return namedVariable; } void @@ -590,29 +851,23 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto if (logInstance != NULL) { LogInstance_setLogStorage(logInstance, logStorage); -#if 1 char domainName[65]; MmsMapping_getMmsDomainFromObjectReference(logRef, domainName); - char domainName2[65]; + char domainNameWithIEDName[65]; - strcpy(domainName2, self->model->name); - strcat(domainName2, domainName); + strcpy(domainNameWithIEDName, self->model->name); + strcat(domainNameWithIEDName, domainName); - MmsDomain* mmsDomain = MmsDevice_getDomain(self->mmsDevice, domainName2); + MmsDomain* mmsDomain = MmsDevice_getDomain(self->mmsDevice, domainNameWithIEDName); if (mmsDomain == NULL) { - printf("IED_SERVER: MmsMapping_setLogStorage: domain %s not found!\n", domainName2); - } -#if 0 - char journalName[65]; - - strcpy(journalName, self->parentLN->name); - strcat(journalName, "$"); - strcat(journalName, self->name); -#endif + if (DEBUG_IED_SERVER) + printf("IED_SERVER: MmsMapping_setLogStorage: domain %s not found!\n", domainNameWithIEDName); + return; + } MmsJournal mmsJournal = NULL; @@ -626,12 +881,14 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto if (mmsJournal != NULL) mmsJournal->logStorage = logStorage; else - printf("IED_SERVER: Failed to retrieve MMS journal for log!\n"); -#endif - + if (DEBUG_IED_SERVER) + printf("IED_SERVER: Failed to retrieve MMS journal for log!\n"); } - //if (DEBUG_IED_SERVER) + if (DEBUG_IED_SERVER) if (logInstance == NULL) printf("IED_SERVER: MmsMapping_setLogStorage no matching log for %s found!\n", logRef); } + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ + diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 32c159c3..92f497a8 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1799,6 +1799,13 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, #endif /* (CONFIG_IEC61850_SAMPLED_VALUES_SUPPORT == 1) */ +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + + /* Log control block - LG */ + if (isLogControlBlock(separator)) + return LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(self, domain, variableId, value, connection); + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ #if (CONFIG_IEC61850_REPORT_SERVICE == 1) /* Report control blocks - BR, RP */ @@ -2625,51 +2632,6 @@ DataSet_isMemberValueWithRef(DataSet* dataSet, MmsValue* value, char* dataRef, c return false; } -#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ - -#if (CONFIG_IEC61850_REPORT_SERVICE == 1) -void -MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag) -{ - LinkedList element = self->reportControls; - - while ((element = LinkedList_getNext(element)) != NULL) { - ReportControl* rc = (ReportControl*) element->data; - - if (rc->enabled || (rc->buffered && rc->dataSet != NULL)) { - int index; - - switch (flag) { - case REPORT_CONTROL_VALUE_UPDATE: - if ((rc->triggerOps & TRG_OPT_DATA_UPDATE) == 0) - continue; - break; - case REPORT_CONTROL_VALUE_CHANGED: - if (((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) && - ((rc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)) - continue; - break; - case REPORT_CONTROL_QUALITY_CHANGED: - if ((rc->triggerOps & TRG_OPT_QUALITY_CHANGED) == 0) - continue; - break; - default: - continue; - } - - if (DataSet_isMemberValue(rc->dataSet, value, &index)) { - ReportControl_valueUpdated(rc, index, flag); - } - } - } -} - -#endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ - -#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) - -#if (CONFIG_IEC61850_LOG_SERVICE == 1) - void MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag flag) { @@ -2680,12 +2642,16 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl if ((lc->enabled) && (lc->dataSet != NULL)) { + uint8_t reasonCode; + switch (flag) { case LOG_CONTROL_VALUE_UPDATE: if ((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0) continue; + reasonCode = TRG_OPT_DATA_UPDATE * 2; + break; case LOG_CONTROL_VALUE_CHANGED: @@ -2693,12 +2659,16 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl ((lc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)) continue; + reasonCode = TRG_OPT_DATA_CHANGED * 2; + break; case LOG_CONTROL_QUALITY_CHANGED: if ((lc->triggerOps & TRG_OPT_QUALITY_CHANGED) == 0) continue; + reasonCode = TRG_OPT_QUALITY_CHANGED * 2; + break; default: @@ -2710,7 +2680,7 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl if (DataSet_isMemberValueWithRef(lc->dataSet, value, dataRef, self->model->name)) { if (lc->logInstance != NULL) { - LogInstance_logSingleData(lc->logInstance, dataRef, value, flag); + LogInstance_logSingleData(lc->logInstance, dataRef, value, reasonCode); } else printf("No log instance available!\n"); @@ -2723,6 +2693,46 @@ MmsMapping_triggerLogging(MmsMapping* self, MmsValue* value, LogInclusionFlag fl #endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ +#if (CONFIG_IEC61850_REPORT_SERVICE == 1) +void +MmsMapping_triggerReportObservers(MmsMapping* self, MmsValue* value, ReportInclusionFlag flag) +{ + LinkedList element = self->reportControls; + + while ((element = LinkedList_getNext(element)) != NULL) { + ReportControl* rc = (ReportControl*) element->data; + + if (rc->enabled || (rc->buffered && rc->dataSet != NULL)) { + int index; + + switch (flag) { + case REPORT_CONTROL_VALUE_UPDATE: + if ((rc->triggerOps & TRG_OPT_DATA_UPDATE) == 0) + continue; + break; + case REPORT_CONTROL_VALUE_CHANGED: + if (((rc->triggerOps & TRG_OPT_DATA_CHANGED) == 0) && + ((rc->triggerOps & TRG_OPT_DATA_UPDATE) == 0)) + continue; + break; + case REPORT_CONTROL_QUALITY_CHANGED: + if ((rc->triggerOps & TRG_OPT_QUALITY_CHANGED) == 0) + continue; + break; + default: + continue; + } + + if (DataSet_isMemberValue(rc->dataSet, value, &index)) { + ReportControl_valueUpdated(rc, index, flag); + } + } + } +} + +#endif /* (CONFIG_IEC61850_REPORT_SERVICE == 1) */ + +#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) void MmsMapping_triggerGooseObservers(MmsMapping* self, MmsValue* value) @@ -2950,6 +2960,10 @@ processPeriodicTasks(MmsMapping* self) #if (CONFIG_IEC61850_SETTING_GROUPS == 1) MmsMapping_checkForSettingGroupReservationTimeouts(self, currentTimeInMs); #endif + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + Logging_processIntegrityLogs(self, currentTimeInMs); +#endif } void diff --git a/src/logging/drivers/README b/src/logging/drivers/README new file mode 100644 index 00000000..dd1e95dd --- /dev/null +++ b/src/logging/drivers/README @@ -0,0 +1,3 @@ +This directory contains the log driver implementation that implements the logging API. The logging API is a service provider interface that has to be implemented by the log driver. +An application can use different log drivers at the same time. +Each log driver provides a public constructor for the driver specific implementation of the LogStorage class. The constructor has to fill the virtual function table of the LogStorage instance with its own implementation functions. diff --git a/src/logging/drivers/sqlite/log_storage_sqlite.c b/src/logging/drivers/sqlite/log_storage_sqlite.c new file mode 100644 index 00000000..13d76138 --- /dev/null +++ b/src/logging/drivers/sqlite/log_storage_sqlite.c @@ -0,0 +1,455 @@ +/* + * log_storage_sqlite.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + + +#include "logging_api.h" +#include "libiec61850_platform_includes.h" + +#include + +#ifndef DEBUG_LOG_STORAGE_DRIVER +#define DEBUG_LOG_STORAGE_DRIVER 0 +#endif + +static uint64_t +SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp); + +static bool +SqliteLogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); + +static bool +SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + +static bool +SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); + +static bool +SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime); + +static void +SqliteLogStorage_destroy(LogStorage self); + + +typedef struct sSqliteLogStorage { + char* filename; + sqlite3* db; + sqlite3_stmt* insertEntryStmt; + sqlite3_stmt* insertEntryDataStmt; + sqlite3_stmt* getEntriesWithRange; + sqlite3_stmt* getEntriesAfter; + sqlite3_stmt* getEntryData; + sqlite3_stmt* getOldEntry; + sqlite3_stmt* getNewEntry; +} SqliteLogStorage; + +static const char* CREATE_TABLE_ENTRYS = "create table if not exists Entries (entryID integer primary key, timeOfEntry integer)"; +static const char* CREATE_TABLE_ENTRY_DATA = "create table if not exists EntryData (entryID integer, dataRef text, value blob, reasonCode integer)"; +static const char* INSERT_ENTRY = "insert into Entries (timeOfEntry) values (?)"; +static const char* INSERT_ENTRY_DATA = "insert into EntryData (entryID, dataRef, value, reasonCode) values (?,?,?,?)"; +static const char* GET_ENTRIES_WITH_RANGE = "select entryID, timeOfEntry from Entries where timeOfEntry >= ? and timeOfEntry <= ?"; +static const char* GET_ENTRIES_AFTER = "select entryID, timeOfEntry from Entries where entryID > ?"; +static const char* GET_ENTRY_DATA = "select dataRef, value, reasonCode from EntryData where entryID = ?"; + +static const char* GET_OLD_ENTRY = "select * from Entries where entryID = (select min(entryID) from Entries where timeOfEntry = (select min(timeOfEntry) from Entries))"; +static const char* GET_NEW_ENTRY = "select * from Entries where entryID = (select max(entryID) from Entries where timeOfEntry = (select max(timeOfEntry) from Entries))"; + +LogStorage +SqliteLogStorage_createInstance(const char* filename) +{ + + sqlite3* db = NULL; + sqlite3_stmt* insertEntryStmt = NULL; + sqlite3_stmt* insertEntryDataStmt = NULL; + sqlite3_stmt* getEntriesWithRange = NULL; + sqlite3_stmt* getEntriesAfter = NULL; + sqlite3_stmt* getEntryData = NULL; + sqlite3_stmt* getOldEntry = NULL; + sqlite3_stmt* getNewEntry = NULL; + char *zErrMsg = 0; + + int rc = sqlite3_open(filename, &db); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_exec(db, CREATE_TABLE_ENTRYS, NULL, 0, &zErrMsg); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_exec(db, CREATE_TABLE_ENTRY_DATA, NULL, 0, &zErrMsg); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare(db, INSERT_ENTRY, -1, &insertEntryStmt, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare(db, INSERT_ENTRY_DATA, -1, &insertEntryDataStmt, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_ENTRIES_WITH_RANGE, -1, &getEntriesWithRange, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_ENTRIES_AFTER, -1, &getEntriesAfter, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_ENTRY_DATA, -1, &getEntryData, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_OLD_ENTRY, -1, &getOldEntry, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_prepare_v2(db, GET_NEW_ENTRY, -1, &getNewEntry, NULL); + if (rc != SQLITE_OK) + goto exit_with_error; + + LogStorage self = (LogStorage) GLOBAL_CALLOC(1, sizeof(struct sLogStorage)); + + SqliteLogStorage* instanceData = (SqliteLogStorage*) GLOBAL_CALLOC(1, sizeof(struct sSqliteLogStorage)); + instanceData->filename = copyString(filename); + instanceData->db = db; + instanceData->insertEntryStmt = insertEntryStmt; + instanceData->insertEntryDataStmt = insertEntryDataStmt; + instanceData->getEntriesWithRange = getEntriesWithRange; + instanceData->getEntriesAfter = getEntriesAfter; + instanceData->getEntryData = getEntryData; + instanceData->getOldEntry = getOldEntry; + instanceData->getNewEntry = getNewEntry; + + self->instanceData = (void*) instanceData; + + self->addEntry = SqliteLogStorage_addEntry; + self->addEntryData = SqliteLogStorage_addEntryData; + self->getEntries = SqliteLogStorage_getEntries; + self->getEntriesAfter = SqliteLogStorage_getEntriesAfter; + self->getOldestAndNewestEntries = SqliteLogStorage_getOldestAndNewestEntries; + self->destroy = SqliteLogStorage_destroy; + + return self; + +exit_with_error: + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - failed to create LogStorage instance!\n"); + + return NULL; +} + + +static uint64_t +SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + int rc; + char *zErrMsg = 0; + + rc = sqlite3_bind_int64(instanceData->insertEntryStmt, 1, (sqlite_int64) timestamp); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_step(instanceData->insertEntryStmt); + + if (rc != SQLITE_DONE) + goto exit_with_error; + + uint64_t id = sqlite3_last_insert_rowid(db); + + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - new entry with ID = %lu\n", id); + + rc = sqlite3_reset(instanceData->insertEntryStmt); + + if (rc != SQLITE_OK) + goto exit_with_error; + + return id; + +exit_with_error: + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - failed to add entry to log!\n"); + + return 0; +} + +static bool +SqliteLogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + char *zErrMsg = 0; + + rc = sqlite3_bind_int64(instanceData->insertEntryDataStmt, 1, (sqlite_int64) entryID); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_bind_text(instanceData->insertEntryDataStmt, 2, dataRef, -1, SQLITE_STATIC); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_bind_blob(instanceData->insertEntryDataStmt, 3, data, dataSize, SQLITE_STATIC); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_bind_int(instanceData->insertEntryDataStmt, 4, reasonCode); + + if (rc != SQLITE_OK) + goto exit_with_error; + + rc = sqlite3_step(instanceData->insertEntryDataStmt); + + if (rc != SQLITE_DONE) + goto exit_with_error; + + rc = sqlite3_reset(instanceData->insertEntryDataStmt); + + if (rc != SQLITE_OK) + goto exit_with_error; + + return true; + +exit_with_error: + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - failed to add entry data!\n"); + + return false; +} + +static void +getEntryData(LogStorage self, uint64_t entryID, LogEntryDataCallback entryDataCallback, void* parameter) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + int rc; + + rc = sqlite3_bind_int64(instanceData->getEntryData, 1, entryID); + + if (rc != SQLITE_OK) + printf("getEntryData 1 rc:%i\n", rc); + + bool sendFinalEvent = true; + + while ((rc = sqlite3_step(instanceData->getEntryData)) == SQLITE_ROW) { + + const char* dataRef = sqlite3_column_text(instanceData->getEntryData, 0); + const uint8_t* data = sqlite3_column_blob(instanceData->getEntryData, 1); + int dataSize = sqlite3_column_bytes(instanceData->getEntryData, 1); + int reasonCode = sqlite3_column_int(instanceData->getEntryData, 2); + + if (entryDataCallback != NULL) { + if (entryDataCallback(parameter, dataRef, data, dataSize, (uint8_t) reasonCode, true) == false) { + sendFinalEvent = false; + break; + } + + } + } + + if (sendFinalEvent) { + if (entryDataCallback != NULL) + entryDataCallback(parameter, NULL, NULL, 0, (uint8_t) 0, false); + } + + rc = sqlite3_reset(instanceData->getEntryData); + + if (rc != SQLITE_OK) + printf("getEntryData reset rc:%i\n", rc); +} + +static bool +SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 1, startingTime); + + if (rc != SQLITE_OK) + printf("SqliteLogStorage_getEntries 1 rc:%i\n", rc); + + rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 2, endingTime); + + if (rc != SQLITE_OK) + printf("SqliteLogStorage_getEntries 2 rc:%i\n", rc); + + bool sendFinalEvent = true; + + while((rc = sqlite3_step(instanceData->getEntriesWithRange)) == SQLITE_ROW) { + int col; + + uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesWithRange, 0); + uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesWithRange, 1); + + if (entryCallback != NULL) { + if (entryCallback(parameter, timestamp, entryID, true) == false) { + sendFinalEvent = false; + break; + } + + } + + getEntryData(self, entryID, entryDataCallback, parameter); + } + + if (sendFinalEvent) + if (entryCallback != NULL) + entryCallback(parameter, 0, 0, false); + + + rc = sqlite3_reset(instanceData->getEntriesWithRange); + + if (rc != SQLITE_OK) + printf("SqliteLogStorage_getEntries reset rc:%i\n", rc); + + return true; +} + +static bool +SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, + uint64_t* oldEntry, uint64_t* oldEntryTime) +{ + bool validOldEntry = false; + bool validNewEntry = false; + + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + /* Get oldest entry */ + + rc = sqlite3_step(instanceData->getOldEntry); + + if (rc == SQLITE_ROW) { + *oldEntry = sqlite3_column_int64(instanceData->getOldEntry, 0); + *oldEntryTime = sqlite3_column_int64(instanceData->getOldEntry, 1); + validNewEntry = true; + } + + sqlite3_reset(instanceData->getOldEntry); + + /* Get newest entry */ + + rc = sqlite3_step(instanceData->getNewEntry); + + if (rc == SQLITE_ROW) { + *newEntry = sqlite3_column_int64(instanceData->getNewEntry, 0); + *newEntryTime = sqlite3_column_int64(instanceData->getNewEntry, 1); + validOldEntry = true; + } + + sqlite3_reset(instanceData->getNewEntry); + + return (validOldEntry && validNewEntry); +} + +static bool +SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, + LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); + + sqlite3* db = instanceData->db; + + int rc; + + rc = sqlite3_bind_int64(instanceData->getEntriesAfter, 1, entryID); + + if (rc != SQLITE_OK) + printf("SqliteLogStorage_getEntriesAfter 1 rc:%i\n", rc); + + bool sendFinalEvent = true; + + while ((rc = sqlite3_step(instanceData->getEntriesAfter)) == SQLITE_ROW) { + int col; + + uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesAfter, 0); + uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesAfter, 1); + + if (entryCallback != NULL) { + if (entryCallback(parameter, timestamp, entryID, true) == false) { + sendFinalEvent = false; + break; + } + } + + getEntryData(self, entryID, entryDataCallback, parameter); + } + + if (sendFinalEvent) + if (entryCallback != NULL) + entryCallback(parameter, 0, 0, false); + + rc = sqlite3_reset(instanceData->getEntriesAfter); + + if (rc != SQLITE_OK) + printf("SqliteLogStorage_getEntriesAfter reset rc:%i\n", rc); + + return true; +} + +static void +SqliteLogStorage_destroy(LogStorage self) +{ + SqliteLogStorage* instanceData = (SqliteLogStorage*) self->instanceData; + + sqlite3_finalize(instanceData->insertEntryStmt); + sqlite3_finalize(instanceData->insertEntryDataStmt); + sqlite3_finalize(instanceData->getEntriesWithRange); + sqlite3_finalize(instanceData->getEntriesAfter); + sqlite3_finalize(instanceData->getEntryData); + sqlite3_finalize(instanceData->getOldEntry); + sqlite3_finalize(instanceData->getNewEntry); + + if (sqlite3_close(instanceData->db) != SQLITE_OK) + printf("SqliteLogStorage: failed to close database %s!\n", instanceData->filename); + + GLOBAL_FREEMEM(instanceData->filename); + GLOBAL_FREEMEM(instanceData); + GLOBAL_FREEMEM(self); +} + + + + diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h index 078b9bb7..726e25f5 100644 --- a/src/logging/logging_api.h +++ b/src/logging/logging_api.h @@ -37,7 +37,7 @@ typedef struct sLogStorage* LogStorage; */ typedef bool (*LogEntryCallback) (void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow); -typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow); +typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, const uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow); struct sLogStorage { diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index 89a9b09a43118f6b7b68e33ad46650d67c4d7b44..b90d292c8e6608df2fe9fed192053992ba0f513b 100644 GIT binary patch delta 15865 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 NRrs*xT+h9S_vMn8gySoKs21W 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 16) + triggerOps = triggerOps - 16; + lcbString += triggerOps + ", "; if (lcb.getIntgPd() != 0) From 069af684e972285f1b3d81b8bc7cfc4a2f7c5bb0 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 3 Jun 2016 18:38:31 +0200 Subject: [PATCH 18/36] - fixed problem with test case sSgN4 (return temporary-unavailable) when no EditSG is selected - continued logging implementation --- examples/mms_utility/mms_utility.c | 36 +++--------- src/iec61850/server/mms_mapping/logging.c | 4 +- src/iec61850/server/mms_mapping/mms_mapping.c | 2 +- .../drivers/sqlite/log_storage_sqlite.c | 10 ++++ src/mms/inc/mms_client_connection.h | 31 +++++++++++ src/mms/inc/mms_value.h | 8 +-- src/mms/inc_private/mms_client_internal.h | 6 +- .../iso_mms/client/mms_client_connection.c | 55 ++++++++++++++++--- src/mms/iso_mms/common/mms_value.c | 19 ++++--- 9 files changed, 118 insertions(+), 53 deletions(-) diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index eac52aa3..b158f631 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -55,9 +55,9 @@ printJournalEntries(LinkedList journalEntries) MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem); - MmsValue_printToBuffer(journalEntry->entryID, buf, 1024); + MmsValue_printToBuffer(MmsJournalEntry_getEntryID(journalEntry), buf, 1024); printf("EntryID: %s\n", buf); - MmsValue_printToBuffer(journalEntry->occurenceTime, buf, 1024); + MmsValue_printToBuffer(MmsJournalEntry_getOccurenceTime(journalEntry), buf, 1024); printf(" occurence time: %s\n", buf); LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables); @@ -66,8 +66,8 @@ printJournalEntries(LinkedList journalEntries) MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem); - printf(" variable-tag: %s\n", journalVariable->tag); - MmsValue_printToBuffer(journalVariable->value, buf, 1024); + printf(" variable-tag: %s\n", MmsJournalVariable_getTag(journalVariable)); + MmsValue_printToBuffer(MmsJournalVariable_getValue(journalVariable), buf, 1024); printf(" variable-value: %s\n", buf); journalVariableElem = LinkedList_getNext(journalVariableElem); @@ -77,26 +77,6 @@ printJournalEntries(LinkedList journalEntries) } } -static void -MmsJournalVariable_destroy(MmsJournalVariable self) -{ - if (self != NULL) { - GLOBAL_FREEMEM(self->tag); - MmsValue_delete(self->value); - GLOBAL_FREEMEM(self); - } -} - -void -MmsJournalEntry_destroy(MmsJournalEntry self) -{ - if (self != NULL) { - MmsValue_delete(self->entryID); - MmsValue_delete(self->occurenceTime); - LinkedList_destroyDeep(self->journalVariables, MmsJournalVariable_destroy); - GLOBAL_FREEMEM(self); - } -} int main(int argc, char** argv) { @@ -265,7 +245,6 @@ int main(int argc, char** argv) { logName[0] = 0; logName++; - uint64_t timestamp = Hal_getTimeInMs(); MmsValue* startTime = MmsValue_newBinaryTime(false); @@ -292,12 +271,13 @@ int main(int argc, char** argv) { LinkedList lastEntry = LinkedList_getLastElement(journalEntries); MmsJournalEntry lastJournalEntry = (MmsJournalEntry) LinkedList_getData(lastEntry); - MmsValue* nextEntryId = MmsValue_clone(lastJournalEntry->entryID); - MmsValue* nextTimestamp = MmsValue_clone(lastJournalEntry->occurenceTime); + MmsValue* nextEntryId = MmsValue_clone(MmsJournalEntry_getEntryID(lastJournalEntry)); + MmsValue* nextTimestamp = MmsValue_clone(MmsJournalEntry_getOccurenceTime(lastJournalEntry)); printJournalEntries(journalEntries); - LinkedList_destroyDeep(journalEntries, MmsJournalEntry_destroy); + LinkedList_destroyDeep(journalEntries, (LinkedListValueDeleteFunction) + MmsJournalEntry_destroy); if (moreFollows) { char buf[100]; diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 6df0be28..70d61384 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -177,6 +177,7 @@ LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) self->dataSetRef = NULL; self->logInstance = NULL; self->intgPd = 0; + self->nextIntegrityScan = 0; return self; } @@ -345,7 +346,6 @@ updateLogStatusInLCB(LogControl* self) LogInstance* logInstance = self->logInstance; if (logInstance != NULL) { - MmsValue_setBinaryTime(self->oldEntrTm, logInstance->oldEntryTime); MmsValue_setBinaryTime(self->newEntrTm, logInstance->newEntryTime); @@ -829,7 +829,7 @@ Logging_processIntegrityLogs(MmsMapping* self, uint64_t currentTimeInMs) if (currentTimeInMs >= logControl->nextIntegrityScan) { - if (DEBUG_IED_SERVER) + //if (DEBUG_IED_SERVER) printf("IED_SERVER: INTEGRITY SCAN for log %s\n", logControl->name); LogControl_logAllDatasetEntries(logControl, self->mmsDevice->deviceName); diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 92f497a8..a5367335 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -1995,7 +1995,7 @@ mmsWriteHandler(void* parameter, MmsDomain* domain, SettingGroup* sg = getSettingGroupByMmsDomain(self, domain); if (sg->editingClient != (ClientConnection) connection) - return DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; } #endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */ diff --git a/src/logging/drivers/sqlite/log_storage_sqlite.c b/src/logging/drivers/sqlite/log_storage_sqlite.c index 13d76138..867e0f08 100644 --- a/src/logging/drivers/sqlite/log_storage_sqlite.c +++ b/src/logging/drivers/sqlite/log_storage_sqlite.c @@ -166,6 +166,8 @@ exit_with_error: static uint64_t SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp) { + printf("SQLITE-DRIVER: add entry\n"); + SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); sqlite3* db = instanceData->db; @@ -366,6 +368,10 @@ SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, *oldEntryTime = sqlite3_column_int64(instanceData->getOldEntry, 1); validNewEntry = true; } + else { + *oldEntry = 0; + *oldEntryTime = 0; + } sqlite3_reset(instanceData->getOldEntry); @@ -378,6 +384,10 @@ SqliteLogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, *newEntryTime = sqlite3_column_int64(instanceData->getNewEntry, 1); validOldEntry = true; } + else { + *newEntry = 0; + *newEntryTime = 0; + } sqlite3_reset(instanceData->getNewEntry); diff --git a/src/mms/inc/mms_client_connection.h b/src/mms/inc/mms_client_connection.h index d57bbf68..593f1dea 100644 --- a/src/mms/inc/mms_client_connection.h +++ b/src/mms/inc/mms_client_connection.h @@ -735,6 +735,37 @@ struct sMmsJournalVariable { MmsValue* value; }; +/** + * \brief Destroy a single MmsJournalEntry instance. + * + * This function will destroy the whole MmsJournalEntry object including the attached list + * of MmsJournalVariable objects. It is intended to be used in conjunction with the + * LinkedList_destroyDeep function in order to free the result of MmsConnection_readJournalTimeRange + * or MmsConnection_readJournalStartAfter + * + * LinkedList_destroyDeep(journalEntries, (LinkedListValueDeleteFunction) + * MmsJournalEntry_destroy); + * + * \param self the MmsJournalEntry instance to destroy + */ +void +MmsJournalEntry_destroy(MmsJournalEntry self); + +const MmsValue* +MmsJournalEntry_getEntryID(MmsJournalEntry self); + +const MmsValue* +MmsJournalEntry_getOccurenceTime(MmsJournalEntry self); + +const LinkedList /* */ +MmsJournalEntry_getJournalVariables(MmsJournalEntry self); + +const char* +MmsJournalVariable_getTag(MmsJournalVariable self); + +const MmsValue* +MmsJournalVariable_getValue(MmsJournalVariable self); + LinkedList MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, diff --git a/src/mms/inc/mms_value.h b/src/mms/inc/mms_value.h index f9177768..61253fb6 100644 --- a/src/mms/inc/mms_value.h +++ b/src/mms/inc/mms_value.h @@ -101,7 +101,7 @@ MmsValue_getArraySize(const MmsValue* self); * \return the element object */ MmsValue* -MmsValue_getElement(MmsValue* array, int index); +MmsValue_getElement(const MmsValue* array, int index); /** * \brief Create an emtpy array. @@ -911,7 +911,7 @@ MmsValue_isDeletable(MmsValue* self); * \param self the MmsValue instance */ MmsType -MmsValue_getType(MmsValue* self); +MmsValue_getType(const MmsValue* self); /** * \brief Get a sub-element of a MMS_STRUCTURE value specified by a path name. @@ -947,8 +947,8 @@ MmsValue_getTypeString(MmsValue* self); * * \return a pointer to the start of the buffer */ -char* -MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize); +const char* +MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize); /** * \brief create a new MmsValue instance from a BER encoded MMS Data element (deserialize) diff --git a/src/mms/inc_private/mms_client_internal.h b/src/mms/inc_private/mms_client_internal.h index c96cfc3b..fad704a3 100644 --- a/src/mms/inc_private/mms_client_internal.h +++ b/src/mms/inc_private/mms_client_internal.h @@ -260,9 +260,6 @@ int mmsClient_createMmsGetNameListRequestAssociationSpecific(long invokeId, ByteBuffer* writeBuffer, const char* continueAfter); -void -mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId); - void mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, MmsValue* startingTime, MmsValue* endingTime); @@ -271,4 +268,7 @@ void mmsClient_createReadJournalRequestStartAfter(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, MmsValue* timeSpecification, MmsValue* entrySpecification); +bool +mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, LinkedList* result); + #endif /* MMS_MSG_INTERNAL_H_ */ diff --git a/src/mms/iso_mms/client/mms_client_connection.c b/src/mms/iso_mms/client/mms_client_connection.c index 6006c803..b3a99d24 100644 --- a/src/mms/iso_mms/client/mms_client_connection.c +++ b/src/mms/iso_mms/client/mms_client_connection.c @@ -1667,19 +1667,58 @@ readJournal(MmsConnection self, MmsError* mmsError, uint32_t invokeId, ByteBuff return response; } -#if 0 -LinkedList -MmsConnection_readJournal(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId) +static void +MmsJournalVariable_destroy(MmsJournalVariable self) { - ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient); + if (self != NULL) { + GLOBAL_FREEMEM(self->tag); + MmsValue_delete(self->value); + GLOBAL_FREEMEM(self); + } +} - uint32_t invokeId = getNextInvokeId(self); +void +MmsJournalEntry_destroy(MmsJournalEntry self) +{ + if (self != NULL) { + MmsValue_delete(self->entryID); + MmsValue_delete(self->occurenceTime); + LinkedList_destroyDeep(self->journalVariables, + (LinkedListValueDeleteFunction) MmsJournalVariable_destroy); + GLOBAL_FREEMEM(self); + } +} + + +const MmsValue* +MmsJournalEntry_getEntryID(MmsJournalEntry self) +{ + return self->entryID; +} - mmsClient_createReadJournalRequest(invokeId, payload, domainId, itemId); +const MmsValue* +MmsJournalEntry_getOccurenceTime(MmsJournalEntry self) +{ + return self->occurenceTime; +} - return readJournal(self, mmsError, invokeId, payload); +const LinkedList /* */ +MmsJournalEntry_getJournalVariables(MmsJournalEntry self) +{ + return self->journalVariables; +} + +const char* +MmsJournalVariable_getTag(MmsJournalVariable self) +{ + return self->tag; +} + +const MmsValue* +MmsJournalVariable_getValue(MmsJournalVariable self) +{ + return self->value; } -#endif LinkedList MmsConnection_readJournalTimeRange(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId, diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index b096c04b..ce1871bd 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -1600,7 +1600,12 @@ MmsValue_newBinaryTime(bool timeOfDay) void MmsValue_setBinaryTime(MmsValue* self, uint64_t timestamp) { - uint64_t mmsTime = timestamp - (441763200000LL); + uint64_t mmsTime; + + if (timestamp > 441763200000LL) + mmsTime = timestamp - (441763200000LL); + else + timestamp = 0; uint8_t* binaryTimeBuf = self->value.binaryTime.buf; @@ -1877,7 +1882,7 @@ MmsValue_setElement(MmsValue* complexValue, int index, MmsValue* elementValue) } MmsValue* -MmsValue_getElement(MmsValue* complexValue, int index) +MmsValue_getElement(const MmsValue* complexValue, int index) { if ((complexValue->type != MMS_ARRAY) && (complexValue->type != MMS_STRUCTURE)) return NULL; @@ -1918,7 +1923,7 @@ MmsValue_isDeletable(MmsValue* self) } MmsType -MmsValue_getType(MmsValue* self) +MmsValue_getType(const MmsValue* self) { return self->type; } @@ -1970,8 +1975,8 @@ MmsValue_getTypeString(MmsValue* self) } } -char* -MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) +const char* +MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) { switch (MmsValue_getType(self)) { case MMS_STRUCTURE: @@ -1985,7 +1990,7 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) int i; for (i = 0; i < arraySize; i++) { - char* currentStr = MmsValue_printToBuffer(MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos); + const char* currentStr = MmsValue_printToBuffer((const MmsValue*) MmsValue_getElement(self, i), buffer + bufPos, bufferSize - bufPos); bufPos += strlen(currentStr); @@ -2100,7 +2105,7 @@ MmsValue_printToBuffer(MmsValue* self, char* buffer, int bufferSize) case MMS_STRING: case MMS_VISIBLE_STRING: - strncpy(buffer, MmsValue_toString(self), bufferSize); + strncpy(buffer, MmsValue_toString((MmsValue*) self), bufferSize); /* Ensure buffer is always 0 terminated */ if (bufferSize > 0) From 3696f1f6d99b35081e8d8b8260b94fcf54f482ad Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 3 Jun 2016 19:11:24 +0200 Subject: [PATCH 19/36] - added packaging changes and DLL version information to cmake scripts (as suggested by cedric) --- CMakeLists.txt | 12 +++--------- src/CMakeLists.txt | 25 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68600b34..a24cbc3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,7 +119,7 @@ endif(BUILD_EXAMPLES) add_subdirectory(src) -INSTALL(FILES ${API_HEADERS} DESTINATION include/libiec61850) +INSTALL(FILES ${API_HEADERS} DESTINATION include/libiec61850 COMPONENT Development) IF(BUILD_PYTHON_BINDINGS) add_subdirectory(pyiec61850) @@ -128,10 +128,6 @@ ENDIF(BUILD_PYTHON_BINDINGS) IF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") INCLUDE(InstallRequiredSystemLibraries) -SET(CPACK_SET_DESTDIR "on") -SET(CPACK_INSTALL_PREFIX "/usr") -SET(CPACK_GENERATOR "DEB") - SET(CPACK_PACKAGE_DESCRIPTION "IEC 61850 MMS/GOOSE client and server library") SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IEC 61850 MMS/GOOSE client and server library") SET(CPACK_PACKAGE_VENDOR "The libIEC61850 project") @@ -142,10 +138,8 @@ SET(CPACK_PACKAGE_VERSION_PATCH "${LIB_VERSION_PATCH}") SET(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CMAKE_SYSTEM_PROCESSOR}") SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") -SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") -SET(CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) - -SET(CPACK_COMPONENTS_ALL Libraries ApplicationData) +SET(CPACK_COMPONENTS_ALL Libraries Development Applications) +#set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CMAKE_PROJECT_NAME}") INCLUDE(CPack) ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d68865a1..e9c60fbb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -287,6 +287,24 @@ ENDIF(WIN32) include (GenerateExportHeader) +set(RES_FILES "") +if ( WIN32 ) + # Adding RC resource file for adding information to the archive + set(RES_FILES "${CMAKE_CURRENT_BINARY_DIR}/version.rc") + message(STATUS "Generating RC file : ${RES_FILES}") + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in + ${RES_FILES} + @ONLY) + if( MINGW ) + set(CMAKE_RC_COMPILER_INIT windres) + ENABLE_LANGUAGE(RC) + SET(CMAKE_RC_COMPILE_OBJECT + " -O coff -i -o ") + endif(MINGW) + set(library_SRCS ${library_SRCS} ${RES_FILES}) +endif( WIN32 ) + add_library (iec61850-shared SHARED ${library_SRCS} ) set_target_properties(iec61850-shared PROPERTIES @@ -348,10 +366,9 @@ endif() ENDIF(WITH_WPCAP) - install (TARGETS iec61850 iec61850-shared - RUNTIME DESTINATION bin - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + RUNTIME DESTINATION bin COMPONENT Applications + ARCHIVE DESTINATION lib COMPONENT Libraries + LIBRARY DESTINATION lib COMPONENT Libraries ) From d8ace91bcfb35fcf47142c137a0d73c7b8bcdfd6 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 4 Jun 2016 21:38:43 +0200 Subject: [PATCH 20/36] - iso_server.c: fixed bug in multi-threaded version: segfault when conection unexpectedly closed --- config/stack_config.h | 2 +- src/mms/iso_server/iso_server.c | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/stack_config.h b/config/stack_config.h index 51ae3cfb..5cbdecba 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -36,7 +36,7 @@ * 0 ==> server runs in multi-threaded mode (one thread for each connection and * one server background thread ) */ -#define CONFIG_MMS_SINGLE_THREADED 1 +#define CONFIG_MMS_SINGLE_THREADED 0 /* * Optimize stack for threadless operation - don't use semaphores diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index 577a95ee..d67f31c0 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -561,8 +561,11 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) if (self->openClientConnections[i] != NULL) { if (IsoConnection_isRunning(self->openClientConnections[i])) { IsoConnection_addHandleSet(self->openClientConnections[i], handles); - } else { + } + else { +#if ((CONFIG_MMS_SINGLE_THREADED == 1) || (CONFIG_MMS_THREADLESS_STACK == 1)) IsoConnection_destroy(self->openClientConnections[i]); +#endif self->openClientConnections[i] = NULL; } } From 23e695dae89a471d49cc82981621ce6263ef6283 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 5 Jun 2016 12:40:58 +0200 Subject: [PATCH 21/36] - removed asn1c dependable code from ServiceError PDU creation - added ServiceError creation function that support serviceSpecific info - server: delete dataset service now returns ServiceError with object-constraint-conflict when data set cannot be deleted because it is used in a control block --- src/iec61850/server/mms_mapping/mms_mapping.c | 10 +- src/mms/inc/mms_common.h | 1 + src/mms/inc_private/mms_server_internal.h | 6 +- src/mms/iso_mms/server/mms_file_service.c | 14 +- .../iso_mms/server/mms_get_namelist_service.c | 16 +- .../server/mms_get_var_access_service.c | 6 +- .../server/mms_named_variable_list_service.c | 88 +++++--- src/mms/iso_mms/server/mms_read_service.c | 14 +- src/mms/iso_mms/server/mms_server_common.c | 208 +++++++++++------- 9 files changed, 221 insertions(+), 142 deletions(-) diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index a5367335..bed90c8b 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2483,10 +2483,10 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li else { /* Check if data set is referenced in a report */ - LinkedList element = self->reportControls; + LinkedList rcElement = self->reportControls; - while ((element = LinkedList_getNext(element)) != NULL) { - ReportControl* rc = (ReportControl*) element->data; + while ((rcElement = LinkedList_getNext(rcElement)) != NULL) { + ReportControl* rc = (ReportControl*) rcElement->data; if (rc->isDynamicDataSet) { if (rc->dataSet != NULL) { @@ -2495,7 +2495,7 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li if (rc->dataSet->logicalDeviceName != NULL) { if (strcmp(rc->dataSet->name, listName) == 0) { if (strcmp(rc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) { - allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; break; } } @@ -2504,7 +2504,7 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li else if (listType == MMS_ASSOCIATION_SPECIFIC) { if (rc->dataSet->logicalDeviceName == NULL) { if (strcmp(rc->dataSet->name, listName) == 0) { - allow = MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED; + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; break; } } diff --git a/src/mms/inc/mms_common.h b/src/mms/inc/mms_common.h index b8bf9444..c1a8169e 100644 --- a/src/mms/inc/mms_common.h +++ b/src/mms/inc/mms_common.h @@ -66,6 +66,7 @@ typedef enum MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE = 41, MMS_ERROR_SERVICE_OTHER = 50, + MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT = 55, MMS_ERROR_SERVICE_PREEMPT_OTHER = 60, diff --git a/src/mms/inc_private/mms_server_internal.h b/src/mms/inc_private/mms_server_internal.h index 2ec666de..0ddbbbd9 100644 --- a/src/mms/inc_private/mms_server_internal.h +++ b/src/mms/inc_private/mms_server_internal.h @@ -168,7 +168,11 @@ MmsPdu_t* mmsServer_createConfirmedResponse(uint32_t invokeId); void -mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType); +mmsServer_createServiceErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType); + +void +mmsServer_createServiceErrorPduWithServiceSpecificInfo(uint32_t invokeId, ByteBuffer* response, + MmsError errorType, uint8_t* serviceSpecificInfo, int serviceSpecficInfoLength); void mmsServer_writeConcludeResponsePdu(ByteBuffer* response); diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index 582fb7cf..2e7a2820 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -220,7 +220,7 @@ mmsServer_handleFileDeleteRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: File (%s) not found\n", filename); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); return; } @@ -228,7 +228,7 @@ mmsServer_handleFileDeleteRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: Delete file (%s) failed\n", filename); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_ACCESS_DENIED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_ACCESS_DENIED); return; } @@ -294,12 +294,12 @@ mmsServer_handleFileOpenRequest( createFileOpenResponse(invokeId, response, filename, frsm); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_OTHER); } else goto exit_invalid_parameter; @@ -388,7 +388,7 @@ mmsServer_handleFileReadRequest( if (frsm != NULL) createFileReadResponse(connection, invokeId, response, frsm); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); } static void @@ -547,7 +547,7 @@ createFileDirectoryResponse(uint32_t invokeId, ByteBuffer* response, int maxPduS if (DEBUG_MMS_SERVER) printf("MMS_SERVER: Error opening directory!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_FILE_NON_EXISTENT); return; } @@ -651,7 +651,7 @@ mmsServer_handleFileRenameRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: rename file failed!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_FILE_OTHER); } } else diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index 7ce55afe..e90c4f1c 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -345,7 +345,7 @@ createNameListResponse( } if (startElement == NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); return; } } @@ -526,7 +526,7 @@ mmsServer_handleGetNameListRequest( LinkedList nameList = getNameListDomainSpecific(connection, domainSpecificName); if (nameList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { createNameListResponse(connection, invokeId, nameList, response, continueAfterId); LinkedList_destroy(nameList); @@ -536,7 +536,7 @@ mmsServer_handleGetNameListRequest( LinkedList nameList = getJournalListDomainSpecific(connection, domainSpecificName); if (nameList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { createNameListResponse(connection, invokeId, nameList, response, continueAfterId); LinkedList_destroyStatic(nameList); @@ -547,7 +547,7 @@ mmsServer_handleGetNameListRequest( LinkedList nameList = getNamedVariableListsDomainSpecific(connection, domainSpecificName); if (nameList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { #if (CONFIG_MMS_SORT_NAME_LIST == 1) @@ -563,7 +563,7 @@ mmsServer_handleGetNameListRequest( else { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList domain specific objectClass %i not supported!\n", objectClass); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } } @@ -621,7 +621,7 @@ mmsServer_handleGetNameListRequest( else { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList VMD specific objectClass %i not supported!\n", objectClass); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } } @@ -641,14 +641,14 @@ mmsServer_handleGetNameListRequest( LinkedList_destroy(nameList); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ #endif /* (MMS_DATA_SET_SERVICE == 1) */ else { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: getNameList(%i) not supported!\n", objectScope); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } } diff --git a/src/mms/iso_mms/server/mms_get_var_access_service.c b/src/mms/iso_mms/server/mms_get_var_access_service.c index d47ecb0e..b1c813ea 100644 --- a/src/mms/iso_mms/server/mms_get_var_access_service.c +++ b/src/mms/iso_mms/server/mms_get_var_access_service.c @@ -221,7 +221,7 @@ createVariableAccessAttributesResponse( if (domain == NULL) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: domain %s not known\n", domainId); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_function; } @@ -237,7 +237,7 @@ createVariableAccessAttributesResponse( if (namedVariable == NULL) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: named variable %s not known\n", nameId); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_function; @@ -266,7 +266,7 @@ createVariableAccessAttributesResponse( if (DEBUG_MMS_SERVER) printf("MMS getVariableAccessAttributes: message to large! send error PDU!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_SERVICE_OTHER); goto exit_function; diff --git a/src/mms/iso_mms/server/mms_named_variable_list_service.c b/src/mms/iso_mms/server/mms_named_variable_list_service.c index 71ce3a5e..1c730454 100644 --- a/src/mms/iso_mms/server/mms_named_variable_list_service.c +++ b/src/mms/iso_mms/server/mms_named_variable_list_service.c @@ -99,6 +99,18 @@ createDeleteNamedVariableListResponse(uint32_t invokeId, ByteBuffer* response, response->size = bufPos; } +static void /* Confirmed service error (ServiceError) */ +createServiceErrorDeleteVariableLists(uint32_t invokeId, ByteBuffer* response, + MmsError errorType, uint32_t numberDeleted) +{ + uint8_t buffer[8]; + + int size = BerEncoder_encodeUInt32WithTL(0x86, numberDeleted, buffer, 0); + + mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, + buffer, size); +} + void mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, uint8_t* buffer, int bufPos, int maxBufPos, @@ -125,6 +137,8 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, MmsDevice* device = MmsServer_getDevice(connection->server); if (scopeOfDelete == DeleteNamedVariableListRequest__scopeOfDelete_specific) { + MmsError serviceError = MMS_ERROR_NONE; + int numberMatched = 0; int numberDeleted = 0; @@ -154,10 +168,14 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, if (MmsNamedVariableList_isDeletable(variableList)) { - if (mmsServer_callVariableListChangedHandler(false, MMS_DOMAIN_SPECIFIC, domain, listName, connection) == MMS_ERROR_NONE) { + MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_DOMAIN_SPECIFIC, domain, listName, connection); + + if (deleteError == MMS_ERROR_NONE) { MmsDomain_deleteNamedVariableList(domain, listName); numberDeleted++; } + else + serviceError = deleteError; } } } @@ -173,10 +191,14 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, if (variableList != NULL) { numberMatched++; - if (mmsServer_callVariableListChangedHandler(false, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection) == MMS_ERROR_NONE) { + MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_ASSOCIATION_SPECIFIC, NULL, listName, connection); + + if (deleteError == MMS_ERROR_NONE) { numberDeleted++; MmsServerConnection_deleteNamedVariableList(connection, listName); } + else + serviceError = deleteError; } } else if (request->listOfVariableListName->list.array[i]->present == ObjectName_PR_vmdspecific) { @@ -190,19 +212,25 @@ mmsServer_handleDeleteNamedVariableListRequest(MmsServerConnection connection, if (variableList != NULL) { numberMatched++; - if (mmsServer_callVariableListChangedHandler(false, MMS_VMD_SPECIFIC, NULL, listName, connection) - == MMS_ERROR_NONE) { + MmsError deleteError = mmsServer_callVariableListChangedHandler(false, MMS_VMD_SPECIFIC, NULL, listName, connection); + + if (deleteError == MMS_ERROR_NONE) { numberDeleted++; mmsServer_deleteVariableList(device->namedVariableLists, listName); } + else + serviceError = deleteError; } } } - createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted); + if (serviceError == MMS_ERROR_NONE) + createDeleteNamedVariableListResponse(invokeId, response, numberMatched, numberDeleted); + else + createServiceErrorDeleteVariableLists(invokeId, response, serviceError, numberDeleted); } else { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); @@ -404,7 +432,7 @@ mmsServer_handleDefineNamedVariableListRequest( char domainName[65]; if (request->variableListName.choice.domainspecific.domainId.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -415,7 +443,7 @@ mmsServer_handleDefineNamedVariableListRequest( MmsDomain* domain = MmsDevice_getDomain(device, domainName); if (domain == NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -423,7 +451,7 @@ mmsServer_handleDefineNamedVariableListRequest( char variableListName[65]; if (request->variableListName.choice.domainspecific.itemId.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -432,7 +460,7 @@ mmsServer_handleDefineNamedVariableListRequest( request->variableListName.choice.domainspecific.itemId.size); if (MmsDomain_getNamedVariableList(domain, variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); } else { MmsError mmsError; @@ -450,15 +478,15 @@ mmsServer_handleDefineNamedVariableListRequest( } else { MmsNamedVariableList_destroy(namedVariableList); - mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); + mmsServer_createServiceErrorPdu(invokeId, response, mmsError); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); + mmsServer_createServiceErrorPdu(invokeId, response, mmsError); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); } @@ -470,7 +498,7 @@ mmsServer_handleDefineNamedVariableListRequest( if (request->variableListName.choice.aaspecific.size > 64) { //TODO send reject PDU instead? - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -479,7 +507,7 @@ mmsServer_handleDefineNamedVariableListRequest( request->variableListName.choice.aaspecific.size); if (MmsServerConnection_getNamedVariableList(connection, variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); } else { MmsError mmsError; @@ -495,16 +523,16 @@ mmsServer_handleDefineNamedVariableListRequest( } else { MmsNamedVariableList_destroy(namedVariableList); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, mmsError); + mmsServer_createServiceErrorPdu(invokeId, response, mmsError); } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE); } else if (request->variableListName.present == ObjectName_PR_vmdspecific) { LinkedList vmdScopeNVLs = MmsDevice_getNamedVariableLists(connection->server->device); @@ -515,7 +543,7 @@ mmsServer_handleDefineNamedVariableListRequest( if (request->variableListName.choice.vmdspecific.size > 64) { //TODO send reject PDU instead? - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); goto exit_free_struct; } @@ -524,7 +552,7 @@ mmsServer_handleDefineNamedVariableListRequest( request->variableListName.choice.vmdspecific.size); if (mmsServer_getNamedVariableListWithName(MmsDevice_getNamedVariableLists(connection->server->device), variableListName) != NULL) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_OBJECT_EXISTS); } else { MmsError mmsError; @@ -541,7 +569,7 @@ mmsServer_handleDefineNamedVariableListRequest( } else { MmsNamedVariableList_destroy(namedVariableList); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED); } } @@ -549,7 +577,7 @@ mmsServer_handleDefineNamedVariableListRequest( } } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED); exit_free_struct: asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); @@ -648,7 +676,7 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if ((request->choice.domainspecific.domainId.size > 64) || (request->choice.domainspecific.itemId.size > 64)) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); goto exit_function; } @@ -669,10 +697,10 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if (variableList != NULL) createGetNamedVariableListAttributesResponse(invokeId, response, variableList); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } #if (MMS_DYNAMIC_DATA_SETS == 1) @@ -681,7 +709,7 @@ mmsServer_handleGetNamedVariableListAttributesRequest( char listName[65]; if (request->choice.aaspecific.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); goto exit_function; } @@ -693,14 +721,14 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if (varList != NULL) createGetNamedVariableListAttributesResponse(invokeId, response, varList); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ else if (request->present == ObjectName_PR_vmdspecific) { char listName[65]; if (request->choice.vmdspecific.size > 64) { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OTHER); goto exit_function; } @@ -714,10 +742,10 @@ mmsServer_handleGetNamedVariableListAttributesRequest( if (varList != NULL) createGetNamedVariableListAttributesResponse(invokeId, response, varList); else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } else { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } exit_function: diff --git a/src/mms/iso_mms/server/mms_read_service.c b/src/mms/iso_mms/server/mms_read_service.c index cfe2af9c..3ad46abb 100644 --- a/src/mms/iso_mms/server/mms_read_service.c +++ b/src/mms/iso_mms/server/mms_read_service.c @@ -431,7 +431,7 @@ encodeReadResponse(MmsServerConnection connection, if (DEBUG_MMS_SERVER) printf("MMS read: message to large! send error PDU!\n"); - mmsServer_createConfirmedErrorPdu(invokeId, response, + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_SERVICE_OTHER); goto exit_function; @@ -647,7 +647,7 @@ handleReadNamedVariableListRequest( if (domain == NULL) { if (DEBUG_MMS_SERVER) printf("MMS read: domain %s not found!\n", domainIdStr); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } else { MmsNamedVariableList namedList = MmsDomain_getNamedVariableList(domain, nameIdStr); @@ -658,7 +658,7 @@ handleReadNamedVariableListRequest( } else { if (DEBUG_MMS_SERVER) printf("MMS read: named variable list %s not found!\n", nameIdStr); - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); } } } @@ -672,7 +672,7 @@ handleReadNamedVariableListRequest( MmsNamedVariableList namedList = mmsServer_getNamedVariableListWithName(connection->server->device->namedVariableLists, listName); if (namedList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { VarAccessSpec accessSpec; @@ -697,7 +697,7 @@ handleReadNamedVariableListRequest( MmsNamedVariableList namedList = MmsServerConnection_getNamedVariableList(connection, listName); if (namedList == NULL) - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { VarAccessSpec accessSpec; @@ -711,7 +711,7 @@ handleReadNamedVariableListRequest( } #endif /* (MMS_DYNAMIC_DATA_SETS == 1) */ else - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } #endif /* MMS_DATA_SET_SERVICE == 1 */ @@ -745,7 +745,7 @@ mmsServer_handleReadRequest( } #endif else { - mmsServer_createConfirmedErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); + mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED); } asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); diff --git a/src/mms/iso_mms/server/mms_server_common.c b/src/mms/iso_mms/server/mms_server_common.c index 46d87bed..ba277499 100644 --- a/src/mms/iso_mms/server/mms_server_common.c +++ b/src/mms/iso_mms/server/mms_server_common.c @@ -1,7 +1,7 @@ /* * mms_server_common.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -45,96 +45,142 @@ mmsServer_createConfirmedResponse(uint32_t invokeId) return mmsPdu; } +static void +mapErrorTypeToErrorClass(MmsError errorType, uint8_t* tag, uint8_t* value) +{ + switch (errorType) { + + case MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED: + *tag = 0x87; /* access */ + *value = 1; + break; + + case MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT: + *tag = 0x87; /* access */ + *value = 2; + break; + + case MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED: + *tag = 0x87; /* access */ + *value = 3; + break; + + case MMS_ERROR_SERVICE_OTHER: + *tag = 0x84; /* service */ + *value = 0; + break; + + case MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT: + *tag = 0x84; /* service */ + *value = 5; + break; + + case MMS_ERROR_DEFINITION_OTHER: + *tag = 0x82; /* definition */ + *value = 0; + break; + + case MMS_ERROR_DEFINITION_OBJECT_UNDEFINED: + *tag = 0x82; /* definition */ + *value = 1; + break; + + case MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED: + *tag = 0x82; /* definition */ + *value = 3; + break; + + case MMS_ERROR_DEFINITION_OBJECT_EXISTS: + *tag = 0x82; /* definition */ + *value = 5; + break; + + case MMS_ERROR_FILE_OTHER: + *tag = 0x8b; /* file */ + *value = 0; + break; + + case MMS_ERROR_FILE_FILE_NON_EXISTENT: + *tag = 0x8b; /* file */ + *value = 7; + break; + + case MMS_ERROR_RESOURCE_OTHER: + *tag = 0x83; /* resource */ + *value = 0; + break; + + case MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE: + *tag = 0x83; /* resource */ + *value = 4; + break; + + default: + + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: unknown errorType!\n"); + + *tag = 0x8c; /* others */ + *value = 0; + break; + + } + +} void -mmsServer_createConfirmedErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType) +mmsServer_createServiceErrorPduWithServiceSpecificInfo(uint32_t invokeId, ByteBuffer* response, + MmsError errorType, uint8_t* serviceSpecificInfo, int serviceSpecficInfoLength) { - MmsPdu_t* mmsPdu = (MmsPdu_t*) GLOBAL_CALLOC(1, sizeof(MmsPdu_t)); - mmsPdu->present = MmsPdu_PR_confirmedErrorPDU; + /* determine encoded size */ - asn_long2INTEGER(&(mmsPdu->choice.confirmedErrorPDU.invokeID), - invokeId); + uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId) + 2; - if (errorType == MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_access; + uint32_t specificInfoSize = 0; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__access_objectnonexistent); - } - else if (errorType == MMS_ERROR_ACCESS_OBJECT_ACCESS_DENIED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_access; + if (serviceSpecificInfo != NULL) + specificInfoSize = 1 + BerEncoder_determineLengthSize(serviceSpecficInfoLength) + + serviceSpecficInfoLength; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__access_objectaccessdenied); - } - else if (errorType == MMS_ERROR_ACCESS_OBJECT_ACCESS_UNSUPPORTED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_access; + uint32_t serviceErrorContentSize = 5 /* errorClass */ + specificInfoSize; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__access_objectaccessunsupported); - } - else if (errorType == MMS_ERROR_SERVICE_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_service; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__service_other); - } - else if (errorType == MMS_ERROR_DEFINITION_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_other); - } - else if (errorType == MMS_ERROR_DEFINITION_OBJECT_EXISTS) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_objectexists); - } - else if (errorType == MMS_ERROR_DEFINITION_OBJECT_UNDEFINED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_objectundefined); - } - else if (errorType == MMS_ERROR_DEFINITION_TYPE_UNSUPPORTED) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_definition; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__definition_typeunsupported); - } - else if (errorType == MMS_ERROR_FILE_FILE_NON_EXISTENT) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_file; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__file_filenonexistent); - } - else if (errorType == MMS_ERROR_FILE_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_file; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__file_other); - } - else if (errorType == MMS_ERROR_RESOURCE_OTHER) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_resource; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__resource_other); - } - else if (errorType == MMS_ERROR_RESOURCE_CAPABILITY_UNAVAILABLE) { - mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.present = - ServiceError__errorClass_PR_resource; - asn_long2INTEGER(&mmsPdu->choice.confirmedErrorPDU.serviceError.errorClass.choice.access, - ServiceError__errorClass__resource_capabilityunavailable); - } + uint32_t serviceErrorSize = 1 + BerEncoder_determineLengthSize(serviceErrorContentSize) + + serviceErrorContentSize; + + uint32_t confirmedErrorContentSize = serviceErrorSize + invokeIdSize; + + /* encode */ + uint8_t* buffer = response->buffer; + int bufPos = response->size; + + bufPos = BerEncoder_encodeTL(0xa2, confirmedErrorContentSize, buffer, bufPos); + + bufPos = BerEncoder_encodeTL(0x80, invokeIdSize - 2, buffer, bufPos); /* invokeID */ + bufPos = BerEncoder_encodeUInt32((uint32_t) invokeId, buffer, bufPos); - der_encode(&asn_DEF_MmsPdu, mmsPdu, - mmsServer_write_out, (void*) response); + bufPos = BerEncoder_encodeTL(0xa2, serviceErrorContentSize, buffer, bufPos); /* serviceError */ + bufPos = BerEncoder_encodeTL(0xa0, 3, buffer, bufPos); /* serviceError */ - asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0); + uint8_t errorCodeTag; + uint8_t errorCodeValue; + + mapErrorTypeToErrorClass(errorType, &errorCodeTag, &errorCodeValue); + + buffer[bufPos++] = errorCodeTag; + buffer[bufPos++] = 1; + buffer[bufPos++] = errorCodeValue; + + if (serviceSpecificInfo != NULL) + bufPos = BerEncoder_encodeOctetString(0xa3, serviceSpecificInfo, serviceSpecficInfoLength, + buffer, bufPos); + + response->size = bufPos; +} + +void /* Confirmed service error (ServiceError) */ +mmsServer_createServiceErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError errorType) +{ + mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, NULL, 0); } int From 2b28c0fed35c98df01b635d31da843be2045027a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 Jun 2016 00:00:02 +0200 Subject: [PATCH 22/36] - added cmake support to build sqlite log driver - made logging ready to be compiled with Visual Studio - added functions to create LCBs and LOGs to dynamic model API - client: added GetLogicalNodeDirectory(LOG) ACSI function --- CMakeLists.txt | 8 +- config/stack_config.h | 8 +- config/stack_config.h.cmake | 3 + examples/CMakeLists.txt | 1 + .../goose_subscriber_example.c | 4 +- .../iec61850_9_2_LE_example/static_model.c | 4 + examples/mms_utility/mms_utility.c | 15 --- examples/server_example1/static_model.c | 4 + examples/server_example2/static_model.c | 4 + examples/server_example3/static_model.c | 4 +- examples/server_example4/static_model.c | 4 + examples/server_example5/static_model.c | 4 + .../server_example_61400_25/static_model.c | 4 + .../static_model.c | 4 + .../server_example_control/static_model.c | 4 + examples/server_example_goose/static_model.c | 4 + .../Makefile.sqliteStatic | 34 ++++++ examples/server_example_logging/README | 8 ++ .../server_example_logging.c | 4 +- .../static_model.c | 4 + .../server_example_threadless/static_model.c | 6 + src/CMakeLists.txt | 2 + .../inc/libiec61850_platform_includes.h | 2 +- src/iec61850/client/client_report.c | 4 +- src/iec61850/client/ied_connection.c | 66 +++++++++- src/iec61850/inc/iec61850_dynamic_model.h | 34 +++++- src/iec61850/inc/iec61850_model.h | 3 - src/iec61850/inc_private/ied_server_private.h | 1 + src/iec61850/inc_private/logging.h | 1 + src/iec61850/server/impl/ied_server.c | 48 ++++++-- src/iec61850/server/mms_mapping/logging.c | 83 ++++++++++--- src/iec61850/server/mms_mapping/mms_mapping.c | 44 ++++++- src/iec61850/server/model/dynamic_model.c | 115 ++++++++++++++++++ src/iec61850/server/model/model.c | 14 --- .../drivers/sqlite/log_storage_sqlite.c | 9 +- src/logging/logging_api.h | 10 +- src/mms/iso_mms/server/mms_domain.c | 4 - .../iso_mms/server/mms_get_namelist_service.c | 4 + src/mms/iso_mms/server/mms_journal_service.c | 7 +- src/version.rc.in | 20 +++ src/vs/libiec61850-wo-goose.def | 18 ++- src/vs/libiec61850.def | 17 +++ third_party/cmake/modules/Findsqlite.cmake | 48 ++++++++ 43 files changed, 596 insertions(+), 93 deletions(-) create mode 100644 examples/server_example_logging/Makefile.sqliteStatic create mode 100644 examples/server_example_logging/README create mode 100644 src/version.rc.in create mode 100644 third_party/cmake/modules/Findsqlite.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a24cbc3c..30066a76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ set(LIB_VERSION_MAJOR "0") set(LIB_VERSION_MINOR "9") set(LIB_VERSION_PATCH "2") +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/") + # feature checks include(CheckLibraryExists) check_library_exists(rt clock_gettime "time.h" CONFIG_SYSTEM_HAS_CLOCK_GETTIME) @@ -28,7 +30,7 @@ set(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS 5 CACHE STRING "Configure the maximum option(BUILD_EXAMPLES "Build the examples" ON) option(BUILD_PYTHON_BINDINGS "Build Python bindings" OFF) -option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" OFF) +option(CONFIG_MMS_SINGLE_THREADED "Compile for single threaded version" ON) option(CONFIG_MMS_THREADLESS_STACK "Optimize stack for threadless operation (warning: single- or multi-threaded server will not work!)" OFF) # choose the library features which shall be included @@ -38,6 +40,8 @@ option(CONFIG_IEC61850_CONTROL_SERVICE "Build with support for IEC 61850 control option(CONFIG_IEC61850_REPORT_SERVICE "Build with support for IEC 61850 reporting services" ON) +option(CONFIG_IEC61850_LOG_SERVICE "Build with support for IEC 61850 logging services" ON) + option(CONFIG_IEC61850_SETTING_GROUPS "Build with support for IEC 61850 setting group services" ON) option(CONFIG_ACTIVATE_TCP_KEEPALIVE "Activate TCP keepalive" ON) @@ -68,6 +72,7 @@ include_directories( src/mms/inc src/mms/inc_private src/mms/iso_mms/asn1c + src/logging ) set(API_HEADERS @@ -102,6 +107,7 @@ set(API_HEADERS src/goose/goose_publisher.h src/sampled_values/sv_subscriber.h src/sampled_values/sv_publisher.h + src/logging/logging_api.h ) IF(MSVC) diff --git a/config/stack_config.h b/config/stack_config.h index 5cbdecba..ae1a58da 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -156,10 +156,10 @@ /* include support for IEC 61850 log services */ #define CONFIG_IEC61850_LOG_SERVICE 1 -/* default results for MMS identify service */ -#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" -#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" -#define CONFIG_DEFAULT_MMS_REVISION "0.9.2" +/* overwrite default results for MMS identify service */ +//#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" +//#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" +//#define CONFIG_DEFAULT_MMS_REVISION "0.9.2" /* MMS virtual file store base path - where file services are looking for files */ #define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/" diff --git a/config/stack_config.h.cmake b/config/stack_config.h.cmake index d1698165..78bf6f88 100644 --- a/config/stack_config.h.cmake +++ b/config/stack_config.h.cmake @@ -142,6 +142,9 @@ /* default reservation time of a setting group control block in s */ #define CONFIG_IEC61850_SG_RESVTMS 100 +/* include support for IEC 61850 log services */ +#cmakedefine01 CONFIG_IEC61850_LOG_SERVICE + /* default results for MMS identify service */ #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" #define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d5be930d..9337d695 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(server_example_complex_array) add_subdirectory(server_example_threadless) add_subdirectory(server_example_61400_25) add_subdirectory(server_example_setting_groups) +add_subdirectory(server_example_logging) add_subdirectory(iec61850_client_example1) add_subdirectory(iec61850_client_example2) add_subdirectory(iec61850_client_example3) diff --git a/examples/goose_subscriber/goose_subscriber_example.c b/examples/goose_subscriber/goose_subscriber_example.c index 4295f474..a3ac5744 100644 --- a/examples/goose_subscriber/goose_subscriber_example.c +++ b/examples/goose_subscriber/goose_subscriber_example.c @@ -12,7 +12,7 @@ #include #include #include - +#include static int running = 1; @@ -28,7 +28,7 @@ gooseListener(GooseSubscriber subscriber, void* parameter) printf(" stNum: %u sqNum: %u\n", GooseSubscriber_getStNum(subscriber), GooseSubscriber_getSqNum(subscriber)); printf(" timeToLive: %u\n", GooseSubscriber_getTimeAllowedToLive(subscriber)); - printf(" timestamp: %llu\n", GooseSubscriber_getTimestamp(subscriber)); + printf(" timestamp: %"PRIu64"\n", GooseSubscriber_getTimestamp(subscriber)); MmsValue* values = GooseSubscriber_getDataSetValues(subscriber); diff --git a/examples/iec61850_9_2_LE_example/static_model.c b/examples/iec61850_9_2_LE_example/static_model.c index 5962f83d..ada63aad 100644 --- a/examples/iec61850_9_2_LE_example/static_model.c +++ b/examples/iec61850_9_2_LE_example/static_model.c @@ -947,6 +947,8 @@ SVControlBlock iedModel_MUnn_LLN0_smv0 = {&iedModel_MUnn_LLN0, "MSVCB01", "xxxxM + + IedModel iedModel = { "TEMPLATE", &iedModel_MUnn, @@ -955,6 +957,8 @@ IedModel iedModel = { NULL, &iedModel_MUnn_LLN0_smv0, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/mms_utility/mms_utility.c b/examples/mms_utility/mms_utility.c index b158f631..28d43794 100644 --- a/examples/mms_utility/mms_utility.c +++ b/examples/mms_utility/mms_utility.c @@ -212,21 +212,6 @@ int main(int argc, char** argv) { char* name = (char*) element->data; printf(" %s\n", name); - - - - - -#if 0 - uint64_t timestamp = Hal_getTimeInMs(); - - MmsValue* startTime = MmsValue_newBinaryTime(false); - MmsValue_setBinaryTime(startTime, timestamp - 60000); - - MmsValue* entrySpec = MmsValue_newOctetString(8, 8); - - MmsConnection_readJournalStartAfter(con, &error, domainName, name, startTime, entrySpec); -#endif } LinkedList_destroy(variableList); diff --git a/examples/server_example1/static_model.c b/examples/server_example1/static_model.c index 3f4b234b..f4bb1e61 100644 --- a/examples/server_example1/static_model.c +++ b/examples/server_example1/static_model.c @@ -1596,6 +1596,8 @@ ReportControlBlock iedModel_Device1_LLN0_report0 = {&iedModel_Device1_LLN0, "LLN + + IedModel iedModel = { "SampleIED", &iedModel_Device1, @@ -1604,6 +1606,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example2/static_model.c b/examples/server_example2/static_model.c index 2e935caf..91d0dcb0 100644 --- a/examples/server_example2/static_model.c +++ b/examples/server_example2/static_model.c @@ -3586,6 +3586,8 @@ ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "r + + IedModel iedModel = { "ied1", &iedModel_Inverter, @@ -3594,6 +3596,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example3/static_model.c b/examples/server_example3/static_model.c index d28f3abd..07b5b4a4 100644 --- a/examples/server_example3/static_model.c +++ b/examples/server_example3/static_model.c @@ -1957,8 +1957,8 @@ ReportControlBlock iedModel_GenericIO_LLN0_report6 = {&iedModel_GenericIO_LLN0, extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; extern LogControlBlock iedModel_GenericIO_LLN0_lcb1; -LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "EventLog", 19, 0, true, true, &iedModel_GenericIO_LLN0_lcb1}; -LogControlBlock iedModel_GenericIO_LLN0_lcb1 = {&iedModel_GenericIO_LLN0, "GeneralLog", "", "", 19, 0, true, true, NULL}; +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; diff --git a/examples/server_example4/static_model.c b/examples/server_example4/static_model.c index c2c99bf9..bf02f9d5 100644 --- a/examples/server_example4/static_model.c +++ b/examples/server_example4/static_model.c @@ -1779,6 +1779,8 @@ ReportControlBlock iedModel_GenericIO_LLN0_report0 = {&iedModel_GenericIO_LLN0, + + IedModel iedModel = { "simpleIO", &iedModel_GenericIO, @@ -1787,6 +1789,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example5/static_model.c b/examples/server_example5/static_model.c index 2e935caf..91d0dcb0 100644 --- a/examples/server_example5/static_model.c +++ b/examples/server_example5/static_model.c @@ -3586,6 +3586,8 @@ ReportControlBlock iedModel_Inverter_LLN0_report0 = {&iedModel_Inverter_LLN0, "r + + IedModel iedModel = { "ied1", &iedModel_Inverter, @@ -3594,6 +3596,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_61400_25/static_model.c b/examples/server_example_61400_25/static_model.c index b34a03f0..3f180925 100644 --- a/examples/server_example_61400_25/static_model.c +++ b/examples/server_example_61400_25/static_model.c @@ -4134,6 +4134,8 @@ DataAttribute iedModel_WTG_WTUR1_SetTurOp_cmAcs = { + + IedModel iedModel = { "WIND", &iedModel_WTG, @@ -4142,6 +4144,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_complex_array/static_model.c b/examples/server_example_complex_array/static_model.c index 3bc9396a..76006197 100644 --- a/examples/server_example_complex_array/static_model.c +++ b/examples/server_example_complex_array/static_model.c @@ -544,6 +544,8 @@ DataAttribute iedModel_ComplexArray_MHAI1_HA_frequency = { + + IedModel iedModel = { "test", &iedModel_ComplexArray, @@ -552,6 +554,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_control/static_model.c b/examples/server_example_control/static_model.c index b538b20e..6921b4f4 100644 --- a/examples/server_example_control/static_model.c +++ b/examples/server_example_control/static_model.c @@ -3874,6 +3874,8 @@ DataAttribute iedModel_GenericIO_GGIO1_Ind4_t = { + + IedModel iedModel = { "simpleIO", &iedModel_GenericIO, @@ -3882,6 +3884,8 @@ IedModel iedModel = { NULL, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_goose/static_model.c b/examples/server_example_goose/static_model.c index 69df5e7f..4c3d6c97 100644 --- a/examples/server_example_goose/static_model.c +++ b/examples/server_example_goose/static_model.c @@ -1909,6 +1909,8 @@ GSEControlBlock iedModel_GenericIO_LLN0_gse1 = {&iedModel_GenericIO_LLN0, "gcbAn + + IedModel iedModel = { "simpleIO", &iedModel_GenericIO, @@ -1917,6 +1919,8 @@ IedModel iedModel = { &iedModel_GenericIO_LLN0_gse0, NULL, NULL, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_logging/Makefile.sqliteStatic b/examples/server_example_logging/Makefile.sqliteStatic new file mode 100644 index 00000000..65ff899a --- /dev/null +++ b/examples/server_example_logging/Makefile.sqliteStatic @@ -0,0 +1,34 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = server_example_logging +PROJECT_SOURCES = server_example_logging.c +PROJECT_SOURCES += static_model.c +PROJECT_SOURCES += $(LIBIEC_HOME)/src/logging/drivers/sqlite/log_storage_sqlite.c +PROJECT_SOURCES += $(LIBIEC_HOME)/third_party/sqlite/sqlite3.c + +PROJECT_ICD_FILE = simpleIO_direct_control.icd + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +CFLAGS += -I$(LIBIEC_HOME)/third_party/sqlite +CFLAGS += -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DHAVE_USLEEP + +LDLIBS += -lm + +CP = cp + +model: $(PROJECT_ICD_FILE) + java -jar $(LIBIEC_HOME)/tools/model_generator/genmodel.jar $(PROJECT_ICD_FILE) + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) + + diff --git a/examples/server_example_logging/README b/examples/server_example_logging/README new file mode 100644 index 00000000..d82ecf4a --- /dev/null +++ b/examples/server_example_logging/README @@ -0,0 +1,8 @@ +BUILD THE EXAMPLE: + +To build the logging example it is required to have sqlite present! + +If you have sqlite installed on the system (including the header files) e.g. in an Ubuntu installation with the sqlite3 package installed, you can simply use the Makefile. + +If you don't have sqlite installed you have to download the sqlite amalgamation package and install the files sqlite3.c, sqlite3.h in the libiec61850/third_party/sqlite folder and use the Makefile.sqliteStatic instead. This will build a version of the example with the sqlite code statically linked. + diff --git a/examples/server_example_logging/server_example_logging.c b/examples/server_example_logging/server_example_logging.c index 76c71d82..76907253 100644 --- a/examples/server_example_logging/server_example_logging.c +++ b/examples/server_example_logging/server_example_logging.c @@ -17,6 +17,8 @@ #include "logging_api.h" +LogStorage SqliteLogStorage_createInstance(const char*); + /* import IEC 61850 device model created from SCL-File */ extern IedModel iedModel; @@ -164,7 +166,7 @@ main(int argc, char** argv) LogStorage_addEntryData(statusLog, entryID, "simpleIOGenerioIO/GPIO1$ST$SPCSO1$t", blob, blobSize, 0); - LogStorage_getEntries(statusLog, 0, Hal_getTimeInMs(), entryCallback, entryDataCallback, NULL); + LogStorage_getEntries(statusLog, 0, Hal_getTimeInMs(), entryCallback, (LogEntryDataCallback) entryDataCallback, NULL); /* MMS server will be instructed to start listening to client connections. */ IedServer_start(iedServer, 102); diff --git a/examples/server_example_setting_groups/static_model.c b/examples/server_example_setting_groups/static_model.c index 2c10dc43..53fdbb3e 100644 --- a/examples/server_example_setting_groups/static_model.c +++ b/examples/server_example_setting_groups/static_model.c @@ -1108,6 +1108,8 @@ extern SettingGroupControlBlock iedModel_PROT_LLN0_sgcb; SettingGroupControlBlock iedModel_PROT_LLN0_sgcb = {&iedModel_PROT_LLN0, 1, 5, 0, false, 0, 0, NULL}; + + IedModel iedModel = { "DEMO", &iedModel_PROT, @@ -1116,6 +1118,8 @@ IedModel iedModel = { NULL, NULL, &iedModel_PROT_LLN0_sgcb, + NULL, + NULL, initializeValues }; diff --git a/examples/server_example_threadless/static_model.c b/examples/server_example_threadless/static_model.c index 1fdf97c7..4f37bdea 100644 --- a/examples/server_example_threadless/static_model.c +++ b/examples/server_example_threadless/static_model.c @@ -1851,6 +1851,10 @@ ReportControlBlock iedModel_GenericIO_LLN0_report3 = {&iedModel_GenericIO_LLN0, +extern LogControlBlock iedModel_GenericIO_LLN0_lcb0; +LogControlBlock iedModel_GenericIO_LLN0_lcb0 = {&iedModel_GenericIO_LLN0, "EventLog", "Events", "GenericIO/LLN0$EventLog", 3, 0, true, true, NULL}; + + IedModel iedModel = { "simpleIO", @@ -1860,6 +1864,8 @@ IedModel iedModel = { NULL, NULL, NULL, + &iedModel_GenericIO_LLN0_lcb0, + NULL, initializeValues }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e9c60fbb..c126643c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,8 @@ set (lib_common_SRCS ./iec61850/server/mms_mapping/reporting.c ./iec61850/server/mms_mapping/mms_goose.c ./iec61850/server/mms_mapping/mms_sv.c +./iec61850/server/mms_mapping/logging.c +./logging/log_storage.c ) set (lib_asn1c_SRCS diff --git a/src/common/inc/libiec61850_platform_includes.h b/src/common/inc/libiec61850_platform_includes.h index d2639c31..97ae2751 100644 --- a/src/common/inc/libiec61850_platform_includes.h +++ b/src/common/inc/libiec61850_platform_includes.h @@ -15,7 +15,7 @@ #include "platform_endian.h" -#define LIBIEC61850_VERSION "0.9.1" +#define LIBIEC61850_VERSION "0.9.2" #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" diff --git a/src/iec61850/client/client_report.c b/src/iec61850/client/client_report.c index 20cc3c1b..a0b7c8af 100644 --- a/src/iec61850/client/client_report.c +++ b/src/iec61850/client/client_report.c @@ -31,6 +31,8 @@ #include "libiec61850_platform_includes.h" +#include + struct sClientReport { ReportCallbackFunction callback; @@ -405,7 +407,7 @@ private_IedConnection_handleReport(IedConnection self, MmsValue* value) matchingReport->timestamp = MmsValue_getBinaryTimeAsUtcMs(timeStampValue); if (DEBUG_IED_CLIENT) - printf("DEBUG_IED_CLIENT: report has timestamp %llu\n", matchingReport->timestamp); + printf("DEBUG_IED_CLIENT: report has timestamp %" PRIu64 "\n", matchingReport->timestamp); } inclusionIndex++; diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index e6c46d1c..3b87f4e9 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -1306,6 +1306,56 @@ addVariablesWithFc(char* fc, char* lnName, LinkedList variables, LinkedList lnDi } } +static LinkedList +getLogicalNodeDirectoryLogs(IedConnection self, IedClientError* error, const char* logicalDeviceName, + const char* logicalNodeName) +{ + MmsConnection mmsCon = self->connection; + + MmsError mmsError; + + LinkedList journals = MmsConnection_getDomainJournals(mmsCon, &mmsError, logicalDeviceName); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + + LinkedList logs = LinkedList_create(); + + LinkedList journal = LinkedList_getNext(journals); + + while (journal != NULL) { + + char* journalName = (char*) LinkedList_getData(journal); + + char* logName = strchr(journalName, '$'); + + if (logName != NULL) { + logName[0] = 0; + logName += 1; + + if (strcmp(journalName, logicalNodeName) == 0) { + char* log = copyString(logName); + LinkedList_add(logs, (void*) log); + } + } + + journal = LinkedList_getNext(journal); + } + + LinkedList_destroy(journals); + + return logs; +} + +static LinkedList +getLogicalNodeDirectoryDataSets(IedConnection self, IedClientError* error, const char* logicalDeviceName, + const char* logicalNodeName) +{ + MmsConnection mmsCon = self->connection; +} + LinkedList /**/ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, const char* logicalNodeReference, ACSIClass acsiClass) @@ -1317,12 +1367,6 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, return NULL; } - if (self->logicalDevices == NULL) - IedConnection_getDeviceModelFromServer(self, error); - - if (*error != IED_ERROR_OK) - return NULL; - char lnRefCopy[130]; strncpy(lnRefCopy, logicalNodeReference, 129); @@ -1341,6 +1385,16 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, char* logicalNodeName = ldSep + 1; + if (acsiClass == ACSI_CLASS_LOG) { + return getLogicalNodeDirectoryLogs(self, error, logicalDeviceName, logicalNodeName); + } + + if (self->logicalDevices == NULL) + IedConnection_getDeviceModelFromServer(self, error); + + if (*error != IED_ERROR_OK) + return NULL; + /* search for logical device */ LinkedList device = LinkedList_getNext(self->logicalDevices); diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index 967c764f..bb82faa5 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -146,7 +146,7 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type * \param parent the parent LN. * \param rptId of the report. If NULL the default report ID (object reference) is used. * \param isBuffered true for a buffered RCB - false for unbuffered RCB - * \param dataSetName name (object reference) of the default data set or NULL if no data + * \param dataSetName name (object reference) of the default data set or NULL if no data set * is set by default * \param confRef the configuration revision * \param trgOps the trigger options supported by this RCB (bit set) @@ -160,6 +160,38 @@ ReportControlBlock* ReportControlBlock_create(const char* name, LogicalNode* parent, char* rptId, bool isBuffered, char* dataSetName, uint32_t confRef, uint8_t trgOps, uint8_t options, uint32_t bufTm, uint32_t intgPd); +/** + * \brief create a new log control block (LCB) + * + * Create a new log control block (LCB) and add it to the given logical node (LN). + * + * \param name name of the LCB relative to the parent LN + * \param parent the parent LN. + * \param dataSetName name (object reference) of the default data set or NULL if no data set + * is set by default + * \param logRef name (object reference) of the default log or NULL if no log is set by default + * \param trgOps the trigger options supported by this LCB (bit set) + * \param intgPd integrity period in milliseconds + * \param logEna if true the log will be enabled by default, false otherwise + * \param reasonCode if true the reasonCode will be included in the log (this is always true in MMS mapping) + * + * \return the new LCB instance + */ +LogControlBlock* +LogControlBlock_create(const char* name, LogicalNode* parent, char* dataSetName, char* logRef, uint8_t trgOps, + uint32_t intgPd, bool logEna, bool reasonCode); + +/** + * \brief create a log (used by the IEC 61850 log service) + * + * \param name name of the LOG relative to the parent LN + * \param parent the parent LN + * + * \return the new LOG instance + */ +Log* +Log_create(const char* name, LogicalNode* parent); + /** * \brief create a setting group control block (SGCB) * diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 969b35d9..961e640d 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -517,9 +517,6 @@ LogicalNode_hasUnbufferedReports(LogicalNode* node); DataSet* LogicalNode_getDataSet(LogicalNode* self, const char* dataSetName); -void -LogicalNode_setLogStorage(LogicalNode* self, const char* logName, LogStorage logStorage); - bool DataObject_hasFCData(DataObject* dataObject, FunctionalConstraint fc); diff --git a/src/iec61850/inc_private/ied_server_private.h b/src/iec61850/inc_private/ied_server_private.h index d2ae3ae0..c32e43e8 100644 --- a/src/iec61850/inc_private/ied_server_private.h +++ b/src/iec61850/inc_private/ied_server_private.h @@ -42,6 +42,7 @@ struct sIedServer MmsMapping* mmsMapping; LinkedList clientConnections; uint8_t writeAccessPolicies; + bool running; }; diff --git a/src/iec61850/inc_private/logging.h b/src/iec61850/inc_private/logging.h index 365f7a07..ea3793dd 100644 --- a/src/iec61850/inc_private/logging.h +++ b/src/iec61850/inc_private/logging.h @@ -48,6 +48,7 @@ typedef struct { DataSet* dataSet; char* dataSetRef; + bool isDynamicDataSet; LogicalNode* logicalNode; MmsDomain* domain; diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index 7dd9eec1..f2dc0b8b 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -404,6 +404,8 @@ IedServer_create(IedModel* iedModel) self->model = iedModel; + // self->running = false; /* not required due to CALLOC */ + self->mmsMapping = MmsMapping_create(iedModel); self->mmsDevice = MmsMapping_getMmsDeviceModel(self->mmsMapping); @@ -445,6 +447,16 @@ IedServer_create(IedModel* iedModel) void IedServer_destroy(IedServer self) { + + /* Stop server if running */ + if (self->running) { +#if (CONFIG_MMS_THREADLESS_STACK == 1) + IedServer_stopThreadless(self); +#else + IedServer_stop(self); +#endif + } + MmsServer_destroy(self->mmsServer); IsoServer_destroy(self->isoServer); @@ -525,17 +537,22 @@ singleThreadedServerThread(void* parameter) void IedServer_start(IedServer self, int tcpPort) { + if (self->running == false) { + #if (CONFIG_MMS_SINGLE_THREADED == 1) - MmsServer_startListeningThreadless(self->mmsServer, tcpPort); + MmsServer_startListeningThreadless(self->mmsServer, tcpPort); - Thread serverThread = Thread_create((ThreadExecutionFunction) singleThreadedServerThread, (void*) self, true); + Thread serverThread = Thread_create((ThreadExecutionFunction) singleThreadedServerThread, (void*) self, true); - Thread_start(serverThread); + Thread_start(serverThread); #else - MmsServer_startListening(self->mmsServer, tcpPort); - MmsMapping_startEventWorkerThread(self->mmsMapping); + MmsServer_startListening(self->mmsServer, tcpPort); + MmsMapping_startEventWorkerThread(self->mmsMapping); #endif + + self->running = true; + } } #endif @@ -558,13 +575,17 @@ IedServer_getDataModel(IedServer self) void IedServer_stop(IedServer self) { - MmsMapping_stopEventWorkerThread(self->mmsMapping); + if (self->running) { + self->running = false; + + MmsMapping_stopEventWorkerThread(self->mmsMapping); #if (CONFIG_MMS_SINGLE_THREADED == 1) - MmsServer_stopListeningThreadless(self->mmsServer); + MmsServer_stopListeningThreadless(self->mmsServer); #else - MmsServer_stopListening(self->mmsServer); + MmsServer_stopListening(self->mmsServer); #endif + } } #endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ @@ -572,7 +593,10 @@ IedServer_stop(IedServer self) void IedServer_startThreadless(IedServer self, int tcpPort) { - MmsServer_startListeningThreadless(self->mmsServer, tcpPort); + if (self->running == false) { + MmsServer_startListeningThreadless(self->mmsServer, tcpPort); + self->running = true; + } } int @@ -590,7 +614,11 @@ IedServer_processIncomingData(IedServer self) void IedServer_stopThreadless(IedServer self) { - MmsServer_stopListeningThreadless(self->mmsServer); + if (self->running) { + self->running = false; + + MmsServer_stopListeningThreadless(self->mmsServer); + } } void diff --git a/src/iec61850/server/mms_mapping/logging.c b/src/iec61850/server/mms_mapping/logging.c index 70d61384..a7b6ad58 100644 --- a/src/iec61850/server/mms_mapping/logging.c +++ b/src/iec61850/server/mms_mapping/logging.c @@ -35,6 +35,8 @@ #include "mms_mapping_internal.h" #include "mms_value_internal.h" +#include "logging_api.h" + #if (CONFIG_IEC61850_LOG_SERVICE == 1) LogInstance* @@ -74,7 +76,7 @@ LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* valu self->locked = true; - //if (DEBUG_IED_SERVER) + if (DEBUG_IED_SERVER) printf("IED_SERVER: Log value - dataRef: %s flag: %i\n", dataRef, flag); uint64_t timestamp = Hal_getTimeInMs(); @@ -83,7 +85,7 @@ LogInstance_logSingleData(LogInstance* self, const char* dataRef, MmsValue* valu int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); - uint8_t* data = GLOBAL_MALLOC(dataSize); + uint8_t* data = (uint8_t*) GLOBAL_MALLOC(dataSize); MmsValue_encodeMmsData(value, data, 0, true); @@ -137,7 +139,7 @@ LogInstance_logEntryData(LogInstance* self, uint64_t entryID, const char* dataRe int dataSize = MmsValue_encodeMmsData(value, NULL, 0, false); - uint8_t* data = GLOBAL_MALLOC(dataSize); + uint8_t* data = (uint8_t*) GLOBAL_MALLOC(dataSize); MmsValue_encodeMmsData(value, data, 0, true); @@ -171,6 +173,7 @@ LogControl_create(LogicalNode* parentLN, MmsMapping* mmsMapping) self->enabled = false; self->dataSet = NULL; + self->isDynamicDataSet = false; self->triggerOps = 0; self->logicalNode = parentLN; self->mmsMapping = mmsMapping; @@ -207,16 +210,14 @@ static void prepareLogControl(LogControl* logControl) { if (logControl->dataSetRef == NULL) { - printf(" no data set specified!\n"); + logControl->enabled = false; return; } DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, logControl->dataSetRef); - if (dataSet == NULL) { - printf(" data set (%s) not found!\n", logControl->dataSetRef); + if (dataSet == NULL) return; - } else logControl->dataSet = dataSet; } @@ -322,7 +323,7 @@ getLogInstanceByLogRef(MmsMapping* self, const char* logRef) while (instance != NULL) { - LogInstance* logInstance = LinkedList_getData(instance); + LogInstance* logInstance = (LogInstance*) LinkedList_getData(instance); if (strcmp(logInstance->name, logName) == 0) { @@ -355,6 +356,18 @@ updateLogStatusInLCB(LogControl* self) } +static void +freeDynamicDataSet(LogControl* self) +{ + if (self->isDynamicDataSet) { + if (self->dataSet != NULL) { + MmsMapping_freeDynamicallyCreatedDataSet(self->dataSet); + self->isDynamicDataSet = false; + self->dataSet = NULL; + } + } +} + MmsDataAccessError LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* domain, char* variableIdOrig, MmsValue* value, MmsServerConnection connection) @@ -460,7 +473,6 @@ LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* doma if (logControl->enabled == false) { /* check if datSet is valid or NULL/empty */ - const char* dataSetRef = MmsValue_toString(value); if (strlen(dataSetRef) == 0) { @@ -470,16 +482,51 @@ LIBIEC61850_LOG_SVC_writeAccessLogControlBlock(MmsMapping* self, MmsDomain* doma else { DataSet* dataSet = IedModel_lookupDataSet(logControl->mmsMapping->model, dataSetRef); + if (dataSet != NULL) { + freeDynamicDataSet(logControl); + + logControl->dataSet = dataSet; + updateValue = true; + + } + +#if (MMS_DYNAMIC_DATA_SETS == 1) + + if (dataSet == NULL) { + + dataSet = MmsMapping_getDomainSpecificDataSet(self, dataSetRef); + + if (dataSet == NULL) { + + if (dataSetRef[0] == '/') { /* check for VMD specific data set */ + MmsNamedVariableList mmsVariableList = + MmsDevice_getNamedVariableListWithName(self->mmsDevice, dataSetRef + 1); + + if (mmsVariableList != NULL) + dataSet = MmsMapping_createDataSetByNamedVariableList(self, mmsVariableList); + } + } + + if (dataSet != NULL) { + freeDynamicDataSet(logControl); + logControl->dataSet = dataSet; + logControl->isDynamicDataSet = true; + + updateValue = true; + } + + } + +#endif /*(MMS_DYNAMIC_DATA_SETS == 1) */ + if (dataSet == NULL) { if (DEBUG_IED_SERVER) printf("IED_SERVER: data set (%s) not found!\n", logControl->dataSetRef); return DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; } - else { - logControl->dataSet = dataSet; - updateValue = true; - } } + + } else return DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; @@ -589,7 +636,11 @@ createTrgOps(LogControlBlock* logControlBlock) { return trgOps; } - +static void +LogControl_updateLogEna(LogControl* self) +{ + MmsValue_setBoolean(MmsValue_getElement(self->mmsValue, 0), self->enabled); +} static MmsVariableSpecification* createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, @@ -743,6 +794,8 @@ createLogControlBlock(MmsMapping* self, LogControlBlock* logControlBlock, if (logControl->enabled) enableLogging(logControl); + LogControl_updateLogEna(logControl); + return lcb; } @@ -871,7 +924,7 @@ MmsMapping_setLogStorage(MmsMapping* self, const char* logRef, LogStorage logSto MmsJournal mmsJournal = NULL; - char* logName = strchr(logRef, '/'); + const char* logName = strchr(logRef, '/'); if (logName != NULL) { logName += 1; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index bed90c8b..4a566188 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2501,6 +2501,14 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li } } } + else if (listType == MMS_VMD_SPECIFIC) { + if (rc->dataSet->logicalDeviceName == NULL) { + if (strcmp(rc->dataSet->name, listName) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } + } + } else if (listType == MMS_ASSOCIATION_SPECIFIC) { if (rc->dataSet->logicalDeviceName == NULL) { if (strcmp(rc->dataSet->name, listName) == 0) { @@ -2514,7 +2522,41 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li } } - //TODO check if data set is referenced in a log control block + +#if (CONFIG_IEC61850_LOG_SERVICE == 1) + /* check if data set is referenced in a log control block*/ + LinkedList logElement = self->logControls; + + while ((logElement = LinkedList_getNext(logElement)) != NULL) { + LogControl* lc = (LogControl*) logElement->data; + + if (lc->isDynamicDataSet) { + if (lc->dataSet != NULL) { + + if (listType == MMS_DOMAIN_SPECIFIC) { + if (lc->dataSet->logicalDeviceName != NULL) { + if (strcmp(lc->dataSet->name, listName) == 0) { + if (strcmp(lc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } + } + } + } + else if (listType == MMS_VMD_SPECIFIC) { + if (lc->dataSet->logicalDeviceName == NULL) { + if (strcmp(lc->dataSet->name, listName) == 0) { + allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; + break; + } + } + } + + } + } + } + +#endif /* (CONFIG_IEC61850_LOG_SERVICE == 1) */ } return allow; diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 206e7b87..6482c461 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -58,6 +58,10 @@ IedModel_create(const char* name/*, MemoryAllocator allocator*/) self->sgcbs = NULL; + self->lcbs = NULL; + + self->logs = NULL; + self->initializer = iedModel_emptyVariableInitializer; return self; @@ -97,6 +101,36 @@ IedModel_addLogicalDevice(IedModel* self, LogicalDevice* lDevice) } } +static void +IedModel_addLog(IedModel* self, Log* log) +{ + if (self->logs == NULL) + self->logs = log; + else { + Log* lastLog = self->logs; + + while (lastLog->sibling != NULL) + lastLog = lastLog->sibling; + + lastLog->sibling = log; + } +} + +static void +IedModel_addLogControlBlock(IedModel* self, LogControlBlock* lcb) +{ + if (self->lcbs == NULL) + self->lcbs = lcb; + else { + LogControlBlock* lastLcb = self->lcbs; + + while (lastLcb->sibling != NULL) + lastLcb = lastLcb->sibling; + + lastLcb->sibling = lcb; + } +} + static void IedModel_addReportControlBlock(IedModel* self, ReportControlBlock* rcb) { @@ -231,6 +265,65 @@ LogicalNode_addDataObject(LogicalNode* self, DataObject* dataObject) } } +static void +LogicalNode_addLog(LogicalNode* self, Log* log) +{ + IedModel* model = (IedModel*) self->parent->parent; + + IedModel_addLog(model, log); +} + +Log* +Log_create(const char* name, LogicalNode* parent) +{ + Log* self = (Log*) GLOBAL_MALLOC(sizeof(Log)); + + self->name = copyString(name); + self->parent = parent; + self->sibling = NULL; + + LogicalNode_addLog(parent, self); + + return self; +} + +static void +LogicalNode_addLogControlBlock(LogicalNode* self, LogControlBlock* lcb) +{ + IedModel* model = (IedModel*) self->parent->parent; + + IedModel_addLogControlBlock(model, lcb); +} + +LogControlBlock* +LogControlBlock_create(const char* name, LogicalNode* parent, char* dataSetName, char* logRef, uint8_t trgOps, + uint32_t intPeriod, bool logEna, bool reasonCode) +{ + LogControlBlock* self = (LogControlBlock*) GLOBAL_MALLOC(sizeof(LogControlBlock)); + + self->name = copyString(name); + self->parent = parent; + + if (dataSetName) + self->dataSetName = copyString(dataSetName); + else + dataSetName = NULL; + + if (logRef) + self->logRef = copyString(logRef); + else + logRef = NULL; + + self->trgOps = trgOps; + self->intPeriod = intPeriod; + self->logEna = logEna; + self->reasonCode = reasonCode; + + LogicalNode_addLogControlBlock(parent, self); + + return self; +} + static void LogicalNode_addReportControlBlock(LogicalNode* self, ReportControlBlock* rcb) { @@ -743,6 +836,28 @@ IedModel_destroy(IedModel* model) sgcb = nextSgcb; } + /* delete all LCBs */ + LogControlBlock* lcb = model->lcbs; + + while (lcb != NULL) { + LogControlBlock* nextLcb = lcb->sibling; + + GLOBAL_FREEMEM(lcb); + + lcb = nextLcb; + } + + /* delete all LOGs */ + Log* log = model->logs; + + while (log != NULL) { + Log* nextLog = log->sibling; + + GLOBAL_FREEMEM(log); + + log = nextLog; + } + /* delete generic model parts */ diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index b6ced897..b2998ec6 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -409,20 +409,6 @@ exit_error: return NULL; } - -void -LogicalNode_setLogStorage(LogicalNode* self, const char* logName, LogStorage logStorage) -{ - assert(self->modelType == LogicalNodeModelType); - assert(logName != NULL); - - LogicalDevice* ld = (LogicalDevice*) self->parent; - - IedModel* iedModel = (IedModel*) ld->parent; - - -} - int LogicalDevice_getLogicalNodeCount(LogicalDevice* logicalDevice) { diff --git a/src/logging/drivers/sqlite/log_storage_sqlite.c b/src/logging/drivers/sqlite/log_storage_sqlite.c index 867e0f08..7ef54173 100644 --- a/src/logging/drivers/sqlite/log_storage_sqlite.c +++ b/src/logging/drivers/sqlite/log_storage_sqlite.c @@ -166,7 +166,8 @@ exit_with_error: static uint64_t SqliteLogStorage_addEntry(LogStorage self, uint64_t timestamp) { - printf("SQLITE-DRIVER: add entry\n"); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - add entry\n"); SqliteLogStorage* instanceData = (SqliteLogStorage*) (self->instanceData); @@ -269,8 +270,8 @@ getEntryData(LogStorage self, uint64_t entryID, LogEntryDataCallback entryDataCa while ((rc = sqlite3_step(instanceData->getEntryData)) == SQLITE_ROW) { - const char* dataRef = sqlite3_column_text(instanceData->getEntryData, 0); - const uint8_t* data = sqlite3_column_blob(instanceData->getEntryData, 1); + const char* dataRef = (const char*) sqlite3_column_text(instanceData->getEntryData, 0); + uint8_t* data = (uint8_t*) sqlite3_column_blob(instanceData->getEntryData, 1); int dataSize = sqlite3_column_bytes(instanceData->getEntryData, 1); int reasonCode = sqlite3_column_int(instanceData->getEntryData, 2); @@ -317,7 +318,6 @@ SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t end bool sendFinalEvent = true; while((rc = sqlite3_step(instanceData->getEntriesWithRange)) == SQLITE_ROW) { - int col; uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesWithRange, 0); uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesWithRange, 1); @@ -412,7 +412,6 @@ SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_ bool sendFinalEvent = true; while ((rc = sqlite3_step(instanceData->getEntriesAfter)) == SQLITE_ROW) { - int col; uint64_t entryID = sqlite3_column_int64(instanceData->getEntriesAfter, 0); uint64_t timestamp = sqlite3_column_int64(instanceData->getEntriesAfter, 1); diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h index 726e25f5..92f77c51 100644 --- a/src/logging/logging_api.h +++ b/src/logging/logging_api.h @@ -24,6 +24,10 @@ #ifndef LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ #define LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include #include @@ -37,7 +41,7 @@ typedef struct sLogStorage* LogStorage; */ typedef bool (*LogEntryCallback) (void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow); -typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, const uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow); +typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow); struct sLogStorage { @@ -82,4 +86,8 @@ LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64 void LogStorage_destroy(LogStorage self); +#ifdef __cplusplus +} +#endif + #endif /* LIBIEC61850_SRC_LOGGING_LOGGING_API_H_ */ diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index ca54284b..689fcc01 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -76,8 +76,6 @@ MmsDomain_getName(MmsDomain* self) void MmsDomain_addJournal(MmsDomain* self, const char* name) { - printf("CREATE JOURNAL\n"); - if (self->journals == NULL) self->journals = LinkedList_create(); @@ -96,8 +94,6 @@ MmsDomain_getJournal(MmsDomain* self, const char* name) MmsJournal mmsJournal = (MmsJournal) LinkedList_getData(journal); - printf(" MMS journal: %s (%s)\n", mmsJournal->name, name); - if (strcmp(mmsJournal->name, name) == 0) return mmsJournal; diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index e90c4f1c..48e3182e 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -538,6 +538,10 @@ mmsServer_handleGetNameListRequest( if (nameList == NULL) mmsServer_createServiceErrorPdu(invokeId, response, MMS_ERROR_ACCESS_OBJECT_NON_EXISTENT); else { +#if (CONFIG_MMS_SORT_NAME_LIST == 1) + StringUtils_sortList(nameList); +#endif + createNameListResponse(connection, invokeId, nameList, response, continueAfterId); LinkedList_destroyStatic(nameList); } diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index e1b3d294..c055f5e4 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -68,7 +68,7 @@ entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFo JournalEncoder encoder = (JournalEncoder) parameter; if (moreFollow) { - //printf("Encode entry ID:%" PRIu64 " timestamp:%" PRIu64 "\n", entryID, timestamp); + if (encoder->moreFollows) { printf("entryCallback return false\n"); return false; @@ -128,7 +128,7 @@ entryDataCallback (void* parameter, const char* dataRef, uint8_t* data, int data uint32_t totalLen = firstVariableLen + secondVariableLen; - if ((bufPos + totalLen) > encoder->maxSize) { + if ((int) (bufPos + totalLen) > encoder->maxSize) { encoder->moreFollows = true; encoder->bufPos = encoder->currentEntryBufPos; /* remove last entry */ return false; @@ -241,8 +241,6 @@ mmsServer_handleReadJournalRequest( uint32_t invokeId, ByteBuffer* response) { - printf("READ-JOURNAL\n"); - char domainId[65]; char logName[65]; uint8_t entryIdBuf[64]; /* maximum size of entry id is 64 bytes! */ @@ -276,7 +274,6 @@ mmsServer_handleReadJournalRequest( case 0xa0: /* journalName */ { - uint8_t objectIdTag = requestBuffer[bufPos++]; bufPos = BerDecoder_decodeLength(requestBuffer, &length, bufPos, maxBufPos); diff --git a/src/version.rc.in b/src/version.rc.in new file mode 100644 index 00000000..377bdc7e --- /dev/null +++ b/src/version.rc.in @@ -0,0 +1,20 @@ +1 VERSIONINFO +FILEVERSION @LIB_VERSION_MAJOR@,@LIB_VERSION_MINOR@,@LIB_VERSION_PATCH@,0 +PRODUCTVERSION @LIB_VERSION_MAJOR@,@LIB_VERSION_MINOR@,@LIB_VERSION_PATCH@,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "FileVersion", "@LIB_VERSION_MAJOR@.@LIB_VERSION_MINOR@.@LIB_VERSION_PATCH@.0" + VALUE "ProductVersion", "@LIB_VERSION_MAJOR@.@LIB_VERSION_MINOR@.@LIB_VERSION_PATCH@.0" + VALUE "ProductName", "libIEC61850" + VALUE "FileDescription", "libIEC61850 - open source library for IEC 61850" + VALUE "LegalCopyright", "Dual license : Commercial or GPLv3" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x400, 1252 + END +END diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index acec3203..72f07795 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -528,4 +528,20 @@ EXPORTS MmsValue_encodeMmsData ControlObjectClient_setInterlockCheck ControlObjectClient_setSynchroCheck - + LogStorage_addEntry + LogStorage_addEntryData + LogStorage_getEntries + LogStorage_getEntriesAfter + LogStorage_getOldestAndNewestEntries + LogStorage_destroy + IedServer_setLogStorage + MmsJournalEntry_destroy + MmsJournalEntry_getEntryID + MmsJournalEntry_getOccurenceTime + MmsJournalEntry_getJournalVariables + MmsJournalVariable_getTag + MmsJournalVariable_getValue + MmsConnection_readJournalTimeRange + MmsConnection_readJournalStartAfter + LogControlBlock_create + Log_create diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index d3322ec0..94415198 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -604,3 +604,20 @@ EXPORTS IedServer_updateVisibleStringAttributeValue ControlObjectClient_setInterlockCheck ControlObjectClient_setSynchroCheck + LogStorage_addEntry + LogStorage_addEntryData + LogStorage_getEntries + LogStorage_getEntriesAfter + LogStorage_getOldestAndNewestEntries + LogStorage_destroy + IedServer_setLogStorage + MmsJournalEntry_destroy + MmsJournalEntry_getEntryID + MmsJournalEntry_getOccurenceTime + MmsJournalEntry_getJournalVariables + MmsJournalVariable_getTag + MmsJournalVariable_getValue + MmsConnection_readJournalTimeRange + MmsConnection_readJournalStartAfter + LogControlBlock_create + Log_create diff --git a/third_party/cmake/modules/Findsqlite.cmake b/third_party/cmake/modules/Findsqlite.cmake new file mode 100644 index 00000000..cfdd566a --- /dev/null +++ b/third_party/cmake/modules/Findsqlite.cmake @@ -0,0 +1,48 @@ +# - Try to find the sqlite library +# Once done this will define +# +# SQLITE_FOUND - system has sqlite +# SQLITE_INCLUDE_DIRS - the sqlite include directory +# SQLITE_LIBRARIES - Link these to use sqlite +# +# Define SQLITE_MIN_VERSION for which version desired. +# + +INCLUDE(FindPkgConfig) + +IF(Sqlite_FIND_REQUIRED) + SET(_pkgconfig_REQUIRED "REQUIRED") +ELSE(Sqlite_FIND_REQUIRED) + SET(_pkgconfig_REQUIRED "") +ENDIF(Sqlite_FIND_REQUIRED) + +IF(SQLITE_MIN_VERSION) + PKG_SEARCH_MODULE(SQLITE ${_pkgconfig_REQUIRED} sqlite>=${SQLITE_MIN_VERSION} sqlite${SQLITE_MIN_VERSION}) +ELSE(SQLITE_MIN_VERSION) + PKG_SEARCH_MODULE(SQLITE ${_pkgconfig_REQUIRED} sqlite) +ENDIF(SQLITE_MIN_VERSION) + +IF(NOT SQLITE_FOUND AND NOT PKG_CONFIG_FOUND) + FIND_PATH(SQLITE_INCLUDE_DIRS sqlite${SQLITE_MIN_VERSION}.h) + FIND_LIBRARY(SQLITE_LIBRARIES sqlite${SQLITE_MIN_VERSION}) + + # Report results + IF(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS) + SET(SQLITE_FOUND 1) + IF(NOT Sqlite_FIND_QUIETLY) + MESSAGE(STATUS "Found Sqlite: ${SQLITE_LIBRARIES}") + ENDIF(NOT Sqlite_FIND_QUIETLY) + ELSE(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS) + IF(Sqlite_FIND_REQUIRED) + MESSAGE(SEND_ERROR "Could not find Sqlite") + ELSE(Sqlite_FIND_REQUIRED) + IF(NOT Sqlite_FIND_QUIETLY) + MESSAGE(STATUS "Could not find Sqlite") + ENDIF(NOT Sqlite_FIND_QUIETLY) + ENDIF(Sqlite_FIND_REQUIRED) + ENDIF(SQLITE_LIBRARIES AND SQLITE_INCLUDE_DIRS) +ENDIF(NOT SQLITE_FOUND AND NOT PKG_CONFIG_FOUND) + +# Hide advanced variables from CMake GUIs +MARK_AS_ADVANCED(SQLITE_LIBRARIES SQLITE_INCLUDE_DIRS) + From 922c5eec50a9aa1f5fe9e7bfa23a7c1094873b31 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 Jun 2016 00:40:32 +0200 Subject: [PATCH 23/36] - make GetLogicalNodeDirectory(DATA SET) dynamic. Creates a request at each call. --- examples/server_example1/server_example1.c | 2 - src/iec61850/client/ied_connection.c | 90 ++++++++++------------ src/iec61850/inc/iec61850_client.h | 5 +- 3 files changed, 43 insertions(+), 54 deletions(-) diff --git a/examples/server_example1/server_example1.c b/examples/server_example1/server_example1.c index 14a0b2a1..f702b1ad 100644 --- a/examples/server_example1/server_example1.c +++ b/examples/server_example1/server_example1.c @@ -49,8 +49,6 @@ int main(int argc, char** argv) { IedServer iedServer = IedServer_create(&iedModel); - // get stored values from persistent storage - // set initial measurement and status values from process /* MMS server will be instructed to start listening to client connections. */ diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index 3b87f4e9..bf1782f4 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -39,7 +39,6 @@ typedef struct sICLogicalDevice { char* name; LinkedList variables; - LinkedList dataSets; } ICLogicalDevice; struct sClientDataSet @@ -165,12 +164,6 @@ ICLogicalDevice_setVariableList(ICLogicalDevice* self, LinkedList variables) self->variables = variables; } -static void -ICLogicalDevice_setDataSetList(ICLogicalDevice* self, LinkedList dataSets) -{ - self->dataSets = dataSets; -} - static void ICLogicalDevice_destroy(ICLogicalDevice* self) { @@ -179,9 +172,6 @@ ICLogicalDevice_destroy(ICLogicalDevice* self) if (self->variables != NULL) LinkedList_destroy(self->variables); - if (self->dataSets != NULL) - LinkedList_destroy(self->dataSets); - GLOBAL_FREEMEM(self); } @@ -1015,16 +1005,6 @@ IedConnection_getDeviceModelFromServer(IedConnection self, IedClientError* error break; } - LinkedList dataSets = MmsConnection_getDomainVariableListNames(self->connection, - &mmsError, name); - - if (dataSets != NULL) - ICLogicalDevice_setDataSetList(icLogicalDevice, dataSets); - else { - *error = iedConnection_mapMmsErrorToIedError(mmsError); - break; - } - LinkedList_add(logicalDevices, icLogicalDevice); logicalDevice = LinkedList_getNext(logicalDevice); @@ -1354,6 +1334,41 @@ getLogicalNodeDirectoryDataSets(IedConnection self, IedClientError* error, const const char* logicalNodeName) { MmsConnection mmsCon = self->connection; + + MmsError mmsError; + + LinkedList dataSets = MmsConnection_getDomainVariableListNames(mmsCon, &mmsError, logicalDeviceName); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + + LinkedList lnDataSets = LinkedList_create(); + + LinkedList dataSet = LinkedList_getNext(dataSets); + + while (dataSet != NULL) { + char* dataSetName = (char*) LinkedList_getData(dataSet); + + char* lnDataSetName = strchr(dataSetName, '$'); + + if (lnDataSetName != NULL) { + lnDataSetName[0] = 0; + lnDataSetName += 1; + + if (strcmp(dataSetName, logicalNodeName) == 0) { + char* lnDataSet = copyString(lnDataSetName); + LinkedList_add(lnDataSets, (void*) lnDataSet); + } + } + + dataSet = LinkedList_getNext(dataSet); + } + + LinkedList_destroy(dataSets); + + return lnDataSets; } LinkedList /**/ @@ -1385,9 +1400,11 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, char* logicalNodeName = ldSep + 1; - if (acsiClass == ACSI_CLASS_LOG) { + if (acsiClass == ACSI_CLASS_LOG) return getLogicalNodeDirectoryLogs(self, error, logicalDeviceName, logicalNodeName); - } + + if (acsiClass == ACSI_CLASS_DATA_SET) + return getLogicalNodeDirectoryDataSets(self, error, logicalDeviceName, logicalNodeName); if (self->logicalDevices == NULL) IedConnection_getDeviceModelFromServer(self, error); @@ -1498,35 +1515,6 @@ IedConnection_getLogicalNodeDirectory(IedConnection self, IedClientError* error, addVariablesWithFc("LG", logicalNodeName, ld->variables, lnDirectory); break; - case ACSI_CLASS_DATA_SET: - { - LinkedList dataSet = LinkedList_getNext(ld->dataSets); - - while (dataSet != NULL) { - char* dataSetName = (char*) dataSet->data; - - char* fcPos = strchr(dataSetName, '$'); - - if (fcPos == NULL) - goto next_data_set_element; - - size_t lnNameLen = fcPos - dataSetName; - - if (strlen(logicalNodeName) != lnNameLen) - goto next_data_set_element; - - if (memcmp(dataSetName, logicalNodeName, lnNameLen) != 0) - goto next_data_set_element; - - LinkedList_add(lnDirectory, copyString(fcPos + 1)); - - next_data_set_element: - - dataSet = LinkedList_getNext(dataSet); - } - } - break; - default: if (DEBUG_IED_CLIENT) printf("IED_CLIENT: ACSI class not yet supported!\n"); diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index b5ce446c..76ec2c26 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1661,7 +1661,10 @@ IedConnection_getLogicalNodeVariables(IedConnection self, IedClientError* error, /** * \brief returns the directory of the given logical node (LN) containing elements of the specified ACSI class * - * Implementation of the GetLogicalNodeDirectory ACSI service. + * Implementation of the GetLogicalNodeDirectory ACSI service. In contrast to the ACSI description this + * function does not always creates a request to the server. For most ACSI classes it simply accesses the + * data model that was retrieved before. An exception to this rule are the ACSI classes ACSI_CLASS_DATASET and + * ACSI_CLASS_LOG. Both always perform a request to the server. * * \param self the connection object * \param error the error code if an error occurs From 97fd7524f82f2c466e43c4b684ac472d95b30145 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 Jun 2016 14:46:14 +0200 Subject: [PATCH 24/36] - added IEC 61850 client API to query logs - added client example to read logs --- examples/CMakeLists.txt | 1 + .../CMakeLists.txt | 17 +++ examples/iec61850_client_example_log/Makefile | 17 +++ .../client_example_log.c | 133 ++++++++++++++++++ src/iec61850/client/ied_connection.c | 85 +++++++++++ src/iec61850/inc/iec61850_client.h | 52 +++++++ src/mms/iso_mms/client/mms_client_journals.c | 51 +------ src/mms/iso_mms/server/mms_journal_service.c | 36 ++--- src/vs/libiec61850-wo-goose.def | 2 + src/vs/libiec61850.def | 3 + 10 files changed, 329 insertions(+), 68 deletions(-) create mode 100644 examples/iec61850_client_example_log/CMakeLists.txt create mode 100644 examples/iec61850_client_example_log/Makefile create mode 100644 examples/iec61850_client_example_log/client_example_log.c diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9337d695..398e901d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(iec61850_client_example4) add_subdirectory(iec61850_client_example5) add_subdirectory(iec61850_client_example_files) add_subdirectory(iec61850_client_example_reporting) +add_subdirectory(iec61850_client_example_log) add_subdirectory(mms_client_example1) add_subdirectory(mms_client_example2) add_subdirectory(mms_client_example3) diff --git a/examples/iec61850_client_example_log/CMakeLists.txt b/examples/iec61850_client_example_log/CMakeLists.txt new file mode 100644 index 00000000..0322bd61 --- /dev/null +++ b/examples/iec61850_client_example_log/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(iec61850_client_example_log_SRCS + client_example_log.c +) + +IF(MSVC) +set_source_files_properties(${iec61850_client_example_log_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(MSVC) + +add_executable(iec61850_client_example_log + ${iec61850_client_example_log_SRCS} +) + +target_link_libraries(iec61850_client_example_log + iec61850 +) diff --git a/examples/iec61850_client_example_log/Makefile b/examples/iec61850_client_example_log/Makefile new file mode 100644 index 00000000..4c379413 --- /dev/null +++ b/examples/iec61850_client_example_log/Makefile @@ -0,0 +1,17 @@ +LIBIEC_HOME=../.. + +PROJECT_BINARY_NAME = client_example_log +PROJECT_SOURCES = client_example_log.c + +include $(LIBIEC_HOME)/make/target_system.mk +include $(LIBIEC_HOME)/make/stack_includes.mk + +all: $(PROJECT_BINARY_NAME) + +include $(LIBIEC_HOME)/make/common_targets.mk + +$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS) + +clean: + rm -f $(PROJECT_BINARY_NAME) diff --git a/examples/iec61850_client_example_log/client_example_log.c b/examples/iec61850_client_example_log/client_example_log.c new file mode 100644 index 00000000..83f8e958 --- /dev/null +++ b/examples/iec61850_client_example_log/client_example_log.c @@ -0,0 +1,133 @@ +/* + * client_example_log.c + * + * This example is intended to be used with server_example_logging + */ + +#include "iec61850_client.h" + +#include +#include +#include + +//#include "hal_thread.h" + +static void +printJournalEntries(LinkedList journalEntries) +{ + char buf[1024]; + + LinkedList journalEntriesElem = LinkedList_getNext(journalEntries); + + while (journalEntriesElem != NULL) { + + MmsJournalEntry journalEntry = (MmsJournalEntry) LinkedList_getData(journalEntriesElem); + + MmsValue_printToBuffer(MmsJournalEntry_getEntryID(journalEntry), buf, 1024); + printf("EntryID: %s\n", buf); + MmsValue_printToBuffer(MmsJournalEntry_getOccurenceTime(journalEntry), buf, 1024); + printf(" occurence time: %s\n", buf); + + LinkedList journalVariableElem = LinkedList_getNext(journalEntry->journalVariables); + + while (journalVariableElem != NULL) { + + MmsJournalVariable journalVariable = (MmsJournalVariable) LinkedList_getData(journalVariableElem); + + printf(" variable-tag: %s\n", MmsJournalVariable_getTag(journalVariable)); + MmsValue_printToBuffer(MmsJournalVariable_getValue(journalVariable), buf, 1024); + printf(" variable-value: %s\n", buf); + + journalVariableElem = LinkedList_getNext(journalVariableElem); + } + + journalEntriesElem = LinkedList_getNext(journalEntriesElem); + } +} + +int main(int argc, char** argv) { + + char* hostname; + int tcpPort = 102; + + if (argc > 1) + hostname = argv[1]; + else + hostname = "localhost"; + + if (argc > 2) + tcpPort = atoi(argv[2]); + + char* logRef = "simpleIOGenericIO/LLN0$EventLog"; + + IedClientError error; + + IedConnection con = IedConnection_create(); + + IedConnection_connect(con, &error, hostname, tcpPort); + + if (error == IED_ERROR_OK) { + + /* read list of logs in LN (optional - if you don't know the existing logs) */ + LinkedList logs = IedConnection_getLogicalNodeDirectory(con, &error, "simpleIOGenericIO/LLN0", ACSI_CLASS_LOG); + + if (error == IED_ERROR_OK) { + printf("Found log in LN simpleIOGenericIO/LLN0:\n"); + + LinkedList log = LinkedList_getNext(logs); + + while (log != NULL) { + char* logName = (char*) LinkedList_getData(log); + + printf(" %s\n", logName); + + log = LinkedList_getNext(log); + } + + LinkedList_destroy(logs); + } + + /* read log control block (using the generic read function) */ + MmsValue* lcbValue = IedConnection_readObject(con, &error, "simpleIOGenericIO/LLN0.EventLog", IEC61850_FC_LG); + + if (error == IED_ERROR_OK) { + + MmsValue* oldEntryTm = MmsValue_getElement(lcbValue, 3); + MmsValue* oldEntry = MmsValue_getElement(lcbValue, 5); + + uint64_t timestamp = MmsValue_getUtcTimeInMs(oldEntryTm); + + bool moreFollows; + + /* + * read the log contents. Be aware that the logRef uses the '$' sign as separator between the LN and + * the log name! This is in contrast to the LCB object reference above. + */ + LinkedList logEntries = IedConnection_queryLogAfter(con, &error, "simpleIOGenericIO/LLN0$EventLog", oldEntry, timestamp, &moreFollows); + + if (error == IED_ERROR_OK) { + printJournalEntries(logEntries); + + LinkedList_destroyDeep(logEntries, (LinkedListValueDeleteFunction) MmsJournalEntry_destroy); + } + else + printf("QueryLog failed (error code: %i)!\n", error); + + //TODO handle moreFollows + + MmsValue_delete(lcbValue); + } + else + printf("Read LCB failed!\n"); + + + IedConnection_abort(con, &error); + } + else { + printf("Failed to connect to %s:%i\n", hostname, tcpPort); + } + + IedConnection_destroy(con); +} + + diff --git a/src/iec61850/client/ied_connection.c b/src/iec61850/client/ied_connection.c index bf1782f4..da745847 100644 --- a/src/iec61850/client/ied_connection.c +++ b/src/iec61850/client/ied_connection.c @@ -2250,6 +2250,91 @@ exit_function: return dataSet; } +LinkedList /* */ +IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, + uint64_t startTime, uint64_t endTime, bool* moreFollows) +{ + MmsError mmsError; + + char logRef[130]; + + strncpy(logRef, logReference, 129); + + char* logDomain = logRef; + char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + MmsValue* startTimeMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(startTimeMms, startTime); + + MmsValue* endTimeMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(endTimeMms, endTime); + + LinkedList journalEntries = MmsConnection_readJournalTimeRange(self->connection, &mmsError, logDomain, logName, + startTimeMms, endTimeMms, moreFollows); + + MmsValue_delete(startTimeMms); + MmsValue_delete(endTimeMms); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + else + return journalEntries; + } + else { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return NULL; + } + +} + + +LinkedList /* */ +IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference, + MmsValue* entryID, uint64_t timeStamp, bool* moreFollows) +{ + MmsError mmsError; + + char logRef[130]; + + strncpy(logRef, logReference, 129); + + char* logDomain = logRef; + char* logName = strchr(logRef, '/'); + + if (logName != NULL) { + + logName[0] = 0; + logName++; + + MmsValue* timeStampMms = MmsValue_newBinaryTime(false); + MmsValue_setBinaryTime(timeStampMms, timeStamp); + + LinkedList journalEntries = MmsConnection_readJournalStartAfter(self->connection, &mmsError, logDomain, logName, + timeStampMms, entryID, moreFollows); + + MmsValue_delete(timeStampMms); + + if (mmsError != MMS_ERROR_NONE) { + *error = iedConnection_mapMmsErrorToIedError(mmsError); + return NULL; + } + else + return journalEntries; + } + else { + *error = IED_ERROR_OBJECT_REFERENCE_INVALID; + return NULL; + } +} + + MmsConnection IedConnection_getMmsConnection(IedConnection self) diff --git a/src/iec61850/inc/iec61850_client.h b/src/iec61850/inc/iec61850_client.h index 76ec2c26..47d46180 100644 --- a/src/iec61850/inc/iec61850_client.h +++ b/src/iec61850/inc/iec61850_client.h @@ -1747,6 +1747,58 @@ MmsVariableSpecification* IedConnection_getVariableSpecification(IedConnection self, IedClientError* error, const char* dataAttributeReference, FunctionalConstraint fc); +/** @} */ + +/** + * @defgroup IEC61850_CLIENT_LOG_SERVICE Log service related functions, data types, and definitions + * + * @{ + */ + +/** + * \brief Implementation of the QueryLogByTime ACSI service + * + * Read log entries from a log at the server. The log entries to read are specified by + * a starting time and an end time. If the complete range does not fit in a single MMS message + * the moreFollows flag will be set to true, to indicate that more entries are available for the + * specified time range. + * + * \param self the connection object + * \param error the error code if an error occurs + * \param logReference log object reference in the form /$ + * \param startTime as millisecond UTC timestamp + * \param endTime as millisecond UTC timestamp + * \param moreFollows (output value) indicates that more entries are available that match the specification. + * + * \return list of MmsJournalEntry objects matching the specification + */ +LinkedList /* */ +IedConnection_queryLogByTime(IedConnection self, IedClientError* error, const char* logReference, + uint64_t startTime, uint64_t endTime, bool* moreFollows); + +/** + * \brief Implementation of the QueryLogAfter ACSI service + * + * Read log entries from a log at the server following the entry with the specified entryID and timestamp. + * If the complete range does not fit in a single MMS message + * the moreFollows flag will be set to true, to indicate that more entries are available for the + * specified time range. + * + * \param self the connection object + * \param error the error code if an error occurs + * \param logReference log object reference in the form /$ + * \param entryID usually the entryID of the last received entry + * \param timeStamp as millisecond UTC timestamp + * \param moreFollows (output value) indicates that more entries are available that match the specification. + * + * \return list of MmsJournalEntry objects matching the specification + */ +LinkedList /* */ +IedConnection_queryLogAfter(IedConnection self, IedClientError* error, const char* logReference, + MmsValue* entryID, uint64_t timeStamp, bool* moreFollows); + + + /** @} */ /** diff --git a/src/mms/iso_mms/client/mms_client_journals.c b/src/mms/iso_mms/client/mms_client_journals.c index f09535a3..919e2c57 100644 --- a/src/mms/iso_mms/client/mms_client_journals.c +++ b/src/mms/iso_mms/client/mms_client_journals.c @@ -320,7 +320,8 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked tag = buffer[bufPos++]; - *moreFollows = false; + if (moreFollows) + *moreFollows = false; if (tag != 0x41) { if (DEBUG_MMS_CLIENT) @@ -355,7 +356,8 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked break; case 0x81: /* moreFollows */ - *moreFollows = BerDecoder_decodeBoolean(buffer, bufPos); + if (moreFollows) + *moreFollows = BerDecoder_decodeBoolean(buffer, bufPos); break; default: @@ -373,51 +375,6 @@ mmsClient_parseReadJournalResponse(MmsConnection self, bool* moreFollows, Linked return true; } -#if 0 -void -mmsClient_createReadJournalRequest(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId) -{ - /* calculate sizes */ - uint32_t invokeIdSize = BerEncoder_UInt32determineEncodedSize(invokeId); - - uint32_t domainIdStringSize = strlen(domainId); - uint32_t domainIdSize = 1 + BerEncoder_determineLengthSize(domainIdStringSize) + domainIdStringSize; - - uint32_t itemIdStringSize = strlen(itemId); - uint32_t itemIdSize = 1 + BerEncoder_determineLengthSize(itemIdStringSize) + itemIdStringSize; - - uint32_t objectIdSize = domainIdSize + itemIdSize; - - uint32_t journalNameSize = 1 + BerEncoder_determineLengthSize(objectIdSize) + (objectIdSize); - - uint32_t journalReadSize = 1 + BerEncoder_determineLengthSize(journalNameSize) + (journalNameSize); - - uint32_t confirmedRequestPduSize = 1 + 2 + 2 + invokeIdSize + journalReadSize; - - /* encode to buffer */ - int bufPos = 0; - uint8_t* buffer = request->buffer; - - bufPos = BerEncoder_encodeTL(0xa0, confirmedRequestPduSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0x02, invokeIdSize, buffer, bufPos); - bufPos = BerEncoder_encodeUInt32(invokeId, buffer, bufPos); - - /* Encode read journal tag (context | structured ) [65 = 41h] */ - buffer[bufPos++] = 0xbf; - buffer[bufPos++] = 0x41; - - bufPos = BerEncoder_encodeLength(journalReadSize, buffer, bufPos); - bufPos = BerEncoder_encodeTL(0xa0, journalNameSize, buffer, bufPos); - - bufPos = BerEncoder_encodeTL(0xa1, objectIdSize, buffer, bufPos); - - bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) domainId, domainIdStringSize, buffer, bufPos); - bufPos = BerEncoder_encodeOctetString(0x1a, (uint8_t*) itemId, itemIdStringSize, buffer, bufPos); - - request->size = bufPos; -} -#endif - void mmsClient_createReadJournalRequestWithTimeRange(uint32_t invokeId, ByteBuffer* request, const char* domainId, const char* itemId, MmsValue* startingTime, MmsValue* endingTime) diff --git a/src/mms/iso_mms/server/mms_journal_service.c b/src/mms/iso_mms/server/mms_journal_service.c index c055f5e4..74d69a77 100644 --- a/src/mms/iso_mms/server/mms_journal_service.c +++ b/src/mms/iso_mms/server/mms_journal_service.c @@ -69,10 +69,8 @@ entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFo if (moreFollow) { - if (encoder->moreFollows) { - printf("entryCallback return false\n"); + if (encoder->moreFollows) return false; - } encoder->currentEntryBufPos = encoder->bufPos; @@ -80,10 +78,6 @@ entryCallback(void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFo encoder->currentEntryId = entryID; encoder->currentTimestamp = timestamp; - - } - else { - printf("Encoded last entry: FINISHED\n"); } return true; @@ -319,9 +313,6 @@ mmsServer_handleReadJournalRequest( memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); - char stringBuf[100]; - MmsValue_printToBuffer(&rangeStart, stringBuf, 100); - hasRangeStartSpec = true; } else { @@ -350,9 +341,6 @@ mmsServer_handleReadJournalRequest( memcpy(rangeStop.value.binaryTime.buf, requestBuffer + bufPos, length); - char stringBuf[100]; - MmsValue_printToBuffer(&rangeStop, stringBuf, 100); - hasRangeStopSpec = true; } else { @@ -382,9 +370,6 @@ mmsServer_handleReadJournalRequest( memcpy(rangeStart.value.binaryTime.buf, requestBuffer + bufPos, length); - char stringBuf[100]; - MmsValue_printToBuffer(&rangeStart, stringBuf, 100); - hasTimeSpec = true; } else { @@ -428,7 +413,9 @@ mmsServer_handleReadJournalRequest( //TODO check if required fields are present if (hasNames == false) { - printf("MMS_SERVER: readJournal missing journal name\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal missing journal name\n"); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } @@ -441,7 +428,9 @@ mmsServer_handleReadJournalRequest( MmsDomain* mmsDomain = MmsDevice_getDomain(mmsDevice, domainId); if (mmsDomain == NULL) { - printf("Domain %s not found\n", domainId); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal domain %s not found\n", domainId); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } @@ -449,12 +438,15 @@ mmsServer_handleReadJournalRequest( MmsJournal mmsJournal = MmsDomain_getJournal(mmsDomain, logName); if (mmsJournal == NULL) { - printf("Journal %s not found\n", logName); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal journal %s not found\n", logName); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } - printf("Read journal %s ...\n", mmsJournal->name); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal - read journal %s ...\n", mmsJournal->name); struct sJournalEncoder encoder; @@ -476,7 +468,9 @@ mmsServer_handleReadJournalRequest( entryCallback, entryDataCallback, &encoder); } else { - printf("MMS_SERVER: readJournal missing valid argument combination\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: readJournal missing valid argument combination\n"); + mmsServer_writeMmsRejectPdu(&invokeId, MMS_ERROR_REJECT_REQUEST_INVALID_ARGUMENT, response); return; } diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 72f07795..b1b511f5 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -545,3 +545,5 @@ EXPORTS MmsConnection_readJournalStartAfter LogControlBlock_create Log_create + IedConnection_queryLogByTime + IedConnection_queryLogAfter diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 94415198..26594733 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -621,3 +621,6 @@ EXPORTS MmsConnection_readJournalStartAfter LogControlBlock_create Log_create + IedConnection_queryLogByTime + IedConnection_queryLogAfter + From 099ccfc0fa5d504e9e7e84404516b4fc1cfe2ddb Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Thu, 9 Jun 2016 14:58:32 +0200 Subject: [PATCH 25/36] - added log examples to examples makefile --- examples/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/Makefile b/examples/Makefile index 7492d071..38262219 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -10,6 +10,7 @@ EXAMPLE_DIRS += iec61850_client_example3 EXAMPLE_DIRS += iec61850_client_example4 EXAMPLE_DIRS += iec61850_client_example5 EXAMPLE_DIRS += iec61850_client_example_reporting +EXAMPLE_DIRS += iec61850_client_example_log EXAMPLE_DIRS += server_example1 EXAMPLE_DIRS += server_example2 EXAMPLE_DIRS += server_example3 @@ -23,6 +24,7 @@ EXAMPLE_DIRS += server_example_complex_array EXAMPLE_DIRS += server_example_61400_25 EXAMPLE_DIRS += server_example_threadless EXAMPLE_DIRS += server_example_setting_groups +EXAMPLE_DIRS += server_example_logging EXAMPLE_DIRS += goose_subscriber EXAMPLE_DIRS += goose_publisher EXAMPLE_DIRS += sv_subscriber @@ -44,6 +46,7 @@ MODEL_DIRS += server_example_complex_array MODEL_DIRS += server_example_61400_25 MODEL_DIRS += server_example_threadless MODEL_DIRS += server_example_setting_groups +MODEL_DIRS += server_example_logging MODEL_DIRS += iec61850_9_2_LE_example all: examples From 52e712437b916f5f13139333efae205c43e4a50a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 10 Jun 2016 11:28:43 +0200 Subject: [PATCH 26/36] - added some options to CDC_LPL_create - added CDC_DPL_create function --- src/iec61850/inc/iec61850_cdc.h | 65 +++++++++++++++++- src/iec61850/inc/iec61850_dynamic_model.h | 2 +- src/iec61850/inc/iec61850_model.h | 2 +- src/iec61850/server/model/cdc.c | 36 ++++++++++ .../server/model/config_file_parser.c | 28 ++++++++ src/vs/libiec61850-wo-goose.def | 1 + src/vs/libiec61850.def | 2 +- tools/model_generator/genconfig.jar | Bin 78699 -> 84530 bytes tools/model_generator/genmodel.jar | Bin 84127 -> 84505 bytes .../tools/DynamicModelGenerator.java | 42 ++++++++++- 10 files changed, 172 insertions(+), 6 deletions(-) diff --git a/src/iec61850/inc/iec61850_cdc.h b/src/iec61850/inc/iec61850_cdc.h index 6144bea5..139f0182 100644 --- a/src/iec61850/inc/iec61850_cdc.h +++ b/src/iec61850/inc/iec61850_cdc.h @@ -46,12 +46,20 @@ extern "C" { */ #define CDC_OPTION_PICS_SUBST (1 << 0) #define CDC_OPTION_BLK_ENA (1 << 1) + +/** Add d (description) data attribute */ #define CDC_OPTION_DESC (1 << 2) + +/** Add dU (unicode description) data attribute */ #define CDC_OPTION_DESC_UNICODE (1 << 3) +/** Add cdcNs and cdcName required when a CDC is an extension to the standard */ #define CDC_OPTION_AC_DLNDA (1 << 4) + +/** Add dataNs (data namespace) required for extended CDCs */ #define CDC_OPTION_AC_DLN (1 << 5) +/** Add the unit data attribute */ #define CDC_OPTION_UNIT (1 << 6) #define CDC_OPTION_FROZEN_VALUE (1 << 7) @@ -87,6 +95,17 @@ extern "C" { #define CDC_OPTION_ANGLE_REF (1 << 23) +/** Options that are only valid for DPL CDC */ +#define CDC_OPTION_DPL_HWREV (1 << 17) +#define CDC_OPTION_DPL_SWREV (1 << 18) +#define CDC_OPTION_DPL_SERNUM (1 << 19) +#define CDC_OPTION_DPL_MODEL (1 << 20) +#define CDC_OPTION_DPL_LOCATION (1 << 21) + +/** Add mandatory data attributes for LLN0 (e.g. LBL configRef) */ +#define CDC_OPTION_AC_LN0_M (1 << 24) +#define CDC_OPTION_AC_LN0_EX (1 << 25) +#define CDC_OPTION_AC_DLD_M (1 << 26) /** * \brief Control model types @@ -233,10 +252,54 @@ CDC_CMV_create(const char* dataObjectName, ModelNode* parent, uint32_t options); DataObject* CDC_SAV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat); - +/** + * \brief create a new LPL (Logical node name plate) CDC instance (data object) + * + * Allowed parent type is LogicalNode + * + * possible options: + * CDC_OPTION_AC_LN0_M (includes "configRev") + * CDC_OPTION_AC_LN0_EX (includes "ldNs") + * CDC_OPTION_AC_DLD_M (includes "lnNs") + * standard options: + * CDC_OPTION_DESC (includes "d") + * CDC_OPTION_DESC_UNICODE (include "du") + * CDC_OPTION_AC_DLNDA (include "cdcNs" and "cdcName") + * CDC_OPTION_AC_DLN (includes "dataNs") + * + * \param dataObjectName the name of the new object + * \param parent the parent of the new data object (either a LogicalNode or another DataObject) + * \param options bit mask to encode required optional elements + * + * \return new DataObject instance + */ DataObject* CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options); +/** + * \brief create a new DPL (Device name plate) CDC instance (data object) + * + * Allowed parent type is LogicalNode + * + * possible options: + * CDC_OPTION_DPL_HWREV (includes "hwRev") + * CDC_OPTION_DPL_SWREV (includes "swRev") + * CDC_OPTION_DPL_SERNUM (includes "serNum") + * CDC_OPTION_DPL_MODEL (includes "model") + * CDC_OPTION_DPL_LOCATION (includes "location") + * standard options: + * CDC_OPTION_AC_DLNDA (include "cdcNs" and "cdcName") + * CDC_OPTION_AC_DLN (includes "dataNs") + * + * \param dataObjectName the name of the new object + * \param parent the parent of the new data object (either a LogicalNode or another DataObject) + * \param options bit mask to encode required optional elements + * + * \return new DataObject instance + */ +DataObject* +CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options); + DataObject* CDC_HST_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint16_t maxPts); diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index bb82faa5..ee011dbe 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -169,7 +169,7 @@ ReportControlBlock_create(const char* name, LogicalNode* parent, char* rptId, bo * \param parent the parent LN. * \param dataSetName name (object reference) of the default data set or NULL if no data set * is set by default - * \param logRef name (object reference) of the default log or NULL if no log is set by default + * \param logRef name (object reference) of the default log or NULL if no log is set by default. THe LDname doesn't contain the IED name! * \param trgOps the trigger options supported by this LCB (bit set) * \param intgPd integrity period in milliseconds * \param logEna if true the log will be enabled by default, false otherwise diff --git a/src/iec61850/inc/iec61850_model.h b/src/iec61850/inc/iec61850_model.h index 961e640d..3a99b086 100644 --- a/src/iec61850/inc/iec61850_model.h +++ b/src/iec61850/inc/iec61850_model.h @@ -1,7 +1,7 @@ /* * model.h * - * Copyright 2013, 2014, 2015 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * diff --git a/src/iec61850/server/model/cdc.c b/src/iec61850/server/model/cdc.c index eb6806e9..acbcaadd 100644 --- a/src/iec61850/server/model/cdc.c +++ b/src/iec61850/server/model/cdc.c @@ -774,11 +774,47 @@ CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options) DataAttribute_create("vendor", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); DataAttribute_create("swRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + if (options & CDC_OPTION_AC_LN0_M) + DataAttribute_create("configRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_AC_LN0_EX) + DataAttribute_create("ldNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0); + + if (options & CDC_OPTION_AC_DLD_M) + DataAttribute_create("lnNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0); + CDC_addStandardOptions(newLPL, options); return newLPL; } +DataObject* +CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options) +{ + DataObject* newDPL = DataObject_create(dataObjectName, parent, 0); + + DataAttribute_create("vendor", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_HWREV) + DataAttribute_create("hwRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_SWREV) + DataAttribute_create("swRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_SERNUM) + DataAttribute_create("serNum", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_MODEL) + DataAttribute_create("model", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + if (options & CDC_OPTION_DPL_LOCATION) + DataAttribute_create("location", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0); + + CDC_addStandardOptions(newDPL, options); + + return newDPL; +} + /* Directional protection activation information (ACD) */ DataObject* CDC_ACD_create(const char* dataObjectName, ModelNode* parent, uint32_t options) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index e34258dc..a1ba90bb 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -215,6 +215,34 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) ReportControlBlock_create(nameString, currentLN, rptId, (bool) isBuffered, dataSetName, confRef, trgOps, options, bufTm, intgPd); } + else if (StringUtils_startsWith((char*) lineBuffer, "LC")) { + uint32_t trgOps; + uint32_t intgPd; + int logEna; + int withReasonCode; + + int matchedItems = sscanf((char*) lineBuffer, "LC(%s %s %s %u %u %i %i)", + nameString, nameString2, nameString3, &trgOps, &intgPd, &logEna, &withReasonCode); + + if (matchedItems < 7) goto exit_error; + + char* dataSet = NULL; + if (strcmp(nameString2, "-") != 0) + dataSet = nameString2; + + char* logRef = NULL; + if (strcmp(nameString3, "-") != 0) + logRef = nameString3; + + LogControlBlock_create(nameString, currentLN, dataSet, logRef, trgOps, intgPd, logEna, withReasonCode); + } + else if (StringUtils_startsWith((char*) lineBuffer, "LOG")) { + int matchedItems = sscanf((char*) lineBuffer, "LOG(%s)", nameString); + + if (matchedItems < 1) goto exit_error; + + Log_create(nameString, currentLN); + } else if (StringUtils_startsWith((char*) lineBuffer, "GC")) { uint32_t confRef; int fixedOffs; diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index b1b511f5..7411d01a 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -547,3 +547,4 @@ EXPORTS Log_create IedConnection_queryLogByTime IedConnection_queryLogAfter + CDC_DPL_create diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index 26594733..d80f83e4 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -623,4 +623,4 @@ EXPORTS Log_create IedConnection_queryLogByTime IedConnection_queryLogAfter - + CDC_DPL_create diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 9e3e43d5ede294468f6e815bf79c9a53658a62d3..eaf73e3970b8dd8b119657202f05ebbd83b798bc 100644 GIT binary patch literal 84530 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%0Am{@k}H%kbB!xmL`n+!7aJ*w?(Yt^e` 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 literal 78699 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

hM^yZ_C;nEu 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 diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index b90d292c8e6608df2fe9fed192053992ba0f513b..cd7d60b72207edcd74004e481918ee26686cb3ac 100644 GIT binary patch delta 9393 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 VqI;r$P@=1|l@@DR0=XRk{|8#C0XYBw delta 8939 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 Date: Fri, 10 Jun 2016 17:00:55 +0200 Subject: [PATCH 27/36] - file provider functions don't change file names; VMD_FILESTORE base part is now added in MMS server file service handling code - IedConnection_readObject and IedConnection_getVariableSpecification can now read whole LNs --- CHANGELOG | 8 ++ README | 6 ++ config/stack_config.h | 2 +- .../filesystem/linux/file_provider_linux.c | 62 ++------------ .../filesystem/win32/file_provider_win32.c | 66 +++------------ src/iec61850/server/mms_mapping/mms_mapping.c | 19 ++++- .../drivers/sqlite/log_storage_sqlite.c | 52 ++++++++---- src/mms/iso_mms/common/mms_value.c | 2 +- src/mms/iso_mms/server/mms_file_service.c | 80 +++++++++++++++---- .../iso_mms/server/mms_server_connection.c | 12 +-- 10 files changed, 157 insertions(+), 152 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 96ca7ca2..fd9d8018 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +Changes to version 0.9.2 +------------------------ +- client/server: support for MMS journals and IEC 61850 log service +- Abstract interface for log storage providers: logging_api.h and LogStorage class +- log storage implementation using sqlite +- added CDC_DPL_create function + + Changes to version 0.9.1 ------------------------ - client: added function MmsConnection_getMmsConnectionParameters diff --git a/README b/README index 5dea8a84..aff27611 100644 --- a/README +++ b/README @@ -90,6 +90,12 @@ C# client API A C#/.NET wrapper and examples and Visual Studio/MonoDevelop project files can be found in the dotnet folder. The examples and the C# wrapper API can be build and run on .NET or Mono. +Experimental Python bindings +---------------------------- + +The experimental Python binding can be created using SWIG with cmake. + +To enable the bindings you have to select the phyton configuration option with ccmake of cmake-gui. Commercial licenses ------------------- diff --git a/config/stack_config.h b/config/stack_config.h index ae1a58da..7a6ebac9 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -161,7 +161,7 @@ //#define CONFIG_DEFAULT_MMS_MODEL_NAME "LIBIEC61850" //#define CONFIG_DEFAULT_MMS_REVISION "0.9.2" -/* MMS virtual file store base path - where file services are looking for files */ +/* MMS virtual file store base path - where MMS file services are looking for files */ #define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/" /* Maximum number of open file per MMS connection (for MMS file read service) */ diff --git a/src/hal/filesystem/linux/file_provider_linux.c b/src/hal/filesystem/linux/file_provider_linux.c index df20c5c5..b807319c 100644 --- a/src/hal/filesystem/linux/file_provider_linux.c +++ b/src/hal/filesystem/linux/file_provider_linux.c @@ -34,52 +34,20 @@ #include "stack_config.h" -#ifndef CONFIG_VIRTUAL_FILESTORE_BASEPATH -#define CONFIG_VIRTUAL_FILESTORE_BASEPATH "./vmd-filestore/" -#endif - -static char fileBasePath[256]; -static bool fileBasePathInitialized = false; - struct sDirectoryHandle { DIR* handle; }; -static void -createFullPathFromFileName(char* fullPath, char* filename) -{ - if (!fileBasePathInitialized) { - strcpy(fileBasePath, CONFIG_VIRTUAL_FILESTORE_BASEPATH); - fileBasePathInitialized = true; - } - - strcpy(fullPath, fileBasePath); - - if (filename != NULL) - strcat(fullPath, filename); -} - - -void -FileSystem_setBasePath(char* basePath) -{ - strcpy(fileBasePath, basePath); - fileBasePathInitialized = true; -} FileHandle FileSystem_openFile(char* fileName, bool readWrite) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, fileName); - FileHandle newHandle = NULL; if (readWrite) - newHandle = (FileHandle) fopen(fullPath, "w"); + newHandle = (FileHandle) fopen(fileName, "w"); else - newHandle = (FileHandle) fopen(fullPath, "r"); + newHandle = (FileHandle) fopen(fileName, "r"); return newHandle; } @@ -99,11 +67,7 @@ FileSystem_closeFile(FileHandle handle) bool FileSystem_deleteFile(char* filename) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, filename); - - if (remove(fullPath) == 0) + if (remove(filename) == 0) return true; else return false; @@ -112,13 +76,7 @@ FileSystem_deleteFile(char* filename) bool FileSystem_renameFile(char* oldFilename, char* newFilename) { - char oldFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - char newFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(oldFullPath, oldFilename); - createFullPathFromFileName(newFullPath, newFilename); - - if (rename(oldFullPath, newFullPath) == 0) + if (rename(oldFilename, newFilename) == 0) return true; else return false; @@ -130,11 +88,7 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific { struct stat fileStats; - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; - - createFullPathFromFileName(fullPath, filename); - - if (stat(fullPath, &fileStats) == -1) + if (stat(filename, &fileStats) == -1) return false; if (lastModificationTimestamp != NULL) @@ -150,11 +104,7 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific DirectoryHandle FileSystem_openDirectory(char* directoryName) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, directoryName); - - DIR* dirHandle = opendir(fullPath); + DIR* dirHandle = opendir(directoryName); DirectoryHandle handle = NULL; diff --git a/src/hal/filesystem/win32/file_provider_win32.c b/src/hal/filesystem/win32/file_provider_win32.c index 95c42600..21511289 100644 --- a/src/hal/filesystem/win32/file_provider_win32.c +++ b/src/hal/filesystem/win32/file_provider_win32.c @@ -37,15 +37,6 @@ #include -#ifndef CONFIG_VIRTUAL_FILESTORE_BASEPATH -#define CONFIG_VIRTUAL_FILESTORE_BASEPATH ".\\vmd-filestore\\" -#endif - -//static char* fileBasePath = CONFIG_VIRTUAL_FILESTORE_BASEPATH; - -static char fileBasePath[256]; -static bool fileBasePathInitialized = false; - struct sDirectoryHandle { HANDLE handle; WIN32_FIND_DATAW findData; @@ -53,40 +44,16 @@ struct sDirectoryHandle { bool available; }; -static void -createFullPathFromFileName(char* fullPath, char* filename) -{ - if (!fileBasePathInitialized) { - strcpy(fileBasePath, CONFIG_VIRTUAL_FILESTORE_BASEPATH); - fileBasePathInitialized = true; - } - - strcpy(fullPath, fileBasePath); - - if (filename != NULL) - strcat(fullPath, filename); -} - -void -FileSystem_setBasePath(char* basePath) -{ - strcpy(fileBasePath, basePath); - fileBasePathInitialized = true; -} FileHandle FileSystem_openFile(char* fileName, bool readWrite) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, fileName); - FileHandle newHandle = NULL; if (readWrite) - newHandle = (FileHandle) fopen(fullPath, "wb"); + newHandle = (FileHandle) fopen(fileName, "wb"); else - newHandle = (FileHandle) fopen(fullPath, "rb"); + newHandle = (FileHandle) fopen(fileName, "rb"); return newHandle; } @@ -106,13 +73,9 @@ FileSystem_closeFile(FileHandle handle) bool FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModificationTimestamp) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; - - createFullPathFromFileName(fullPath, filename); - WIN32_FILE_ATTRIBUTE_DATA fad; - if (GetFileAttributesEx(fullPath, GetFileExInfoStandard, &fad) == 0) + if (GetFileAttributesEx(filename, GetFileExInfoStandard, &fad) == 0) return false; if (lastModificationTimestamp != NULL) { @@ -136,13 +99,12 @@ FileSystem_getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModific DirectoryHandle FileSystem_openDirectory(char* directoryName) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, directoryName); - DirectoryHandle dirHandle = (DirectoryHandle) GLOBAL_CALLOC(1, sizeof(struct sDirectoryHandle)); - strcat(fullPath, "\\*"); + char fullPath[MAX_PATH + 1]; + + strncpy(fullPath, directoryName, MAX_PATH - 3); + strncat(fullPath, "\\*", MAX_PATH); /* convert UTF-8 path name to WCHAR */ WCHAR unicodeFullPath[MAX_PATH + 1]; @@ -201,11 +163,7 @@ getNextDirectoryEntry(DirectoryHandle directory, bool* isDirectory) bool FileSystem_deleteFile(char* filename) { - char fullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(fullPath, filename); - - if (remove(fullPath) == 0) + if (remove(filename) == 0) return true; else return false; @@ -214,13 +172,7 @@ FileSystem_deleteFile(char* filename) bool FileSystem_renameFile(char* oldFilename, char* newFilename) { - char oldFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - char newFullPath[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 255]; - - createFullPathFromFileName(oldFullPath, oldFilename); - createFullPathFromFileName(newFullPath, newFilename); - - if (rename(oldFullPath, newFullPath) == 0) + if (rename(oldFilename, newFilename) == 0) return true; else return false; diff --git a/src/iec61850/server/mms_mapping/mms_mapping.c b/src/iec61850/server/mms_mapping/mms_mapping.c index 4a566188..b056b83b 100644 --- a/src/iec61850/server/mms_mapping/mms_mapping.c +++ b/src/iec61850/server/mms_mapping/mms_mapping.c @@ -2901,10 +2901,27 @@ MmsMapping_createMmsVariableNameFromObjectReference(const char* objectReference, return NULL; if (i == objRefLength) - i = 0; + i = 0; /* for the case when no LD name is present */ else i++; + + if (fc == IEC61850_FC_NONE) { + + int len = objRefLength - i; + + char* mmsVariableName; + + if (buffer == NULL) + mmsVariableName = (char*) GLOBAL_MALLOC(len); + else + mmsVariableName = buffer; + + strcpy(mmsVariableName, objectReference + i); + + return mmsVariableName; + } + char* fcString = FunctionalConstraint_toString(fc); if (fcString == NULL) diff --git a/src/logging/drivers/sqlite/log_storage_sqlite.c b/src/logging/drivers/sqlite/log_storage_sqlite.c index 7ef54173..07b0409c 100644 --- a/src/logging/drivers/sqlite/log_storage_sqlite.c +++ b/src/logging/drivers/sqlite/log_storage_sqlite.c @@ -76,6 +76,18 @@ static const char* GET_ENTRY_DATA = "select dataRef, value, reasonCode from Entr static const char* GET_OLD_ENTRY = "select * from Entries where entryID = (select min(entryID) from Entries where timeOfEntry = (select min(timeOfEntry) from Entries))"; static const char* GET_NEW_ENTRY = "select * from Entries where entryID = (select max(entryID) from Entries where timeOfEntry = (select max(timeOfEntry) from Entries))"; +static char* +copyStringInternal(const char* string) +{ + int newStringLength = strlen(string) + 1; + + char* newString = (char*) malloc(newStringLength); + + memcpy(newString, string, newStringLength); + + return newString; +} + LogStorage SqliteLogStorage_createInstance(const char* filename) { @@ -131,10 +143,11 @@ SqliteLogStorage_createInstance(const char* filename) if (rc != SQLITE_OK) goto exit_with_error; - LogStorage self = (LogStorage) GLOBAL_CALLOC(1, sizeof(struct sLogStorage)); + LogStorage self = (LogStorage) calloc(1, sizeof(struct sLogStorage)); + + SqliteLogStorage* instanceData = (SqliteLogStorage*) calloc(1, sizeof(struct sSqliteLogStorage)); - SqliteLogStorage* instanceData = (SqliteLogStorage*) GLOBAL_CALLOC(1, sizeof(struct sSqliteLogStorage)); - instanceData->filename = copyString(filename); + instanceData->filename = copyStringInternal(filename); instanceData->db = db; instanceData->insertEntryStmt = insertEntryStmt; instanceData->insertEntryDataStmt = insertEntryDataStmt; @@ -263,8 +276,10 @@ getEntryData(LogStorage self, uint64_t entryID, LogEntryDataCallback entryDataCa rc = sqlite3_bind_int64(instanceData->getEntryData, 1, entryID); - if (rc != SQLITE_OK) - printf("getEntryData 1 rc:%i\n", rc); + if (rc != SQLITE_OK) { + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - getEntryData 1 rc:%i\n", rc); + } bool sendFinalEvent = true; @@ -292,7 +307,8 @@ getEntryData(LogStorage self, uint64_t entryID, LogEntryDataCallback entryDataCa rc = sqlite3_reset(instanceData->getEntryData); if (rc != SQLITE_OK) - printf("getEntryData reset rc:%i\n", rc); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - getEntryData reset rc:%i\n", rc); } static bool @@ -308,12 +324,14 @@ SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t end rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 1, startingTime); if (rc != SQLITE_OK) - printf("SqliteLogStorage_getEntries 1 rc:%i\n", rc); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries 1 rc:%i\n", rc); rc = sqlite3_bind_int64(instanceData->getEntriesWithRange, 2, endingTime); if (rc != SQLITE_OK) - printf("SqliteLogStorage_getEntries 2 rc:%i\n", rc); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries 2 rc:%i\n", rc); bool sendFinalEvent = true; @@ -341,7 +359,8 @@ SqliteLogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t end rc = sqlite3_reset(instanceData->getEntriesWithRange); if (rc != SQLITE_OK) - printf("SqliteLogStorage_getEntries reset rc:%i\n", rc); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntries reset rc:%i\n", rc); return true; } @@ -407,7 +426,8 @@ SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_ rc = sqlite3_bind_int64(instanceData->getEntriesAfter, 1, entryID); if (rc != SQLITE_OK) - printf("SqliteLogStorage_getEntriesAfter 1 rc:%i\n", rc); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntriesAfter 1 rc:%i\n", rc); bool sendFinalEvent = true; @@ -433,7 +453,8 @@ SqliteLogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_ rc = sqlite3_reset(instanceData->getEntriesAfter); if (rc != SQLITE_OK) - printf("SqliteLogStorage_getEntriesAfter reset rc:%i\n", rc); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage_getEntriesAfter reset rc:%i\n", rc); return true; } @@ -452,11 +473,12 @@ SqliteLogStorage_destroy(LogStorage self) sqlite3_finalize(instanceData->getNewEntry); if (sqlite3_close(instanceData->db) != SQLITE_OK) - printf("SqliteLogStorage: failed to close database %s!\n", instanceData->filename); + if (DEBUG_LOG_STORAGE_DRIVER) + printf("LOG_STORAGE_DRIVER: sqlite - SqliteLogStorage: failed to close database %s!\n", instanceData->filename); - GLOBAL_FREEMEM(instanceData->filename); - GLOBAL_FREEMEM(instanceData); - GLOBAL_FREEMEM(self); + free(instanceData->filename); + free(instanceData); + free(self); } diff --git a/src/mms/iso_mms/common/mms_value.c b/src/mms/iso_mms/common/mms_value.c index ce1871bd..d2eca37f 100644 --- a/src/mms/iso_mms/common/mms_value.c +++ b/src/mms/iso_mms/common/mms_value.c @@ -1605,7 +1605,7 @@ MmsValue_setBinaryTime(MmsValue* self, uint64_t timestamp) if (timestamp > 441763200000LL) mmsTime = timestamp - (441763200000LL); else - timestamp = 0; + mmsTime = 0; uint8_t* binaryTimeBuf = self->value.binaryTime.buf; diff --git a/src/mms/iso_mms/server/mms_file_service.c b/src/mms/iso_mms/server/mms_file_service.c index 2e7a2820..1f25ba73 100644 --- a/src/mms/iso_mms/server/mms_file_service.c +++ b/src/mms/iso_mms/server/mms_file_service.c @@ -123,13 +123,69 @@ encodeFileAttributes(uint8_t tag, uint32_t fileSize, char* gtString, uint8_t* bu } } +static void +createExtendedFilename(char* extendedFileName, char* fileName) +{ + strcpy(extendedFileName, CONFIG_VIRTUAL_FILESTORE_BASEPATH); + strncat(extendedFileName, fileName, sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256); +} + +static bool +getFileInfo(char* filename, uint32_t* fileSize, uint64_t* lastModificationTimestamp) +{ + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, filename); + + return FileSystem_getFileInfo(extendedFileName, fileSize, lastModificationTimestamp); +} + +static FileHandle +openFile(char* fileName, bool readWrite) +{ + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, fileName); + + return FileSystem_openFile(extendedFileName, readWrite); +} + +static DirectoryHandle +openDirectory(char* directoryName) +{ + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, directoryName); + + return FileSystem_openDirectory(extendedFileName); +} + +static bool +renameFile(char* oldFilename, char* newFilename) { + char extendedOldFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + char extendedNewFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedOldFileName, oldFilename); + createExtendedFilename(extendedNewFileName, newFilename); + + return FileSystem_renameFile(extendedOldFileName, extendedNewFileName); +} + +static bool +deleteFile(char* fileName) { + char extendedFileName[sizeof(CONFIG_VIRTUAL_FILESTORE_BASEPATH) + 256]; + + createExtendedFilename(extendedFileName, fileName); + + return FileSystem_deleteFile(extendedFileName); +} static void createFileOpenResponse(uint32_t invokeId, ByteBuffer* response, char* fullPath, MmsFileReadStateMachine* frsm) { uint64_t msTime; - FileSystem_getFileInfo(fullPath, &(frsm->fileSize), &msTime); + getFileInfo(fullPath, &(frsm->fileSize), &msTime); char gtString[30]; @@ -216,7 +272,7 @@ mmsServer_handleFileDeleteRequest( if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: Delete file (%s)\n", filename); - if (!FileSystem_getFileInfo(filename, NULL, NULL)) { + if (!getFileInfo(filename, NULL, NULL)) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: File (%s) not found\n", filename); @@ -224,7 +280,7 @@ mmsServer_handleFileDeleteRequest( return; } - if (!FileSystem_deleteFile(filename)) { + if (!deleteFile(filename)) { if (DEBUG_MMS_SERVER) printf("MMS_SERVER: mms_file_service.c: Delete file (%s) failed\n", filename); @@ -284,7 +340,7 @@ mmsServer_handleFileOpenRequest( MmsFileReadStateMachine* frsm = getFreeFrsm(connection); if (frsm != NULL) { - FileHandle fileHandle = FileSystem_openFile(filename, false); + FileHandle fileHandle = openFile(filename, false); if (fileHandle != NULL) { frsm->fileHandle = fileHandle; @@ -441,9 +497,10 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire { int directoryNameLength = strlen(directoryName); - DirectoryHandle directory = FileSystem_openDirectory(directoryName); + DirectoryHandle directory = openDirectory(directoryName); if (directory != NULL) { + bool isDirectory; char* fileName = FileSystem_readDirectory(directory, &isDirectory); @@ -457,13 +514,7 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire strcat(directoryName, fileName); - if (isDirectory) { - bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows); - } - else { - bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows); - - } + bufPos = addFileEntriesToResponse(buffer, bufPos, maxBufSize, directoryName, continueAfterFileName, moreFollows); if (*moreFollows == true) break; @@ -485,8 +536,7 @@ addFileEntriesToResponse(uint8_t* buffer, int bufPos, int maxBufSize, char* dire uint32_t fileSize; - if (FileSystem_getFileInfo(directoryName, &fileSize, &msTime)) { - + if (getFileInfo(directoryName, &fileSize, &msTime)) { char gtString[30]; Conversions_msTimeToGeneralizedTime(msTime, (uint8_t*) gtString); @@ -642,7 +692,7 @@ mmsServer_handleFileRenameRequest( } if ((strlen(currentFileName) != 0) && (strlen(newFileName) != 0)) { - if (FileSystem_renameFile(currentFileName, newFileName)){ + if (renameFile(currentFileName, newFileName)){ /* send positive response */ createNullResponseExtendedTag(invokeId, response, 0x4b); } diff --git a/src/mms/iso_mms/server/mms_server_connection.c b/src/mms/iso_mms/server/mms_server_connection.c index d4057621..eced91b7 100644 --- a/src/mms/iso_mms/server/mms_server_connection.c +++ b/src/mms/iso_mms/server/mms_server_connection.c @@ -1,7 +1,7 @@ /* * mms_server_connection.c * - * Copyright 2013 Michael Zillgith + * Copyright 2013-2016 Michael Zillgith * * This file is part of libIEC61850. * @@ -108,8 +108,6 @@ handleConfirmedRequestPdu( return; } - if (DEBUG_MMS_SERVER) printf("tag %02x extended tag: %i size: %i\n", tag, extendedTag, length); - if (extendedTag) { switch(tag) { @@ -155,7 +153,8 @@ handleConfirmedRequestPdu( switch(tag) { case 0x02: /* invoke Id */ invokeId = BerDecoder_decodeUint32(buffer, length, bufPos); - if (DEBUG_MMS_SERVER) printf("invokeId: %i\n", invokeId); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: received request with invokeId: %i\n", invokeId); self->lastInvokeId = invokeId; break; @@ -256,7 +255,7 @@ MmsServerConnection_parseMessage(MmsServerConnection self, ByteBuffer* message, return MMS_ERROR; if (DEBUG_MMS_SERVER) - printf("mms_server: recvd MMS-PDU type: %02x size: %i\n", pduType, pduLength); + printf("MMS_SERVER: recvd MMS-PDU type: %02x size: %i\n", pduType, pduLength); switch (pduType) { case 0xa8: /* Initiate request PDU */ @@ -272,7 +271,8 @@ MmsServerConnection_parseMessage(MmsServerConnection self, ByteBuffer* message, retVal = MMS_CONCLUDE; break; case 0xa4: /* Reject PDU - silently ignore */ - if (DEBUG) printf("received reject PDU!\n"); + if (DEBUG_MMS_SERVER) + printf("MMS_SERVER: received reject PDU!\n"); retVal = MMS_OK; break; default: From b83e174ef96b17138b1c4a62438728acb6aeb07c Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 10 Jun 2016 17:51:24 +0200 Subject: [PATCH 28/36] - added documentation for the log storage SPI --- src/doxygen.config | 1 + src/logging/logging_api.h | 104 +++++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/doxygen.config b/src/doxygen.config index cae69572..6dc0c632 100644 --- a/src/doxygen.config +++ b/src/doxygen.config @@ -237,6 +237,7 @@ INPUT += "hal/inc/hal_thread.h" INPUT += "hal/inc/hal_ethernet.h" INPUT += "hal/inc/hal_filesystem.h" INPUT += "hal/inc/hal_time.h" +INPUT += "logging/logging_api.h" INPUT_ENCODING = UTF-8 diff --git a/src/logging/logging_api.h b/src/logging/logging_api.h index 92f77c51..403c3fb3 100644 --- a/src/logging/logging_api.h +++ b/src/logging/logging_api.h @@ -31,16 +31,49 @@ extern "C" { #include #include + +/** \addtogroup server_api_group + * @{ + */ + +/** + * @defgroup LOGGING_SPI Service provider interface (SPI) for log storage implementations + * + * This interface has to be implemented by the log storage provider. The Log storage provider + * has to provide a specific constructor that creates an instance of LogStorage by allocating + * the required memory fot the struct sLogStorage data structure and populate the function + * pointers with provider specific implementation functions. + * + * @{ + */ + +/** The LogStorage object handle */ typedef struct sLogStorage* LogStorage; /** + * \brief Will be called for each new LogEntry by the getEntries and getEntriesAfter functions * - * \param moreFollow - more data will follow - if false, the data is not valid + * \param parameter - a user provided parameter that is passed to the callback handler + * \param timestamp - the entry timestamp of the LogEntry + * \param entryID - the entryID of the LogEntry + * \param moreFollow - more data will follow - if false, the data is not valid and no more data will follow * * \return true ready to receive new data, false abort operation */ typedef bool (*LogEntryCallback) (void* parameter, uint64_t timestamp, uint64_t entryID, bool moreFollow); +/** + * \brief Will be called for each new LogEntryData by the getEntries and getEntriesAfter functions + * + * \param parameter - a user provided parameter that is passed to the callback handler + * \param dataRef - the data reference of the LogEntryData + * \param data - the data content as an unstructured binary data block + * \param dataSize - the size of the binary data block + * \param reasonCode - the reasonCode of the LogEntryData + * \param moreFollow - more data will follow - if false, the data is not valid and no more data will follow + * + * \return true ready to receive new data, false abort operation + */ typedef bool (*LogEntryDataCallback) (void* parameter, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode, bool moreFollow); struct sLogStorage { @@ -64,28 +97,95 @@ struct sLogStorage { }; - +/** + * \brief Add an entry to the log + * + * \param self the pointer of the LogStorage instance + * \param timestamp the entry time of the new entry + * + * \return the entryID of the new entry + */ uint64_t LogStorage_addEntry(LogStorage self, uint64_t timestamp); +/** + * \brief Add new entry data to an existing log entry + * + * \param self the pointer of the LogStorage instance + * \param entryID the ID of the log entry where the data will be added + * \param dataRef the data reference of the log entry data + * \param data the data content as an unstructured binary data block + * \param dataSize - the size of the binary data block + * \param reasonCode - the reasonCode of the LogEntryData + * + * \return true if the entry data was successfully added, false otherwise + */ bool LogStorage_addEntryData(LogStorage self, uint64_t entryID, const char* dataRef, uint8_t* data, int dataSize, uint8_t reasonCode); +/** + * \brief Get log entries specified by a time range + * + * \param self the pointer of the LogStorage instance + * \param startingTime start time of the time range + * \param endingTime end time of the time range + * \param entryCallback callback function to be called for each new log entry + * \param entryDataCallback callback function to be called for each new log entry data + * \param parameter - a user provided parameter that is passed to the callback handler + * + * \return true if the request has been successful, false otherwise + */ bool LogStorage_getEntries(LogStorage self, uint64_t startingTime, uint64_t endingTime, LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); +/** + * \brief Get log entries specified by a start log entry + * + * The request will return all log entries that where entered into the log after the + * log entry specified by startingTime and entryID. + * + * \param self the pointer of the LogStorage instance + * \param startingTime timestamp of the start log entry + * \param entryID entryID of the start log entry + * \param entryCallback callback function to be called for each new log entry + * \param entryDataCallback callback function to be called for each new log entry data + * \param parameter - a user provided parameter that is passed to the callback handler + * + * \return true if the request has been successful, false otherwise + */ bool LogStorage_getEntriesAfter(LogStorage self, uint64_t startingTime, uint64_t entryID, LogEntryCallback entryCallback, LogEntryDataCallback entryDataCallback, void* parameter); +/** + * \brief Get the entry time and entryID of the oldest and the newest log entries + * + * This function is used to update the log status information in the LCB + * + * \param self the pointer of the LogStorage instance + * \param newEntry pointer to store the entryID of the newest entry + * \param newEntryTime pointer to store the entry time of the newest entry + * \param oldEntry pointer to store the entryID of the oldest entry + * \param oldEntryTime pointer to store the entry time of the oldest entry + * + */ bool LogStorage_getOldestAndNewestEntries(LogStorage self, uint64_t* newEntry, uint64_t* newEntryTime, uint64_t* oldEntry, uint64_t* oldEntryTime); +/** + * \brief Destroy the LogStorage instance and free all related resources + * + * \param self the pointer of the LogStorage instance + */ void LogStorage_destroy(LogStorage self); +/**@}*/ + +/**@}*/ + #ifdef __cplusplus } #endif From 6bf13423cb889246422e0a1712ebeb2bc0824ed7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Fri, 10 Jun 2016 21:28:52 +0200 Subject: [PATCH 29/36] - added missing cmake file for server_example_logging --- .../server_example_logging/CMakeLists.txt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 examples/server_example_logging/CMakeLists.txt diff --git a/examples/server_example_logging/CMakeLists.txt b/examples/server_example_logging/CMakeLists.txt new file mode 100644 index 00000000..598cfaef --- /dev/null +++ b/examples/server_example_logging/CMakeLists.txt @@ -0,0 +1,41 @@ + +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/sqlite/sqlite3.h") +message("Found sqlite source code -> compile sqlite-log driver with static sqlite library") + +include_directories( + . + ${CMAKE_SOURCE_DIR}/third_party/sqlite +) + +set(server_example_SRCS + server_example_logging.c + static_model.c + ${CMAKE_SOURCE_DIR}/src/logging/drivers/sqlite/log_storage_sqlite.c +) + +set(sqlite_SRCS + ${CMAKE_SOURCE_DIR}/third_party/sqlite/sqlite3.c +) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION") + +IF(WIN32) +set_source_files_properties(${server_example_SRCS} + PROPERTIES LANGUAGE CXX) +ENDIF(WIN32) + +add_executable(server_example_logging + ${server_example_SRCS} + ${sqlite_SRCS} +) + +target_link_libraries(server_example_logging + iec61850 +) + +ELSE() + +message("server-example-logging: sqlite not found") + +ENDIF() + From 325c3e0b7e437ad187165fcfdeb9968f6193d9e1 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 11 Jun 2016 07:39:10 +0200 Subject: [PATCH 30/36] - removed FileSystem_setBasePath from windows export files - ISO server: fixed race problem when opening/closing connections in multithreaded configuration --- src/mms/iso_server/iso_server.c | 131 +++++++++++++++++--------------- src/vs/libiec61850-wo-goose.def | 1 - src/vs/libiec61850.def | 1 - 3 files changed, 70 insertions(+), 63 deletions(-) diff --git a/src/mms/iso_server/iso_server.c b/src/mms/iso_server/iso_server.c index d67f31c0..f32bc31d 100644 --- a/src/mms/iso_server/iso_server.c +++ b/src/mms/iso_server/iso_server.c @@ -67,24 +67,23 @@ struct sIsoServer { #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) LinkedList openClientConnections; - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore openClientConnectionsMutex; /* mutex for openClientConnections list */ -#endif - #else IsoConnection* openClientConnections; #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore userLock; +#endif +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + Semaphore openClientConnectionsMutex; /* mutex for openClientConnections list */ Semaphore connectionCounterMutex; #endif + int connectionCounter; }; -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) static inline void lockClientConnections(IsoServer self) { @@ -102,6 +101,18 @@ static void addClientConnection(IsoServer self, IsoConnection connection) { +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + Semaphore_wait(self->connectionCounterMutex); +#endif + + self->connectionCounter++; + if (DEBUG_ISO_SERVER) + printf("IsoServer: increase connection counter to %i!\n", self->connectionCounter); + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + lockClientConnections(self); +#endif + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) LinkedList_add(self->openClientConnections, connection); #else @@ -118,30 +129,37 @@ addClientConnection(IsoServer self, IsoConnection connection) } } #endif + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + Semaphore_post(self->connectionCounterMutex); +#endif + } static void removeClientConnection(IsoServer self, IsoConnection connection) { + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + lockClientConnections(self); +#endif + #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) #if (CONFIG_MMS_SINGLE_THREADED == 0) -#if (CONFIG_MMS_THREADLESS_STACK != 1) - lockClientConnections(self); -#endif - LinkedList_remove(self->openClientConnections, connection); -#if (CONFIG_MMS_THREADLESS_STACK != 1) - unlockClientConnections(self); -#endif - #endif /* (CONFIG_MMS_SINGLE_THREADED == 0) */ #else + int i; for (i = 0; i < CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS; i++) { @@ -155,18 +173,22 @@ removeClientConnection(IsoServer self, IsoConnection connection) } } #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif } static void closeAllOpenClientConnections(IsoServer self) { -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) - -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); #endif +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) + LinkedList openConnection = LinkedList_getNext(self->openClientConnections); while (openConnection != NULL) { IsoConnection isoConnection = (IsoConnection) openConnection->data; @@ -186,11 +208,6 @@ closeAllOpenClientConnections(IsoServer self) self->openClientConnections = NULL; #endif - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - unlockClientConnections(self); -#endif - #else int i; @@ -206,7 +223,12 @@ closeAllOpenClientConnections(IsoServer self) } } +#endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); #endif + } static void @@ -214,7 +236,7 @@ handleClientConnections(IsoServer self) { #if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); #endif @@ -237,12 +259,15 @@ handleClientConnections(IsoServer self) openConnection = LinkedList_getNext(openConnection); } -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) unlockClientConnections(self); #endif #else +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + lockClientConnections(self); +#endif int i; @@ -260,6 +285,11 @@ handleClientConnections(IsoServer self) } } + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif + #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ } @@ -319,8 +349,6 @@ handleIsoConnections(IsoServer self) IsoConnection isoConnection = IsoConnection_create(connectionSocket, self); - private_IsoServer_increaseConnectionCounter(self); - addClientConnection(self, isoConnection); self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter, @@ -357,8 +385,6 @@ handleIsoConnectionsThreadless(IsoServer self) IsoConnection isoConnection = IsoConnection_create(connectionSocket, self); - private_IsoServer_increaseConnectionCounter(self); - addClientConnection(self, isoConnection); self->connectionHandler(ISO_CONNECTION_OPENED, self->connectionHandlerParameter, @@ -419,12 +445,9 @@ IsoServer_create() GLOBAL_CALLOC(CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS, sizeof(IsoConnection)); #endif -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) self->connectionCounterMutex = Semaphore_create(1); -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) self->openClientConnectionsMutex = Semaphore_create(1); -#endif - #endif /* (CONFIG_MMS_THREADLESS_STACK != 1) */ self->connectionCounter = 0; @@ -525,12 +548,13 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) handles = Handleset_new(); if (handles != NULL) { -#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); #endif +#if (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) + LinkedList openConnection = LinkedList_getNext(self->openClientConnections); LinkedList lastConnection = self->openClientConnections; @@ -550,10 +574,6 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) lastConnection = lastConnection->next; } -#if (CONFIG_MMS_THREADLESS_STACK != 1) - unlockClientConnections(self); -#endif - #else int i; @@ -570,6 +590,11 @@ IsoServer_waitReady(IsoServer self, unsigned int timeoutMs) } } } + +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) + unlockClientConnections(self); +#endif + #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ Handleset_addSocket(handles, self->serverSocket); result = Handleset_waitReady(handles, timeoutMs); @@ -671,42 +696,26 @@ IsoServer_destroy(IsoServer self) LinkedList_destroyStatic(self->openClientConnections); #endif /* (CONFIG_MMS_SINGLE_THREADED == 1) */ -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) lockClientConnections(self); - Semaphore_destroy(self->openClientConnectionsMutex); #endif #else GLOBAL_FREEMEM(self->openClientConnections); #endif /* (CONFIG_MAXIMUM_TCP_CLIENT_CONNECTIONS == -1) */ -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_destroy(self->connectionCounterMutex); + Semaphore_destroy(self->openClientConnectionsMutex); #endif GLOBAL_FREEMEM(self); } -void -private_IsoServer_increaseConnectionCounter(IsoServer self) -{ -#if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_wait(self->connectionCounterMutex); -#endif - - self->connectionCounter++; - if (DEBUG_ISO_SERVER) - printf("IsoServer: increase connection counter to %i!\n", self->connectionCounter); - -#if (CONFIG_MMS_THREADLESS_STACK != 1) - Semaphore_post(self->connectionCounterMutex); -#endif -} - void private_IsoServer_decreaseConnectionCounter(IsoServer self) { -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_wait(self->connectionCounterMutex); #endif @@ -715,7 +724,7 @@ private_IsoServer_decreaseConnectionCounter(IsoServer self) if (DEBUG_ISO_SERVER) printf("IsoServer: decrease connection counter to %i!\n", self->connectionCounter); -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_post(self->connectionCounterMutex); #endif } @@ -725,13 +734,13 @@ private_IsoServer_getConnectionCounter(IsoServer self) { int connectionCounter; -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_wait(self->connectionCounterMutex); #endif connectionCounter = self->connectionCounter; -#if (CONFIG_MMS_THREADLESS_STACK != 1) +#if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED == 0) Semaphore_post(self->connectionCounterMutex); #endif diff --git a/src/vs/libiec61850-wo-goose.def b/src/vs/libiec61850-wo-goose.def index 7411d01a..1113aa2e 100644 --- a/src/vs/libiec61850-wo-goose.def +++ b/src/vs/libiec61850-wo-goose.def @@ -113,7 +113,6 @@ EXPORTS FileSystem_readDirectory @302 FileSystem_readFile @303 FileSystem_renameFile @304 - FileSystem_setBasePath @305 FunctionalConstraint_fromString @313 FunctionalConstraint_toString @314 GSEControlBlock_create @316 diff --git a/src/vs/libiec61850.def b/src/vs/libiec61850.def index d80f83e4..7d0c71c2 100644 --- a/src/vs/libiec61850.def +++ b/src/vs/libiec61850.def @@ -113,7 +113,6 @@ EXPORTS FileSystem_readDirectory @302 FileSystem_readFile @303 FileSystem_renameFile @304 - FileSystem_setBasePath @305 FunctionalConstraint_fromString @313 FunctionalConstraint_toString @314 GSEControlBlock_create @316 From 9f96006ffbcc41dcb1a6b5a9db85a548c04682ad Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 11 Jun 2016 10:11:14 +0200 Subject: [PATCH 31/36] - added logs and LCBs to config file example - fixed parser bug in config_file_parser.c --- CHANGELOG | 3 + .../{vmd-filestore => }/model.cfg | 9 +- .../simpleIO_direct_control_goose.icd | 14 +- .../simpleIO_direct_control.icd | 573 +++++++++--------- .../server/model/config_file_parser.c | 4 + tools/model_generator/genmodel.jar | Bin 84505 -> 84529 bytes tools/model_generator/modelviewer.jar | Bin 84105 -> 84523 bytes 7 files changed, 323 insertions(+), 280 deletions(-) rename examples/server_example_config_file/{vmd-filestore => }/model.cfg (95%) diff --git a/CHANGELOG b/CHANGELOG index fd9d8018..e2d99d48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,10 @@ Changes to version 0.9.2 - client/server: support for MMS journals and IEC 61850 log service - Abstract interface for log storage providers: logging_api.h and LogStorage class - log storage implementation using sqlite +- server: negative delete data set response is now compatible with new test procedures (TPCL 1.1) +- FileSystem API is now intended to access the complete file system. Path translation for VMD filestore is done by MMS file service implementation (removed FileSystem_setBasePath function) - added CDC_DPL_create function +- MMS server: fixed raced condition when opening/closing connections in multi-threaded configuration. Changes to version 0.9.1 diff --git a/examples/server_example_config_file/vmd-filestore/model.cfg b/examples/server_example_config_file/model.cfg similarity index 95% rename from examples/server_example_config_file/vmd-filestore/model.cfg rename to examples/server_example_config_file/model.cfg index 58fdeaee..02feeb04 100644 --- a/examples/server_example_config_file/vmd-filestore/model.cfg +++ b/examples/server_example_config_file/model.cfg @@ -1,8 +1,3 @@ -Dynamic model generator -parse data type templates ... -parse IED section ... -parse communication section ... -Found connectedAP accessPoint1 for IED simpleIO MODEL(simpleIO){ LD(GenericIO){ LN(LLN0){ @@ -42,6 +37,10 @@ DE(GGIO1$MX$AnIn4); } RC(EventsRCB01 Events 0 Events 1 24 111 50 1000); RC(AnalogValuesRCB01 AnalogValues 0 AnalogValues 1 24 111 50 1000); +LC(EventLog Events GenericIO/LLN0$EventLog 19 0 0 1) +LC(GeneralLog - - 19 0 0 1) +LOG(GeneralLog) +LOG(EventLog) GC(gcbEvents events Events 2 0 -1 -1 ){ PA(4 273 4096 010ccd010001); } diff --git a/examples/server_example_config_file/simpleIO_direct_control_goose.icd b/examples/server_example_config_file/simpleIO_direct_control_goose.icd index e66f9a95..72552c45 100644 --- a/examples/server_example_config_file/simpleIO_direct_control_goose.icd +++ b/examples/server_example_config_file/simpleIO_direct_control_goose.icd @@ -67,7 +67,19 @@ - + + + + + + + + + + + + + diff --git a/examples/server_example_logging/simpleIO_direct_control.icd b/examples/server_example_logging/simpleIO_direct_control.icd index b48a383e..216f1f76 100644 --- a/examples/server_example_logging/simpleIO_direct_control.icd +++ b/examples/server_example_logging/simpleIO_direct_control.icd @@ -1,281 +1,306 @@ -

-
- - - Station bus - 10 - -
-

10.0.0.2

-

255.255.255.0

-

10.0.0.1

-

0001

-

00000001

-

0001

-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+ + + Station bus + 10 + +
+

10.0.0.2

+

255.255.255.0

+

10.0.0.1

+

0001

+

00000001

+

0001

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + - - + + + + + + + + + + - - - status-only - - - - - - MZ Automation - - - 0.7.3 - - - libiec61850 server example - - - - - - - - status-only - - - - - direct-with-normal-security - - - - - direct-with-normal-security - - - - - direct-with-normal-security - - - - - direct-with-normal-security - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status-only - direct-with-normal-security - sbo-with-normal-security - direct-with-enhanced-security - sbo-with-enhanced-security - - - not-supported - bay-control - station-control - remote-control - automatic-bay - automatic-station - automatic-remote - maintenance - process - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + MZ Automation + + + 0.7.3 + + + libiec61850 server example + + +
+ + + + + status-only + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index a1ba90bb..1d4b67dc 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -241,6 +241,10 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) if (matchedItems < 1) goto exit_error; + /* remove trailing ')' character */ + int nameLen = strlen(nameString); + nameString[nameLen - 1] = 0; + Log_create(nameString, currentLN); } else if (StringUtils_startsWith((char*) lineBuffer, "GC")) { diff --git a/tools/model_generator/genmodel.jar b/tools/model_generator/genmodel.jar index cd7d60b72207edcd74004e481918ee26686cb3ac..d9a0b3fb232cde40c64a32a9c4497a4ab005815d 100644 GIT binary patch delta 9328 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 y>%G&6W+)YAlfe delta 9400 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

yi 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 Date: Sat, 11 Jun 2016 12:30:15 +0200 Subject: [PATCH 32/36] - fixed problem in getNameList service when no logs are present --- CHANGELOG | 5 +++-- demos/beaglebone/static_model.c | 6 +++--- demos/beaglebone/static_model.h | 2 +- src/mms/iso_mms/server/mms_domain.c | 15 +++++++++------ src/mms/iso_mms/server/mms_get_namelist_service.c | 13 ++++++++----- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e2d99d48..376ac936 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,8 +6,9 @@ Changes to version 0.9.2 - server: negative delete data set response is now compatible with new test procedures (TPCL 1.1) - FileSystem API is now intended to access the complete file system. Path translation for VMD filestore is done by MMS file service implementation (removed FileSystem_setBasePath function) - added CDC_DPL_create function -- MMS server: fixed raced condition when opening/closing connections in multi-threaded configuration. - +- MMS server: fixed race condition when opening/closing connections in multi-threaded configuration. +- client: IedConnection_readObject and IedConnection_getVariableSpecification functions can now be applied to whole LNs +- client: GetLogicalNodeDirectory for data set and log ACSI classes will not use cached model data but request the information from server. This way the client can get up to date information about newly created dynamic data sets Changes to version 0.9.1 ------------------------ diff --git a/demos/beaglebone/static_model.c b/demos/beaglebone/static_model.c index 1010b5c6..50ce8fbb 100644 --- a/demos/beaglebone/static_model.c +++ b/demos/beaglebone/static_model.c @@ -1,7 +1,7 @@ /* * static_model.c * - * automatically generated from beagle_demo.icd + * automatically generated from beagle_demo.iid */ #include "static_model.h" @@ -1257,7 +1257,7 @@ DataAttribute iedModel_GenericIO_GGIO1_SPCSO2_Cancel_ctlVal = { NULL, 0, IEC61850_FC_CO, - IEC61850_INT8, + IEC61850_BOOLEAN, 0, NULL, 0}; @@ -2279,7 +2279,7 @@ DataAttribute iedModel_GenericIO_TIM_GAPC1_OpCntRs_Oper_ctlVal = { NULL, 0, IEC61850_FC_CO, - IEC61850_BOOLEAN, + IEC61850_INT32, 0, NULL, 0}; diff --git a/demos/beaglebone/static_model.h b/demos/beaglebone/static_model.h index a50ade81..8409861e 100644 --- a/demos/beaglebone/static_model.h +++ b/demos/beaglebone/static_model.h @@ -1,7 +1,7 @@ /* * static_model.h * - * automatically generated from beagle_demo.icd + * automatically generated from beagle_demo.iid */ #ifndef STATIC_MODEL_H_ diff --git a/src/mms/iso_mms/server/mms_domain.c b/src/mms/iso_mms/server/mms_domain.c index 689fcc01..57bc35ac 100644 --- a/src/mms/iso_mms/server/mms_domain.c +++ b/src/mms/iso_mms/server/mms_domain.c @@ -88,16 +88,19 @@ MmsDomain_addJournal(MmsDomain* self, const char* name) MmsJournal MmsDomain_getJournal(MmsDomain* self, const char* name) { - LinkedList journal = LinkedList_getNext(self->journals); + if (self->journals != NULL) { - while (journal != NULL) { + LinkedList journal = LinkedList_getNext(self->journals); - MmsJournal mmsJournal = (MmsJournal) LinkedList_getData(journal); + while (journal != NULL) { - if (strcmp(mmsJournal->name, name) == 0) - return mmsJournal; + MmsJournal mmsJournal = (MmsJournal) LinkedList_getData(journal); - journal = LinkedList_getNext(journal); + if (strcmp(mmsJournal->name, name) == 0) + return mmsJournal; + + journal = LinkedList_getNext(journal); + } } return NULL; diff --git a/src/mms/iso_mms/server/mms_get_namelist_service.c b/src/mms/iso_mms/server/mms_get_namelist_service.c index 48e3182e..6204e247 100644 --- a/src/mms/iso_mms/server/mms_get_namelist_service.c +++ b/src/mms/iso_mms/server/mms_get_namelist_service.c @@ -188,15 +188,18 @@ getJournalListDomainSpecific(MmsServerConnection connection, char* domainName) if (domain != NULL) { nameList = LinkedList_create(); - LinkedList journalList = domain->journals; + if (domain->journals != NULL) { - while ((journalList = LinkedList_getNext(journalList)) != NULL) { + LinkedList journalList = domain->journals; - MmsJournal journal = (MmsJournal) LinkedList_getData(journalList); + while ((journalList = LinkedList_getNext(journalList)) != NULL) { - LinkedList_add(nameList, (void*) journal->name); - } + MmsJournal journal = (MmsJournal) LinkedList_getData(journalList); + + LinkedList_add(nameList, (void*) journal->name); + } + } } return nameList; From eba3bf9adba1fbc22691e9d9a37e15c8d07413e9 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 11 Jun 2016 20:05:26 +0200 Subject: [PATCH 33/36] - added separate HAL thread implementation for BSD (OS X) --- Makefile | 2 +- src/CMakeLists.txt | 2 +- src/hal/thread/bsd/thread_bsd.c | 122 ++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/hal/thread/bsd/thread_bsd.c diff --git a/Makefile b/Makefile index eeaa5cd0..2aec4a61 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ LIB_SOURCE_DIRS += src/hal/filesystem/linux LIB_SOURCE_DIRS += src/hal/time/unix else ifeq ($(HAL_IMPL), BSD) LIB_SOURCE_DIRS += src/hal/socket/bsd -LIB_SOURCE_DIRS += src/hal/thread/linux +LIB_SOURCE_DIRS += src/hal/thread/bsd LIB_SOURCE_DIRS += src/hal/ethernet/bsd LIB_SOURCE_DIRS += src/hal/filesystem/linux LIB_SOURCE_DIRS += src/hal/time/unix diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c126643c..49938c32 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -203,7 +203,7 @@ set (lib_windows_SRCS set (lib_bsd_SRCS ./hal/socket/bsd/socket_bsd.c ./hal/ethernet/bsd/ethernet_bsd.c -./hal/thread/linux/thread_linux.c +./hal/thread/bsd/thread_bsd.c ./hal/filesystem/linux/file_provider_linux.c ./hal/time/unix/time.c ) diff --git a/src/hal/thread/bsd/thread_bsd.c b/src/hal/thread/bsd/thread_bsd.c new file mode 100644 index 00000000..df16fc9c --- /dev/null +++ b/src/hal/thread/bsd/thread_bsd.c @@ -0,0 +1,122 @@ +/** + * thread_bsd.c + * + * Copyright 2016 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ + +#include +#include +#include +#include "hal_thread.h" + +#include "libiec61850_platform_includes.h" + +struct sThread { + ThreadExecutionFunction function; + void* parameter; + pthread_t pthread; + int state; + bool autodestroy; +}; + +Semaphore +Semaphore_create(int initialValue) +{ + char tmpname[] = {"/tmp/libiec61850.XXXXXX"}; + mktemp(tmpname); + Semaphore self = sem_open(tmpname, O_CREAT, 0666, initialValue); + + return self; +} + +/* Wait until semaphore value is more than zero. Then decrease the semaphore value. */ +void +Semaphore_wait(Semaphore self) +{ + sem_wait((sem_t*) self); +} + +void +Semaphore_post(Semaphore self) +{ + sem_post((sem_t*) self); +} + +void +Semaphore_destroy(Semaphore self) +{ + sem_close(self); +} + +Thread +Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy) +{ + Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread)); + + if (thread != NULL) { + thread->parameter = parameter; + thread->function = function; + thread->state = 0; + thread->autodestroy = autodestroy; + } + + return thread; +} + +static void* +destroyAutomaticThread(void* parameter) +{ + Thread thread = (Thread) parameter; + + thread->function(thread->parameter); + + GLOBAL_FREEMEM(thread); + + pthread_exit(NULL); +} + +void +Thread_start(Thread thread) +{ + if (thread->autodestroy == true) { + pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread); + pthread_detach(thread->pthread); + } + else + pthread_create(&thread->pthread, NULL, thread->function, thread->parameter); + + thread->state = 1; +} + +void +Thread_destroy(Thread thread) +{ + if (thread->state == 1) { + pthread_join(thread->pthread, NULL); + } + + GLOBAL_FREEMEM(thread); +} + +void +Thread_sleep(int millies) +{ + usleep(millies * 1000); +} From 6a318616c3cab096482ded5301d84292eba3a0a7 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 11 Jun 2016 20:38:59 +0200 Subject: [PATCH 34/36] - updated CHANGELOG --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 376ac936..c343f4c9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,9 @@ Changes to version 0.9.2 - MMS server: fixed race condition when opening/closing connections in multi-threaded configuration. - client: IedConnection_readObject and IedConnection_getVariableSpecification functions can now be applied to whole LNs - client: GetLogicalNodeDirectory for data set and log ACSI classes will not use cached model data but request the information from server. This way the client can get up to date information about newly created dynamic data sets +- changed HAL thread implementation for bsd to be compatible with MacOS X 10.10 +- fixed problem with test case sSgN4: return temporarily-unavailable when no EditSG is selected +- fixed bug in ethernet_win32.c Changes to version 0.9.1 ------------------------ From 8a925e754f49824bc9000c25ea892e277814c33a Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 11 Jun 2016 20:40:17 +0200 Subject: [PATCH 35/36] - set single-threaded as default configuration --- config/stack_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/stack_config.h b/config/stack_config.h index 7a6ebac9..c414690d 100644 --- a/config/stack_config.h +++ b/config/stack_config.h @@ -36,7 +36,7 @@ * 0 ==> server runs in multi-threaded mode (one thread for each connection and * one server background thread ) */ -#define CONFIG_MMS_SINGLE_THREADED 0 +#define CONFIG_MMS_SINGLE_THREADED 1 /* * Optimize stack for threadless operation - don't use semaphores From 0a7b84639acd01af4f6598ae56e42489bd6d54ec Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sat, 11 Jun 2016 22:58:34 +0200 Subject: [PATCH 36/36] - added README to create the sqlite log storage driver --- README | 11 +++++++++++ third_party/sqlite/README | 1 + 2 files changed, 12 insertions(+) create mode 100644 third_party/sqlite/README diff --git a/README b/README index aff27611..a4231e6b 100644 --- a/README +++ b/README @@ -85,6 +85,17 @@ Note: The ".." at the end of the command line tells cmake where to find the main To select some configuration options you can use ccmake or cmake-gui. + +Building the sqlite logging driver +----------------------------------- + +You can use the driver by including the src/logging/drivers/sqlite/log_storage_sqlite.c file into your application build. + +On Ubuntu Linux (and simpilar Linux distributions) it is enough to install the sqlite dev packages from the standard repository. For other OSes (e.g. Windows) and cross-compiling it is recomended to download the amalagation source code (from https://www.sqlite.org/download.html) of sqlite and copy them to the third_party/sqlite folder. + +On windows the cmake skript will detect the sqlite source code and also creates the example project for logging. + + C# client API -------------- diff --git a/third_party/sqlite/README b/third_party/sqlite/README new file mode 100644 index 00000000..2e1781bf --- /dev/null +++ b/third_party/sqlite/README @@ -0,0 +1 @@ +Copy the sqlite amalagamation source code files (sqlite3.c sqlite3ext.h sqlite3.h) in this directory. You can download an archive withan archive with the files here: https://www.sqlite.org/download.html