/*
 *  mms_goose.c
 *
 *  Copyright 2013-2024 Michael Zillgith
 *
 *  This file is part of libIEC61850.
 *
 *  libIEC61850 is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  libIEC61850 is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with libIEC61850.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  See COPYING file for the complete license text.
 */

#include "stack_config.h"

#if (CONFIG_INCLUDE_GOOSE_SUPPORT == 1)

#define GOOSE_MAX_MESSAGE_SIZE 1518

#include "libiec61850_platform_includes.h"
#include "mms_mapping.h"
#include "linked_list.h"

#include "hal_thread.h"

#include "reporting.h"
#include "mms_mapping_internal.h"

#include "mms_goose.h"
#include "goose_publisher.h"
#include "ied_server_private.h"

#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
#endif

struct sMmsGooseControlBlock {
    char* name;
    bool goEna;
    unsigned int isDynamicDataSet:1;
    unsigned int useVlanTag:1;

    char* dstAddress;

    MmsDomain* domain;
    LogicalNode* logicalNode;
    MmsVariableSpecification* mmsType;
    MmsValue* mmsValue;
    GoosePublisher publisher;

    DataSet* dataSet;

    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;

    char* gooseInterfaceId;

    bool stateChangePending;
};

static void
setNdsCom(MmsGooseControlBlock mmsGCB, bool value)
{
    MmsValue* ndsComValue = MmsValue_getElement(mmsGCB->mmsValue, 4);

    if (ndsComValue)
    {
        MmsValue_setBoolean(ndsComValue, value);
    }
}

static bool
getNdsCom(MmsGooseControlBlock mmsGCB)
{
    bool ndsCom = true;

    MmsValue* ndsComValue = MmsValue_getElement(mmsGCB->mmsValue, 4);

    if (ndsComValue)
        ndsCom = MmsValue_getBoolean(ndsComValue);

    return ndsCom;
}

bool
MmsGooseControlBlock_getNdsCom(MmsGooseControlBlock self)
{
    return getNdsCom(self);
}

bool
MmsGooseControlBlock_getGoEna(MmsGooseControlBlock self)
{
    bool retVal = false;

    if (self->mmsValue)
    {
        MmsValue* goEnaValue = MmsValue_getElement(self->mmsValue, 0);

        if (goEnaValue)
        {
            retVal = MmsValue_getBoolean(goEnaValue);
        }
    }

    return retVal;
}

int
MmsGooseControlBlock_getMinTime(MmsGooseControlBlock self)
{
    int retVal = -1;

    if (self->mmsValue)
    {
        MmsValue* minTimeValue = MmsValue_getElement(self->mmsValue, 6);

        if (minTimeValue)
        {
            retVal = MmsValue_toInt32(minTimeValue);
        }
    }

    return retVal;
}

int
MmsGooseControlBlock_getMaxTime(MmsGooseControlBlock self)
{
    int retVal = -1;

    if (self->mmsValue)
    {
        MmsValue* maxTimeValue = MmsValue_getElement(self->mmsValue, 7);

        if (maxTimeValue)
        {
            retVal = MmsValue_toInt32(maxTimeValue);
        }
    }

    return retVal;
}

bool
MmsGooseControlBlock_getFixedOffs(MmsGooseControlBlock self)
{
    bool retVal = false;

    if (self->mmsValue)
    {
        MmsValue* fixedOffsValue = MmsValue_getElement(self->mmsValue, 8);

        if (fixedOffsValue)
        {
            retVal = MmsValue_getBoolean(fixedOffsValue);
        }
    }

    return retVal;
}

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)

