/* * mms_goose.c * * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * * libIEC61850 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libIEC61850 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with libIEC61850. If not, see . * * See COPYING file for the complete license text. */ #include "stack_config.h" #if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) #include "libiec61850_platform_includes.h" #include "mms_mapping.h" #include "linked_list.h" #include "array_list.h" #include "hal_thread.h" #include "reporting.h" #include "mms_mapping_internal.h" #include "mms_goose.h" #include "goose_publisher.h" struct sMmsGooseControlBlock { char* name; bool goEna; char* dstAddress; MmsDomain* domain; LogicalNode* logicalNode; MmsVariableSpecification* mmsType; MmsValue* mmsValue; GoosePublisher publisher; DataSet* dataSet; bool isDynamicDataSet; LinkedList dataSetValues; uint64_t nextPublishTime; int retransmissionsLeft; /* number of retransmissions left for the last event */ int minTime; int maxTime; #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore publisherMutex; #endif MmsMapping* mmsMapping; char* goCBRef; char* goId; char* dataSetRef; }; MmsGooseControlBlock MmsGooseControlBlock_create() { MmsGooseControlBlock self = (MmsGooseControlBlock) GLOBAL_CALLOC(1, sizeof(struct sMmsGooseControlBlock)); #if (CONFIG_MMS_THREADLESS_STACK != 1) self->publisherMutex = Semaphore_create(1); #endif return self; } void MmsGooseControlBlock_destroy(MmsGooseControlBlock self) { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_destroy(self->publisherMutex); #endif if (self->publisher != NULL) GoosePublisher_destroy(self->publisher); if (self->dataSetValues != NULL) LinkedList_destroyStatic(self->dataSetValues); if (self->goCBRef != NULL) GLOBAL_FREEMEM(self->goCBRef); if (self->goId != NULL) GLOBAL_FREEMEM(self->goId); if (self->dataSetRef != NULL) GLOBAL_FREEMEM(self->dataSetRef); if (self->dataSet != NULL) { if (self->isDynamicDataSet) { MmsMapping_freeDynamicallyCreatedDataSet(self->dataSet); self->isDynamicDataSet = false; self->dataSet = NULL; } } MmsValue_delete(self->mmsValue); GLOBAL_FREEMEM(self); } MmsDomain* MmsGooseControlBlock_getDomain(MmsGooseControlBlock self) { return self->domain; } DataSet* MmsGooseControlBlock_getDataSet(MmsGooseControlBlock self) { return self->dataSet; } char* MmsGooseControlBlock_getLogicalNodeName(MmsGooseControlBlock self) { return self->logicalNode->name; } char* MmsGooseControlBlock_getName(MmsGooseControlBlock self) { return self->name; } MmsValue* MmsGooseControlBlock_getMmsValues(MmsGooseControlBlock self) { return self->mmsValue; } MmsVariableSpecification* MmsGooseControlBlock_getVariableSpecification(MmsGooseControlBlock self) { return self->mmsType; } bool MmsGooseControlBlock_isEnabled(MmsGooseControlBlock self) { return self->goEna; } void MmsGooseControlBlock_enable(MmsGooseControlBlock self) { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->publisherMutex); #endif if (!MmsGooseControlBlock_isEnabled(self)) { if (self->dataSetRef != NULL) { GLOBAL_FREEMEM(self->dataSetRef); if (self->dataSet != NULL) if (self->isDynamicDataSet) { MmsMapping_freeDynamicallyCreatedDataSet(self->dataSet); self->isDynamicDataSet = false; self->dataSet = NULL; } if (self->dataSetValues != NULL) { LinkedList_destroyStatic(self->dataSetValues); self->dataSetValues = NULL; } } self->dataSet = NULL; char* dataSetRef = MmsValue_toString(MmsValue_getElement(self->mmsValue, 2)); if (dataSetRef != NULL) { self->dataSetRef = copyString(dataSetRef); self->dataSet = IedModel_lookupDataSet(self->mmsMapping->model, self->dataSetRef); self->isDynamicDataSet = false; if (self->dataSet == NULL) { self->dataSet = MmsMapping_getDomainSpecificDataSet(self->mmsMapping, self->dataSetRef); self->isDynamicDataSet = true; } } if (self->dataSet != NULL) { MmsValue* goEna = MmsValue_getElement(self->mmsValue, 0); MmsValue_setBoolean(goEna, true); MmsValue* dstAddress = MmsValue_getElement(self->mmsValue, 5); CommParameters commParameters; commParameters.appId = MmsValue_toInt32(MmsValue_getElement(dstAddress, 3)); commParameters.vlanId = MmsValue_toInt32(MmsValue_getElement(dstAddress, 2)); commParameters.vlanPriority = MmsValue_toInt32(MmsValue_getElement(dstAddress, 1)); MmsValue* macAddress = MmsValue_getElement(dstAddress, 0); memcpy(commParameters.dstAddress, MmsValue_getOctetStringBuffer(macAddress), 6); self->publisher = GoosePublisher_create(&commParameters, self->mmsMapping->gooseInterfaceId); self->minTime = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 6)); self->maxTime = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 7)); GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3); GoosePublisher_setDataSetRef(self->publisher, self->dataSetRef); GoosePublisher_setGoCbRef(self->publisher, self->goCBRef); uint32_t confRev = MmsValue_toUint32(MmsValue_getElement(self->mmsValue, 3)); GoosePublisher_setConfRev(self->publisher, confRev); bool needsCom = MmsValue_getBoolean(MmsValue_getElement(self->mmsValue, 4)); GoosePublisher_setNeedsCommission(self->publisher, needsCom); if (self->goId != NULL) GoosePublisher_setGoID(self->publisher, self->goId); //prepare data set values self->dataSetValues = LinkedList_create(); DataSetEntry* dataSetEntry = self->dataSet->fcdas; while (dataSetEntry != NULL) { LinkedList_add(self->dataSetValues, dataSetEntry->value); dataSetEntry = dataSetEntry->sibling; } self->goEna = true; } } #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->publisherMutex); #endif } void MmsGooseControlBlock_disable(MmsGooseControlBlock self) { if (MmsGooseControlBlock_isEnabled(self)) { MmsValue* goEna = MmsValue_getElement(self->mmsValue, 0); MmsValue_setBoolean(goEna, false); self->goEna = false; #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->publisherMutex); #endif if (self->publisher != NULL) { GoosePublisher_destroy(self->publisher); self->publisher = NULL; LinkedList_destroyStatic(self->dataSetValues); self->dataSetValues = NULL; } #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->publisherMutex); #endif } } void MmsGooseControlBlock_checkAndPublish(MmsGooseControlBlock self, uint64_t currentTime) { if (currentTime >= self->nextPublishTime) { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->publisherMutex); #endif GoosePublisher_publish(self->publisher, self->dataSetValues); if (self->retransmissionsLeft > 0) { self->nextPublishTime = currentTime + CONFIG_GOOSE_EVENT_RETRANSMISSION_INTERVAL; if (self->retransmissionsLeft > 1) GoosePublisher_setTimeAllowedToLive(self->publisher, CONFIG_GOOSE_EVENT_RETRANSMISSION_INTERVAL * 3); else GoosePublisher_setTimeAllowedToLive(self->publisher, CONFIG_GOOSE_STABLE_STATE_TRANSMISSION_INTERVAL * 3); self->retransmissionsLeft--; } else { GoosePublisher_setTimeAllowedToLive(self->publisher, CONFIG_GOOSE_STABLE_STATE_TRANSMISSION_INTERVAL * 3); self->nextPublishTime = currentTime + CONFIG_GOOSE_STABLE_STATE_TRANSMISSION_INTERVAL; } #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->publisherMutex); #endif } } void MmsGooseControlBlock_observedObjectChanged(MmsGooseControlBlock self) { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->publisherMutex); #endif uint64_t currentTime = GoosePublisher_increaseStNum(self->publisher); self->retransmissionsLeft = CONFIG_GOOSE_EVENT_RETRANSMISSION_COUNT; if (self->retransmissionsLeft > 0) { self->nextPublishTime = currentTime + CONFIG_GOOSE_EVENT_RETRANSMISSION_INTERVAL; GoosePublisher_setTimeAllowedToLive(self->publisher, CONFIG_GOOSE_EVENT_RETRANSMISSION_INTERVAL * 3); } else { self->nextPublishTime = currentTime + CONFIG_GOOSE_STABLE_STATE_TRANSMISSION_INTERVAL; GoosePublisher_setTimeAllowedToLive(self->publisher, CONFIG_GOOSE_STABLE_STATE_TRANSMISSION_INTERVAL * 3); } GoosePublisher_publish(self->publisher, self->dataSetValues); #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->publisherMutex); #endif } static MmsVariableSpecification* createMmsGooseControlBlock(char* gcbName) { MmsVariableSpecification* gcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); gcb->name = copyString(gcbName); gcb->type = MMS_STRUCTURE; gcb->typeSpec.structure.elementCount = 9; gcb->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(9, sizeof(MmsVariableSpecification*)); MmsVariableSpecification* namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("GoEna"); namedVariable->type = MMS_BOOLEAN; gcb->typeSpec.structure.elements[0] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("GoID"); namedVariable->typeSpec.visibleString = -129; namedVariable->type = MMS_VISIBLE_STRING; gcb->typeSpec.structure.elements[1] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("DatSet"); namedVariable->typeSpec.visibleString = -129; namedVariable->type = MMS_VISIBLE_STRING; gcb->typeSpec.structure.elements[2] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("ConfRev"); namedVariable->type = MMS_UNSIGNED; namedVariable->typeSpec.unsignedInteger = 32; gcb->typeSpec.structure.elements[3] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("NdsCom"); namedVariable->type = MMS_BOOLEAN; gcb->typeSpec.structure.elements[4] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("DstAddress"); MmsMapping_createPhyComAddrStructure(namedVariable); gcb->typeSpec.structure.elements[5] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("MinTime"); namedVariable->type = MMS_UNSIGNED; namedVariable->typeSpec.unsignedInteger = 32; gcb->typeSpec.structure.elements[6] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("MaxTime"); namedVariable->type = MMS_UNSIGNED; namedVariable->typeSpec.unsignedInteger = 32; gcb->typeSpec.structure.elements[7] = namedVariable; namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("FixedOffs"); namedVariable->type = MMS_BOOLEAN; gcb->typeSpec.structure.elements[8] = namedVariable; return gcb; } static GSEControlBlock* getGCBForLogicalNodeWithIndex(MmsMapping* self, LogicalNode* logicalNode, int index) { int gseCount = 0; GSEControlBlock* gcb = self->model->gseCBs; /* Iterate list of GoCBs */ while (gcb != NULL ) { if (gcb->parent == logicalNode) { if (gseCount == index) return gcb; gseCount++; } gcb = gcb->sibling; } return NULL ; } static char* createDataSetReference(char* domainName, char* lnName, char* dataSetName) { char* dataSetReference; dataSetReference = createString(5, domainName, "/", lnName, "$", dataSetName); return dataSetReference; } MmsVariableSpecification* GOOSE_createGOOSEControlBlocks(MmsMapping* self, MmsDomain* domain, LogicalNode* logicalNode, int gseCount) { MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification)); namedVariable->name = copyString("GO"); namedVariable->type = MMS_STRUCTURE; namedVariable->typeSpec.structure.elementCount = gseCount; namedVariable->typeSpec.structure.elements = (MmsVariableSpecification**) GLOBAL_CALLOC(gseCount, sizeof(MmsVariableSpecification*)); int currentGCB = 0; while (currentGCB < gseCount) { GSEControlBlock* gooseControlBlock = getGCBForLogicalNodeWithIndex( self, logicalNode, currentGCB); MmsVariableSpecification* gseTypeSpec = createMmsGooseControlBlock(gooseControlBlock->name); MmsValue* gseValues = MmsValue_newStructure(gseTypeSpec); namedVariable->typeSpec.structure.elements[currentGCB] = gseTypeSpec; MmsGooseControlBlock mmsGCB = MmsGooseControlBlock_create(); mmsGCB->goCBRef = createString(5, MmsDomain_getName(domain), "/", logicalNode->name, "$GO$", gooseControlBlock->name); if (gooseControlBlock->appId != NULL) { // gcb->goID = copyString(gooseControlBlock->appId); MmsValue* goID = MmsValue_getElement(gseValues, 1); MmsValue_setVisibleString(goID, gooseControlBlock->appId); } if (gooseControlBlock->dataSetName != NULL) mmsGCB->dataSetRef = createDataSetReference(MmsDomain_getName(domain), logicalNode->name, gooseControlBlock->dataSetName); else mmsGCB->dataSetRef = NULL; MmsValue* dataSetRef = MmsValue_getElement(gseValues, 2); MmsValue_setVisibleString(dataSetRef, mmsGCB->dataSetRef); /* Set communication parameters */ uint8_t priority = CONFIG_GOOSE_DEFAULT_PRIORITY; uint8_t dstAddr[] = CONFIG_GOOSE_DEFAULT_DST_ADDRESS; uint16_t vid = CONFIG_GOOSE_DEFAULT_VLAN_ID; uint16_t appId = CONFIG_GOOSE_DEFAULT_APPID; if (gooseControlBlock->address != NULL) { priority = gooseControlBlock->address->vlanPriority; vid = gooseControlBlock->address->vlanId; appId = gooseControlBlock->address->appId; int i; for (i = 0; i < 6; i++) { dstAddr[i] = gooseControlBlock->address->dstAddress[i]; } } MmsValue* dstAddress = MmsValue_getElement(gseValues, 5); MmsValue* addr = MmsValue_getElement(dstAddress, 0); MmsValue_setOctetString(addr, dstAddr, 6); MmsValue* prio = MmsValue_getElement(dstAddress, 1); MmsValue_setUint8(prio, priority); MmsValue* vlanId = MmsValue_getElement(dstAddress, 2); MmsValue_setUint16(vlanId, vid); MmsValue* appIdVal = MmsValue_getElement(dstAddress, 3); MmsValue_setUint16(appIdVal, appId); MmsValue* confRef = MmsValue_getElement(gseValues, 3); MmsValue_setUint32(confRef, gooseControlBlock->confRef); mmsGCB->dataSet = NULL; mmsGCB->mmsValue = gseValues; mmsGCB->mmsType = gseTypeSpec; mmsGCB->name = gooseControlBlock->name; mmsGCB->domain = domain; mmsGCB->logicalNode = logicalNode; if (gooseControlBlock->minTime == -1) mmsGCB->minTime = CONFIG_GOOSE_EVENT_RETRANSMISSION_INTERVAL; else mmsGCB->minTime = gooseControlBlock->minTime; if (gooseControlBlock->maxTime == -1) mmsGCB->maxTime = CONFIG_GOOSE_STABLE_STATE_TRANSMISSION_INTERVAL; else mmsGCB->maxTime = gooseControlBlock->maxTime; MmsValue* minTime = MmsValue_getElement(gseValues, 6); MmsValue_setUint32(minTime, mmsGCB->minTime); MmsValue* maxTime = MmsValue_getElement(gseValues, 7); MmsValue_setUint32(maxTime, mmsGCB->maxTime); mmsGCB->mmsMapping = self; LinkedList_add(self->gseControls, mmsGCB); currentGCB++; } return namedVariable; } #endif