From f536d1c324a5415dc4bb4424de3c2633b73d3003 Mon Sep 17 00:00:00 2001 From: Michael Zillgith Date: Sun, 31 Mar 2024 20:16:42 +0100 Subject: [PATCH] - IED server: extended config file format to support arrays of data objects (LIB61850-415) --- .../server/model/config_file_parser.c | 79 +++++-- src/iec61850/server/model/dynamic_model.c | 131 +++++++----- src/iec61850/server/model/model.c | 26 ++- tools/model_generator/genconfig.jar | Bin 101613 -> 101863 bytes .../tools/DynamicModelGenerator.java | 192 +++++++++++------- 5 files changed, 268 insertions(+), 160 deletions(-) diff --git a/src/iec61850/server/model/config_file_parser.c b/src/iec61850/server/model/config_file_parser.c index 1e6b5405..f9489375 100644 --- a/src/iec61850/server/model/config_file_parser.c +++ b/src/iec61850/server/model/config_file_parser.c @@ -1,7 +1,7 @@ /* * config_file_parser.c * - * Copyright 2014-2023 Michael Zillgith + * Copyright 2014-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -304,7 +304,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) currentModelNode = currentModelNode->parent; } } - else if (indendation == 1) { if (StringUtils_startsWith((char*) lineBuffer, "LD")) @@ -514,8 +513,15 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) if (matchedItems != 2) goto exit_error; currentModelNode = (ModelNode*) DataObject_create(nameString, currentModelNode, arrayElements); + + if (arrayElements > 0) + { + inArray = true; + currentArrayNode = currentModelNode; + } } - else if (StringUtils_startsWith((char*) lineBuffer, "[")) { + else if (StringUtils_startsWith((char*) lineBuffer, "[")) + { if (inArray == false) { goto exit_error; } @@ -526,27 +532,55 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) goto exit_error; } - if (StringUtils_endsWith((char*)lineBuffer, ";")) + if (arrayIndex < 0) { + goto exit_error; + } + + if (currentArrayNode->modelType == DataAttributeModelType) { - /* array of basic data attribute */ - ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + if (StringUtils_endsWith((char*)lineBuffer, ";")) + { + /* array of basic data attribute */ + ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); - if (arrayElementNode) { - setValue((char*)lineBuffer, (DataAttribute*)arrayElementNode); + if (arrayElementNode) { + setValue((char*)lineBuffer, (DataAttribute*)arrayElementNode); + } + else { + goto exit_error; + } } - else { - goto exit_error; + else if (StringUtils_endsWith((char*)lineBuffer, "{")) + { + /* array of constructed data attribtute */ + currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + + if (currentModelNode) { + inArrayElement = true; + } + else { + goto exit_error; + } } } - else if (StringUtils_endsWith((char*)lineBuffer, "{")) + else if (currentArrayNode->modelType == DataObjectModelType) { - /* array of constructed data attribtute */ - currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); + if (StringUtils_endsWith((char*)lineBuffer, "{")) + { + /* array of constructed data attribtute */ + currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex); - if (currentModelNode) { - inArrayElement = true; + if (currentModelNode) { + inArrayElement = true; + } + else { + goto exit_error; + } } - else { + else + { + if (DEBUG_IED_SERVER) + printf("Unexpected character at end of line: %s\n", lineBuffer); goto exit_error; } } @@ -567,7 +601,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) DataAttribute* dataAttribute = DataAttribute_create(nameString, currentModelNode, (DataAttributeType) attributeType, (FunctionalConstraint) functionalConstraint, triggerOptions, arrayElements, sAddr); - if (arrayElements > 0) { + if (arrayElements > 0) + { inArray = true; currentArrayNode = (ModelNode*)dataAttribute; } @@ -576,7 +611,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) int lineLength = (int) strlen((char*) lineBuffer); - if (lineBuffer[lineLength - 1] == '{') { + if (lineBuffer[lineLength - 1] == '{') + { indendation++; currentModelNode = (ModelNode*) dataAttribute; } @@ -585,7 +621,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) { char* start = strchr((char*) lineBuffer, '('); - if (start) { + if (start) + { start++; StringUtils_copyStringMax(nameString, 130, start); @@ -598,7 +635,8 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) /* check for index */ char* sep = strchr(nameString, ' '); - if (sep) { + if (sep) + { char* indexStr = sep + 1; *sep = 0; @@ -633,7 +671,6 @@ ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle) if (StringUtils_createBufferFromHexString(nameString, (uint8_t*) nameString2) != 6) goto exit_error; - PhyComAddress* dstAddress = PhyComAddress_create((uint8_t) vlanPrio, (uint16_t) vlanId, (uint16_t) appId, (uint8_t*) nameString2); diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 68710320..bfe266ed 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -1,7 +1,7 @@ /* * dynamic_model.c * - * Copyright 2014-2022 Michael Zillgith + * Copyright 2014-2024 Michael Zillgith * * This file is part of libIEC61850. * @@ -75,10 +75,12 @@ IedModel_addDataSet(IedModel* self, DataSet* dataSet) { if (self->dataSets == NULL) self->dataSets = dataSet; - else { + else + { DataSet* lastDataSet = self->dataSets; - while (lastDataSet != NULL) { + while (lastDataSet != NULL) + { if (lastDataSet->sibling == NULL) { lastDataSet->sibling = dataSet; break; @@ -94,7 +96,8 @@ IedModel_addLogicalDevice(IedModel* self, LogicalDevice* lDevice) { if (self->firstChild == NULL) self->firstChild = lDevice; - else { + else + { LogicalDevice* sibling = self->firstChild; while (sibling->sibling != NULL) @@ -109,7 +112,8 @@ IedModel_addLog(IedModel* self, Log* log) { if (self->logs == NULL) self->logs = log; - else { + else + { Log* lastLog = self->logs; while (lastLog->sibling != NULL) @@ -124,7 +128,8 @@ IedModel_addLogControlBlock(IedModel* self, LogControlBlock* lcb) { if (self->lcbs == NULL) self->lcbs = lcb; - else { + else + { LogControlBlock* lastLcb = self->lcbs; while (lastLcb->sibling != NULL) @@ -139,7 +144,8 @@ IedModel_addReportControlBlock(IedModel* self, ReportControlBlock* rcb) { if (self->rcbs == NULL) self->rcbs = rcb; - else { + else + { ReportControlBlock* lastRcb = self->rcbs; while (lastRcb->sibling != NULL) @@ -155,7 +161,8 @@ IedModel_addSettingGroupControlBlock(IedModel* self, SettingGroupControlBlock* s { if (self->sgcbs == NULL) self->sgcbs = sgcb; - else { + else + { SettingGroupControlBlock* lastSgcb = self->sgcbs; while (lastSgcb->sibling != NULL) @@ -171,7 +178,8 @@ IedModel_addGSEControlBlock(IedModel* self, GSEControlBlock* gcb) { if (self->gseCBs == NULL) self->gseCBs = gcb; - else { + else + { GSEControlBlock* lastGcb = self->gseCBs; while (lastGcb->sibling) @@ -187,7 +195,8 @@ IedModel_addSMVControlBlock(IedModel* self, SVControlBlock* smvcb) if (self->svCBs == NULL) { self->svCBs = smvcb; } - else { + else + { SVControlBlock* lastSvCB = self->svCBs; while (lastSvCB->sibling) @@ -202,7 +211,8 @@ LogicalDevice_createEx(const char* inst, IedModel* parent, const char* ldName) { LogicalDevice* self = (LogicalDevice*) GLOBAL_CALLOC(1, sizeof(LogicalDevice)); - if (self) { + if (self) + { self->name = StringUtils_copyString(inst); self->modelType = LogicalDeviceModelType; self->parent = (ModelNode*) parent; @@ -257,7 +267,8 @@ LogicalNode_create(const char* name, LogicalDevice* parent) { LogicalNode* self = (LogicalNode*) GLOBAL_MALLOC(sizeof(LogicalNode)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = (ModelNode*) parent; self->modelType = LogicalNodeModelType; @@ -311,7 +322,8 @@ Log_create(const char* name, LogicalNode* parent) { Log* self = (Log*) GLOBAL_MALLOC(sizeof(Log)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; self->sibling = NULL; @@ -336,7 +348,8 @@ LogControlBlock_create(const char* name, LogicalNode* parent, const char* dataSe { LogControlBlock* self = (LogControlBlock*) GLOBAL_MALLOC(sizeof(LogControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; self->sibling = NULL; @@ -388,7 +401,8 @@ ReportControlBlock_create(const char* name, LogicalNode* parent, const char* rpt { ReportControlBlock* self = (ReportControlBlock*) GLOBAL_MALLOC(sizeof(ReportControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; @@ -469,7 +483,8 @@ SettingGroupControlBlock_create(LogicalNode* parent, uint8_t actSG, uint8_t numO SettingGroupControlBlock* self = (SettingGroupControlBlock*) GLOBAL_MALLOC(sizeof(SettingGroupControlBlock)); - if (self) { + if (self) + { self->parent = parent; self->actSG = actSG; self->numOfSGs = numOfSGs; @@ -497,7 +512,8 @@ GSEControlBlock_create(const char* name, LogicalNode* parent, const char* appId, { GSEControlBlock* self = (GSEControlBlock*) GLOBAL_MALLOC(sizeof(GSEControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; @@ -541,7 +557,8 @@ SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, c { SVControlBlock* self = (SVControlBlock*) GLOBAL_MALLOC(sizeof(SVControlBlock)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->parent = parent; @@ -587,7 +604,8 @@ PhyComAddress_create(uint8_t vlanPriority, uint16_t vlanId, uint16_t appId, uint { PhyComAddress* self = (PhyComAddress*) GLOBAL_MALLOC(sizeof(PhyComAddress)); - if (self) { + if (self) + { self->vlanPriority = vlanPriority; self->vlanId = vlanId; self->appId = appId; @@ -630,7 +648,8 @@ DataObject_create(const char* name, ModelNode* parent, int arrayElements) { DataObject* self = (DataObject*) GLOBAL_MALLOC(sizeof(DataObject)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->modelType = DataObjectModelType; self->firstChild = NULL; @@ -640,21 +659,23 @@ DataObject_create(const char* name, ModelNode* parent, int arrayElements) self->elementCount = arrayElements; self->arrayIndex = -1; - if (arrayElements > 0) { + if (arrayElements > 0) + { int i; - for (i = 0; i < arrayElements; i++) { + for (i = 0; i < arrayElements; i++) + { DataObject* arrayElement = (DataObject*) GLOBAL_MALLOC(sizeof(DataObject)); - if (self) { - self->name = NULL; - self->modelType = DataObjectModelType; - self->firstChild = NULL; - self->parent = parent; - self->sibling = NULL; + if (arrayElement) { + arrayElement->name = NULL; + arrayElement->modelType = DataObjectModelType; + arrayElement->firstChild = NULL; + arrayElement->parent = (ModelNode*) self; + arrayElement->sibling = NULL; - self->elementCount = 0; - self->arrayIndex = i; + arrayElement->elementCount = 0; + arrayElement->arrayIndex = i; DataObject_addChild(self, (ModelNode*) arrayElement); } @@ -703,7 +724,8 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type { DataAttribute* self = (DataAttribute*) GLOBAL_MALLOC(sizeof(DataAttribute)); - if (self) { + if (self) + { self->name = StringUtils_copyString(name); self->elementCount = arrayElements; self->arrayIndex = -1; @@ -717,13 +739,16 @@ DataAttribute_create(const char* name, ModelNode* parent, DataAttributeType type self->triggerOptions = triggerOptions; self->sAddr = sAddr; - if (arrayElements > 0) { + if (arrayElements > 0) + { int i; - for (i = 0; i < arrayElements; i++) { + for (i = 0; i < arrayElements; i++) + { DataAttribute* arrayElement = (DataAttribute*) GLOBAL_MALLOC(sizeof(DataAttribute)); - if (arrayElement) { + if (arrayElement) + { arrayElement->name = NULL; arrayElement->elementCount = 0; arrayElement->arrayIndex = i; @@ -785,7 +810,8 @@ DataSet_create(const char* name, LogicalNode* parent) { DataSet* self = (DataSet*) GLOBAL_MALLOC(sizeof(DataSet)); - if (self) { + if (self) + { LogicalDevice* ld = (LogicalDevice*) parent->parent; self->name = StringUtils_createString(3, parent->name, "$", name); @@ -831,11 +857,12 @@ DataSet_addEntry(DataSet* self, DataSetEntry* newEntry) if (self->fcdas == NULL) self->fcdas = newEntry; - else { + else + { DataSetEntry* lastEntry = self->fcdas; - while (lastEntry != NULL) { - + while (lastEntry != NULL) + { if (lastEntry->sibling == NULL) { lastEntry->sibling = newEntry; break; @@ -851,21 +878,24 @@ DataSetEntry_create(DataSet* dataSet, const char* variable, int index, const cha { DataSetEntry* self = (DataSetEntry*) GLOBAL_MALLOC(sizeof(DataSetEntry)); - if (self) { + if (self) + { char variableName[130]; StringUtils_copyStringMax(variableName, 130, variable); char* separator = strchr(variableName, '/'); - if (separator != NULL) { + if (separator != NULL) + { *separator = 0; self->variableName = StringUtils_copyString(separator + 1); self->logicalDeviceName = StringUtils_copyString(variableName); self->isLDNameDynamicallyAllocated = true; } - else { + else + { self->variableName = StringUtils_copyString(variable); self->logicalDeviceName = dataSet->logicalDeviceName; self->isLDNameDynamicallyAllocated = false; @@ -891,14 +921,15 @@ DataSetEntry_create(DataSet* dataSet, const char* variable, int index, const cha static void ModelNode_destroy(ModelNode* modelNode) { - if (modelNode) { - + if (modelNode) + { if (modelNode->name) GLOBAL_FREEMEM(modelNode->name); ModelNode* currentChild = modelNode->firstChild; - while (currentChild != NULL) { + while (currentChild != NULL) + { ModelNode* nextChild = currentChild->sibling; ModelNode_destroy(currentChild); @@ -906,7 +937,8 @@ ModelNode_destroy(ModelNode* modelNode) currentChild = nextChild; } - if (modelNode->modelType == DataAttributeModelType) { + if (modelNode->modelType == DataAttributeModelType) + { DataAttribute* dataAttribute = (DataAttribute*) modelNode; if (dataAttribute->mmsValue != NULL) { @@ -929,8 +961,8 @@ IedModel_destroy(IedModel* model) LogicalDevice* ld = model->firstChild; - while (ld != NULL) { - + while (ld != NULL) + { if (ld->name) GLOBAL_FREEMEM(ld->name); @@ -939,7 +971,8 @@ IedModel_destroy(IedModel* model) LogicalNode* ln = (LogicalNode*) ld->firstChild; - while (ln != NULL) { + while (ln != NULL) + { GLOBAL_FREEMEM(ln->name); /* delete all data objects */ @@ -960,7 +993,6 @@ IedModel_destroy(IedModel* model) GLOBAL_FREEMEM(currentLn); } - LogicalDevice* currentLd = ld; ld = (LogicalDevice*) ld->sibling; @@ -1092,4 +1124,3 @@ IedModel_destroy(IedModel* model) GLOBAL_FREEMEM(model); } } - diff --git a/src/iec61850/server/model/model.c b/src/iec61850/server/model/model.c index 9593de48..55b6409e 100644 --- a/src/iec61850/server/model/model.c +++ b/src/iec61850/server/model/model.c @@ -808,12 +808,14 @@ ModelNode_getChildWithIdx(ModelNode* self, int idx) { ModelNode* foundElement = NULL; - if (self->modelType == DataObjectModelType || self->modelType == DataAttributeModelType) { + if (self->modelType == DataObjectModelType || self->modelType == DataAttributeModelType) + { ModelNode* nextNode = self->firstChild; int currentIdx = 0; - while (nextNode) { + while (nextNode) + { if (currentIdx == idx) { foundElement = nextNode; break; @@ -845,14 +847,17 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint ModelNode* matchingNode = NULL; - while (nextNode != NULL) { + while (nextNode != NULL) + { int nodeNameLen = strlen(nextNode->name); - if (nodeNameLen == nameElementLength) { - if (memcmp(nextNode->name, name, nodeNameLen) == 0) { - + if (nodeNameLen == nameElementLength) + { + if (memcmp(nextNode->name, name, nodeNameLen) == 0) + { if (separator == NULL) { - if (nextNode->modelType == DataAttributeModelType) { + if (nextNode->modelType == DataAttributeModelType) + { DataAttribute* da = (DataAttribute*) nextNode; if (da->fc == fc) { @@ -861,9 +866,10 @@ ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint } } } - else { - - if (nextNode->modelType == DataAttributeModelType) { + else + { + if (nextNode->modelType == DataAttributeModelType) + { DataAttribute* da = (DataAttribute*) nextNode; if (da->fc == fc) { diff --git a/tools/model_generator/genconfig.jar b/tools/model_generator/genconfig.jar index 2bb8ddbeb70a96e73b38d3b1b054fd8150bd7e81..51e83774a58706e93db31c714ed857db6d08ca1d 100644 GIT binary patch delta 10271 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 delta 10015 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 0) { + /* data object is an array */ + for (int i = 0; i < dataObject.getCount(); i++) { + output.print("[" + i + "]{\n"); + + exportDataObjectChild(output, dataObject, isTransient); + + output.print("}\n"); + } + } + else { + exportDataObjectChild(output, dataObject, isTransient); + } + + } + + private void printDataAttributeValue(PrintStream output, DataAttribute dataAttribute, boolean isTransient) + { + if (dataAttribute.isBasicAttribute()) { + DataModelValue value = dataAttribute.getValue(); + + /* if no value is given use default value for type if present */ + if (value == null) { + value = dataAttribute.getDefinition().getValue(); + + if (value != null) + if (value.getValue() == null) + value.updateEnumOrdValue(ied.getTypeDeclarations()); + } + + if (value != null) { + + switch (dataAttribute.getType()) { + case ENUMERATED: + case INT8: + case INT16: + case INT32: + case INT64: + output.print("=" + value.getIntValue()); + break; + case INT8U: + case INT16U: + case INT24U: + case INT32U: + output.print("=" + value.getLongValue()); + break; + case BOOLEAN: + { + Boolean boolVal = (Boolean) value.getValue(); + + if (boolVal.booleanValue()) + output.print("=1"); + } + break; + case UNICODE_STRING_255: + output.print("=\"" + value.getValue()+ "\""); + break; + case CURRENCY: + case VISIBLE_STRING_32: + case VISIBLE_STRING_64: + case VISIBLE_STRING_129: + case VISIBLE_STRING_255: + case VISIBLE_STRING_65: + output.print("=\"" + value.getValue()+ "\""); + break; + case FLOAT32: + case FLOAT64: + output.print("=" + value.getValue()); + break; + case TIMESTAMP: + case ENTRY_TIME: + output.print("=" + value.getLongValue()); + break; + + default: + System.out.println("Unknown default value for " + dataAttribute.getName() + " type: " + dataAttribute.getType()); + break; + } + + } + + output.println(";"); + } + else { + output.println("{"); + + for (DataAttribute subDataAttribute : dataAttribute.getSubDataAttributes()) { + exportDataAttribute(output, subDataAttribute, isTransient); + } + + output.println("}"); + } + } + private void exportDataAttribute(PrintStream output, DataAttribute dataAttribute, boolean isTransient) { output.print("DA(" + dataAttribute.getName() + " "); @@ -493,83 +588,22 @@ public class DynamicModelGenerator { else output.print("0"); - output.print(")"); - - if (dataAttribute.isBasicAttribute()) { - DataModelValue value = dataAttribute.getValue(); - - /* if no value is given use default value for type if present */ - if (value == null) { - value = dataAttribute.getDefinition().getValue(); - - if (value != null) - if (value.getValue() == null) - value.updateEnumOrdValue(ied.getTypeDeclarations()); - } - - if (value != null) { - - switch (dataAttribute.getType()) { - case ENUMERATED: - case INT8: - case INT16: - case INT32: - case INT64: - output.print("=" + value.getIntValue()); - break; - case INT8U: - case INT16U: - case INT24U: - case INT32U: - output.print("=" + value.getLongValue()); - break; - case BOOLEAN: - { - Boolean boolVal = (Boolean) value.getValue(); - - if (boolVal.booleanValue()) - output.print("=1"); - } - break; - case UNICODE_STRING_255: - output.print("=\"" + value.getValue()+ "\""); - break; - case CURRENCY: - case VISIBLE_STRING_32: - case VISIBLE_STRING_64: - case VISIBLE_STRING_129: - case VISIBLE_STRING_255: - case VISIBLE_STRING_65: - output.print("=\"" + value.getValue()+ "\""); - break; - case FLOAT32: - case FLOAT64: - output.print("=" + value.getValue()); - break; - case TIMESTAMP: - case ENTRY_TIME: - output.print("=" + value.getLongValue()); - break; - - default: - System.out.println("Unknown default value for " + dataAttribute.getName() + " type: " + dataAttribute.getType()); - break; - } - - } - - output.println(";"); - } - else { - output.println("{"); + output.print(")"); - for (DataAttribute subDataAttribute : dataAttribute.getSubDataAttributes()) { - exportDataAttribute(output, subDataAttribute, isTransient); + if (dataAttribute.getCount() > 0) { + output.print("{\n"); + + for (int i = 0; i < dataAttribute.getCount(); i++) { + output.print("[" + i + "]"); + + printDataAttributeValue(output, dataAttribute, isTransient); } - output.println("}"); + output.print("}\n"); + } + else { + printDataAttributeValue(output, dataAttribute, isTransient); } - } public static void main(String[] args) throws FileNotFoundException {