static void
copyGCBValuesToTrackingObject(MmsGooseControlBlock gc)
{
    if (gc->mmsMapping->gocbTrk)
    {
        GocbTrkInstance trkInst = gc->mmsMapping->gocbTrk;

        if (trkInst->goEna)
            MmsValue_setBoolean(trkInst->goEna->mmsValue, MmsGooseControlBlock_isEnabled(gc));

        if (trkInst->goID)
            MmsValue_setVisibleString(trkInst->goID->mmsValue, gc->goId);

        if (trkInst->datSet)
        {
            char datSet[130];
            LogicalNode* ln = (LogicalNode*) gc->logicalNode;
            LogicalDevice* ld = (LogicalDevice*) ln->parent;
            char* iedName = gc->mmsMapping->mmsDevice->deviceName;
            snprintf(datSet, 129, "%s%s/%s", iedName, ld->name, gc->dataSet->name);
            datSet[129] = 0;
            StringUtils_replace(datSet, '$', '.');
            MmsValue_setVisibleString(trkInst->datSet->mmsValue, datSet);
        }

        if (trkInst->confRev)
        {
            uint32_t confRev = MmsValue_toUint32(MmsValue_getElement(gc->mmsValue, 3));
            MmsValue_setUint32(trkInst->confRev->mmsValue, confRev);
        }

        if (trkInst->ndsCom)
        {
            bool ndsCom = MmsValue_getBoolean(MmsValue_getElement(gc->mmsValue, 4));
            MmsValue_setBoolean(trkInst->ndsCom->mmsValue, ndsCom);
        }

        if (trkInst->dstAddress)
        {
          MmsValue_update(trkInst->dstAddress->mmsValue, MmsValue_getElement(gc->mmsValue, 5));
        }
    }
}

static void
updateGenericTrackingObjectValues(MmsGooseControlBlock gc, IEC61850_ServiceType serviceType, MmsDataAccessError errVal)
{
    ServiceTrkInstance trkInst = NULL;

    if (gc->mmsMapping->gocbTrk)
    {
        trkInst = (ServiceTrkInstance) gc->mmsMapping->gocbTrk;
    }

    if (trkInst)
    {
        if (trkInst->serviceType)
            MmsValue_setInt32(trkInst->serviceType->mmsValue, (int) serviceType);

        if (trkInst->t)
            MmsValue_setUtcTimeMsEx(trkInst->t->mmsValue, Hal_getTimeInMs(), gc->mmsMapping->iedServer->timeQuality);

        if (trkInst->errorCode)
            MmsValue_setInt32(trkInst->errorCode->mmsValue,
                    private_IedServer_convertMmsDataAccessErrorToServiceError(errVal));

        char objRef[130];

        /* create object reference */
        LogicalNode* ln = (LogicalNode*) gc->logicalNode;
        LogicalDevice* ld = (LogicalDevice*) ln->parent;

        char* iedName = gc->mmsMapping->iedServer->mmsDevice->deviceName;

        snprintf(objRef, 129, "%s%s/%s.%s", iedName, ld->name, gc->logicalNode->name, gc->name);
        objRef[129] = 0;

        if (trkInst->objRef) {
            IedServer_updateVisibleStringAttributeValue(gc->mmsMapping->iedServer, trkInst->objRef, objRef);
        }
    }
}

#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

MmsGooseControlBlock
MmsGooseControlBlock_create()
{
	MmsGooseControlBlock self = (MmsGooseControlBlock) GLOBAL_CALLOC(1, sizeof(struct sMmsGooseControlBlock));

	if (self)
    {
	    self->useVlanTag = true;
#if (CONFIG_MMS_THREADLESS_STACK != 1)
	    self->publisherMutex = Semaphore_create(1);
#endif
	}

    return self;
}

void
MmsGooseControlBlock_destroy(MmsGooseControlBlock self)
{
    if (self)
    {
#if (CONFIG_MMS_THREADLESS_STACK != 1)
        Semaphore_destroy(self->publisherMutex);
#endif

#if (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1)
        if (self->publisher)
            GoosePublisher_destroy(self->publisher);
#endif /* (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1) */

        if (self->dataSetValues)
            LinkedList_destroyStatic(self->dataSetValues);

        if (self->goCBRef)
            GLOBAL_FREEMEM(self->goCBRef);

        if (self->goId)
            GLOBAL_FREEMEM(self->goId);

        if (self->dataSetRef)
            GLOBAL_FREEMEM(self->dataSetRef);

        if (self->dataSet && self->isDynamicDataSet)
        {
            MmsMapping_freeDynamicallyCreatedDataSet(self->dataSet);
            self->isDynamicDataSet = false;
            self->dataSet = NULL;
        }

        if (self->gooseInterfaceId)
            GLOBAL_FREEMEM(self->gooseInterfaceId);

        MmsValue_delete(self->mmsValue);

        GLOBAL_FREEMEM(self);
    }
}

void
MmsGooseControlBlock_useGooseVlanTag(MmsGooseControlBlock self, bool useVlanTag)
{
    self->useVlanTag = useVlanTag;
}

void
MmsGooseControlBlock_setGooseInterfaceId(MmsGooseControlBlock self, const char* interfaceId)
{
    if (self->gooseInterfaceId)
        GLOBAL_FREEMEM(self->gooseInterfaceId);

    self->gooseInterfaceId = StringUtils_copyString(interfaceId);
}

MmsDomain*
MmsGooseControlBlock_getDomain(MmsGooseControlBlock self)
{
    return self->domain;
}

DataSet*
MmsGooseControlBlock_getDataSet(MmsGooseControlBlock self)
{
    return self->dataSet;
}

LogicalNode*
MmsGooseControlBlock_getLogicalNode(MmsGooseControlBlock self)
{
    return self->logicalNode;
}

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;
}

static int
calculateMaxDataSetSize(DataSet* dataSet)
{
    int dataSetSize = 0;

    DataSetEntry* dataSetEntry = dataSet->fcdas;

    while (dataSetEntry)
    {
        dataSetSize +=  MmsValue_getMaxEncodedSize(dataSetEntry->value);

        dataSetEntry = dataSetEntry->sibling;
    }

    return dataSetSize;
}

bool
MmsGooseControlBlock_enable(MmsGooseControlBlock self, MmsMapping* mmsMapping)
{
    bool retVal = false;

#if (CONFIG_MMS_THREADLESS_STACK != 1)
    Semaphore_wait(self->publisherMutex);
#endif

    if (!MmsGooseControlBlock_isEnabled(self))
    {
        if (self->dataSetRef)
        {
            GLOBAL_FREEMEM(self->dataSetRef);

            if (self->dataSet && self->isDynamicDataSet)
            {
                MmsMapping_freeDynamicallyCreatedDataSet(self->dataSet);
                self->isDynamicDataSet = false;
                self->dataSet = NULL;
            }

            if (self->dataSetValues)
            {
                LinkedList_destroyStatic(self->dataSetValues);
                self->dataSetValues = NULL;
            }
        }

        self->dataSet = NULL;

        const char* dataSetRef = MmsValue_toString(MmsValue_getElement(self->mmsValue, 2));

        if (dataSetRef)
        {
            self->dataSetRef = StringUtils_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)
        {
            int dataSetSize = calculateMaxDataSetSize(self->dataSet);

            /*  Calculate maximum GOOSE message size */
            int maxGooseMessageSize =  26 + 51 + 6;
            maxGooseMessageSize += (int)strlen(self->goCBRef);

            if (self->goId)
                maxGooseMessageSize += (int)strlen(self->goId);
            else
                maxGooseMessageSize += (int)strlen(self->goCBRef);

            maxGooseMessageSize += (int)strlen(self->dataSetRef);

            maxGooseMessageSize += dataSetSize;

            if (maxGooseMessageSize > GOOSE_MAX_MESSAGE_SIZE)
            {
                setNdsCom(self, true);

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                copyGCBValuesToTrackingObject(self);
                updateGenericTrackingObjectValues(self, IEC61850_SERVICE_TYPE_SET_GOCB_VALUES, DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
            }
            else
            {
                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);

                if (mmsMapping->useIntegratedPublisher)
                {
#if (CONFIG_IEC61850_L2_GOOSE == 1)
                    if (self->gooseInterfaceId)
                        self->publisher = GoosePublisher_createEx(&commParameters, self->gooseInterfaceId, self->useVlanTag);
                    else
                        self->publisher = GoosePublisher_createEx(&commParameters, self->mmsMapping->gooseInterfaceId, self->useVlanTag);
#endif /* (CONFIG_IEC61850_L2_GOOSE == 1) */

#if (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1)

                    if (self->publisher)
                    {
                        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)
                        {
                            LinkedList_add(self->dataSetValues, dataSetEntry->value);
                            dataSetEntry = dataSetEntry->sibling;
                        }
                    }
                    else
                    {
                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: Failed to create GOOSE publisher!\n");
                    }

#endif /* (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1) */
                }

                self->goEna = true;

                retVal = true;

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
                copyGCBValuesToTrackingObject(self);
                updateGenericTrackingObjectValues(self, IEC61850_SERVICE_TYPE_SET_GOCB_VALUES, DATA_ACCESS_ERROR_SUCCESS);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */
            }
        }
    }
    else
    {
        if (DEBUG_IED_SERVER)
            printf("GoCB already enabled!\n");
    }

#if (CONFIG_MMS_THREADLESS_STACK != 1)
    Semaphore_post(self->publisherMutex);
#endif

    return retVal;
}

void
MmsGooseControlBlock_disable(MmsGooseControlBlock self, MmsMapping* mmsMapping)
{
    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 (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1)

        if (mmsMapping->useIntegratedPublisher)
        {
            if (self->publisher)
            {
                GoosePublisher_destroy(self->publisher);
                self->publisher = NULL;
                LinkedList_destroyStatic(self->dataSetValues);
                self->dataSetValues = NULL;
            }
        }

#endif /* (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1) */

#if (CONFIG_IEC61850_SERVICE_TRACKING == 1)
        MmsDataAccessError retVal = DATA_ACCESS_ERROR_SUCCESS;
        copyGCBValuesToTrackingObject(self);
        updateGenericTrackingObjectValues(self, IEC61850_SERVICE_TYPE_SET_GOCB_VALUES, retVal);
#endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */

#if (CONFIG_MMS_THREADLESS_STACK != 1)
        Semaphore_post(self->publisherMutex);
#endif
    }
}

void
MmsGooseControlBlock_checkAndPublish(MmsGooseControlBlock self, uint64_t currentTime, MmsMapping* mapping)
{
    if (self->publisher)
    {
        if (currentTime >= self->nextPublishTime)
        {
            IedServer_lockDataModel(mapping->iedServer);

            if (currentTime >= self->nextPublishTime)
            {
#if (CONFIG_MMS_THREADLESS_STACK != 1)
                Semaphore_wait(self->publisherMutex);
#endif

#if (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1)

                GoosePublisher_publish(self->publisher, self->dataSetValues);

                if (self->retransmissionsLeft > 0)
                {
                    self->nextPublishTime = currentTime + self->minTime;

                    if (self->retransmissionsLeft > 1)
                        GoosePublisher_setTimeAllowedToLive(self->publisher, self->minTime * 3);
                    else
                        GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3);

                    self->retransmissionsLeft--;
                }
                else
                {
                    GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3);

                    self->nextPublishTime = currentTime + self->maxTime;
                }

#endif /* (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1) */

#if (CONFIG_MMS_THREADLESS_STACK != 1)
                Semaphore_post(self->publisherMutex);
#endif
            }

            IedServer_unlockDataModel(mapping->iedServer);

        }
        else if ((self->nextPublishTime - currentTime) > ((uint32_t) self->maxTime * 2))
        {
            self->nextPublishTime = currentTime + self->minTime;
        }
    }
}

void
MmsGooseControlBlock_setStateChangePending(MmsGooseControlBlock self)
{
    self->stateChangePending = true;
}

void
MmsGooseControlBlock_publishNewState(MmsGooseControlBlock self)
{
#if (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1)

    if (self->publisher == false)
        return;

    if (self->stateChangePending)
    {
#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 + self->minTime;

            GoosePublisher_setTimeAllowedToLive(self->publisher, self->minTime * 3);
        }
        else
        {
            self->nextPublishTime = currentTime + self->maxTime;

            GoosePublisher_setTimeAllowedToLive(self->publisher, self->maxTime * 3);
        }

        GoosePublisher_publish(self->publisher, self->dataSetValues);

        self->stateChangePending = false;

#if (CONFIG_MMS_THREADLESS_STACK != 1)
        Semaphore_post(self->publisherMutex);
#endif
    }

#endif /* (CONFIG_IEC61850_L2_GOOSE == 1 || CONFIG_IEC61850_R_GOOSE == 1) */
}

static MmsVariableSpecification*
createMmsGooseControlBlock(char* gcbName)
{
    MmsVariableSpecification* gcb = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    gcb->name = StringUtils_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 = StringUtils_copyString("GoEna");
    namedVariable->type = MMS_BOOLEAN;

    gcb->typeSpec.structure.elements[0] = namedVariable;

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = StringUtils_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 = StringUtils_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 = StringUtils_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 = StringUtils_copyString("NdsCom");
    namedVariable->type = MMS_BOOLEAN;

    gcb->typeSpec.structure.elements[4] = namedVariable;

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = StringUtils_copyString("DstAddress");
    MmsMapping_createPhyComAddrStructure(namedVariable);

    gcb->typeSpec.structure.elements[5] = namedVariable;

    namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1, sizeof(MmsVariableSpecification));
    namedVariable->name = StringUtils_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 = StringUtils_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 = StringUtils_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 = StringUtils_createString(5, domainName, "/", lnName, "$", dataSetName);

    return dataSetReference;
}

void
GOOSE_sendPendingEvents(MmsMapping* self)
{
    if (self->useIntegratedPublisher)
    {
        LinkedList element = self->gseControls;

        while ((element = LinkedList_getNext(element)) != NULL)
        {
            MmsGooseControlBlock gcb = (MmsGooseControlBlock) element->data;

            if (MmsGooseControlBlock_isEnabled(gcb))
            {
                MmsGooseControlBlock_publishNewState(gcb);
            }
        }
    }
}

MmsVariableSpecification*
GOOSE_createGOOSEControlBlocks(MmsMapping* self, MmsDomain* domain,
        LogicalNode* logicalNode, int gseCount)
{
    MmsVariableSpecification* namedVariable = (MmsVariableSpecification*) GLOBAL_CALLOC(1,
            sizeof(MmsVariableSpecification));
    namedVariable->name = StringUtils_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 = StringUtils_createString(5, MmsDomain_getName(domain), "/", logicalNode->name,
                "$GO$", gooseControlBlock->name);

        if (gooseControlBlock->appId != NULL)
        {
            MmsValue* goID = MmsValue_getElement(gseValues, 1);

            MmsValue_setVisibleString(goID, gooseControlBlock->appId);

            mmsGCB->goId = StringUtils_copyString(gooseControlBlock->appId);
        }

        if ((gooseControlBlock->dataSetName != NULL) && (gooseControlBlock->dataSetName[0] != 0))
        {
        	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->confRev);

        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);

        if (mmsGCB->dataSetRef)
            setNdsCom(mmsGCB, false);
        else
            setNdsCom(mmsGCB, true);

        mmsGCB->mmsMapping = self;

        mmsGCB->stateChangePending = false;

        LinkedList_add(self->gseControls, mmsGCB);

        currentGCB++;
    }

    return namedVariable;
}

#endif /* (CONFIG_INCLUDE_GOOSE_SUPPORT == 1) */
